Entwickler-Ecke
Windows API - Icons im SysTray "aufräumen"
Hoffensterchen - Mi 25.05.11 20:41
Titel: Icons im SysTray "aufräumen"
Hallo liebe Leutz,
ich schreibe ein Programm, dass in bestimmten Situationen ein laufenden Fremdprogramm beendet, ein paar Operationen durchführt und danach das Fremdprogramm wieder startet.
Leider läßt sich das Fremdprogramm nicht "still" beenden, d.h. für ein "geordnetet" Beenden muß immer eine Sicherheitsfrage geklickt werden. Das Programm läuft unsichtbar, es zeigt sich nur durch mehrere Icons im Systray.
Mein Programm beendet das Fremdprogramm also "hart" per TerminateProcess(...), was auch problemlos funktioniert, da keine Daten gespeichert oder Dateien geschlossen werden müssen.
Allerdings gibt es ein kosmetisches Problem: Die Icons im Systray verschwinden erst, wenn man mit der Maus drüberfährt. Meine Frage also:
Wie kann ich den Systray "aufräumen", d.h. per Code alle "toten" Icons zu entfernen? Gibt es dafür sowas wie ein "Refresh" oder etwas in der Art?
Danke für Eure Hilfe!
Hoffensterchen
Edith sagt: Wasse nich im Koppe has'... Sorry, ich vergaß zu erwähnen, daß mein Programm unter Windows XP SP3 läuft... :oops:
jaenicke - Mi 25.05.11 21:03
Hallo und :welcome:
Nein, dafür gibt es keine Möglichkeit. Du bist nicht der erste, der danach fragt. ;-)
Manche haben versucht den Mauszeiger per Programm herumzuschubsen und sowas, aber wirklich zuverlässig funktionieren tut das nicht.
Hast du denn schon versucht die Sicherheitsabfrage einfach durch dein Programm zu bestätigen? Das wäre doch wohl der sinnvollere und vor allem einfachere Weg...
Gerd Kayser - Mi 25.05.11 21:10
jaenicke hat folgendes geschrieben : |
Hast du denn schon versucht die Sicherheitsabfrage einfach durch dein Programm zu bestätigen? Das wäre doch wohl der sinnvollere und vor allem einfachere Weg... |
Ich würde eine WM_Quit-Message an das Programm schicken. Dann dürfte die Sicherheitsabfrage nicht erscheinen. Bei TerminateProcess könnten Reste im System verbleiben.
Hoffensterchen - Mi 25.05.11 21:14
Danke erstmal!
Mpft... :x
jaenicke hat folgendes geschrieben: |
Hast du denn schon versucht die Sicherheitsabfrage einfach durch dein Programm zu bestätigen? Das wäre doch wohl der sinnvollere und vor allem einfachere Weg... |
Ist mein Plan B... :wink:
Gerd Kayser hat folgendes geschrieben: |
Ich würde eine WM_Quit-Message an das Programm schicken. |
Ömm...
Wie geht das...? :oops:
jaenicke - Mi 25.05.11 21:20
Stichworte:
FindWindow oder EnumWindows sowie SendMessage oder PostMessage.
Gerd Kayser - Do 26.05.11 01:32
Hoffensterchen hat folgendes geschrieben : |
Wie kann ich den Systray "aufräumen", d.h. per Code alle "toten" Icons zu entfernen? Gibt es dafür sowas wie ein "Refresh" oder etwas in der Art? |
Was man in der TaskbarNotification-Area sieht, sind keine Icons oder Buttons. Der Bereich ist nichts anderes als ein Bildchen mit mehreren gemalten "Icons". Deshalb kann man die einzelnen "Icons" auch nicht mit Tastaturbefehlen ansteuern, sondern nur mit der Maus. Ob nun ein Eintrag noch gültig ist, wird von dem TNA-Prozess nur geprüft, wenn der Mauszeiger direkt auf einem der Bildchen steht.
Vorbemerkung zu meiner Lösung:
1. Getestet habe ich es nur unter Windows 7 32-Bit. Bei anderen Windows-Versionen sind die Parameter bei FindWindow(Ex) anzupassen.
2. Fehlerbehandlung habe ich keine eingebaut. Das überlasse ich Euch (vorgerückte Stunde und mit meiner Schäferhündin muß ich noch eine Runde Gassi gehen).
3. Ob man besondere Rechte benötigt, habe ich nicht geprüft.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93:
| procedure TForm1.Button1Click(Sender: TObject); var Anzahl : integer; begin Anzahl := TNA_Refresh; Label1.Caption := 'Gelöschte Icon-Leichen: ' + IntToStr(Anzahl); end;
function TForm1.TNA_Refresh: integer; var wnd : HWND; AnzahlIcons : integer; Schleife : integer; Koordinaten : TPoint; AltKoordinaten : TPoint; pID : cardinal; hProg : THandle; Rect : TRect; pAdr : Pointer; Gelesen : cardinal; AnzahlBytes : integer; begin Result := - 1;
wnd := FindWindow('Shell_TrayWnd', nil); wnd := FindWindowEx(wnd, 0, 'TrayNotifyWnd', nil); wnd := FindWindowEx(wnd, 0, 'SysPager', nil); wnd := FindWindowEx(wnd, 0, 'ToolbarWindow32', nil);
if wnd <> 0 then begin GetCursorPos(AltKoordinaten);
AnzahlIcons := SendMessage(wnd, TB_ButtonCount, 0, 0);
GetWindowThreadProcessID(wnd, @pID);
hProg := OpenProcess(Process_VM_Operation or Process_VM_Read or Process_VM_Write, false, pID);
AnzahlBytes := SizeOf(Rect);
pAdr := VirtualAllocEx(hProg, nil, AnzahlBytes, MEM_Reserve or MEM_Commit, Page_ReadWrite);
for Schleife := AnzahlIcons - 1 downto 0 do begin SendMessage(wnd, TB_GetRect, Schleife, integer(pAdr));
ReadProcessMemory(hProg, pAdr, @Rect, AnzahlBytes, Gelesen);
Koordinaten.X := Rect.Left + 3; Koordinaten.Y := Rect.Top + 3; Windows.ClientToScreen(wnd, Koordinaten); SetCursorPos(Koordinaten.X, Koordinaten.Y); end;
VirtualFreeEx(hProg, pAdr, 0, MEM_Release); CloseHandle(hProg);
SetCursorPos(AltKoordinaten.X, AltKoordinaten.Y);
Result := AnzahlIcons - SendMessage(wnd, TB_ButtonCount, 0, 0); end; end; |
Edit-1: Schleife in eine downto-Schleife geändert.
thepaine91 - Do 26.05.11 09:37
Ich würde empfehlen mal dem Link von Luckie zu folgen dort sind ein paar gute Lösungsansätze die vor allem sauberer sind als die Maus hin und her zu schieben.
Gerd Kayser - Do 26.05.11 12:34
thepaine91 hat folgendes geschrieben : |
dort sind ein paar gute Lösungsansätze die vor allem sauberer sind als die Maus hin und her zu schieben. |
Wenn ich den Thread dort richtig gelesen habe, geht es dort um das Verstecken eines Icons und nicht um das Aktualisieren des TNA-Bereichs. Auch wenn man eine Leiche im Keller versteckt, so ändert das nichts daran, dass im Keller eine Leiche liegt. ;-)
Zur Mausschieberei: Ich habe die Ausführungszeit der Funktion mit GetTickCount gemessen. Sie beträgt 0 MSek. Der Cursor ist weder am Flackern noch sonstwas. Der Anwender bekommt davon überhaupt nichts mit.
Meine Lösung funktioniert jedenfalls unter Windows 7 32-Bit. Das müssen andere Lösungsansätze erst einmal zeigen. Wenn ich nachher mal etwas Zeit habe, werde ich es unter XP mal testen (nach dem Heraussuchen der FindWindow-Parameter).
thepaine91 - Do 26.05.11 13:27
Ja aber auf Seite 4 ist eine Lösung zu finden die laut Author die Icons nicht nur versteckt sondern entfernt. Genauer hab ich es mir auch nicht angeguckt.
Nebenbei das Mauszeigerverschieben mag wunderbar funktionieren wenn aber ein Topmost Fenster drüber liegt trifft der Mauszeiger das Icon nicht also wird es auch nicht entfernt. Sobald das Fenster dann aber verschoben wird kommt es wieder zum Vorschein. Der Fall ist zwar selten aber dennoch kann es vorkommen.
Gerd Kayser - Do 26.05.11 16:30
thepaine91 hat folgendes geschrieben : |
Ja aber auf Seite 4 ist eine Lösung zu finden die laut Author die Icons nicht nur versteckt sondern entfernt. Genauer hab ich es mir auch nicht angeguckt. |
Meinst Du tna_181.zip? An die Datei komme ich leider nicht heran, weil ich dort im Forum keinen Account habe. Und überall Accounts anlegen möchte ich auch nicht gerade. Aber vielleicht kannst Du mir das per Mail schicken.
Zitat: |
wenn aber ein Topmost Fenster drüber liegt trifft der Mauszeiger das Icon nicht also wird es auch nicht entfernt. |
Werde ich nachher mal versuchen hier nachzustellen. Bislang ist es mir auf die Schnelle nur gelungen, Fenster
hinter die Taskbar zu verschieben. Dabei ist mir allerdings aufgefallen, dass ich einen Fall übersehen habe. Nämlich: Wenn die Taskbar auf automatisches Ausblenden eingestellt ist. Aber das Problem lässt sich ja lösen.
thepaine91 - Do 26.05.11 16:36
Jo hab einen Account in der DP, schicke es dir per PN.
Ich hab es getestet und es passiert das was ich beschrieben habe. Ist aber nach wie vor ein seltener Fall denke ich.
Gerd Kayser - So 29.05.11 13:51
Heute habe ich mal ausgiebig das Programm aus tna_181.zip unter Windows 7 32-Bit getestet. Als Testobjekt habe ich TV-Guide (Fernsehprogramm von Sky) benutzt. Die Anwendung erzeugt zusätzlich einen Prozess (iSaverCtrl.exe) mit einem TNA-Icon. Bei meinen Tests ist mir Folgendes aufgefallen:
1. Das Programm löscht nur das TNA-Icon eines vorgegebenen,
laufenden Prozesses und lässt eine Prozessleiche zurück (Icon wird gelöscht, Prozess aber nicht beendet).
2. Ein verwaistes Icon (Prozess gekillt) wird nicht gelöscht.
3. Das Programm enthält eine Stolperfalle.
Delphi-Quelltext
1:
| ProcPath:= 'C:\Programme\iSaver\iSaverCtrl.exe'; |
Das nachfolgende FileExists funktioniert zwar, aber
Delphi-Quelltext
1:
| if SameText(ProcPath, GetProcPath(IconData.Wnd)) then |
wird immer fehlschlagen, weil GetProcPath "C:\Program Files\..." zurückliefert.
Fazit: In der vorliegenden Form ist das Programm völlig ungeeignet, weil es die Icons im TNA-Bereich nicht aufräumt (siehe Punkt 2). Und das Löschen eines Icons, ohne den dazugehörigen Prozess zu beenden, ist eigentlich sinnlos (siehe Punkt 1).
Aber das Programm hat mich auf eine Idee gebracht, wie man das Refreshen realisieren könnte (auch unter XP und Vista). Dazu brauche ich aber einige Tage Zeit. Auch als Ruheständler ist man nicht immer Herr seiner Zeit.
Gerd Kayser - Do 02.06.11 14:13
Hier ist die Lösung. Hat zwar einige Stunden gedauert, weil ich mal wieder zu kompliziert gedacht hatte. Dabei ist die Lösung eigentlich recht simpel. ;-)
Die Lösung funktioniert auch bei einer Taskleiste, die ausgeblendet ist. Ich habe zwar das Programm nur unter Windows 7 32-Bit getestet, aber es sollte ab Windows XP funktionieren. Ob das Programm unter 64-Bit funktioniert, kann ich hier leider nicht testen.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128:
| unit Main;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, CommCtrl, ShellAPI;
type TMainform = class(TForm) Refresh: TButton; Label1: TLabel; procedure RefreshClick(Sender: TObject); procedure FormCreate(Sender: TObject); private public function TNA_Refresh : integer; function SuchenTNAWindow : HWnd; end;
var Mainform: TMainform;
implementation
{$R *.dfm}
function TMainform.TNA_Refresh : integer; var Wnd : HWnd; AnzahlIcons : integer; Schleife : integer; pID : THandle; HProcess : THandle; pMemory : Pointer; AnzahlBytes : integer; Gelesen : cardinal; TB : TTBButton; IconData : TNotifyIconData; begin Result := - 1; AnzahlBytes := 256; IconData.cbSize:= SizeOf(TNotifyIconData);
Wnd := SuchenTNAWindow; if Wnd <> 0 then begin AnzahlIcons := SendMessage(Wnd, TB_ButtonCount, 0, 0);
GetWindowThreadProcessId(Wnd, @pID);
HProcess := OpenProcess(Process_VM_Operation or Process_VM_Read or Process_VM_Write, false, pID);
pMemory := VirtualAllocEx(HProcess, nil, AnzahlBytes, MEM_Reserve or MEM_Commit, Page_ReadWrite);
for Schleife := AnzahlIcons - 1 downto 0 do begin if SendMessage(Wnd, TB_GetButton, Schleife, Cardinal(pMemory)) <> 0 then if ReadProcessMemory(HProcess, pMemory, @TB, SizeOf(TB), Gelesen) then if ReadProcessMemory(HProcess, Pointer(TB.dwData), @IconData.Wnd, SizeOf(IconData.Wnd) + SizeOf(IconData.uID), Gelesen) then if not IsWindow(IconData.Wnd) then Shell_NotifyIcon(NIM_Delete, @IconData); end;
VirtualFreeEx(HProcess, pMemory, 0, MEM_Release); CloseHandle(HProcess);
Result := AnzahlIcons - SendMessage(wnd, TB_ButtonCount, 0, 0); end; end;
procedure TMainform.FormCreate(Sender: TObject); begin ReportMemoryLeaksOnShutdown := true; end;
function TMainform.SuchenTNAWindow : HWnd; begin Result := FindWindow('Shell_TrayWnd', nil); Result := FindWindowEx(Result, 0, 'TrayNotifyWnd', nil); Result := FindWindowEx(Result, 0, 'SysPager', nil); Result := FindWindowEx(Result, 0, 'ToolbarWindow32', nil); end;
procedure TMainform.RefreshClick(Sender: TObject); var Ergebnis : integer; begin Ergebnis := TNA_Refresh; if Ergebnis > - 1 then Label1.Caption := 'Gelöschte Icons: ' + IntToStr(Ergebnis) else Label1.Caption := 'TNA-Fenster nicht gefunden!'; end;
end. |
Delete - Do 02.06.11 14:59
Na, das sieht doch nach einer sauberen Lösung aus.
jaenicke - Do 02.06.11 16:14
Gerd Kayser hat folgendes geschrieben : |
Ob das Programm unter 64-Bit funktioniert, kann ich hier leider nicht testen. |
Nein, so nicht, da die Datenstruktur logischerweise bei einem 64-Bit Prozess anders aussieht. (64-Bit Pointer, ...)
Diese Version funktioniert immer (wobei eigentlich die Prüfung sein müsste, ob der Zielprozess 64-bittig ist, aber da war ich zu faul nachzuschauen wie das geht und für 32-Bit Delphi geht es auch so ;-)):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
| function FindTNAWindow: HWND; begin Result := FindWindow('Shell_TrayWnd', nil); Result := FindWindowEx(Result, 0, 'TrayNotifyWnd', nil); Result := FindWindowEx(Result, 0, 'SysPager', nil); Result := FindWindowEx(Result, 0, 'ToolbarWindow32', nil); end;
function IsWow64Process: Boolean; type TIsWow64Process = function(hProcess: THandle; var Wow64Process: BOOL): BOOL; stdcall; var Value: BOOL; IsWow64ProcessFunc: TIsWow64Process; begin IsWow64ProcessFunc := GetProcAddress(GetModuleHandle('kernel32'), 'IsWow64Process'); Result := Assigned(IsWow64ProcessFunc); if Result then begin if not IsWow64ProcessFunc(GetCurrentProcess, Value) then raise Exception.Create('Bad process handle'); Result := Value; end; end;
function RefreshTNAIcons: Integer; type TTBButton64 = packed record iBitmap: Integer; idCommand: Integer; fsState: Byte; fsStyle: Byte; bReserved: array [1 .. 6] of Byte; dwData: Int64; iString: Int64; end; var TNAWindow: HWND; i, IconCount: Integer; TrayProcessID: THandle; TrayProcessHandle: THandle; pMemory: Pointer; BufferSize, ReadBytes: Cardinal; TB: TTBButton; TB64: TTBButton64; IconData: TNotifyIconData; ReadSuccessful, UseWow64Data, CanProcessEntry: Boolean; begin Result := -1; BufferSize := 256; IconData.cbSize := SizeOf(TNotifyIconData);
TNAWindow := FindTNAWindow; if TNAWindow <> 0 then begin IconCount := SendMessage(TNAWindow, TB_BUTTONCOUNT, 0, 0); GetWindowThreadProcessId(TNAWindow, @TrayProcessID); TrayProcessHandle := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE, False, TrayProcessID); pMemory := VirtualAllocEx(TrayProcessHandle, nil, BufferSize, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE); try UseWow64Data := IsWow64Process; for i := IconCount - 1 downto 0 do begin if SendMessage(TNAWindow, TB_GETBUTTON, i, lParam(pMemory)) <> 0 then if UseWow64Data then CanProcessEntry := ReadProcessMemory(TrayProcessHandle, pMemory, @TB64, SizeOf(TB64), ReadBytes) and ReadProcessMemory(TrayProcessHandle, Pointer(TB64.dwData), @IconData.Wnd, SizeOf(IconData.Wnd) + SizeOf(IconData.uID), ReadBytes) else CanProcessEntry := ReadProcessMemory(TrayProcessHandle, pMemory, @TB, SizeOf(TB), ReadBytes) and ReadProcessMemory(TrayProcessHandle, Pointer(TB.dwData), @IconData.Wnd, SizeOf(IconData.Wnd) + SizeOf(IconData.uID), ReadBytes); if CanProcessEntry and not IsWindow(IconData.Wnd) then Shell_NotifyIcon(NIM_DELETE, @IconData); end; finally VirtualFreeEx(TrayProcessHandle, pMemory, 0, MEM_RELEASE); CloseHandle(TrayProcessHandle); end; Result := IconCount - SendMessage(TNAWindow, TB_BUTTONCOUNT, 0, 0); end; end; |
Wie wäre es, wenn du das gleich einmal in der Library als neuen Eintrag einstellst? ;-)
ral24092 - Di 07.05.19 08:04
Man muss "NOTIFYICONOVERFLOWWINDOW" finden, dann "NOTIFYICONOVERFLOWWINDOW" aktivieren und zum Schluss die Maus über "NOTIFYICONOVERFLOWWINDOW" laufen lassen.
Im Anhang der vollständige Code.
LG Rale
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!