从通知区域中删除 NotifyIcon
Posted
技术标签:
【中文标题】从通知区域中删除 NotifyIcon【英文标题】:Removing NotifyIcon from the notification area 【发布时间】:2013-10-11 11:56:07 【问题描述】:当应用突然终止时,是否可以从通知区域(系统托盘)中删除NotifyIcon
?
如果没有,如何在应用下次运行时将其移除?
【问题讨论】:
请定义“突然终止”。 当应用程序崩溃或意外关闭时。 在这些情况下,您仍然有机会优雅地删除通知图标。我假设您正在正确使用 try/finally。这实际上只是您无法防御的强制终止(TerminateProcess)。 有这个,但它不在 Delphi 中:***.com/questions/74723/… 其实我之前从来没有想过,但是看到你的用户名让我意识到:Delphi其实是一个典型的标量场的梯度。 【参考方案1】:突然?不。您的程序已不复存在,因此没有机会运行任何代码来告诉外壳程序它应该删除图标。
要删除图标,请将鼠标移到它上面。 shell 会尝试通知你的程序,意识到那里已经没有任何东西了,然后自己删除图标。
【讨论】:
我知道当带有托盘图标的应用程序崩溃时,Windows 的行为就是这样。但我想在应用下次运行时将其删除。 @DelPhi,您可以记下以前的图标参数并在启动时尝试NIM_DELETE
。
我正在使用 try/finally 和这个命令 Shell_NotifyIcon(NIM_DELETE, @IconData);。
@DelPhi 如果你想删除它,那么就这样做。您必须从创建图标的应用程序中执行此操作。
@David Heffernan,我不知道它是否已经创建。该命令仅删除正在运行的 IconData【参考方案2】:
在 Windows 7 及更高版本上,通知图标可以通过用户定义的 GUID 来识别。在早期版本中,它们由 HWND 和 ID 号的组合来标识。由于不能保证您的应用程序在下次运行时获得相同的 HWND 值,因此您可以对由 HWND 标识的旧图标执行任何操作的唯一方法是,如果您记住了以前的 HWND 值,那么您可以使用它来删除旧图标,然后使用新的 HWND 添加新图标。但是对于 GUID 标识的图标,GUID 需要是持久的(因为它存储在注册表中以存储与图标关联的应用程序设置),因此您应该能够根据需要简单地继续更新现有图标,或者将其删除如果需要。
【讨论】:
从文档中不清楚该 GUID 是否真正标识了单个图标,或者只是单个 type 图标。也就是说,如果一个程序的多个实例同时运行,它们是否都必须为它们的图标使用不同的 GUID?或者仅仅是显示多个图标的实例必须为每个图标使用不同的 GUID?我希望后者,因为前者难以实施。如果这是真的,那么程序的后一个实例仍然无法清理前一个崩溃实例的图标。 文档建议 guid 唯一标识应用程序可执行文件的给定副本的特定图标,而不是像 HWND+ID 组合那样为应用程序的给定进程标识一个特定图标。文档确实说应用程序中的多个图标需要使用单独的 guid。此外,guid 存储在注册表中,并将应用程序的完整路径与每个 guid 一起存储,因此并行安装必须在每个安装中为相同的图标使用不同的 guid。如果应用程序移动到新路径,则必须取消注册旧路径的 guid,以便新路径可以与 guid 关联。 因此在这些条件下,听起来应用进程的后一个实例可以控制前一个应用进程的图标,因为它们在由 guid 标识时不与任何给定进程绑定。但我可能错了。我仍然在我的应用中使用 HWND+ID 组合,它们尚未更新为使用 guid。 我刚刚进行了测试。在具有由 guid 标识的一个图标的 Win7 上,运行该应用程序的多个实例,系统托盘中仅显示一个图标副本。所以这确实建议每个 guid 有一个图标实例,并且多个应用程序实例可以共享一个图标实例。【参考方案3】:FWIW,由于到目前为止代码不存在,我想我会把它扔进去。我不知道它是否对 OP 有帮助,但它应该是正确方向的良好指导。
unit csystray;
removes dead system tray icons, by Glenn1234 @ ***.com
since this uses "less than supported by Microsoft" means, it may
not work on all operating system. It was tested on Windows XP
interface
uses commCtrl, shellapi, windows;
type
TTrayInfo = packed record
hWnd: HWnd;
uID: UINT;
uCallBackMessage: UINT;
Reserved1: array[0..1] of longint;
Reserved2: array[0..2] of longint;
hIcon: HICON;
end;
PTBButton = ^TTBButton;
_TBBUTTON = packed record
iBitmap: Integer;
idCommand: Integer;
fsState: Byte;
fsStyle: Byte;
bReserved: array[1..2] of Byte;
dwData: Longint;
iString: Integer;
end;
TTBButton = _TBBUTTON;
procedure RemoveStaleTrayIcons;
implementation
procedure RemoveStaleTrayIcons;
const
VMFLAGS = PROCESS_VM_OPERATION or PROCESS_VM_READ OR PROCESS_VM_WRITE;
var
ProcessID: THandle;
ProcessHandle: THandle;
trayhandle: HWnd;
ExplorerButtonInfo: Pointer;
i: integer;
ButtonCount: Longint;
BytesRead: Longint;
ButtonInfo: TTBButton;
TrayInfo: TTrayInfo;
ClassNameA: Array[0..255] of char;
outlen: integer;
TrayIconData: TNotifyIconData;
begin
// walk down the window hierarchy to find the notification area window
trayhandle := FindWindow('Shell_TrayWnd', '');
trayhandle := FindWindowEx(trayhandle, 0, 'TrayNotifyWnd', nil);
trayhandle := FindWindowEx(trayhandle, 0, 'SysPager', nil);
trayhandle := FindWindowEx(trayhandle, 0, 'ToolbarWindow32', nil);
if trayhandle = 0 then exit;
// find the notification area process and open it up for reading.
GetWindowThreadProcessId(trayhandle, @ProcessID);
ProcessHandle := OpenProcess(VMFLAGS, false, ProcessID);
ExplorerButtonInfo := VirtualAllocEx(ProcessHandle, nil, Sizeof(TTBButton),
MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
// the notification area is a tool bar. Get the number of buttons.
ButtonCount := SendMessage(trayhandle, TB_BUTTONCOUNT, 0, 0);
if ExplorerButtonInfo <> nil then
try
// iterate the buttons & check.
for i := (ButtonCount - 1) downto 0 do
begin
// get button information.
SendMessage(trayhandle, TB_GETBUTTON, i, LParam(ExplorerButtonInfo));
ReadProcessMemory(ProcessHandle, ExplorerButtonInfo, @ButtonInfo,
Sizeof(TTBButton), BytesRead);
// if there's tray data, read and process
if Buttoninfo.dwData <> 0 then
begin
ReadProcessMemory(ProcessHandle, PChar(ButtonInfo.dwData),
@TrayInfo, Sizeof(TTrayInfo), BytesRead);
// here's the validation test, this fails if the master window is invalid
outlen := GetClassName(TrayInfo.hWnd, ClassNameA, 256);
if outlen < 1 then
begin
// duplicate the shell icon removal, i.e. my component's DeleteTray
TrayIconData.cbSize := sizeof(TrayIconData);
TrayIconData.Wnd := TrayInfo.hWnd;
TrayiconData.uID := TrayInfo.uID;
TrayIconData.uCallbackMessage := TrayInfo.uCallBackMessage;
Shell_NotifyIcon(NIM_DELETE, @TrayIconData);
end;
end;
end;
finally
VirtualFreeEx(ProcessID, ExplorerButtonInfo, Sizeof(TTBButton), MEM_RELEASE);
end;
end;
end.
【讨论】:
我不会说挖掘另一个进程的内存以获取未记录的信息真的是“正确方向的指导”。以上是关于从通知区域中删除 NotifyIcon的主要内容,如果未能解决你的问题,请参考以下文章
不小心删除了linux mint的通知区域怎么办?(上网icon)