Entwickler-Ecke
WinForms - Anwendung im Autostart startet Explorer nur im Hintergrund
Delete - Fr 06.03.15 23:48
Titel: Anwendung im Autostart startet Explorer nur im Hintergrund
Hallo,
ich habe ein kleines Tool gebastelt, welches sich nach dem Start in den Tray minimiert, HTTP-Requests entgegen nimmt und bei einem eingehenden Request ein Explorer-Fenster im Vordergrund öffnet.
Dies funktioniert grundsätzlich problemlos, wenn ich das Tool per Hand starte.
Mein Problem ist jetzt, dass dieses Tool im Autostart liegen soll (Verknüpfung im Autostart-Ordner), damit dies bei einer Anmeldung bei Windows gestartet wird.
Bei einem ankommenden Request geht das Explorer-Fenster zwar auf, allerdings immer im Hintergrund.
In der Taskleiste blinkt dann nur das Explorer-Icon und muss per Hand in den Vordergrund geholt werden, was nervig ist.
Ich möchte, dass der Explorer - wie auch beim händischen Start - immer im Vordergrund geöffnet wird.
Folgendes habe ich schon probiert, geholfen hat es aber leider nicht.
C#-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:
| [DllImport("User32.dll")] private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)] static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd);
const int SW_RESTORE = 9;
protected void Open() { string path = @"c:\";
try { using (Process process = new Process()) { process.StartInfo.FileName = "explorer"; process.StartInfo.Arguments = "/e," + path; process.Start(); process.WaitForInputIdle(1000); ShowWindow(process.Handle, SW_RESTORE); SetForegroundWindow(process.Handle); BringWindowToTop(process.Handle); } } catch(Exception e) {
} } |
Hat jemand eine Idee, warum das Tool durch den Autostart den Explorer nur im Hintergrund öffnet?
LG
Auch hier gefragt:
http://www.mycsharp.de/wbb2/thread.php?threadid=114105
Delete - Sa 07.03.15 18:00
Es liegt im Autostart des jeweiligen Benutzers. Laut Taskmanager läuft es auch unter dem jeweiligen Benutzer.
Dachte auch erst, dass evtl. nicht der eigentliche Benutzer das Programm ausführt.
Die Verzeichnisse sind bei Win 7 und Win 8.1 identisch. Kann das Problem auch auf beiden Systemen reproduzieren.
Hatte auch schon den Virenscanner im Verdacht, nach kompletter Löschung besteht das Problem aber weiterhin.
Delete - So 08.03.15 21:07
Oh mist, das habe ich übersehen. OK, werde das morgen gleich mal ausprobieren.
Evtl. funktioniert das ja :) Danke erstmal für die Hilfe.
Delete - Mo 09.03.15 22:37
TopMost hat leider keine Veränderung gebracht.
Auch das "Force Window to Front" konnte nicht wirklich Veränderung bringen.
Ich habe es so wie in dem verlinkten Beispiel probiert und dann noch so:
C#-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:
| [DllImport("User32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.dll")] public static extern int AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);
[DllImport("User32.dll")] public static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")] public static extern IntPtr GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId);
public static void SetForegroundWindowEx(IntPtr hndl) { IntPtr threadID1 = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero); IntPtr threadID2 = hndl;
if (threadID1 == threadID2) { SetForegroundWindow(hndl); } else { AttachThreadInput(threadID2, threadID1, true); SetForegroundWindow(hndl); AttachThreadInput(threadID2, threadID1, false); } }
protected void RunExplorer() { Thread.Sleep(3000);
SetForegroundWindowEx(this.hwnd); using (Process process = new Process()) { process.StartInfo.FileName = "explorer"; process.StartInfo.Arguments = "/e"; process.Start(); SetForegroundWindowEx(GetWindowThreadProcessId(IntPtr.Zero, (IntPtr)process.Id));
} } |
Das juckt den Explorer aber irgendwie gar nicht.
Bin echt überfragt, warum das nicht hinhaut. Evtl. hab ich das auch falsch verwendet?
Th69 - Di 10.03.15 20:55
Warum hast du WaitForInputIdle wieder aus dem Code herausgenommen? Solange die Anwendung nicht gestartet ist (und bereit ist für Eingaben), solange kann das Fenster auch nicht angesprochen werden (Stichwort: Message-Loop).
Kann aber auch generell am Explorer liegen (da dieser ja eine spezielle Systemanwendung ist).
Evtl. mal für den Explorer unter "Ordneroptionen"/"Ansicht" die Einstellung "Ordnerfenster in einem eigenen Prozess starten" aktivieren.
Delete - Mi 11.03.15 00:14
Da hast du natürlich recht. Das habe ich durch das ganze rumgeteste rausgeworfen und nicht mehr bedacht. Probier ich morgen gleich nochmal.
Message-Loop sagt mir was. Mir ist das schon mal in einem anderen Projekt mit WindowMessages untergekommen.
Wo ich mir unsicher bin, ist die Übergabe des Handles bzw. das ermitteln der Thread-ID's.
Ist das so überhaupt korrekt? Hätte ja noch
process.Handle zur Auswahl.
Das Beispiel habe ich von hier:
http://dotnet-snippets.de/snippet/fenster-wirklich-in-den-vordergrund-des-desktops-bringen/1005
Da wird die Form übergeben und davon das Handle verwendet. Ich denke, da ist meine
process.Id (Zeile 42) ist eh falsch und process.Handle korrekt?
Habe hier bisschen mit dem Process Monitor versucht die Handles zu verfolgen und konnte zumindestens das Fenster im Vordergrund anhand des Handles finden.
Der Handle stellt doch den Hauptthread des jeweiligen Prozesses dar, oder? Finde dazu immer nur den Begriff "Window Handle", es stand im Proess Monitor aber als "Thread-ID" drin, wenn ich jetzt nicht völlig irre.
Den Explorer als eigenen Prozess zu starten ist schon aktiv.
Delete - Mi 11.03.15 18:44
Ich habe jetzt noch ein bisschen Probiert und ein grundsätzliches Problem gefunden:
Wenn ich den Explorer über ein Process-Objekt starte, habe ich einen Prozess, der den eigentlichen Explorer startet, sich selber aber sofort wieder Beendet.
Dadurch habe ich keinen gültiges Handle mehr.
Über folgenden Code komme ich an den eigentlichen Prozess. Nicht schön, aber ich wüsste nicht wie sonst.
Verwende ich hier das MainWindowHandle es Explorer-Prozesses, funktioniert es auch.
C#-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:
| [DllImport("User32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.dll")] public static extern int AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);
[DllImport("User32.dll")] public static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")] public static extern IntPtr GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")] static extern bool AllowSetForegroundWindow(int dwProcessId);
public void SetForegroundWindowEx(IntPtr hwnd) { IntPtr threadForeground = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero); IntPtr threadExplorer = hwnd;
if (threadForeground == threadExplorer) { SetForegroundWindow(threadExplorer); } else { AttachThreadInput(threadExplorer, threadForeground, true); SetForegroundWindow(threadExplorer); AttachThreadInput(threadExplorer, threadForeground, false); } }
public Process GetExplorerProcess() { var all = Process.GetProcessesByName("explorer"); Process process = null; foreach (var p in all) { if (process == null || p.StartTime > process.StartTime) process = p; } return process; }
protected void OpenExplorer(string path, string args) { int currentPID = Process.GetCurrentProcess().Id; IntPtr currentHwnd = Process.GetCurrentProcess().MainWindowHandle;
AllowSetForegroundWindow(currentPID); SetForegroundWindowEx(currentHwnd);
using (Process process = new Process()) { process.StartInfo.FileName = "explorer"; process.StartInfo.Arguments = "/e," + args + path; process.Start(); process.WaitForExit(5000);
Thread.Sleep(3000); Process processEx = this.GetExplorerProcess();
if (processEx.MainWindowHandle != IntPtr.Zero) { AllowSetForegroundWindow(processEx.Id); SetForegroundWindowEx(processEx.MainWindowHandle); } } } |
In Windows 8.1 scheint das soweit alles schön zu funktionieren, auch wenn es nicht wirklich toll ist.
Bei Windows 7 klappt es aber wieder nicht mehr, bzw. nur sporadisch.
Solange mein Tool nach dem Autostart im Tray liegt, kommen auch die Explorer-Fenster in den Hintergrund.
Erst wenn ich mein Tool in den Vordergrund hole, sind auch nachfolgende Explorer-Fenster im Vordergrund.
Starte ich mein Tool nicht im Autostart, sondern per Hand und
nach dem Internet-Browser, dann kommen neue Explorer-Fenster immer in den Vordergrund, auch wenn mein Tool im Tray liegt. Hier allerdings auch nur direkt beim öffnen. Das nach vorne Rufen nach dem Sleep klappt hier nicht, wenn ich vorher schnell ein anderes Fenster in den Vordergrund rufe.
Bin überfragt, wie und ob ich sowas überhaupt irgendwie lösen kann.
Infos zu dem Verhalten habe ich hier gefunden, was ich soweit nachvollziehen kann:
http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx
Das verlinkte Beispiel habe ich ja oben schon so eingebaut:
http://blogs.msdn.com/b/oldnewthing/archive/2008/08/01/8795860.aspx
Einzige Möglichkeit das global zu umgehen, wäre den ForegroundLockTimeout über die Registry auf 0 zu setzen (
https://technet.microsoft.com/de-de/library/cc957208.aspx)
Das klappt offenbar und behebt das Problem komplett. Das ist aber keine tolle Lösung, da was in der Registry zu verändern, zumal das ja für alle Fenster gilt. Wäre Murks.
Bin ratlos :(
Th69 - Do 12.03.15 15:06
Ich würde dir gerne noch weiterhelfen, weiß aber jetzt nicht wie?!
Einzig was mir auffällt - und was du selber ja auch geschrieben hast - daß du noch ein wenig mit Prozess- und Window-Handles durcheinanderkommst.
Bei
AttachThreadInput mußt du ja die Prozess-Id übergeben, aber du benutzt das Window-Handle (auch wenn du die Variable
threadExplorer genannt hast) und die ersten beiden Parameter scheinst du vertauscht zu haben (zumindestens im Vergleich zu dem Code aus meinem verlinkten Artikel).
Ich weiß, daß die Benutzung der WinAPI nicht ganz einfach ist, aber wenn du so spezielle Anforderungen an dein Programm hast, dann mußt du da durch und die einzelnen Funktionen verstehen. Fast jede WinAPI-Funktion bietet als Rückgabewert (oder per
GetLastError()) die Möglichkeit einen Fehlercode, denn du dann abfragen kannst. Per P/Invoke dann einfach als Attribut
SetLastError=true setzen, s.a.
Calling Win32 DLLs in C# with P/Invoke [
https://msdn.microsoft.com/en-us/magazine/cc164123.aspx].
Viel Erfolg noch und gib nicht auf!
Delete - Do 12.03.15 22:28
Danke für die ausführliche Antwort.
Ich habe die Funktionen alle im grob mal in der Doku nachgelesen, z.B. hier
http://www.pinvoke.net/default.aspx/user32.attachthreadinput und dann im MSDN.
Bin da aber tatsächlich etwas unbewusst damit umgegangen und so auch die vertauschten Parameter bei
AttachThreadInput. Ist mir selber nicht aufgefallen.
Mir fehlt da noch ein wenig die Routine auf dem Gebiet :) Komme eigentlich aus der Webentwicklung.
Zitat: |
Bei AttachThreadInput mußt du ja die Prozess-Id übergeben |
Bist du sicher, dass hier die Prozess-ID gefordert ist? Die verwendete Überladung von
GetWindowThreadProcessId müsste doch die Thread-ID liefern und nicht die Process-ID?
Siehe hier:
http://www.pinvoke.net/default.aspx/user32.getwindowthreadprocessid
Das Handle zu verwenden ist aber natürlich Quatsch. Lag am Unverständnis von Handle/Thread-ID/Process-ID.
Ich habe jetzt aber eine Lösung gefunden, die Funktioniert. Siehe hier:
http://stackoverflow.com/a/22737820
Der ganze Trick liegt in der Funktion
SetWindowPos und diese tut das, was ich möchte.
Hier sieht man auch nochmal, dass hier mit den Thread-ID's gearbeitet wird, wenn ich das richtig verstehe.
Das Ermitteln des gestarteten Explorer-Prozess habe ich nun auch etwas angepasst, da mir das Sleep zu unsicher war.
Damit geht es deutlich besser:
C#-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:
| public static Process GetLastStarted() { Process[] processes = Process.GetProcessesByName("explorer"); Process process = null;
foreach (Process p in processes) { if (process == null || p.StartTime > process.StartTime) process = p; }
try { int count = 0;
while (process.MainWindowHandle == IntPtr.Zero && count < 30) { Thread.Sleep(100); process.Refresh(); count++; } if(count >= 30) return null; return process; } catch { }
return null; } |
Damit habe ich scheinbar alles, was ich eigentlich wollte :)
Meine Tests waren jedenfalls erfolgreich.
Werde mir jetzt noch ein bisschen PInvoke zur Gemüte führen und versuche da einfach noch bisschen mehr Routine zu bekommen.
Hatte das schon mal in Verbindung mit der Nutzung der Zwischenablage, aber das ist auch schon Jahre her.
Danke jedenfalls für die Hilfe, Geduld und die ganzen Infos :)
Th69 - Fr 13.03.15 11:22
Hallo,
ja, du hast Recht mit der Thread-Id (anstatt Process-Id) - aber eben kein Windows-Handle. ;-)
Freut mich, daß du jetzt eine Lösung gefunden hast.
Ich verstehe nur nicht, warum du diese Sleep()-Schleife benutzt, anstatt WaitForInputIdle()?
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 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!