MFC Nachrichtenbearbeitung

Wie Sie bereits im vorherigen Kapitel erfahren haben, werden in einem 'normalen' WINDOWS Programm die relevanten Nachrichten an ein Fenster innerhalb der Fensterprozedur verarbeitet; und diese Fensterprozedur gibt es selbstverständlich auch in einem MFC Programm. Doch keine Angst, Sie müssen nun nicht etwa die MFC-Fensterprozedur ändern um Nachrichten zu verarbeiten. MFC Programme gehen hier einen anderen Weg.

Um Nachrichten an ein Fenster innerhalb einer MFC-Anwendung zu verarbeiten wird zunächst durch den Anwendungs-Assistent innerhalb der Fensterklasse das Makro DECLARE_MESSAGE_MAP(...) eingefügt. Dieses Makro ist in der Datei AfxWin.h definiert und deklariert drei statische Member für die Nachrichtenbearbeitung, darunter unter anderem auch eine Nachrichtentabelle. Stellen Sie sich unter der Nachrichtentabelle einfach ein Feld mit Methodenzeigern vor die für die Verarbeitung der Nachrichten zuständig sind. Um die zu einer bestimmten Nachricht gehörende Methode auszuführen, wird die Nachricht (32-Bit Wert) als Index in diese Tabelle verwendet und die dazugehörig Methode indirekt aufgerufen. So funktioniert die Nachrichtenverarbeitung unter der MFC vereinfacht dargestellt.

Das Makro DECLARE_MESSAGE_MAP(...) verändert die Zugriffsrechte innerhalb der Klasse. Wenn Sie nach diesem Makro von Hand weitere Member zur Klasse hinzufügen, so sollten Sie das Zugriffsrecht explizit neu setzen. Beim Hinzufügen von Member über den Klassen-Assistent erledigt dieser dies automatisch.

Nachdem die drei statischen Member innerhalb der Klasse durch DECLARE_MESSAGE_MAP(...) deklariert worden sind, wird die Nachrichtentabelle in der Quellcodedatei des Fensters durch die beiden Makros BEGIN_MESSAGE_MAP(...) und END_MESSAGE_MAP(...) definiert. Sehen wir uns jetzt die Nachrichtentabelle aus dem vorherigen Beispiel einmal an:

Falls Sie das Projekt aus der vorherigen Lektion nicht mehr geöffnet haben, so öffnen Sie dieses nun. Öffnen Sie danach im Arbeitsbereich die Klasse CMainFrame und führen dann einen Doppelklick auf den Konstruktor der Klasse aus. Im Editorfenster wird nun der Konstruktor angezeigt. Etwas oberhalb des Konstruktor sollten Sie nun die Nachrichtentabelle sehen:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
        // HINWEIS - Hier werden Mapping-Makros vom Klassen-Assistenten
        // eingefügt und entfernt.
        // Innerhalb dieser generierten Quelltextabschnitte NICHTS VERÄNDERN!
    ON_WM_CREATE()
    ON_WM_SETFOCUS()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Die Nachrichtentabelle enthält zwei Makros: ON_WM_CREATE(...) und ON_WM_SETFOCUS(...). Durch diese beiden Makros leitet die MFC die beiden WINDOWS Nachrichten WM_CREATE und WM_SETFOCUS an das Fenster weiter. Alle anderen eintreffenden Nachrichten werden durch die MFC verarbeitet. Jetzt fehlt uns nur noch eines: die mit diesen Nachrichten verbundenen Methoden. Die MFC ruft beim Eintreffen einer mittels ON_WM_XXX(  ) spezifizierten Nachricht eine Methode mit einem genau definierten Namen auf. Dieser Name leitet sich dabei aus dem Makronamen ON_WM_XXX(...) ab, indem zunächst die Passage _WM_ aus dem Makronamen entfernt wird. Anschließend wird der Makroname in Kleinbuchstaben umgewandelt und dann an den Wortgrenzen Großbuchstaben eingesetzt. Aber keine Angst, Sie müssen in der Regel nun nicht etwa selbst die Einträge in der Nachrichtentabelle vornehmen und die Methoden definieren. Auch hier hilft Ihnen der Klassen-Assistent, wie wir nachher gleich sehen werden.

Ein weiterer wichtiger Punkt bei der Nachrichtenbearbeitung ist das so genannte message cracking. Während 'normale' WINDOWS Nachrichten ihre Daten in den beiden Zusatzinformationen wParam und lParam übergeben, erhalten MFC Methoden die Informationen bereits in aufbereiteter Form. Ein Beispiel dazu: Die bekannte WM_SIZE Nachricht übergibt in wParam den Grund für die Größenänderung und in lParam in kodierter Form (LOWORD, HIWORD) die neue Größe der Client-Area. Das Prototyping der MFC Methode für diese Nachricht sieht aber wie folgt aus:

afx_msg void OnSize( UINT nType, int cx, int cy );

nType entspricht dem Parameter wParam und gibt den Grund für die Größenänderung an und cx/cy enthält die neue Größe der Client-Area. So angenehm diese Dekodierung der Nachrichtendaten zum Arbeiten ist, am Anfang werden Sie aber genau deswegen nicht umhinkommen die Prototypen der Nachrichtenmethoden des öfteren in der Online-Hilfe nachzuschlagen.

Wenden wir uns jetzt kurz der Nachrichtenbearbeitung des Ausgabebereichs zu. Der Anwendungs-Assistent hat zur Klasse CChildView bereits den Nachrichtenbearbeiter OnPaint(...) hinzugefügt. Diese Methode wird, wie der Name schon vermuten lässt, als Reaktion auf eine WM_PAINT Nachricht aufgerufen.

void CChildView::OnPaint()
{
    CPaintDC dc(this); // Gerätekontext zum Zeichnen
   
    // ZU ERLEDIGEN: Fügen Sie hier Ihren Code für die Nachrichtenbehandlung hinzu
   
    // Rufen Sie nicht CWnd::OnPaint() für Nachrichten zum Zeichnen auf
}

Innerhalb dieser Methode hat der Anwendungs-Assistent  die Definition eines CPaintDC Objektes eingefügt. Durch diese Definition werden die für die WM_PAINT Bearbeitung so wichtigen Anweisungen BeginPaint(...) und EndPaint(...) ausgeführt. Wenn Sie sich nicht mehr sicher sind, was diese beiden API-Funktionen bewirken, klicken Sie hier; sie wurden im Kapitel 2 im Praxisteil behandelt. Der Konstruktor von CPaintDC ruft die Methode BeginPaint(...) und der Destruktor EndPaint(...) auf.

Setzen Sie niemals innerhalb der OnPaint(...) Methode einen Breakpoint oder öffnen Sie nie eine Messagebox. Durch das Anhalten des Programms oder das Anzeigen der Messagebox wird der Fensterinhalt sofort wieder als ungültig gekennzeichnet, was wiederum das erneute Auslösen einer WM_PAINT Nachricht und damit den sofortigen Aufruf von OnPaint(...) zur Folge hätte!

Bevor wir nun das Beispiel erweitern sehen wir uns noch eines der vielen hilfreichen Makros der MFC an, das Makro TRACE(...). Mit Hilfe des Makros TRACE(...) kann eine Debug-Version Ausgaben im Ausgabefenster des Debuggers vornehmen. Die Syntax des Makros ist gleich wie die der printf(...) Funktion. In einer Release-Version der Anwendung ist dieses Makro leer, d.h. es wird kein Code hierfür erzeugt.

Erweitern wir uns Beispiel aus der vorherigen Lektion nun. Da wir zum jetzigen Zeitpunkt noch keine Ausgaben im Fenster selbst vornehmen können, erweitern wir die die Methode OnPaint(...) des Ausgabeobjektes um das erwähnte TRACE(...) Makro:
void CChildView::OnPaint()
{
    CPaintDC dc(this); // Gerätekontext zum Zeichnen
   
    // ZU ERLEDIGEN: Fügen Sie hier Ihren Code für die Nachrichten....
   
    // Rufen Sie nicht CWnd::OnPaint() für Nachrichten zum Zeichnen auf
    TRACE("Fenster wird neu gezeichnet!\n");
}

Übersetzen und starten Sie das Programm nun. Immer wenn das Fenster in seiner Größe verändert wird erfolgt eine Ausgabe im Debuggerfenster.

Als nächstes fügen wir die Behandlungsroutine für die WM_SIZE Nachricht zum Ausgabebereich hinzu. Klicken Sie in der Klassenansicht die Klasse CChildView des Ausgabebereichs mit der rechten Maustaste an. Im dann eingeblendeten Kontextmenü wählen Sie Behandlungsroutine für Windows-Nachrichten hinzufügen... aus. Daraufhin wird folgender Dialog eingeblendet:

Windows-Nachrichten hinzufügen

Im linken Teil Neue Windows-Nachrichten/-Ereignisse werden die Nachrichten aufgeführt, die die ausgewählte Klasse bearbeiten kann. Wenn Sie eine der Nachrichten anklicken erscheint im unteren Teil des Dialogs eine kurze Beschreibung der Nachricht. Im rechten Teil Vorhand. Behandlungsroutinen für Nachrichten/-Ereignisse werden alle Nachrichten aufgelistet, die bereits durch das Fenster verarbeitet werden; im Beispiel also die Nachricht WM_PAINT.

Klicken Sie jetzt im linken Teil die Nachricht WM_SIZE an und anschließend den Button Hinzufügen und Bearbeiten. Der Klassen-Assistent fügt jetzt die ausgewählte Nachricht zur Nachrichtentabelle hinzu und erstellt gleichzeitig ein Grundgerüst der entsprechenden Methode zur Behandlung dieser Nachricht. Im Editorfenster wird dann diese Methode automatisch angezeigt. Fügen Sie der Methode eine TRACE-Meldung wie unten angegeben hinzu.

void CChildView::OnSize(UINT nType, int cx, int cy)
{
    CWnd ::OnSize(nType, cx, cy);
   
    // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
   
TRACE("CChildView-Size: %d,%d\n",cx,cy);
}

Gehen wir noch einen Schritt weiter. Fügen Sie nun zur Klasse des Rahmenfensters auf die gleiche Art und Weise ebenfalls eine Behandlungsroutine für die WM_SIZE Nachricht hinzu und erweitern Sie die eingefügte Methode auch hier um eine entsprechende TRACE(...) Anweisung:

void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
    CFrameWnd::OnSize(nType, cx, cy);
   
    // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
    TRACE("CMainFrame-Size: %d,%d\n",cx,cy);
}

Übersetzen und starten Sie das Programm nun wieder. Beobachten Sie auch die Ausgaben im Debuggerfenster. Was passiert, wenn Sie das Fenster durch Ziehen am Rahmen vergrößern bzw. verkleinern? Welche Ausgaben erhalten Sie wenn Sie das Fenster zum Symbol verkleinern und dann wieder in seiner normalen Größe darstellen?

Im ersten Fall, dem Verändern der Fenstergröße durch Ziehen am Rahmen, werden Sie folgende Ausgabe erhalten:

CChildView-Size: 347,444
CMainFrame-Size: 351,466
Fenster wird neu gezeichnet!

Zuerst wird die OnSize(...) Methode des Ausgabebereichs ausgeführt und dann die des Rahmenfensters. Beide Fenster erhalten also die Nachricht zugesandt und können darauf entsprechend reagieren. Beachten Sie auch die unterschiedlichen Größenangaben bei der Ausgabe. Das Rahmenfenster ist (logischerweise) immer etwas größer als der Ausgabebereich. Dies liegt daran, dass z.B. die Statusleiste ebenfalls Teil des Rahmenfenster ist.

Sehen wir uns jetzt den zweiten Versuch an, das Verkleinern des Fensters zum Symbol. Dort werden Sie folgende Ausgabe erhalten:

CMainFrame-Size: 0,0
CMainFrame-Size: 351,466
Fenster wird neu gezeichnet!
<<< Hier wird Fenster verkleinert
<<< und hier wieder vergrößert

Hier erhält nur das Rahmenfenster eine Benachrichtigung über das Verändern der Fenstergröße. Jedoch wird, wenn das Fenster wieder in seiner normalen Größe dargestellt wird, dem Ausgabebereich eine WM_PAINT Nachricht zugesandt. Schließlich muss er ja seinen Inhalt neu darstellen.

So und jetzt sind Sie wieder dran!

Erweitern Sie die Anwendung so, dass vor dem Schließen des Fensters nochmals abgefragt wird ob die Anwendung auch tatsächlich beendet werden soll. Nur wenn diese Abfrage bejaht wird soll die Anwendung auch beendet werden. Wenn Sie sich nicht mehr sicher sind welche Nachricht Sie hier bearbeiten müssen, so klicken Sie hier. Aber bitte es vorher selbst einmal versuchen!

Wenn Sie die richtig Nachricht gefunden und zum Fenster hinzugefügt haben sollten Sie sich unbedingt auch die Online-Hilfe zur dieser Methode einmal ansehen!

Lösung zur Schließung der Anwendung

void CMainFrame::OnClose()
{
    // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen....
   
   
if (MessageBox("Anwendung beenden?","HINWEIS",MB_YESNO|MB_ICONQUESTION) == IDYES)
        CFrameWnd::OnClose();
}

Sie müssen hier die WM_CLOSE Nachricht des Rahmenfenster abfangen! Wenn Sie versucht haben diese Nachricht in der Klasse des Ausgabebereichs der Anwendung zu bearbeiten, so werden Sie feststellen, dass der Klassen-Assistent diese Nachricht hier gar nicht angeboten hat. Schließlich befindet sich das Schließmenü ja auch im Rahmenfenster.

Innerhalb der OnClose(...) wird eine Messagebox zur Abfrage verwendet. Wenn die Abfrage bejaht wird, wird die Standard-Methode aufgerufen die dann das Fenster schließt. Laut Online-Hilfe ruft die Standard-Methode die Methode DestroyWindow(...) auf um das Fenster zu zerstören.

Eigentlich wieder gar nicht so schwer, wenn man's weiß.

Ende der Lösung

Das fertige Beispiel zu dieser und vorherigen Lektion finden Sie im Programmverzeichnis unter 03EPMMFC\MFCBasis.

Ich hoffe Sie haben jetzt einen kleinen Einblick in die Nachrichtenbearbeitung innerhalb der MFC und auch die wertvolle Hilfe des Klassen-Assistenten hierbei schätzen gelernt. Im weiteren Verlauf des Kurses werden Sie das Einbringen von Nachrichtenbearbeitern noch oft üben können.

In der nächsten Lektion befassen wir uns einmal etwas näher mit dem Thema Untergeordnete Fenster.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470