Um zu verstehen, warum man in C einen Standard eingeführt hat bedarf es ein wenig mehr der Geschichte zu dieser Sprache.C C++ C/C++ Programmieren C Kurs Programmierung Systemprogrammierung Win32 api Win-Api Windows Systemprogrammierung C Programmierung Win32 api Win-Api Windows Systemprogrammierung Kapitel 7: Eingabe von Maus und Tastatur verarbeiten

Um eine benutzerfreundliche Anwendung zu erstellen, macht es Sinn, die Eingabe des Anwenders über die Tastatur oder der Maus abzufragen. Bisher haben Sie sich mit vordefinierten Steuerelemente nicht selbst darum kümmern müssen. Diese reagieren standardmäßig auf solche Eingaben. Im Prinzip könnten Sie es auch dabei belassen. Aber irgendwann kommen Sie vielleicht in Verlegenheit, eine Eingabe des Anwenders von Tastatur oder Maus selbst zu bearbeiten.

7.1. Maus Eingaben            zurück  zum Inhaltsverzeichnis

Wie bei allen anderem auch, reagiert die Windows-Applikation auf Aktionen der Maus mit Nachrichten. Wird bspw. die Maus auf dem Stammfenster bewegt, wird die Nachricht WM_MOUSEMOVE gesendet. Diese können Sie wieder wie jede andere Nachricht auch in der Windows-Prozedur abfragen und auswerten. Hierzu einige gängigen Maus-Nachrichten.

Nachricht Bedeutung
WM_MOUSEMOVE Mauscursor wurde bewegt
WM_LBUTTONDOWN Linke Maustaste wurde gedrückt
WM_LBUTTONUP Linke Maustaste wurde losgelassen
WM_LBUTTONDBLCLK Linke Maustaste wurde doppelt geklickt
WM_RBUTTONDOWN Rechte Maustaste wurde gedrückt
WM_RBUTTONUP Rechte Maustaste wurde losgelassen
WM_RBUTTONDBLCLK Rechte Maustaste wurde doppelt geklickt
WM_NCMOUSEMOVE Die Maus wurde außerhalb des Clientfensters bewegt.

Wollen Sie jetzt wissen, an welcher Position sich der Mauscursor befindet oder wo genau mit der linken Maus doppelt geklickt wurde, müssen Sie, wenn eine der eben genannten Nachrichten auftritt, den lParam-Wert der Prozedur auswerten. Dieser enthält die x, y-Position des Cursors auf dem Bildschirm. Dabei befindet sich die x-Position in den unteren zwei Bytes von lParam und y befindet sich in den oberen zwei Bytes. Wenn Sie bspw. wissen wollen, wo, die rechte Maustaste gedrückt wurde, können Sie das folgendermaßen ermitteln:

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam )
{
   int x, y;
   switch( uMsg )
      {
        //wurde die rechte Maustaste gedrückt?
         case WM_RBUTTONDOWN:
        //…wen ja, wo
            x = LOWORD( lParam );
            y = HIWORD( lParam );
…

Die x, y-Koordinaten, die dabei zurückgegeben werden, sind relativ zur linken oberen Ecke des Clientfensters (Stammfenster).

7.2. Tastatur-Eingaben            zurück  zum Inhaltsverzeichnis

Wie bei einer Aktion der Maus sendet Windows der Anwendung eine Nachricht, wenn die Tastatur betätigt wird. Hierzu ein kurzer Überblick zu gängigen Tastatur-Nachrichten:

Nachricht Bedeutung
WM_CHAR ASCII-Code, wenn ein Buchstabe gedrückt wurde.
WM_KEYDOWN Eine Taste wurde gedrückt
WM_KEYUP Eine Taste wurde losgelassen
WM_SYSCHAR ASCII-Code, wenn ein Buchstabe gedrückt wurde und gleichzig die ALT-Taste.

Wollen Sie bspw. beim Eintreten der Nachricht WM_CHAR den ASCII-Code auswerten, müssen Sie lediglich wParam überprüfen. Darin befindet sich der ASCII-Code der Eingabe von Tastatur. In dem folgenden Codeausschnitt wird bspw. überprüft, ob die Buchstabentaste "a", "b", "+" oder "-" betätigt wurde.

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam )
  int ch;
  switch( uMsg )
   {
      case WM_CHAR:
       {
         switch( wParam )
            {
               case 'a' :  ch = 'a';  break;
               case 'b' :  ch = 'b';  break;
               case '+' :  ch = '+';  break;
               case '-' :  ch  = '-'; break;
               default  :  ch  = '?'; break;
            }
// In ch befindet sich der ASCII-Code, falls ein Buchstabe
// gedrückt wurde
…
       }
      break;
…

Des Weiteren gibt es noch so genannte virtuelle Tastaturcodes, welche Ihr Programm unabhängig von der Tastatur des Anwenders zurückgibt, da dieser immer für die Tastatur gleich sein sollte. Ein virtueller Tastaturcode wird zurückgeliefert bei den Nachrichten WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN und WM_SYSKEYUP. Der virtuelle Code befindet sich dabei in wParam der Nachricht. Die virtuellen Tastaturcodes beginnen alle mit dem Präfix VK_…. Bspw. lautet der virtuelle Code für die Escape-Taste VK_ESCAPE oder für die F1-Taste VK_F1. Oder gerne verwenden, die Pfeiltasten VK_UP, VK_RIGHT, VK_DOWN und VK_LEFT. Die einzelnen virtuellen Tasten und Ihre Konstanten entnehmen Sie bitte selbst aus der MSDN-Dokumentation.

7.3. Anwendungsbeispiel (Win32-Listing)            zurück  zum Inhaltsverzeichnis

Zur Demonstration von Eingaben mit der Maus und der Tastatur erfolgt hierzu ein kleines Beispiel mit anschließender Erläuterung.

#include <windows.h>

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam );

LPCSTR MainClassName = "Eingabe vom Anwender";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   WNDCLASSEX wc;
   MSG wmsg;
   HWND hWnd;

   wc.cbSize  = sizeof(WNDCLASSEX);
   wc.style          = 0;
   wc.lpfnWndProc = WndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = hInstance;
   wc.hIcon = LoadIcon(GetModuleHandle(NULL),
                     IDI_APPLICATION);
   wc.hCursor         = LoadCursor(NULL, IDC_CROSS);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName  = MainClassName;
   wc.lpszClassName = MainClassName;
   wc.hIconSm         = LoadIcon(GetModuleHandle(NULL),
                       IDI_APPLICATION);

   if(!RegisterClassEx(&wc))
      {
       MessageBox(NULL, "Windows Registrations Fehler", "Error!",
                  MB_ICONEXCLAMATION | MB_OK);
       return 0;
      }

    hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, MainClassName,
                          "Gerätekontex Beispiel",
                          WS_SYSMENU|WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          600, 480 ,NULL,NULL,hInstance, NULL);


    if(hWnd == NULL)
    {
      if(MessageBox(NULL, "Fehler beim Erstellen des Fensters!",
                 "Error!", MB_ICONEXCLAMATION | MB_OK) == IDOK)
          return 0;
    }

while(GetMessage(&wmsg,NULL,0,0))
   {
      TranslateMessage(&wmsg);
      DispatchMessage(&wmsg);
   }
   return wmsg.wParam;
}


LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam )
{
  static POINT  ptCursor;
  static HDC    hDC;
  static char   szMsg[128];
  static char   szTmp[30];
  static int x, y;
  BYTE cBuf[256];
  RECT rc;

  switch( uMsg )
   {
      case WM_KEYDOWN :
           {
              GetKeyboardState(cBuf);
              if( cBuf[VK_SHIFT]&0x80 && cBuf[VK_CONTROL]&0x80
                                      && cBuf['A']&0x80)
                   MessageBox(NULL, "Sie haben die Tasten\n"
                          "[CONTOL]+[SHIFT]+[A] gleichzeitig\n"
                              "gedrückt",
                              "[CONTROL] + [SHIFT] + [A]",
                              MB_ICONINFORMATION|MB_OK
                             |MB_DEFBUTTON1);
              else
              {
                SetRect(&rc, 0, 0, 480, 20);
                InvalidateRect( hWnd, &rc, TRUE );
                UpdateWindow( hWnd );
                hDC = GetDC( hWnd );
                GetKeyNameText( lParam, szTmp, 30 );
                wsprintf(szMsg, "Taste \"%s\" betätigt",szTmp);
                TextOut( hDC, 10, 0, szMsg, lstrlen( szMsg ) );
                ReleaseDC( hWnd, hDC );
              }
           }
           break;
      case WM_KEYUP :
            switch( wParam )
               {
                int bAntwort;
                case VK_ESCAPE:
                bAntwort =
                 MessageBox(NULL,"Sie haben \"ESC\" gedrückt!\n"
                           "Programm beenden?","Abfrage",
                           MB_ICONINFORMATION | MB_OKCANCEL |
                           MB_DEFBUTTON1);
                 if(bAntwort == IDOK)
                    PostQuitMessage(0);
                 else
                    break;
                }
           break;
      case WM_SYSCHAR :
           MessageBox(NULL, "Sie haben einen ASCII-Code für\n"
                      "eine Buchstabentaste gedrückt,\n"
                      "während die [ALT]-Taste gedrückt war",
                      "[ALT] + ASCII-Taste = WM_SYSCHAR",
                      MB_ICONINFORMATION | MB_OK |
                      MB_DEFBUTTON1);
           break;


      case WM_MOUSEMOVE :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,"Cursor X = %04d,Y = %04d",x, y );
              TextOut( hDC, 10, 30, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;
     case WM_LBUTTONDOWN :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,
              "Linke Maustaste an Pos.: X = %04d, Y = %04d",x,y);
              TextOut( hDC, 10, 60, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;
     case WM_RBUTTONDOWN :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,
              "Rechte Maustaste an Pos.: X = %04d, Y = %04d"
                                                       ,x,y);
              TextOut( hDC, 10, 90, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;
      case WM_DESTROY :
              PostQuitMessage(0);
              break;

      default :
            return(DefWindowProc( hWnd, uMsg, wParam, lParam ) );
   }

   return( 0L );
}

Programm bei der Ausführung

Programm bei der Ausführung

Jetzt zur Analyse des Quellcodes. In der WinMain()-Funktion bleibt alles wie schon gehabt beim Alten. Steigen wir am besten gleich bei der WndProc()-Prozedur in der Auswertung der Nachrichten ein:

  switch( uMsg )
   {
      case WM_KEYDOWN :
           {
              GetKeyboardState(cBuf);
              if( cBuf[VK_SHIFT]&0x80 && cBuf[VK_CONTROL]&0x80
                  && cBuf['A']&0x80)
                   MessageBox(NULL, "Sie haben die Tasten\n"
                            "[CONTOL]+[SHIFT]+[A] gleichzeitig\n"
                              "gedrückt",
                              "[CONTROL] + [SHIFT] + [A]",
                              MB_ICONINFORMATION|MB_OK
                             |MB_DEFBUTTON1);
              else
              {
                SetRect(&rc, 0, 0, 480, 20);
                InvalidateRect( hWnd, &rc, TRUE );
                UpdateWindow( hWnd );
                hDC = GetDC( hWnd );
                GetKeyNameText( lParam, szTmp, 30 );
                wsprintf(szMsg, "Taste \"%s\" betätigt",szTmp);
                TextOut( hDC, 10, 0, szMsg, lstrlen( szMsg ) );
                ReleaseDC( hWnd, hDC );
              }
           }
           break;

Der erste Fall währe somit die Nachricht WM_KEYDOWN, die Nachricht, dass eine Taste gedrückt wurde. Diese Nachricht wird mit zwei verschiedenen Möglichkeiten ausgewertet. Mit der Funktion GetKeyboardState() kopieren Sie den Status aller 256 virtueller Tasten in den Puffer cBuf, welchen Sie am Anfang der Prozedur mit BYTE cBuf[256] deklariert haben. Mithilfe dieser Funktion können Sie den Status überprüfen, ob mehrere Tasten Gleichzeitig gedrückt wurden. In diesem Beispiel, wird die Tastenkombination STRG+SHIFT+A überprüft:

if( cBuf[VK_SHIFT]&0x80 && cBuf[VK_CONTROL]&0x80
                        && cBuf['A']&0x80)

Anstatt cBuf['A'] sollten Sie in der Regel auch hier den virtuellen Tastaturcode verwenden, also cBuf[VK_A]. Ich habe hier nur eine andere Schreibweise verwendet, da mein Compiler diese virtuelle Taste nicht unterstützt?! Unter Visual C++ hingegen macht dieser Code keine Probleme. Mit einer bedingten Compilierung lässt sich das Problem aber auch beheben, um Probleme mit unterschiedlichen Compilern zu vermeiden:

#ifndef VK_A
   #define VK_A 'A'
#endif

Wurde die Tastenkombination STRG+SHIFT+A gedrückt, wird folgenden MessageBox angezeigt:

Tastenkombination STRG+SHIFT+A wurde ermittelt

Tastenkombination STRG+SHIFT+A wurde ermittelt

Natürlich können Sie damit jede andere beliebige Tastenkombination, die gleichzeitig betätigt, wurde auswerten.

Wurde diese Tastenkombination nicht betätigt, wird die else-Verzweigung der Nachricht WM_DEYDOWN ausgeführt. Dabei richten Sie gleich den Bereich im Fenster ein (SetRect) worin der Text ausgegeben werden soll. Mit InvalidateRect und UpdateWindow sorgen Sie erstmal dafür, dass dieser Bereich neu gezeichnet wird. Danach Allozieren Sie eine Gerätekontex mit GetDC. Das sollte Ihnen alles nicht mehr fremd sein. Danach lassen Sie sich mit der Funktion GetKeyNameText() den Namen der Taste ermitteln, welche Sie gedrückt hatten. Die Funktion dient als Tastaturcode-zu-String-Konverter, wenn Sie so wollen. Der erste Parameter ist dabei lParam, gefolgt vom Puffer, in dem der Name der Tastatur geschrieben wird und der Anzahl der Zeichen für den Tastennamen, der in den Puffer geschrieben werden soll. Den ermittelten String kopieren Sie jetzt mit weiteren Zeichen mit der Funktion wsprintf() in den Puffer szMsg. Die Funktion wsprintf() stellt nichts anderes als die Windows-Version der Standard-Funktion sprintf() da. Anschießend wird nur noch der Text ausgegeben (TextOut) und der Gerätkontex wieder freigegeben (ReleaseDC).

Die nächste Nachricht, WM_KEYUP, hätte man in diesem Beispiel auch bei WM_DEYDOWN auswerten können und dient lediglich zur Demonstration dieser Nachricht. Denn wenn jemand eine Taste gedrückt hat, wird er diese wohl auch wieder loslassen, sofern er nicht mit dem Kopf auf die Tastatur knallt und nicht mehr aufsteht ;).

      case WM_KEYUP :
            switch( wParam )
               {
                int bAntwort;
                case VK_ESCAPE:
                bAntwort =
                 MessageBox(NULL,"Sie haben \"ESC\" gedrückt!\n"
                           "Programm beenden?","Abfrage",
                           MB_ICONINFORMATION | MB_OKCANCEL |
                           MB_DEFBUTTON1);
                 if(bAntwort == IDOK)
                    PostQuitMessage(0);
                 else
                    break;
                }
           break;

Die Taste, welche gedrückt und eben losgelassen wurde befindet sich in wParam. Im Beispiel testen Sie nur, ob die Taste Escape (VK_ESCAPE) gedrückt wurde. Ist dies der Fall, öffnet sich eine MessageBox und fragt ab, ob das Programm beendet werden soll.

Taste ESCAPE wurde betätigt

Taste ESCAPE wurde betätigt

Betätigen Sie den OK-Button (IDOK), wird die Anwendung mit PostQuitMessage() beendet.

Im nächsten Fall wird die Nachricht WM_SYSCHAR ausgewertet, was bedeutet, dass der Anwender die ALT-Taste und eine Buchstabentaste betätigt hat. Dabei wird wieder eine einfache MessageBox ausgegeben.

      case WM_SYSCHAR :
           MessageBox(NULL, "Sie haben einen ASCII-Code für\n"
                      "eine Buchstabentaste gedrückt,\n"
                      "während die [ALT]-Taste gedrückt war",
                      "[ALT] + ASCII-Taste = WM_SYSCHAR",
                      MB_ICONINFORMATION | MB_OK |
                      MB_DEFBUTTON1);
           break;

In den nächsten drei Fällen werden Mausnachrichten ausgewertet. Alle drei Nachrichten werden dabei gleich behandelt. Zuerst wird jeweils die x und y-Position des Mauscursors ermittelt und anschließend ausgegeben. Der Text von WM_MOUSEMOVE wird verändert, sobald der Mauscursor bewegt wird. Die anderen beiden Mausnachrichten werden verändert, wenn der linke Mausbutton oder der Rechte gedrückt wurde.

      case WM_MOUSEMOVE :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,"Cursor X = %04d,Y = %04d",x, y );
              TextOut( hDC, 10, 30, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;
     case WM_LBUTTONDOWN :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,
              "Linke Maustaste an Pos.: X = %04d, Y = %04d",x,y);
              TextOut( hDC, 10, 60, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;
     case WM_RBUTTONDOWN :
              x = LOWORD(lParam);
              y = HIWORD(lParam);
              hDC = GetDC( hWnd );
              wsprintf( szMsg,
              "Rechte Maustaste an Pos.: X = %04d, Y = %04d"
                                                       ,x,y);
              TextOut( hDC, 10, 90, szMsg, lstrlen( szMsg ) );
              ReleaseDC( hWnd, hDC );
              break;

Mithilfe der Auswertung der Mausposition und der Kenntnis vom Kapitel "Malen und Zeichnen" lässt sich ohne großen Aufwand ein einfaches Zeichenprogramm realisieren was folgende Ausgabe macht:

Die Schrift könnte besser sein ;)

Die Schrift könnte besser sein ;)

Hierzu das entsprechende Listing, welches ich Ihnen Kommentarlos zum Studieren überlasse.

#include <windows.h>

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam );

LPCSTR MainClassName = "Zeichnen";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   WNDCLASSEX wc;
   MSG wmsg;
   HWND hWnd;

   wc.cbSize = sizeof(WNDCLASSEX);
   wc.style          = 0;
   wc.lpfnWndProc = WndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = hInstance;
   wc.hIcon = LoadIcon(GetModuleHandle(NULL),
                     IDI_APPLICATION);
   wc.hCursor         = LoadCursor(NULL, IDC_CROSS);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName  = MainClassName;
   wc.lpszClassName = MainClassName;
   wc.hIconSm         = LoadIcon(GetModuleHandle(NULL),
                       IDI_APPLICATION);

   if(!RegisterClassEx(&wc))
      {
       MessageBox(NULL, "Windows Registrations Fehler", "Error!",
                  MB_ICONEXCLAMATION | MB_OK);
       return 0;
      }

    hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, MainClassName,
                          "Gerätekontex Beispiel",
                          WS_SYSMENU|WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          400, 300 ,NULL,NULL,hInstance, NULL);


    if(hWnd == NULL)
    {
      if(MessageBox(NULL, "Fehler beim Erstellen des Fensters!",
                 "Error!", MB_ICONEXCLAMATION | MB_OK) == IDOK)
          return 0;
    }

while(GetMessage(&wmsg,NULL,0,0))
   {
      TranslateMessage(&wmsg);
      DispatchMessage(&wmsg);
   }
   return wmsg.wParam;
}


LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam )
{
   static int x1=-1,y1=-1,x2=-1,y2=-1;
   HDC hdc;
   PAINTSTRUCT ps;
   HPEN pen = CreatePen( PS_SOLID, 4, RGB(255, 0, 0 ));

  switch (uMsg)
  {
    case WM_PAINT:
      hdc=BeginPaint(hWnd, &ps);
      SelectObject(hdc, pen);
      MoveToEx(hdc,x1,y1, NULL);
      LineTo(hdc,x2,y2);
      DeleteObject(pen);
      EndPaint(hWnd, &ps);
      return 0;
    case WM_MOUSEMOVE:
      x1=x2;  //für MoveToEx
      y1=y2;  //für MoveToEx
      x2=LOWORD(lParam); //neue x-Position
      y2=HIWORD(lParam); //neue y-Postion
      //nur Zeichnen, wenn linker Button gedrückt ist
      if(wParam & MK_LBUTTON)
      {
        InvalidateRect(hWnd,NULL,FALSE);
      }
      return 0;
    //rechte Maustaste->Bildschirm löschen
    case WM_RBUTTONDOWN:
      x1=x2=y1=y2=-1;
      InvalidateRect(hWnd, NULL, TRUE);
      break;
    case WM_DESTROY :
      PostQuitMessage(0);
      break;

      default :
          return( DefWindowProc( hWnd, uMsg, wParam, lParam ) );
   }
   return( 0L );
}

Weiter mit Kapitel 8: Datei-Ein/Ausgabe            zum Inhaltsverzeichnis