2013-10-04 130 views
4

当应用程序突然终止时,是否可以从通知区域(系统托盘)中删除NotifyIcon从通知区域中删除NotifyIcon

如果不是,下次应用程序运行时如何将其删除?

+2

请定义“突然终止”。 –

+1

当应用程序崩溃或意外关闭。 – DelPhi

+1

在这些情况下,您仍然有机会优雅地删除通知图标。我假设你正在尝试/最终正确使用。这实际上只是强制终止(TerminateProcess),你无法抵御。 –

回答

5

突然?不可以。您的程序已不复存在,所以没有机会运行任何代码来告诉shell它应该删除图标。

要移除图标,请将鼠标移到该图标上。 shell会尝试通知你的程序,意识到这里什么都没有了,并且自己去掉图标。

+0

我知道这只是Windows带有托盘图标的应用程序崩溃时的行为方式。但是我想在下次运行应用程序时将其删除。 – DelPhi

+0

@DelPhi,你可以写下以前的图标参数并在启动时尝试'NIM_DELETE'。 –

+0

我使用try/finally和这个命令{Shell_NotifyIcon(NIM_DELETE,@IconData);}。 – DelPhi

3

在Windows 7和更高版本上,通知图标可以由用户定义的GUID标识。在较早的版本中,它们由HWND和ID号码的组合来代替。由于您的应用程序不能保证在下次运行时获得相同的HWND值,因此您可以对由HWND标识的旧图标执行任何操作的唯一方法是如果您记住了以前的HWND值,因此您可以使用它来删除旧图标,然后使用新的HWND添加新图标。但是,对于GUID标识的图标,GUID需要保持持久性(因为它存储在注册表中以存储与图标关联的应用设置),所以您应该能够根据需要随时更新现有图标,或者将其删除如果需要。

+0

从文档中不清楚该GUID是否确实标识了单个图标,或者只是一个*类型的图标。也就是说,如果一个程序的多个实例同时运行,它们都必须为它们的图标使用不同的GUID吗?还是只是显示多个图标的实例必须为每个图标使用不同的GUID?由于前者难以实施,我期望后者。如果这是真的,那么稍后的程序实例仍然无法清理先前崩溃的实例的图标。 –

+0

文档建议guid唯一标识应用程序可执行文件的特定副本的特定图标,而不是像HWND + ID组合一样用于应用程序的给定进程。文档确实说应用中的多个图标需要使用单独的GUID。此外,guid存储在注册表中,它存储每个guid的应用程序完整路径,因此并排安装必须在每次安装中对同一图标使用不同的guid。如果应用程序移动到新路径,则旧路径的GUID必须未注册,以便新路径可以与GUID关联。 –

+0

因此,在这些条件下,它听起来像是应用程序进程的后期实例可以控制以前的应用程序进程的图标,因为它们在通过guid进行标识时并不与任何给定的进程绑定。但我可能是错的。我仍然在我的应用程序中使用HWND + ID组合,但尚未更新为使用GUID。 –

0

FWIW,因为代码目前还不存在,所以我想我会把它写进去。我不知道它是否对OP有帮助,但它应该是正确方向的良好指导。

unit csystray; 
    { removes dead system tray icons, by Glenn1234 @ stackoverflow.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. 
+1

我不会说挖掘另一个进程的内存来获取无证信息真的是“正确方向的指导”。 –