2013-10-04 82 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

我不會說挖掘另一個進程的內存來獲取無證信息真的是「正確方向的指導」。 –