Best Practice: Controls stuern von externem Process

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von MichaHo.

    Best Practice: Controls stuern von externem Process

    Hallo,
    kurz zum Hintergrund. Wir haben in der Produktion verschiedene HDMI-PC and Bildschirmen hängen, damit die Produktionsmitarbeiter Qualitäts Daten in eine Excel eingeben können. An mnchen Stellen hängen auch 2 Monitore um gleichzeitig zur Eingabe noch Produktionsdaten anzuzeigen. Da solche PC immer ein gefundenes Fressen für Nachtschichten ist, habe ich eine kleine KioskApp erstellt, die beim Starten des PC direkt gestartet wird (über die Registry die Shell ersetzt).
    Nun ist es so, das wenn ein Mitarbiter den Button für Excel drückt und dann Excel minimiert, das dann Excel im Hintergrund ist (kann durch Alt+Tab wieder gefunden werden). Das führt dann dazu, dasdie Mitarbeiter Excel über den Button wieder öffnen.
    Da beim Öffnen von Excel eine bestimmte Datei geöffnet wird, gibt es hier halt das Problem, das die Datei dann durch die andere Instanz von Excel gesperrt ist.

    Ich hab keine Möglichkeit gefunden, Excel nur einmal zu starten (gibt glaub nur nen Registry Eintrag). Also gehe ich nun so vor, das ich den Button beim Starten Excel auf Enabled = false setze und beim Beenden von Excel wieder auf true.

    Das ganze sieht so aus:

    C#-Quellcode

    1. private void StartProcess(Button btn, string processname)
    2. {
    3. btn.Enabled = false;
    4. Process proc = new Process();
    5. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    6. proc.StartInfo.FileName = processname;
    7. proc.EnableRaisingEvents = true;
    8. proc.Exited += new EventHandler((sender,e) => Process_Exited(sender,e,btn));
    9. proc.Start();
    10. }
    11. private void EnableButton(Button btn)
    12. {
    13. btn.Enabled = true;
    14. }
    15. private void Process_Exited(object sender, EventArgs e, Button b)
    16. {
    17. if(this.InvokeRequired)
    18. {
    19. this.Invoke((Action)(() => EnableButton(b)));
    20. return;
    21. }
    22. EnableButton(b);
    23. }
    24. private void btnExcel_Click(object sender, EventArgs e)
    25. {
    26. StartProcess(btnExcel, "Excel.exe");
    27. }


    Nun wollte ich fragen, ob dies so richtig ist, oder ob es anders/besser gelöst werden kann.
    Die StartPrcoess Methode habe ich erstellt, weil es teilweise 3 bis 6 Button sind, die dort behandelt werden müssen.

    Danke Euch
    "Hier könnte Ihre Werbung stehen..."
    Hallo @MichaHo

    Ich hatte mal eine ähnliche Aufgabenstellung (bei uns war es ein Terminal welche Fahrzeugauslieferungen angezeigt hat, am Terminal ein NFC Reader und ein Nummernblock).
    Ich würde ich sogar noch mehrere Überlegungen anstellen. Was ist wenn "etwas hängt", was ist wenn irgendwas abstützt.

    Ich hatte mir damals einen "Watchdog" gebaut. Dieser startet als Dienst - kann auch ne exe ohne UI sein.
    Nach dem start überprüft dieser die "Umgebung", startet die App die gestartet werden soll. Bei dir eben Excel oder so.
    Im Hintergrund überwachte er allerdings den gestarteten Process und prüft ob immer ob eine Antwort zurückkommt. Wenn nein - App "hängt" evtl. und startet diese neu.
    Sollte zwar nicht vorkommen - aber für den Fall der Fälle eben. In unserem Fall kann es z.b. mal vorkommen das nach dem Start nicht alle Netzlaufwerke verfügbar sind, der Watchdog startet dann den Rechner neu um auf Nummer sicher zu gehen.

    So ein Watchdog ist echt praktisch - vorallem weil man diesen getrennt von der App entwickeln und verbessern, und für andere Projekte verwenden kann.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hi @Nofear23m,
    ich hab mal mit Backgroundworker rum experimentiert, das ging nur mäßig.
    Einen Dienst hab ich auch schon mal geschrieben um Messdaten von einem Netzwerk Thermometer in eine Datenbank zu schreiben, das ging prima.

    Hier ist es allerdings so, das ich lediglich unterbinden will, das die Mitarbeiter den Button (z.Bsp. Excel) mehrmals drücken.

    das KioskProgramm sieht zum Beispiel so aus:

    Mitarbeiter in der Nachtschicht kommen auf die lustigsten Ideen, das meiste was man über Tastenkombinationen machen kann bei Windows 10 hab ich per Gruppenrichtlinie abgeschaltet.
    "Hier könnte Ihre Werbung stehen..."
    @MichaHo Du könntest doch die laufenden Excel-Prozesse zählen und da ggf. eingreifen (aufm W7 sehe ich die einzeln, W10 kann ich jetzt nicht testen).
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany in Windows 10 werden mehrere offene Excel "Mappen" zumindest in den Details als einmal angezeigt:


    In der Liste der Prozesse stehen natürlich 3x drin:


    Schließe ich in der Detail-Ansicht dann die Excel.exe gehen alle Fenster zu.
    Grüße , xChRoNiKx

    Nützliche Links:
    Visual Studio Empfohlene Einstellungen | Try-Catch heißes Eisen
    @RodFromGermany Coole Idee, hatte ich noch garnicht dran gedacht, hab mich zu sehr auf den Button konzentriert.
    Wie gesagt, mein Code Gedöns oben funktioniert auch einwandfrei.
    Ich hab dennoch mal diese Möglichkeit gebastelt, aber so ganz klappt es noch nicht, bzw. klappt es nur, wenn man keine Arbeitsmappe geöffnet hat oder eine neue Arbeitsmappe erstellt.
    So sieht es im TaskManager aus:

    ich den Code mal so abgeändert:

    C#-Quellcode

    1. private static readonly List<string> CurrentProcesses = new List<string>();
    2. public bool IsInUseMode => CurrentProcesses.Contains(Process.GetCurrentProcess().ProcessName);
    3. private void StartProcess(Button btn, string processname)
    4. {
    5. //btn.Enabled = false;
    6. if (IsInUseMode || CurrentProcesses.Count > 1) return;
    7. Process proc = new Process();
    8. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    9. proc.StartInfo.FileName = processname;
    10. //proc.EnableRaisingEvents = true;
    11. //proc.Exited += new EventHandler((sender, e) => Process_Exited(sender, e, btn));
    12. proc.Start();
    13. private void btnExcel_Click(object sender, EventArgs e)
    14. {
    15. CurrentProcesses.Add("Excel.exe");
    16. StartProcess(btnExcel, "Excel.exe");
    17. }
    18. }


    ich müsste glaub abfragen, ob eine Arbeitsmappe geöffnet ist, nur wie??

    EDIT: @xChRoNiKx genau so ist es.... ich müsste also irgendwie abfragen, ob eine Mappe geöffnet ist

    EDIT2: auch das Ändern der If Abfrage bringt nichts:

    C#-Quellcode

    1. if (Process.GetCurrentProcess().ProcessName.Contains(processname)) return;
    es wird trotzdem eine neue Excel Instanz geöffnet
    "Hier könnte Ihre Werbung stehen..."

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „MichaHo“ ()

    Das wäre dann ein Unterschied zwischen W7 und W10. :/

    MichaHo schrieb:

    es wird trotzdem eine neue Excel Instanz geöffnet
    Und die schmeiß wieder raus. Oder?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hi @RodFromGermany würde ich ja tun, aber irgendwie hab ich das Gefühl, das es da noch einen Unterschied gibt ob eine Mappe geöffnet ist oder nicht.

    Jedenfalls ergeben diese Abfragen

    C#-Quellcode

    1. if (Process.GetCurrentProcess().ProcessName.Contains(processname)) return; //geht nicht
    2. if (Process.GetProcessesByName(processname).Length > 0) return; //geht nicht
    keine Reaktion.
    Es wird dennoch wieder eine Instanz geöffnet, von daher wüsste ich jetzt nicht, wie ich diese Instanz wieder schließe wenn er sie garnicht erkennt...
    "Hier könnte Ihre Werbung stehen..."
    Hi,

    kannst du nicht mit der Prozess ID vllt. werkeln?
    Sobald du startest vorher überprüfen welche Instanzen vorhanden sind und wie deren ID ist und dann die neue Instanz, deine ID, zwischenspeichern? Wird ja alles über deinen Launcher gemacht, sozusagen, oder?

    Gruß,
    Drahuverar
    Option Strict On!
    @MichaHo Unter der Nebenbedingung W7 (W10 kann ich hier nicht testen):
    Sobald ein Excel offen ist, hast Du die kommunizierende Process-Instanz.
    Wird eine zweite geöffnet, schließt Du die, die nicht die erste ist (.Equals()).
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany hab es jetzt mal so abgeändert:

    C#-Quellcode

    1. private void StartProcess(Button btn, string processname)
    2. {
    3. Process proc = new Process();
    4. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    5. proc.StartInfo.FileName = processname;
    6. proc.Start();
    7. if (Process.GetProcesses().Equals(proc)) proc.Close();
    8. }


    funktioniert wenn ich nur excel öffne ohne eine neue Mappe oder ohne eine Mappe auszuwählen.
    Öffne ich excel, wähle eine Mappe, drücke den Button geht wieder neu neue Instanz auf.
    Das witzige ist, der Prozess ansich in den Details hat IMMER die gleiche Prozess ID, egal wieviele Mappen ich öffne:


    schieße ich per code die Process.ID ab, sind ALLE Mappen zu. (was nicht gewollt ist)
    "Hier könnte Ihre Werbung stehen..."
    Hi, Ja, Office 365 (also 2016 zur Zeit).
    Die Excelmappe ist bekannt, aber es geht ja auch um andere Anwendungen die auch nur einmal gestartet wedren sollen.
    Ein Programm zum mBeispiel heisst TE_PaketKleben_Admin.exe
    Selbst das öffnet sich mit

    C#-Quellcode

    1. private void StartProcess(Button btn, string processname)
    2. {
    3. Process proc = new Process();
    4. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    5. proc.StartInfo.FileName = processname;
    6. proc.Start();
    7. if (Process.GetProcessesByName(processname).Equals(proc.ProcessName)) proc.Close();
    8. }

    mehrfach...
    Ich glaub ich muss doch zu meinem ursprünglichen Code im Eingangspost zurück, das hat zumindest funktioniert.
    "Hier könnte Ihre Werbung stehen..."
    Nur so als Randinfo:
    GetProcessesByName erwartet den Prozessnamen ohne .exe. (und gibt unter anderem ein Array von Prozessen zurück)
    Bedeutet
    if(Process.GetProcessesByName("EXCEL").Length > 0) gibt true zurück wenn Excel gestartet ist.

    LG
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    @fichz ich war mir fast sicher, das ich das schon probiert habe... hab ich aber scheinbar nicht, denn DAS ist der einzige Weg, der funktioniert (bis jetzt).
    meine StartProcess sieht nun so aus:

    C#-Quellcode

    1. private void StartProcess(Button btn, string processname)
    2. {
    3. if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processname)).Length > 0) return;
    4. Process proc = new Process();
    5. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    6. proc.StartInfo.FileName = processname;
    7. proc.Start();
    8. }

    funktioniert soweit für alle Buttons die benutzt werden müssen.
    Danke

    P.S. wenn jetzt noch jemand weis wie man das Fenster (oder den Process) in den Vordergrund holt wenn der Button das 2. Mal gedrückt wird....
    "Hier könnte Ihre Werbung stehen..."
    @fichz Danke Dir, das klappt hervorragend:

    C#-Quellcode

    1. [DllImport("User32.dll")]
    2. private static extern bool SetForegroundWindow(IntPtr handle);
    3. [DllImport("User32.dll")]
    4. private static extern bool ShowWindow(IntPtr handle, int nCmdShow);
    5. [DllImport("User32.dll")]
    6. private static extern bool IsIconic(IntPtr handle);
    7. const int SW_RESTORE = 9;
    8. public static void BringProcessToFront(Process process)
    9. {
    10. IntPtr handle = process.MainWindowHandle;
    11. if (IsIconic(handle))
    12. {
    13. ShowWindow(handle, SW_RESTORE);
    14. }
    15. SetForegroundWindow(handle);
    16. }
    17. private void StartProcess(Button btn, string processname)
    18. {
    19. Process[] p = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processname));
    20. if (p.Length > 0)
    21. {
    22. BringProcessToFront(p[0]);
    23. return;
    24. }
    25. Process proc = new Process();
    26. proc.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
    27. proc.StartInfo.FileName = processname;
    28. proc.Start();
    29. }
    "Hier könnte Ihre Werbung stehen..."