|
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:

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!
 |
Bevor Sie an dieser Übung verzweifeln kann ich Ihnen auch wieder die Lösung
anzeigen. |
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.
|