Window Messages (Erste Schritte mit Win32 und C++)

Eine GUI-Anwendung muss auf Ereignisse des Benutzers und des Betriebssystems reagieren.

  • Ereignisse des Benutzers beinhalten jede Art von Interaktion mit dem Programm: Mausklicks, Tastaturanschläge, Touch-Bildschirmgesten usw.
  • Ereignisse des Betriebssystems beinhalten alles „außerhalb“ des Programms, das sich auf das Verhalten des Programms auswirken kann. Beispielsweise könnte der Benutzer ein neues Hardwaregerät anschließen, oder Windows könnte in einen Energiesparmodus (Ruhezustand) versetzt werden.

Diese Ereignisse können jederzeit auftreten, während das Programm ausgeführt wird, in fast jeder Reihenfolge. Wie können Sie ein Programm strukturieren, dessen Ausführungsablauf nicht vorab bekannt ist?

Um dieses Problem zu lösen, verwendet Windows ein Nachrichtenübergabemodell. Das Betriebssystem kommuniziert mit Ihrem Anwendungsfenster, indem es Nachrichten an dieses übergibt. Eine Nachricht ist einfach ein numerischer Code, der für ein bestimmtes Ereignis steht. Wenn der Benutzer beispielsweise die linke Maustaste betätigt, empfängt das Fenster eine Nachricht mit dem folgenden Nachrichtencode.

#define WM_LBUTTONDOWN    0x0201

Manchen Nachrichten sind Daten zugeordnet. Beispielsweise enthält die WM_LBUTTONDOWN-Nachricht die X-Koordinate und die Y-Koordinate des Mauscursors.

Um eine Nachricht an ein Fenster zu übergeben, ruft das Betriebssystem die für dieses Fenster registrierte Fensterprozedur auf. (Und jetzt wissen Sie auch, wofür das Fensterverfahren da ist.)

Nachrichtenschleife

Eine Anwendung empfängt Tausende von Nachrichten, während sie ausgeführt wird. (Denken Sie daran, dass jeder Tastenanschlag und Mausklick eine Nachricht generiert.) Darüber hinaus kann eine Anwendung über mehrere Fenster verfügen, die jeweils über ein eigenes Fensterverfahren verfügen. Wie empfängt das Programm alle diese Nachrichten und überträgt sie an das korrekte Fensterverfahren? Die Anwendung benötigt eine Schleife, um die Nachrichten abzurufen und an die korrekten Fenster zu verteilen.

Für jeden Thread, der ein Fenster erstellt, erstellt das Betriebssystem eine Warteschlange für Fensternachrichten. Diese Warteschlange enthält Nachrichten für alle Fenster, die in diesem Thread erstellt werden. Die Warteschlange selbst ist aus Ihrem Programm ausgeblendet. Sie können die Warteschlange nicht direkt bearbeiten. Sie können aber eine Nachricht aus der Warteschlange abrufen, indem Sie die GetMessage-Funktion aufrufen.

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Mit dieser Funktion wird die erste Nachricht aus dem Kopf der Warteschlange entfernt. Wenn die Warteschlange leer ist, werden die Funktionsblöcke blockiert, bis eine andere Nachricht in die Warteschlange gestellt wird. Die Tatsache, dass GetMessage blockiert, führt nicht dazu, dass Ihr Programm nicht reagiert. Wenn keine Nachrichten vorhanden sind, gibt es für das Programm nichts zu tun. Wenn Sie Hintergrundverarbeitungen durchführen müssen, können Sie zusätzliche Threads erstellen, die weiterhin ausgeführt werden, während GetMessage auf eine andere Nachricht wartet. (Siehe Vermeiden von Engpässen in Ihrer Fensterprozedur.)

Der erste Parameter von GetMessage ist die Adresse einer MSG-Struktur. Wenn die Funktion erfolgreich ist, füllt sie die MSG-Struktur mit Informationen zur Nachricht. Dazu gehören das Zielfenster und der Nachrichtencode. Mit den anderen drei Parametern können Sie filtern, welche Nachrichten Sie aus der Warteschlange erhalten. In fast allen Fällen setzen Sie diese Parameter auf Null.

Obwohl die MSG-Struktur Informationen über die Nachricht enthält, werden Sie diese Struktur fast nie direkt untersuchen. Stattdessen übergeben Sie sie direkt an zwei andere Funktionen.

TranslateMessage(&msg); 
DispatchMessage(&msg);

Die TranslateMessage-Funktion bezieht sich auf Tastatureingaben. Sie übersetzt Tastenanschläge (Taste nach unten, Taste nach oben) in Zeichen. Sie müssen nicht wirklich wissen, wie diese Funktion funktioniert; denken Sie einfach daran, sie vor DispatchMessage aufzurufen.

Die Funktion DispatchMessage lässt das Betriebssystem die Fensterprozedur des Fensters aufrufen, das das Ziel der Nachricht ist. Mit anderen Worten: Das Betriebssystem sucht das Fensterhandle in der Tabelle der Fenster, sucht den Funktionszeiger, der dem Fenster zugeordnet ist, und ruft die Funktion auf.

Nehmen Sie beispielsweise an, der Benutzer betätigt die linke Maustaste. Dies führt zu einer Ereigniskette:

  1. Das Betriebssystem fügt eine WM_LBUTTONDOWN-Nachricht in die Nachrichtenwarteschlange ein.
  2. Ihr Programm ruft die GetMessage-Funktion auf.
  3. GetMessage ruft die WM_LBUTTONDOWN-Nachricht aus der Warteschlange ab und füllt die MSG-Struktur.
  4. Ihr Programm ruft die Funktionen TranslateMessage und DispatchMessage auf.
  5. Innerhalb von DispatchMessage ruft das Betriebssystem die Fensterprozedur auf.
  6. Ihre Fensterprozedur kann entweder auf die Nachricht antworten oder sie ignorieren.

Wenn die Fensterprozedur zurückgegeben wird, kehrt sie zurück zu DispatchMessage. Dies führt zur Rückkehr zur Nachrichtenschleife für die nächste Nachricht. Solange Ihr Programm ausgeführt wird, werden Nachrichten weiterhin in der Warteschlange eintreffen. Daher müssen Sie über eine Schleife verfügen, die Nachrichten kontinuierlich aus der Warteschlange abruft und sie verteilt. Sie können sich die Schleife wie folgt vorstellen:

// WARNING: Don't actually write your loop this way.

while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

So wie sie geschrieben ist, würde diese Schleife natürlich nie enden. Hier kommt der Rückgabewert für die GetMessage-Funktion ins Spiel. Normalerweise gibt GetMessage einen Nicht-Null-Wert zurück. Wenn Sie die Anwendung beenden und aus der Nachrichtenschleife ausbrechen möchten, rufen Sie die PostQuitMessage-Funktion auf.

        PostQuitMessage(0);

Die PostQuitMessage-Funktion setzt eine WM_QUIT-Nachricht in die Nachrichtenwarteschlange. WM_QUIT ist eine besondere Nachricht: Sie veranlasst GetMessage, Null zurückzugeben, was das Ende der nachrichtenschleife bezeichnet. Hier ist die überarbeitete Nachrichtenschleife.

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Solange GetMessage einen Nicht-Null-Werte zurückgibt wird der Ausdruck in der While-Schleife als „true“ ausgewertet. Nach dem Aufruf von PostQuitMessage wird der Ausdruck als „false“ ausgewertet, und das Programm beendet die Schleife. (Ein interessantes Ergebnis dieses Verhaltens ist, dass Ihr Fensterverfahren nie eine WM_QUIT-Nachricht erhält. Daher müssen Sie in Ihrer Fensterprozedur keine Fallerklärung für diese Nachricht haben.)

Die nächste offensichtliche Frage ist, wann PostQuitMessage aufgerufen werden soll. Wir kommen im Thema Schließen des Fensters auf diese Frage zurück, zuerst müssen wir aber unser Fensterverfahren schreiben.

Gepostete Nachrichten im Vergleich zu gesendeten Nachrichten

Im vorherigen Abschnitt ging es um Nachrichten, die in eine Warteschlange gesetzt werden. Manchmal ruft das Betriebssystem eine Fensterprozedur direkt auf und umgeht dabei die Warteschlange.

Die Terminologie für diesen Unterschied kann verwirrend sein:

  • Das Posten einer Nachricht bedeutet, dass die Nachricht in die Nachrichtenwarteschlange wechselt und über die Nachrichtenschleife (GetMessage und DispatchMessage) verteilt wird.
  • Das Senden einer Nachricht bedeutet, dass die Nachricht die Warteschlange überspringt und das Betriebssystem das Fensterverfahren direkt aufruft.

Für den Moment ist dieser Unterschied nicht sehr wichtig. Das Fensterverfahren behandelt alle Nachrichten. Einige Nachrichten umgehen die Warteschlange jedoch und wechseln direkt zu Ihrem Fensterverfahren. Es kann jedoch einen Unterschied machen, wenn Ihre Anwendung zwischen Fenstern kommuniziert. Eine ausführlichere Erläuterung dieses Problems finden Sie im Thema Über Nachrichten und Nachrichtenwarteschlangen.

Nächste

Schreiben der Fensterprozedur