Wer sich in Windows-Programmierer-Foren tummelt, dem wird die folgende Frage mindestens einmal in der Woche über den Weg laufen:
Ich möchte in einem Dialog die Eingabetaste abfangen, so dass sich der Dialog nicht schließt.
Wie geht das?
Vom Sinn und Unsinn dieses Wunsches wollen wir mal hier nicht reden. Also schauen wir mal auf dieses Problem etwas genauer.
Die Lösung WM_COMMAND mit IDOK abfangen, lasse ich nicht gelten , denn evtl. hat der Dialog ja einen OK Button. (In der MFC käme das mit dem Überschreiben von CDialog::OnOK gleich)
Die Standardantwort von MFC Entwicklern lautet immer:
Überschreibe PreTranslateMessage und fange WM_KEYDOWN mit VK_RETURN dort ab und behandle die Nachricht dort.
OK. Aber was macht ein Win32-API Entwickler. Der hat keinen Einfluss auf die Message-Loop. Muss der nun zu der Hook-Kanone greifen?
Wo liegt eigentlich das Problem?
Die Funktion, die VK_RETURN in ein WM_COMMAND mit IDOK umwandelt ist die API Funktion IsDialogMessage. Diese Funktion wird in der Messageloop von modalen Dialogen verwendet. In der MFC direkt in CDialog::PreTranslateMessage und in Win32 Dialogen wird sie in der Messageloop die DialogBox ausführt.
Diese Funktion geht aber mit solchen Tastatureingaben nicht einfach wahllos um. Sie macht das sehr intelligent. Das sieht man schon daran, dass man in einer aufgeklappten ComboBox die Eingabetaste drücken kann und es schließt die ComboBox und nicht der Dialog.
Ebenfalls kennt man den Effekt, dass ein mehrzeiliges Eingabefeld (Edit Control) sehr wohl die Eingabetaste als Zeilenschaltung nutzen kann und auch hier schließt der Dialog nicht.
Kennt also IsDialogMessage seine Pappenheimer von Controls?
Woher weiß IsDialogMessage also wann VK_RETURN mal so und mal so behandelt werden muss?
Die Antwort ist einfach: IsDialogMessage fragt seine Controls ab was sie gerne hätten!
Und das wird erreicht mit einer einfachen Windows-Nachricht, die in diesem Zusammenhang fast nie genannt wird: WM_GETDLGCODE.
Bevor IsDialogMessage eine Tastatureingabe selbst behandelt, fragt die Funktion das Zielfenster mit WM_GETDLGCODE ab, ob das Fenster evtl. selbst Verwendung für diese Tastatureingabe hat.
WM_GETDLGCODE kann hier sehr flexibel reagieren, denn in wParam erhält die Nachricht den virtuellen Tastencode und das Control weiß nun sehr genau um was es hier geht.
Kann man also in jedem Control (das natürlich den Fokus hat) die Eingabetaste selbst behandeln?
Antwort: Ja!
Das Control muss nur einfach WM_GETDLGCODE behandeln für die Taste VK_RETURN. In diesem Fall gibt es DLGC_WANTMESSAGE zurück (ist übrigends identisch zu DLGC_WANTALLKEYS). IsDIalogMessage wird in diesem Fall die Eingabetaste an das Control weitergeben und nicht selbst behandeln. So macht es übrigens auch eine ComboBox die aufgeklappt ist wenn die Eingabetaste gedrückt wird.
Ich habe einfach mal hier eine Win32 Lösung aufgebaut. Es wird einfach das entsprechende Control mit einer eigenen WndProc gesubclassed und das sieht etwa so aus:
LRESULT CALLBACK SpecialWndProc(HWND hWnd,
UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_GETDLGCODE:
if (wParam==VK_RETURN)
return DLGC_WANTMESSAGE;
break;
case WM_KEYDOWN:
if (wParam==VK_RETURN)
{
MessageBox(hWnd,_T("VK_RETURN received!"),NULL,MB_OK);
return 0;
}
break;
}
WNDPROC pWndProc = reinterpret_cast(GetWindowLongPtr(hWnd,GWL_USERDATA));
return CallWindowProc(pWndProc,hWnd,message,wParam,lParam);
}
...
// Subclass the control
HWND hWndEdit = GetDlgItem(hDlg,IDC_EDIT1);
LONG_PTR pOldWndProc = SetWindowLongPtr(hWndEdit,GWL_WNDPROC,
reinterpret_cast(SpecialWndProc));
SetWindowLongPtr(hWndEdit,GWL_USERDATA,pOldWndProc);
Effekt: Jedes Control, dass man mit dieser WndProc subclassed wird die Messagebox anzeigen und der Dialog wird nicht geschlossen! Subclassed man also alle Controls eines Dialoges, die den Fokus bekommen können, dann kann man sofort ohne Hook, auch die Eingabetaste selbst behandeln.
Sinnvollerweise sendet man bei Erhalt von VK_RETURN dann einfach eine entsprechende Nachricht an das Parent, dass die gewünschte Aktion dann ausführt, sofern nicht das Control selbst die Eingabetaste direkt behandelt.
Man kommt also auch in reinen Win32 API Programmen ganz ohne Hooks aus, um VK_RETURN in Dialogen so zu behandeln wie man es selbst gerne möchte!
Ich würde in diesem Zusammenhang unbedingt das default-button hanlding in dialogen und natürlich DM_GETDEFID erwähnen.
Ich weiß nicht genau was DM_GETDEFID mit dem Topic zu tun hat.
Damit legt man doch nur den Button fest, der bei VK_RETURN ausgelöst wird. Mir ging es darum zu zeigen, dass ein Control selbst festlegen kann wie VK_RETURN behandelt wird und das man damit auch grundsätzlich die Eingabetaste auf eigene Weise nutzen kann ohne den Dialog zu schließen.