MessageBox aus anderem Prozess bestätigen



  • Hallo Forum User,

    ich habe eine Application mit einem Thread der zyklisch diverse Werte aktualisiert und dazu eine Ethernet Verbindung zu einer SPS verwendet.

    Mein Problem ist, das zum Beispiel beim Ausschalten der Steuerung diese (logischerweise) nicht mehr auf Abfrage aus dem Thread antworten kann.
    Der Thread erkennt dies durch ein TimeOut und versucht zuerst durch dis- und re-connect die Verbindung wieder herzustellen. Wenn das nicht klappt, zeigt der Thread anschließend dem Bediener eine Meldung mittels einer Instanz von TTaskDialog (das ist ein modales Dialog) an.
    Dieser Dialog hat in diesem Fall ein "Reconnect" und einen "Abbrechen" Button.

    Solange dieser Dialog aus dem Thread ansteht, habe ich (auch klar) das Problem, dass ich aus meiner Hauptanwendung aus das Programm nicht beenden kann. Denn nach dem Aufruf von Terminate() warte ich auf die Beendigung den Threads, was aber nicht erfolgen kann. Der steckt ja im Dialog und wartet auf Bediener-Reaktion.

    Anstatt ein Modales ein nicht modale Fenster zu verwenden, hilft mir leider auch nicht. Denn ich will ja den Thread solange anhalten, bis der Bediener auf "Reconnect" oder "Abbrechen" klickt. Je nachdem versuche ich dann wieder ein Reconnect oder deaktiviere die Verbindung und sende der Main via PostMessage() ein SC_CLOSE und beende das Programm.

    Also war meine Idee, bei der Aufforderung an das Hauptprogramm sich zu beenden (durch z.B. ein Exit oder durch WM_QUERYENDSESSION) das Dialogfenster vom Thread selber zu schließen. Nur weiß ich nicht genau wie bzw. mir fehlt der Ansatz dafür.

    Ich habe schon vieles durch Tante Google gefunden. Das meiste bezieht sich auf die WinAPI. Im Grunde ist die auch kein Problem. Doch wollte ich erstmal hier hören, ob es nicht einen einfacheren Weg (nein WinAPI ist nicht schwieriger) gibt, das zu lösen.

    Ich danke jedem für seine Ideen, Gedanken und Ratschläge. 😃

    Schönes (Pfingst) Wochenende



  • Meiner Meinung nach gehört der Dialog aus dem Thread herausgenommen, oder zumindest sollte er als non-modaler Dialog ausgelegt sein.
    Einen Thread steuert man am besten über Events. Innerhalb des Threads verwendet man dann WaitForMultipleObjects(). Damit kannst Du innerhalb des Threads prüfen, ob der User den Dialog bestätigt hat und trotzdem auf andere Events reagieren. Sind zumindest meine Erfahrungen dazu.



  • Hallo Million Voices,

    In Grunde verstehe ich voll und ganz hinter Deine Aussage. Nur war es zum Zeitpunkt der Entwicklung des Threads die einfachste Art, sich selber durch ein Modales Fenster zeitweise anzuhalten. Denn der Thread wiederholt eine Anzahl an Abfragen an die SPS über die Ethernet- Schnittstelle. Dadurch konnten halt mehrere Dinge auf einmal erledigt werden. Zum einen die Fehlermeldung, zum anderen das Warten auf die Reaktion des Bedieners und die (eventuelle) Beseitigung des Grundes für die Meldung.

    So richtig gefallen hat mir das damals auch nicht, aber aus Zeitgründen war es doch die logischste Entscheidung.

    Natürlich könnte ich das ganze jetzt umbauen, doch mit der Zeit sind die Aufgaben des Threads gewachsten und der Aufwand ist doch schon ein etwas größere geworden.

    Ich habe mir über die Feiertage noch so einiges mittels Google in Netz angeschaut. Ich denke, das ich hiermit mal in den WinAPI Forum nachfragen sollte. Es gibt wohl die Möglichkeit, per Botschaften eine simulierte Bestätigung an das Dialog zu senden.

    Ich danke Dir für Deine Reaktion und Meinung.



  • Wenn sich das Abfragefenster im selben Windows-System befindet, ist das nicht besonders schwer. Mit FindWindow oder FindWindowEx nach dem Dialogfenster suchen, wobei es von Vorteil ist, wenn der Fenstertitel charakteristisch ist und nur einmal vorkommt. Dann mit FindWindowEx nach dem "Unterfenster" Button mit dem entsprechenden Text suchen. Wenn dessen Handle gefunden wurde, kann das Drücken des Knopfes simuliert werden, meist klappt das mit Mausklicksimulation:
    PostMessage (Button, WM_LBUTTONDOWN, 0,0);
    Sleep (20);
    Application->ProcessMessages();
    PostMessage (Button, WM_LBUTTONUP, 0, 0);
    Wenn du zusätzlich eine Microsoft-Entwicklungsumgebung besitzt, ist es sehr hilfreich, das Dialogfenster mit dem Programm Spy++ einmal zu untersuchen, ansonsten einmal nach dem Freeware-Programm XSpy googeln.



  • Danke W. Posur, das ist es auch, was ich so mit Googles Hilfe bisher gefunden habe. Daher folgte auch mein Gedanke, diese Post in WinAPI weiter zu führen.

    Ich habe mir das Spy++ besorgt und angeschaut. (Pfingsten; zwar nicht der Heilige, aber so mancher andere Geist ist in mir gefahren.)

    Bisher habe ich mir auch schon eine Funktion mittels FindWindow() (für das Handel per Fenster Titel) und EnumChildWindows() aufgebaut. EnumChildWindows() deswegen, weil ich mit FindWindowsEx() nicht direkt an den Button komme. Der ist in einer tieferen Ebene. (Fenster Handle->"DirectUIHWND"->"CrtlNotifySink"->"Button", alles ClassNames)

    Bisher habe ich keine andere Idee, als rekursiv durch alle Klassen zu laufen, bis ich den korrekten Fester Titel (in meinem Fall das "Abbrechen") und den korrekten ClassName habe (halt das Button). Das die Texte hierbei bekannt und korrekt sein müssen, ist klar. Auch dass, wenn es zwei Fenster mit gleichem Caption gibt, ich keine genauere Analyse bekommen, welcher der korrekt ist.

    Aber immerhin ein guter Fortschritt. Sollte jemand sowas schon fertig haben und hier Code posten oder mir den einen oder anderen Tipp geben, danke ich Euch schon mal im voraus.

    Ach ja, wenn meine Informationen korrekt sind, kann der Tastendruck auch durch ein PostMessage(Button, BN_CLICKED,0,0); erfolgen. Dann wird der Rest (das Sleep() und des WM_LBUTTONUP) nicht mehr gebraucht.



  • Du brauchst keine Enumeration oder Callback-Funktion.
    Wenn du das Handle deines Dialogfensters gefunden hast, kannst du den Button mit FindWindowEx leicht finden, der este Parameter dabei ist das Handle des Hauptfensters.
    Beispiel:
    ButtonHandle = FindWindowEx (FensterHandle, NULL, "Button", "&Abbrechen");



  • Ich bekomme aber als Rückgabe ein NULL und kein Handel.

    Daher habe ich mir eine kleine TestApp erstellt. Dort sind ein PageControl mit zwei Tabs, auf jedem Tab 2 Buttons, und ein Panel drauf. Das PageControl und das Panel kann ich direkt ansprechen. Die Buttons (alle mit unterschiedlichen Cptns) kann ich nicht so ansprechen. Erst wenn ich mit FindWindowsEx(Fenster, NextWindow,..) mit das Handel der Unterfenster hole.

    Deswegen dachte ich sofort, ich muss durch die Fenster enumerieren, mir dann mittels Caption und Class das korrekte Handel suchen.

    Sollte denn das ButtonHandle = FindWindowEx (FensterHandle, NULL, "Button", "&Abbrechen"); auch durch die Untermenge hindurch suchen und mir das erste passende Handel zurück geben?



  • Wenn du kein Handle zurückbekommst, war die Fragestellung falsch.
    Du solltest dir mit Spy++ mal den Button ansehen. Dort siehst du dann sein Handle und auch das Parent-Fenster. Möglich, daß du erst das Handle des Tabs bzw. Panels finden mußt und den Button erst als Unterfenster des Panels findest. Spy++ kann dir die Hierarchie des Ganzen gut anzeigen, der Button muß so zu finden sein.
    Wichtig bei der Analyse des Scheiterns ist auch, daß der Name des Buttons korrekt ist &Abbrechen ist richtig beim Unterstrich unter dem A, ansonsten heißte es einfach Abbrechen, schließlich zeigt dir Spy++ auch an, ob das Control wirklich vom Typ Button ist.



  • Genau das habe ich gemacht. Die Struktur, die mir Spy++ anzeigt, sieht in so aus:

    Window 000F079C "SPS Verbindungsfehler" #32770 (Dialog)
    		Window 0007093A "" DirectUIHWND
    			Window 000F07EA "" CtrlNotifySink
    				Window 001802FE "" ScrollBar
    			Window 000E080E "" CtrlNotifySink
    				Window 0005093E "" ScrollBar
    			Window 00060942 "" CtrlNotifySink
    				Window 000B08EC "" ScrollBar
    			Window 00040938 "" CtrlNotifySink
    				Window 00050590 "" ScrollBar
    			Window 0005058E "" CtrlNotifySink
    				Window 0005058C "" SysLink
    			Window 0004058A "" CtrlNotifySink
    				Window 00040586 "" SysLink
    			Window 00040588 "" CtrlNotifySink
    				Window 00050584 "Reconnect" Button
    			Window 000E0840 "" CtrlNotifySink
    				Window 00080874 "Abbrechen" Button
    

    Die Fragestellung ist also mit "Button" und "Abbrechen" korrekt.
    Dennoch bekomme ich das Null zurück und daher wollte ich die Enumeration mit EnumChildWindows() verwenden. Unter Umständen weiß ich ja nicht, wie "tief" ich suchen muss.

    Mir macht es nur ein wenig Sorgen, dass diese Routine eventuell sehr lange braucht, um das gesuchte zu finden. Das können ja (z.B. bei eine Application) hunderte oder gar tausende Fenster sein. Da ich das aber eigentlich nur auf das Dialog Fenster loslassen will, sollte die Suchzeit unerheblich sein.



  • HWND Fehlerdialog = FindWindowEx (NULL, NULL, "#32770", "SPS Verbindungsfehler");
    HWND AbbruchButton = FindWindowEx (Fehlerdialog, NULL, "Button", "Abbrechen");

    Fehlerdialog ist dann das richtige Oberfenster-Handle, AbbruchButton liefert aber NULL ?



  • Genau so ist es. Ich habe es extra nochmals mit Deinen Angaben getestet.
    Das "Fehlerdialog" Handle wurde mit 0x0014078C (halt die akt. Adr.) und das "AbbruchButton" mit NULL beantwortet.

    Das ist aber nicht nur im Dialog so, sondern auch in meinem kleinen Test Application. Dort bekomme ich mit FindWindwEx() auch ein NULL, wenn ich nicht im passenden ChildWindow bin.

    Ach ja, Windows 7, C++ Builder XE (inkl. SP und Patches) und Unicode.Ich glaube zwar nicht, dass das von Entscheidung ist, aber jetzt ist es halt mal gesagt.
    Und da ich Unicode verwende, habe ich die Texte in _T() eingekleidet.

    Habe aber auch schon mit w_chart[] gearbeitet, nur um auf Nr. sicher zu gehen.



  • Dann probier mal, ob dies funktioniert:

    HWND Fehlerdialog = FindWindowEx (NULL, NULL, "#32770", "SPS Verbindungsfehler");
    HWND DirectUI = FindWindowEx (Fehlerdialog, NULL, "DirectUIHWND", NULL);
    HWND Notify = FindWindowEx (DirectUI, NULL, "CtrlNotifySink", NULL);
    for (int i = 0; i < 7; i ++)
    Notify = GetNextWindow (Notify, GW_HWNDNEXT);
    HWND AbbruchButton = FindWindowEx (Notify, NULL, "Button", "Abbrechen");



  • Ja, das funktioniert einwandfrei, hier kann ich sauber jedes einzelne Handel, mit Spy++ vergleichend, sehen bzw durchlaufen.
    Das entspricht auch meinen Erfahrungen mit meinem Test Programm.

    Was mir halt an diesem Aufbau nicht gefällt, ist die Tatsache, das ich doch schon relativ genaue Infos über den Aufbau des (in meinem Fall vom TaskDlg) "fremden" Programms haben muss. (Die Namen, die Anzahl nötiger GetNextWindow() usw.)

    Ich wollte mir eine allgemeine Lösung aufbauen um an jedes Handel in jeder Anwendung zu kommen. Ausgefiltert nach Caption und ClassName.

    Jedoch habe ich so eben eine Lösung für mein spezielles Problem gefunden. Das TaskDlg scheint eine "richtig" Message Behandlung zu haben. Zumindest habe ich in der WinAPI Hilfe diesen Eintrag gefunden: http://msdn.microsoft.com/en-us/library/windows/desktop/bb787499(v=vs.85).aspx

    Dem nach kann man durch:

    HWND Fehlerdialog = FindWindowEx (NULL, NULL, _T("#32770"), _T("SPS Verbindungsfehler")); 
    PostMessage(Fehlerdialog, TDM_CLICK_BUTTON, ID_CANCEL,0);
    

    die Botschaften Behandlung direkt ansprechen, wenn die ID bekannt ist.

    Ich werde mir aber dennoch die oben genannte Funktion fertigstellen. Ich denke, dafür werde ich bestimmt noch einen Anwendungsfall bekommen. Nicht immer wird ja die ID bekannt sein, oder in fremden Prg's gleich bleiben.

    Ich danke Dir für Deine Hilfe. Wenn ich die Funktion fertig habe, werde ich die hier posten. Das kann aber eine Zeit lang dauern, da ich ein paar sehr dringende Aufgabe vorziehen muss.



  • Okay,
    war mir nicht klar, daß dein Fenster nicht vom BCB sondern von .NET kommt. Dann solltest du tatsächlich mit der dafür vorgesehenen Funktion arbeiten.



  • Hmm, mit ist/war nicht bewusst, dass der TaskDialog von .Net stammt.

    Im Builder ist der in der Tool-Palette unter Vista-Dialogfelder als TTaskDialog und den habe ich in Verwendung. Generiert zur Laufzeit mit TTaskDialog *TaskDlg = new TTaskDialog(NULL);

    Doch das Verhalten, dass man im korrekten ChildWindow sein muss um das Handel zu erhalten, dass habe ich in keinem Forum, Hilfe oder Google Eintrag gesehen. Oder ich habe es übersehen.

    Jedenfalls kann mit einem einfachen ButtonHandle = FindWindowEx (FensterHandle, NULL, "Button", "&Abbrechen"); hier nicht gearbeitet werden, da es eindeutig zu einem Null führt. Und das hat noch nicht einmal was mit dem taskDialog zu tun, dass Verhalten habe ich auch mit der Test Application.

    Ich finde die ganze Sache schon sehr verwunderlich.

    Ich danke Dir für Deine Hilfe. Vielleicht findest Du ja mal ein paar Minuten und kannst den Vorgang mal bei Dir wiederholen. Würde mich schon interessieren, ob es bei anderen auch so ist.



  • Ist wahrscheinlich gar kein .NET, dein Link hat mich nur verwirrt.
    TTaskDialog kannte ich nicht, gibt es erst ab Vista, brauche hier aber noch XP Kompatibilität und arbeite mit älteren Builder-Versionen. Schein ein Dialog der WINAPI zu sein ähnlich einer Message-Box nur mit mehr Möglichkeiten.

    Bei deinem Code war allerdings &Abbrechen nicht richtig, es muß dort Abbrechen stehen (gemäß Spy++). Du kannst ja mal statt "Abbrechen" NULL eingeben und schauen, ob dann der erste Button gefunden wird.



  • Das mit dem "&Abbrechen" hatte ich beim einfügen schon erkannt. (hatte aus Deinem vorherigem Post und nicht aus meinem Code kopiert, sorry für die Verwirrung)

    Wenn ich das Caption auf NULL stelle, bekomme ich das Handel vom gleichen Button zurück. Ist aber eigentlich auch klar, da ist ja nur ein Button im ChildWindow. Wenn es zwei wären, denke ich, würde ich das Handle vom ersten bekommen. Da weißt man dann nicht genau, welches es ist.

    Daher will ich ja eine Filtermaske mit ClassName und Caption. Mehr Angaben habe ich ja nicht (aus einem Fremden Prozess) und das es in einem Windows zwei gleiche Button mit gleichem Text gibt, macht ja nicht viel Sinn.
    Wenn es jedoch zwei Window Element gibt, die gleichen Text in mehreren Buttons haben, habe ich Pech. (z.B. zwei Panels mit 2 gleichen Buttons)

    War eigentlich der Beitrag jetzt OffTopic? Immerhin haben wir ja nur WinAPI Funktionen besprochen.

    P.S. Du hast recht, TTaskDialog ist (nach alter Borland Manier) eine Kapselung von TaskDialog aus der WinAPI:
    http://msdn.microsoft.com/en-us/library/windows/desktop/bb756938.aspx



  • Okay, die Sache ist ja jetzt gelöst.
    War BCB using WINAPI, also nicht off topic.
    Die direkte Funktion des TaskDialogs ist sicherlich die beste Lösung.
    Alternativ kann man auch mit EnumChildWindows arbeiten (benötigt eine Callback-Funktion, die z. B. die Caption prüft), bei den wenigen Child Windows kann das auch nicht lange dauern.


Anmelden zum Antworten