从通知区域中删除 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的主要内容,如果未能解决你的问题,请参考以下文章

删除win7任务栏通知区域图标的方法

不小心删除了linux mint的通知区域怎么办?(上网icon)

如何从 Windows 服务显示通知区域气球和图标?

在 iOS 7 中取消 UITableViewCell 滑动删除时收到通知

处理win7任务栏通知区域图标异常问题

Android:从通知栏中删除通知