Benutzer mit den meisten Antworten
Tastaturmessages bei nicht modalen Dialogen

Frage
-
Hallo Forum
Ein Problem so alt wie Windows und immer wieder stoße ich auf unbekanntes Terrain.
Ich habe eine Dialog-basierte MFC Anwendung, wobei die App das Dialogfenster nicht-modal ins Leben ruft (Create). Anhand verschiedener Ereignisse kann die App dann das Fenster auch wieder vernichten und sich selber beenden usw.
Ich hatte ursprünglich versucht (um die Sache einfach zu halten), die Routinen OnCancel() und OnOK() zu überschreiben und das Ereignis auf irgendwelche interne Variablen zu schreiben, um einerseits zu verhindern, dass die ENTER oder ESC Taste gleich das ganze Programm beenden, andererseits auch um diese Tasten eben anderweitig zur Verfügung zu haben.
Ich bilde mir auch ein, dass dieses Konstrukt ganz gut funktioniert hat, bis - nach vielen Erweiterungen im Programm - mich der Kunde darauf hingewiesen hat, dass bei bestimmten Feldern (die selber ausprogrammiert sind), die ENTER Taste nicht funktioniert.
Nun bin ich der Sache nachgegangen und habe festgestellt, dass ich gar kein OnCancel() und OnOK() mehr aufgerufen bekomme.
In der Folge habe ich also der Hauptdialogklasse eine "PreTranslateMessage" Funktion eingefügt, um mal nachzusehen, was überhaupt so passiert ... und ich muss feststellen, dass ich PreTranslateMessage auch nicht aufgerufen bekomme.
Ich bin mir nicht sicher, ob das Hauptdialogfenster schon nicht-modal war, als alles noch funktioniert hat...ich sitze beim Kunden und habe keinen Zugriff auf die uralten Backups. Kann es daran liegen? Wie bekomme ich Tastaturdrucke in einen nicht-modalen Dialog rein?
Grüße
FireHeart
Antworten
-
> http://www.codeproject.com/KB/dialog/gettingmodeless.aspx
Der behandelt genau das was ich eben geschrieben habe. Einfach das DIng erzeugen und die Main-Message-Loop den Rets machen lassen.
> Allerdings vermisse ich dort ebenfalls ein paar erwähnende Worte über die Message Pump. Ich kann das Beispiel auch leider nicht compilieren, weil ich derzeit nur VC6 besitze....Brauchst Du auch gar nicht. Eigentlich musst Du Dir um die Message-Loop keine Gedanken machen. Erzeuge das Fenster und lass die Main-Message-Loop den Rest machen...
Dein Ansatz ist enfach falch in einer Funktion den Dialog zu erzeugen und die Entsorgung auch noch in der selben zu machen.
Das ist eben nicht der Windows Ansatz...Sorge immer dafür, dass Deine Handler so schnell wie möglich Ihren Job beenden. Dann werden auch die Messsages wieder gezogen.
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de- Als Antwort markiert Fire-Heart Mittwoch, 17. August 2011 11:34
Alle Antworten
-
Hallo Forum
Ich habe jetzt ein kleines Testprogramm geschrieben und folgendes festgestellt:
Wenn ich den Dialog modal aufrufe, dann kommen alle Tastaturmessages über PreTranslateMessage herein und es läuft alles wie gewohnt.
Wenn ich den Dialog von der App aus nichtmodal aufrufe, dann muss ich zunächst noch in den Dialog ein "LookForMessage" einbauen, so wie ich das immer mache:
void CPTDlg::LookForMessage(void) {
MSG message;
for (int i=0; i<50; i++) {
if (::PeekMessage(&message,NULL,0,0,PM_REMOVE)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
}
}das ich dann von der App in der Beende-Mich-Schleife aufrufe:
BOOL CPTApp::InitInstance() {
...
CPTDlg dlg;
dlg.Create();
while (!dlg.m_want_close) {
dlg.LookForMessage();
Sleep(20);
}
dlg.DestroyWindow();// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}Soweit so gut. Nur jetzt bekomme ich tatsächlich keine Tastaturmessages mehr in meinen Dialog. Warum nicht?
Muss ich in diesem Fall in der App eine PreTranslateMessage überschreiben worin ich die Message an den Dialog weiterleite? Und wie geht das dann konkret? Welche Funktionen sind zu verwenden?
Grüße
FireHeart
-
Entschuldige, aber das ist absoluter Quatsch was Du da machst.
Schmeiß bitte die Funktion LookForMessage aus Deinem System.
1. Jedes UI-Window benötigt eine Message-Loop. Wo ist diese bitte bei Dir? LookForMessage zählt nicht ;) , denn die ist keine MFC Message-Loop (siehe 2.)
2. Nur wenn man auch dir MFC Funktionen für den Message-Pump nutzt oder die eingebaute Message-Loop von CWinApp::Run hast Du auch die Funktion von PreTranslateMessage.
3. Dialog Nachrichten werden durch IsDialogMessage behandelt. Wenn eine MFC Message-Loop läuft wird diese Funktion automatisch in CDialog::PreTranslateMessage aufgerufen.
4. Was Du das machst lässt sich sofort mit DoModal machen, denn Dein Dialog wird erzeugt soll bearbeitet werden und schließen.
5. Wenn Du die Eingabetaste und Escape behandeln willst dann ist PreTranslateMessage die richtige Position. Ansonsten:
http://blog.m-ri.de/index.php/2008/09/21/die-return-taste-in-dialogen-eine-unendliche-geschichte/
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
Hallo Martin
Es scheint ich komme der Sache schon näher. Ich war ja immer der Meinung, dass "LookForMessage" nur ein untaugliches Hilfskonstrukt sei, es hatte aber irgendwie funktioniert und blieb ich dabei.
Wie verwende ich die Message Pump im geschilderten Fall für den Dialog richtig? Kannst Du mir da ein paar Codeschnipsel posten?
zu 4.: Natürlich könnte ich in diesem einfachen Fall auch DoModal verwenden, aber das war ja nur ein Beispiel/Testprogramm.
Grüße
FireHeart
-
> zu 4.: Natürlich könnte ich in diesem einfachen Fall auch DoModal verwenden, aber das war ja nur ein Beispiel/Testprogramm.
Selbst wenn es nur ein Testprogramm ist. Warum solltest Du eine eigene Message-Pump bauen? Die MFC sieht im Framework vor, dass entweder CWinApp::Run verwendet wird, oder DoModal.
Wenn man eine eigene Messageloop benötigen sollte wäre es so etwas:
MSG msg; while (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) { if (!AfxPumpMessage()) break; // terminate if WM_QUIT received }
Konstrukte dieser Form benutzt man z.B. wenn man eine AbortProc beim Drucken einbaut...
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
Hallo Martin
Ich möchte ja jetzt nicht deine kostbare Zeit vergeuden, aber wie stelle ich es jetzt wirklich an, wenn ich einen nicht-modalen Dialog mit Create() ins Leben rufe, dass der dann auch seine Messages kriegt?
Ich hab grad eben selbst ein wenig im Internet gestöbert und bin zu einer leicht modifizierten "LookForMessage" Routine gekommen:
void CFooDlg::LookForMessage(void) {
MSG message;
while(PeekMessage(&message, NULL, 0, 0,PM_REMOVE)) {
if(!IsDialogMessage(&message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}Wenn ich die von der App aus (in der besagten Schleife) aufrufe, funktionieren meine Keystrokes jetzt auch wieder. Ich könnte also das Problem als gelöst betrachten, bloß hätte ich dann offenbar nichts daraus gelernt.
Also mal ganz von vorne (vielleicht stimmt ja hier schon etwas nicht):
Ich erzeuge mir normalerweise mit dem Wizzard einen Dialog und füge dann die Funktion
BOOL Create(void);
ein, die im Body folgendermaßen aussieht:
BOOL CFooDlg::Create(void) {
return(CDialog::Create(IDD));
}Dann lege ich mir im aufrufenden Modul (kann auch die App selbst sein) die Dialogklasse an und erzeuge sie ... und wenn ich sie nicht mehr brauche, zerstöre ich sie:
CFooDlg dlg;
dlg.Create();
....
dlg.DestroyWindow();
Dazwischen muss ich jetzt aber dafür sorgen, dass das Ding auch Messages kriegt....wie mach ich das auf saubere Weise?
Grüße
FireHeart
-
Hallo Forum
Auf Codeproject gibt es auch einen ganz interessanten Artikel:
http://www.codeproject.com/KB/dialog/gettingmodeless.aspx
Allerdings vermisse ich dort ebenfalls ein paar erwähnende Worte über die Message Pump. Ich kann das Beispiel auch leider nicht compilieren, weil ich derzeit nur VC6 besitze....
Grüße
FireHeart
-
Ich möchte ja jetzt nicht deine kostbare Zeit vergeuden, aber wie stelle ich es jetzt wirklich an, wenn ich einen nicht-modalen Dialog mit Create() ins Leben rufe, dass der dann auch seine Messages kriegt?
In einem normalen Programm läuft immer eine Message Loop.
Dh. SDI Programm startet, Du erzeugst einen Dialog mit Create und die Hauptmessage Loop in CWinApp::Run verteilt weiter die Nachrichten.
> Ich hab grad eben selbst ein wenig im Internet gestöbert und bin zu einer leicht modifizierten "LookForMessage" Routine gekommen:Und auch das ist Unfug.
Wenn ich die von der App aus (in der besagten Schleife) aufrufe, funktionieren meine Keystrokes jetzt auch wieder. Ich könnte also das Problem als gelöst betrachten, bloß hätte ich dann offenbar nichts daraus gelernt.
Nein. Das hast Du nicht.
Du hast immer noch nicht verstanden, dass es in einem Programm i.A. eine einzige Messageloop gibt und Du keine zweite irgendwo bauen musst.Dann lege ich mir im aufrufenden Modul (kann auch die App selbst sein) die Dialogklasse an und erzeuge sie ... und wenn ich sie nicht mehr brauche, zerstöre ich sie:
CFooDlg dlg;
dlg.Create();
....
dlg.DestroyWindow();Und das ist genau eben Unsinn. Wenn Du für die Laufzeit dieser Funktion den Dialog benutzten willst dann benötigst Du keinen Nicht-Modalen Dialog. Dann kannst Du auch DoModal nehmen und das hat den gleichen Effekt. Und DoModal hat seine eigene Message-Loop
Dazwischen muss ich jetzt aber dafür sorgen, dass das Ding auch Messages kriegt....wie mach ich das auf saubere Weise?
Eben nicht.
Nicht-Modale Dialoge werden i.A. auf dem Heap oder als Member in einem View erzeugt. Diese werden mit Create erzeugt und man geht einfach mit return aus dem Handler raus, der den Dialog erzeugt hat.Die Message-Loop Deiner CWinApp sorgt auch für Deinen nicht modalen Dialog.
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
> http://www.codeproject.com/KB/dialog/gettingmodeless.aspx
Der behandelt genau das was ich eben geschrieben habe. Einfach das DIng erzeugen und die Main-Message-Loop den Rets machen lassen.
> Allerdings vermisse ich dort ebenfalls ein paar erwähnende Worte über die Message Pump. Ich kann das Beispiel auch leider nicht compilieren, weil ich derzeit nur VC6 besitze....Brauchst Du auch gar nicht. Eigentlich musst Du Dir um die Message-Loop keine Gedanken machen. Erzeuge das Fenster und lass die Main-Message-Loop den Rest machen...
Dein Ansatz ist enfach falch in einer Funktion den Dialog zu erzeugen und die Entsorgung auch noch in der selben zu machen.
Das ist eben nicht der Windows Ansatz...Sorge immer dafür, dass Deine Handler so schnell wie möglich Ihren Job beenden. Dann werden auch die Messsages wieder gezogen.
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de- Als Antwort markiert Fire-Heart Mittwoch, 17. August 2011 11:34
-
Hallo Martin
Ich habe das jetzt in einem kleinen Testprogramm ausprobiert.
Wenn ich also (in einer Dialog-basierten Anwendung, kein SDI, MDI) das Hauptfenster modal erzeuge und dann von diesem Dialog aus weitere nicht-modale Dialoge aufmache, dann scheinen die tatsächlich ohne weiteres Zutun zu funktionieren.
Ich müsste also bei meiner Anwendung - die hauptsächlich eine Server Anwendung ohne Fenster ist und bloß ein paar Info-Fensterchen nicht-modal anzeigen kann, ein unsichtbares Hauptfenster erzeugen, das alle diese Info-Fensterchen verwaltet. Alle Menu-Aufrufe zum Anzeigen/Vernichten dieser Fensterchen sollten dann auch in die Klasse des unsichtbaren Hauptfensters gehen und nicht wie jetzt in die App.
Somit hab ich jetzt was dazugelernt....
Eine letzte Frage aber noch:
Was kann passieren, wenn ich es voerst mal so wie von mir oben beschrieben belasse? Ist es nur unsauber, oder kann ich mir ernsthafte Schwierigkeiten damit einhandeln? (Das System läuft beim Kunden schon und es ist kaum Zeit das alles umzureißen)
Grüße
FireHeart
-
> Was kann passieren, wenn ich es voerst mal so wie von mir oben beschrieben belasse? Ist es nur unsauber, oder kann ich mir ernsthafte Schwierigkeiten damit einhandeln? (Das System läuft beim Kunden schon und es ist kaum Zeit das alles umzureißen)
Beim Kunden? <händeüberdemkopfzusammenschlag>
Deine Kunden tun mir jetzt schon leid ;)Dein Programm ist im schlechtesten Fall reentrant oder bearbeitet Nachrichten nicht.
So wie der Code jetzt ist wirst Du z.B. Info-Fenster mehrfach anzeigen lassen, könne, was sicherlich nicht der Sinn ist.
Durch Deine Methode kann es sein, dass PreTranslateMessage ganz ausgehebelt wird und damit Nachrichten falsch behandelt werden. Das kann bis zum Crash oder der absoluten Fehlfunktion führen.
Ich kann mir die schlimmsten Sachen da noch vorstellen.Das ist für mich ein nogo!
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
>Beim Kunden? <händeüberdemkopfzusammenschlag>
>Deine Kunden tun mir jetzt schon leid ;)Ich hab diese Dinge eigentlich schon jahrelang so gemacht und es gab nie Probleme...unsere Systeme laufen normalerweise äußerst stabil ...
Nichtsdestotrotz gebe ich mich lernwillig und -fähig. Ich hab schon mal den einen Teil des Paketes auf "modales Hauptfenster" umgestellt....funktioniert wieder alles. Der andere Teil wird etwas aufwändiger werden..mal sehen..
Grüße
FireHeart
-
Hallo Forum
Keine Ahnung, ob dies noch jemand liest, sonst mach ich halt einen neuen Thread auf.
Was zum Beispiel mache ich aber bei sehr simplen dialog-basierten Programmchen, die irgendetwas abarbeiten (z.B. tausende Dateien durchsuchen, bearbeiten, etc.), wenn ich gewisse Informationen aus dem Bearbeitungsprozess anzeigen will. Da das Fenster ja mit Arbeit blockiert ist, bleibt mir meistens der Dialog (fast) eingefroren, bis die Tätigkeit beendet ist.
Ich habe mir früher eben auch immer damit beholfen, in der Arbeitsroutine an bestimmten Stellen eine Funktion "LookForMessage" aufzurufen (im selben Dialog), damit die Zeichenmessages durchkommen und der Dialog auch wirklich anzeigt, was gerade getan wird. (Jetzt weiß ich auch wieder, woher dieses "LookForMessage" eigentlich stammt...weil meine frühesten Probleme waren ebendiese).
Ist das dann auch nicht der Windows-Zugang zum Problem? Wie löse ich das dann?
Grüße
FireHeart
-
Man/Ich macht/e einen Workerthread auf und aus dem postet man Nachrichten an den Main/UI-Thread, der diese Informationen anzeigt.
Die Main-Message-Loop daf weiter laufen, die entsprechenden Buttons die die gleiche Aktion auslösen werden disabled, man kann noch einen Abbrechen Button aufmachen etc....Wenn Du eine Message-Loop erneut aufmachst, bedeutet, dass das Du reentrant wirst, d.h. Deine UI kann durch diese Messageloop erneut Nachrichten bekommen, die genau, das auslösen, was Du schon gerade tust...
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
Hier mal ein bisschen Sample-Code zum nachlesen:
http://www.microsoft.com/msj/0798/c0798.aspx
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de -
Hallo Martin
Habe bei dem von Dir angegebenen Link nachgelesen und mit Erleichterung festgestellt, dass ich es sinngemäß auch immer so gemacht habe. Der einzige Unterschied besteht im Detail zum Anwerfen der Message Pump:
Paul DiLascia verwendet:
MSG msg;
while (::PeekMessage(&msg, NULL,
NULL, NULL, PM_NOREMOVE)) {
AfxGetThread()->PumpMessage();
}
Ich habe immer:
void CPTDlg::LookForMessage(void) {
MSG message;
for (int i=0; i<50; i++) {
if (::PeekMessage(&message,NULL,0,0,PM_REMOVE)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
}
}gemacht, wobei ich LookForMessage() des nicht-modalen Dialogs vom zeitintensiven Programmteil bei jedem Schleifendurchlauf (oder sonstige Gelegenheit) aufgerufen habe...genau so, wie im Beispiel mit AbortMessage. Und es funktioniert auch prima.
Im Laufe dieses Forums-Threads habe ich dann noch die Erweiterung mit "IsDialog(...)" gemacht
void CFooDlg::LookForMessage(void) {
MSG message;
while(PeekMessage(&message, NULL, 0, 0,PM_REMOVE)) {
if(!IsDialogMessage(&message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}worauf dann offenbar auch die Tastatur wieder einigermaßen funktioniert hat. (Ich hab halt den "Abort" Knopf meistens mit der Maus bedient).
Der Artikel beschreibt auch, dass "PumpMessage" offenbar noch mehr tut als das. Ich werde es auf jeden Fall mal versuchen.
Das Problem "reentrant" löst mal üblicherweise sowieso, indem man in der "OnButton" Routine zunächst mal genau diesen Button disabled und nach Beenden der Sache wieder enabled. Das macht ja auch für den Anwender Sinn.
Grüße
FireHeart
-
Paul DiLascia verwendet 2 Methoden.
Eine ohne weiteren Thread, eine mit Thread.Ich bevorzuge immer die zweite Methode, die kommt ohne Messeage-Pump aus.
Ich hatte es schon geschrieben. Du musst in jedem Fall AfxPumpMessage verwenden musst, sonst greifen die MFC Routinen nicht!
Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de