Tastaturabfrage bei nichtmodalen Dialogen



  • Hi Community,
    ich möchte einen nichtmodalen Dialog erzeugen und von dem die Tastatureingaben (WM_KEYDOWN oder WM_SYSKEYDOWN) abfangen. Nach welchem Prinzip muß ich da vorgehen?

    Erstmal grobe Beschreibung:
    Dieser Dialog ist immer im Vordergrund (egal ob aktiv oder nicht) und informiert den Nutzer über die aktuellen Vorgänge. Der Fokus (d.h. aktives Fenster) kann mit der Maus zwischen dem Hauptfenster und dem Dialogfenster geklickt werden.
    Im Dialog funktionieren "ALT+F4" und der Klick auf das Schließen-Button in der Titelzeile. Diese beiden Aktionen sorgen dafür, daß der Dialog geschlossen wird.

    Soweit, sogut.
    Jetzt möchte ich das ganze so modifizieren daß ein "ALT+F4" die ganze Applikation beendet (also nicht nur den Dialog beendet). Zusätzlich soll ein Shortcut z.B. mit Taste "F11" dafür sorgen, daß der Dialog toggelt (also sichtbar und wieder unsichtbar machen).
    Also müßte ich die Tastatureingaben aus der Message Queue abfragen können.

    Jetzt noch ein (möglicherweise entscheidender) Hinweis: Im Dialog sind nur 5 Static-Controls (rein informative Textfelder) und hat die Titelleiste mit 'Schließen'-Feld. Keine Buttons, keine Edit-Controls, kein Menü usw.

    Dumm ist nur, daß in allen Tutorials (die ich kenne) immer ein Menü und/oder aktive Controls wie z.B. Edit-Controls oder die OK- bzw. Abbruch-Buttons in einem nichtmodalen Dialog enthalten sind.

    Deshalb zunächst mal die drei wichtigsten Fragen an Euch:
    a) Ist so ein nichtmodaler Dialog ohne Menü, ohne Buttons und ohne Editfelder überhaupt zulässig? Ich denke mal: ja.
    b) Macht es bei einem solch spartanisch ausgestatten nichtmodalen Dialog einen Sinn, in der Nachrichtenschleife die Funktion IsDialogMessage() aufzurufen?
    c) Wenn der Dialog aktiviert wird, auf welches Element soll der Fokus gesetzt werden? Auf das ganze Dialog-Fenster? Oder auf das erste Static-Control?

    Nun zu meinem Programmaufbau (von den tatsächlichen Sourcen natürlich nur aufs nötigste reduziert):

    Auschnitt von WinMain mit dem Hauptfenster und der Nachrichtenschleife:

    signed int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE hinstance_previous, LPSTR psz_cmdline, int iCmdShow )
    {
    
    ...
    
      //Window class registrieren
      wndclass_framefenster.style = 0;
      wndclass_framefenster.lpfnWndProc = (WNDPROC)Procedure_Window_Frame;
      wndclass_framefenster.cbClsExtra = 0;
      wndclass_framefenster.cbWndExtra = 0;
      wndclass_framefenster.hInstance = hinstance_app;
      wndclass_framefenster.hIcon = LoadIcon( hinstance_app, MAKEINTRESOURCE( IDI_ICON ) );
      wndclass_framefenster.hCursor = LoadCursor( hinstance_app, MAKEINTRESOURCE( IDC_SPLITVERT ) );
      wndclass_framefenster.hbrBackground = (HBRUSH)( COLOR_3DFACE + 1 );
      wndclass_framefenster.lpszMenuName = NULL;
      wndclass_framefenster.lpszClassName = tcharsz_app_name;
      RegisterClass( &wndclass_framefenster );
    
    ...
    
      hwnd_window_frame = CreateWindowEx( 0, tcharsz_app_name, tcharsz_app_fenstername,
                                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                          CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                                          (HWND)NULL, hmenu_hauptmenue, hinstance_app, NULL );
      ShowWindow( hwnd_window_frame, iCmdShow );
    
    ...
    
      //Die eigentliche Nachrichtenschleife.
      do
      {
        b_retvalue = GetMessage( &msg_message, NULL, 0, 0 );
        if ( b_retvalue > 0 )
        {
          //Eine normale Nachricht ist eingetroffen
          if ( ( hwnd_dialog_info == NULL )
               || ( IsDialogMessage( hwnd_dialog_info, &msg_message ) == 0 ) )
                                                                      //IsDialogMessage() determines whether a message is intended
                                                                      //for the specified dialog box and, if it is, processes the
                                                                      //message. If the message has not been processed, the return
                                                                      //value is zero. Because the IsDialogMessage() performs all
                                                                      //necessary translating and dispatching of messages, a
                                                                      //message processed by IsDialogMessage() MUST NOT be passed
                                                                      //to TranslateMessage() or DispatchMessage()!
          {
            if ( TranslateAccelerator( hwnd_window_frame, haccel_accelerator, &msg_message ) == 0 )
                                                                      //Prüft ob die in struct_msg_message enthaltene Nachricht ein
                                                                      //Tastaturereignis beschreibt: Wenn ja, folgt ein Vergleich
                                                                      //mit der über ptr_haccel_accelerator angegebenen Tabelle.
                                                                      //Sollte die Routine dort fündig werden, übergibt sie der
                                                                      //Window-Prozedur eine Nachricht des Typs WM_COMMAND bzw.
                                                                      //WM_SYSCOMMAND und liefert einen Wert ungleich 0 zurück.
            {
              TranslateMessage( &msg_message );
              DispatchMessage( &msg_message );
            }
          }
        }
        else
        {
          if ( b_retvalue < 0 )
          {
            //Ein Fehler ist aufgetreten
            Programm_ende_mit_free_memory( -1, TEXT( "Funktion GetMessage() liefert Fehler" ) );
                                                                      //Ein Fehler ist aufgetreten, Abbruch des Programms.
          }
        }
      } while ( b_retvalue != 0 );                                    //Solange abarbeiten, bis Nachricht WM_QUIT eintrifft.
    
    ...
    
    }
    

    Ausschnitt von Window-Prozedur des (Haupt-)Applikationsfensters (d.h. von hwnd_window_frame):

    LRESULT CALLBACK Procedure_Window_Frame( HWND hwnd_frame, UINT message, WPARAM wParam, LPARAM lParam )
    {
      switch ( message )
      {
        case WM_CREATE:
        //Mehrere Child-Fenster erzeugen
        TreeView_OnWM_CREATE();                                       //TreeView Child-Fenster erzeugen.
        InfoView_OnWM_CREATE();                                       //InfoView Child-Fenster erzeugen.
        LogView_OnWM_CREATE();                                        //LogView Child-Fenster erzeugen.
        DialogInfo_OnWM_CREATE();                                     //Info-Dialog erzeugen.
        Statusbar_OnWM_CREATE();                                      //Statusbar Child-Fenster erzeugen.
        SetFocus( hwnd_window_tree );                                 //Fokus für Tastatureingaben auf TreeView-Fenster setzen.
        ...
        return( 0 );
        break;
    
        case WM_COMMAND:
        //Ein Menüeintrag wurde ausgewählt,
        //eine Notification wurde von einem Control-Element gesendet oder
        //ein Abkürzungsbefehl (accelerator keystroke) ist eingetreten
        switch( LOWORD( wParam ) )
        {
          case IDM_ANSICHT_DIALOGINFO:                                //Wird auch durch "F11" per Accelerator angesprungen.
          DialogInfo_toggeln();                                       //SetFocus wird bei Bedarf gesetzt.
          break;
        }
        break;
    
    ... (weitere case-messages)
    
        case WM_DESTROY:
        //Ein Klick auf Schließfeld der Titelleiste oder "Alt+F4"
        Statusbar_OnWM_DESTROY();
        DialogInfo_OnWM_DESTROY();
        LogView_OnWM_DESTROY();
        InfoView_OnWM_DESTROY();
        TreeView_OnWM_DESTROY();
        PostQuitMessage( 0 );
        return( 0 );
        break;
      }
      return( DefWindowProc( hwnd_frame, message, wParam, lParam ) );
    }
    

    Ausschnitt von Dialog-Prozedur:

    BOOL CALLBACK Procedure_Dialog_Info( HWND hwnd_dialog, UINT message, WPARAM wParam, LPARAM lParam )
    {
      switch ( message )
      {
        case WM_INITDIALOG:
        //Dialogbox initialisieren
        DialogInfo_aktualisieren( hwnd_dialog );
        Dialogbox_in_fenstermitte_zentrieren( hwnd_dialog );
        return( TRUE );                                               //TRUE=System sets the focus to the
                                                                      //control specified by wParam.
        break;
    
        case WM_KEYDOWN:                                   // <------ Dieser Zweig wird nie erreicht!
        switch ( wParam )
        {
          case VK_F11:                                              //Taste "F11".
          DialogInfo_schliessen();
          SetFocus( hwnd_window_tree );                             //Fokus für Tastatureingaben auf TreeView-Fenster setzen.
          return( 0 );
          break;
        }
        break;
    
        case WM_CLOSE:
        DialogInfo_schliessen();
        return( 0 );
        break;
      }
      return( FALSE );                                              //Nachricht wurde nicht bearbeitet -> FALSE.
    }
    

    Warum kommen keine Messages vom Typ WM_KEYDOWN (und auch WM_SYSKEYDOWN) in Procedure_Dialog_Info() an? Aber WM_CLOSE funktioniert! 😕

    Muß ich da etwas Subclassen? Wenn ja, was?
    Hoffe, Ihr könnt mir da weiterhelfen?

    Martin


  • Mod

    Weil nur das Fenster, dass den Focus hat die Tastaturnachrichten erhält. Der Dialog hat aber nicht den Focus, sondern eines Deiner eingebetteten Child Windows. Das ist der Grund warum bestimmte Tastaturbehandlungen in der Messageloop passieren (TranslateAccelerator + IsDialogMessage)

    Wenn Du schon einen Accelerator verwendest, dann verpass dem doch z.B. einen Eintrag für F11 und lass Dir ein eigenen WM_COMMAND mit spezieller ID senden.



  • Hallo Martin, erstmal Danke für Deine Mühe, sich mein Problem näher anzusehen.

    Martin Richter schrieb:

    Der Dialog hat aber nicht den Focus, sondern eines Deiner eingebetteten Child Windows.

    Irgendwie stimmt das nicht mit meiner Beobachtung überein. Oder ich habe möglicherweise das Prinzip mit dem Fokus nicht richtig verstanden?

    Meine Beobachtung ist:
    Wenn die Dialog-Box aktiv ist und ich betätige die Tastenkombination "ALT+F4", dann wird die Dialog-Box wie erwartet geschlossen!

    Daraus stelle ich zwei Fragen die wahrscheinlich wichtig sind um die Ursache erkennen zu können:
    War für die Auswertung der Tasten der Dialog-Manager oder "nur" der Windows-Manager verantwortlich? (Hintergrund: Soviel ichs weiß baut der Dialog-Manager auf den Windows-Manager auf)
    Die noch wichtigere Frage: Hatte die Dialog-Box nun den Eingabe-Fokus gehabt oder nicht?

    Denn die eingebetteten Child-Windows wie auch der Accelerator vom Frame-Window reagierten nicht auf auf Tastendrücke wenn der nichtmodale Dialog-Box aktiv (d.h. Titelleiste ist blau) ist -> hatten somit keinen Fokus!

    Martin


  • Mod

    Mmacher schrieb:

    Hallo Martin, erstmal Danke für Deine Mühe, sich mein Problem näher anzusehen.

    Martin Richter schrieb:

    Der Dialog hat aber nicht den Focus, sondern eines Deiner eingebetteten Child Windows.

    Irgendwie stimmt das nicht mit meiner Beobachtung überein. Oder ich habe möglicherweise das Prinzip mit dem Fokus nicht richtig verstanden?

    Mmacher schrieb:

    Meine Beobachtung ist:
    Wenn die Dialog-Box aktiv ist und ich betätige die Tastenkombination "ALT+F4", dann wird die Dialog-Box wie erwartet geschlossen!

    Eben und diese Umwandlung von einem ALT+F4 wird druch TranslateMessage vorgenommen! ALT+F4 wird in der Nachrichtenschleife behandelt. Nicht in der M
    MsgProc

    Mmacher schrieb:

    Daraus stelle ich zwei Fragen die wahrscheinlich wichtig sind um die Ursache erkennen zu können:
    War für die Auswertung der Tasten der Dialog-Manager oder "nur" der Windows-Manager verantwortlich? (Hintergrund: Soviel ichs weiß baut der Dialog-Manager auf den Windows-Manager auf)

    Es gibt keinen Windows Manager.
    Für die Behandlung der Tasatureingaben sind IsDialogMessaage und TranslateMessage, TranslateAccelerator verantwortlich!

    Mmacher schrieb:

    Die noch wichtigere Frage: Hatte die Dialog-Box nun den Eingabe-Fokus gehabt oder nicht?

    Nein Den Focus hat ein Edit Control. Diese bkeommt die normalen anderen Tastatureingaben die "übrig" bleiben.

    Mmacher schrieb:

    Denn die eingebetteten Child-Windows wie auch der Accelerator vom Frame-Window reagierten nicht auf auf Tastendrücke wenn der nichtmodale Dialog-Box aktiv (d.h. Titelleiste ist blau) ist -> hatten somit keinen Fokus!

    Siehe oben. Alles was dialogübergreifned passiert wird in der MeesageLoop behandelt.

    Sez doch mal einen Brakpoint auf WM_CLOSE! Dann Führe mal ALT+F4 aus. Dann schau Dir mal den Stacktrace an.
    Fange WM_NEXTDLGCTL ab und schau Dir den Stacktrace an, wer was aufruft.
    (Einen Symbolserver solltest Du Dir dafür einrichten!)



  • In der Zwischenzeit habe ich weiter debuggt.
    Und dabei herausgefunden, daß die Dialogbox tatsächlich den Fokus hatte!
    Denn GetMessage() liefert bei Tasteneingabe Nachrichten mit hwnd von einem Child-Window der Dialog-Box! -> muß also den Fokus trotz fehlender Buttons, Editfelder usw. gehabt haben. (es sei denn ich habe das mit dem Fokus doch nicht so richtig verstanden)

    Bevor IsDialogMessage() aufgerufen wird (siehe Code oben), habe ich die Message selbst gefiltert:

    ...
    //Eine normale Nachricht ist eingetroffen
    
    b_IsDialogMessage_erlaubt = 1;                            //Aufruf von IsDialogMessage() zunächst erlauben.
    if ( IsChild( hwnd_dialog_info, msg.hwnd ) == TRUE )
    {
      //Nachricht stammt von nichtmodaler Dialogbox.
      switch ( msg.message )
      {
        case WM_KEYDOWN:
        switch ( msg.wParam )
        {
          case VK_F4:
          if ( GetKeyState( VK_CONTROL ) < 0 )                //Tasten "Ctrl"+"F4".
          {
            SendMessage( hwnd_dialog_info, WM_CLOSE, (WPARAM)0, (LPARAM)0 );
            b_IsDialogMessage_erlaubt = 0;                    //Aufruf von IsDialogMessage() blockieren.
          }
          break;
    
          case VK_TAB:                                        //Taste "Tab".
          SendMessage( hwnd_window_frame, WM_SETFOCUS, (WPARAM)hwnd_dialog_info, (LPARAM)0 );
                                                              //Fokus auf ein Child-Window des Hauptfensters setzen.
          b_IsDialogMessage_erlaubt = 0;                      //Aufruf von IsDialogMessage() blockieren.
          break;
        }
        break;
    
        case WM_SYSKEYDOWN:
        switch ( msg.wParam )
        {
          case VK_F4:                                         //Tasten "Alt"+"F4".
          SendMessage( hwnd_window_frame, WM_CLOSE, (WPARAM)0, (LPARAM)0 );
          b_IsDialogMessage_erlaubt = 0;                      //Aufruf von IsDialogMessage() blockieren.
          break;
        }
      }
    }
    
    if ( b_IsDialogMessage_erlaubt != 0 )
    {
      if ( ( hwnd_dialog_info == NULL )
           || ( IsDialogMessage( hwnd_dialog_info, &msg_message ) == 0 ) )
    ...
    

    und siehe da: es funktioniert!!!!!!!

    Allerdings weiß ich nicht ob das der richtige Weg ist oder ob ich mir eine "Zeitbombe" gestrickt habe? 🙄

    Ich werde mich mal am Wochenende mit diesem Thema näher befassen.
    Martin


  • Mod

    Ich verstehe nicht was dies soll. Sofern TranslateMessage/DispatchMessage aufgerufen werden brauchst Du Dich um Alt+F4 nicht kümmern...

    Zu was hat Dir dieser Code jetzt geholfen?


Log in to reply