Maus-Nachrichten
Wie Sie ja in der Zwischenzeit wissen, ist WINDOWS ein nachrichtenbasierendes
System. Und so werden auch alle Mausaktionen des Anwenders durch Nachrichten dem
Fenster mitgeteilt. Nachrichten die beim Drücken einer Maustaste ausgelöst werden
sind vom Typ WM_xBUTTONDOWN, wobei x die jeweilige Maustaste kennzeichnet. Für die
linke Maustaste ist x durch 'L', für die rechte durch 'R' und für die mittlere durch
'M' zu ersetzen.
Für diese Nachrichten stellt die MFC innerhalb der Klasse CWnd bereits
vordefinierte Nachrichtenbearbeiter bereit, die entsprechend der allgemeinen Namensgebung
OnXButtonDown(...) heißen. Das X steht für die gedrückte Maustaste
und ist entsprechend wieder durch den Buchstaben 'L', 'M' oder 'R' zu ersetzen.
So wird z.B. die Methode OnLButtonDown(...) immer dann aufgerufen, wenn
die linke Maustaste gedrückt wird. Im ersten Parameter erhält die Methode Informationen
über den Zustand der Maus- und Kontrolltasten zum Zeitpunkt des Tastendrucks. Diese
Information kann ein Kombination aus folgenden Konstanten sein:
|
Konstante
|
Bedeutung
|
| MK_LBUTTON |
Linke Maustaste ist gedrückt. |
| MK_MBUTTON |
Mittlere Maustaste ist gedrückt. |
| MK_RBUTTON |
Rechte Maustaste ist gedrückt. |
| MK_SHIFT |
Die SHIFT-Taste ist gedrückt. |
| MK_CONTROL |
Die STRG-Taste ist gedrückt. |
Im zweiten Parameter erhält die Methode die Mausposition in Form eines CPoint-Objekts.
Diese Position ist relativ zur Client-Area und immer in physikalischen Einheiten,
d.h. Sie ist unabhängig vom gerade einstellten Mapping-Mode des Fensters.
Analog der Nachricht beim Drücken der Maustaste gibt es weitere Nachrichten,
die das Loslassen der Maustaste signalisieren. Diese Nachrichten heißen entsprechend
WM_xBUTTONUP. X ist auch hier wieder durch die entsprechende Kennung der
Maustaste (L,M,R) zu ersetzen. Und auch hierfür bietet die Klasse CWnd
vordefinierte Nachrichten-Methoden mit den Namen OnXButtonUp(...). Diese
Methoden erhalten die gleichen Parameter wie die OnXButtonDown(...) Methoden.
Bei einem Doppelklick mit einer der Maustasten wird eine weitere Nachricht vom
Typ WM_xBUTTONDBLCLK versendet. Die entsprechenden MFC-Standardmethoden besitzt
den Namen OnXButtonDblClk(...) mit den bekannten Mausnachrichten-Parametern.
 |
Ein Fenster erhält nur dann WM_xBUTTONDBLCLK Nachrichten, wenn
im Fensterstil der Stil CS_DBLCLKS angegeben wurde. Dies ist bei CWnd-Objekten
standardmäßig der Fall.
Beachten Sie ferner, dass bei einem Doppelklick mit einer Maustaste zuerst
eine WM_xBUTTONDOWN, dann eine WM_xBUTTONUP und erst dann die WM_xBUTTONDBLCLK
Nachricht an die Anwendung gesandt wird!
|
Verlassen wir damit die Maustasten und wenden uns der Mausbewegung zu. Wird die
Maus innerhalb des aktiven Fensters bewegt, so erhält das Fenster Nachrichten vom
Typ WM_MOUSEMOVE. Beachten Sie bitte, dass im Regelfall das aktive Fenster nur dann
diese Nachricht erhält, wenn sich der Cursor (Mauszeiger) innerhalb des Fensters
befindet. Ausnahme: Maus wurde mittels SetCapture(...) eingefangen (siehe
weiter unten). Die Standard-Methode für die Bearbeitung dieser Nachricht ist die
CWnd-Methode OnMouseMove(...). Auch hier entsprechen die Parameter
wieder denen beim Betätigen der Maustasten.
 |
Steigen wir jetzt in die Praxis ein. Damit wir uns nicht mit
dem üblichen Vorarbeiten beschäftigen müssen, gibt es für diese Übung ein vorgefertigtes
Rahmenprogramm. Kopieren Sie sich das Projekt 99Templates\MouseMsg
in Ihr Arbeitsverzeichnis. Sie können das Programm anschließend übersetzen und
starten. |
Das erste was Ihnen nach dem Starten des Beispiel vermutlich auffällt ist die
Tatsache, dass die Anwendung zwei Fenster besitzt. Im zweiten Fenster, das als Popup-Window
in der OnCreate(...) Methode des Hauptfensters erstellt wird, soll später
die aktuelle Mausposition innerhalb des Ansichtsobjekts der Anwendung sowie die
gedrückten Tasten ausgegeben werden. Im Augenblick erhalten Sie im Fenster natürlich
noch keine Ausgaben. Damit das Ansichtsobjekt Zugriff auf das Popup-Fenster hat,
wurde dem Hauptfenster noch eine Methode GetMouseWnd(...) hinzugefügt.
 |
Erweitern wir jetzt zunächst die Funktionalität des Popup-Fensters.
Wie erwähnt sollen in diesem Fenster die aktuelle Mausposition im Ansichtsobjekt
sowie die gedrückten Maustasten ausgegeben werden. Diese Daten könnten einfach
durch den Aufruf einer public-Methode des Popup-Fensters ans Fensters
übertragen werden. Da WINDOWS aber ein nachrichtenbasierendes System ist, werden
wir diese Daten durch eine anwenderdefinierte Nachricht ans Popup-Fenster senden.
Erstellen wir zuerst die anwenderdefinierte Nachricht. Öffnen Sie dazu die Header-Datei
WndMousePos.h und fügen Sie am Beginn der Datei folgende Definition
der Nachrichten-Konstante ein:
#ifndef AFX_WNDMOUSEPOS_H__37A8C813_5321_11D2_8F91_923CC248A32F__INCLUDED_
#define AFX_WNDMOUSEPOS_H__37A8C813_5321_11D2_8F91_923CC248A32F__INCLUDED_
// WndMousePos.h : Header-Datei
//
#define WM_MMOVE (WM_APP+1)
/////////////////////////////////////////////////////////////////////////////
// Fenster CWndMousePos |
|
WM_MMOVE ist die Nachrichten-ID unserer anwenderdefinierten Nachricht. Die Konstante
WM_APP ist in WINUSER.H definiert und gibt die Start-ID für anwendungslokale
Nachrichten vor.
 |
Beachten Sie bitte, dass Sie Nachrichten ab der ID WM_USER
nur für fensterklassen-private Nachrichten verwenden sollten. Da die anwenderdefinierte
Nachricht aber zwischen zwei verschiedenen Fensterklassen übertragen wird sollte
hier als Start-ID WM_APP verwendet werden. |
Als nächstes muss für diese Nachricht ein Nachrichtenbearbeiter definiert und
die Nachricht in die Nachrichtentabelle eingetragen werden.
 |
Versuchen Sie nun einmal selbst, für die Nachricht WM_MMOVE
einen Nachrichtenbearbeiter innerhalb der Klasse CWndMousePos zu definieren.
Sie brauchen ihn noch nicht 'mit Leben füllen', das kommt erst noch. Geben Sie
dem Nachrichtenbearbeiter, nach MFC-Konvention, den Namen OnMMove(...).
Benennen Sie die beiden Parameter der Methode wFlags und lPos.
Die Datentypen der Nachrichten-Parameter werden ja von WINDOWS vorgegeben.
Damit Sie es aber etwas leichter haben, verrate ich Ihnen noch mit welchem Makro
Sie in der Nachrichtentabelle die anwenderdefinierte Nachricht eintragen. Sie
müssen anwenderdefinierte Nachrichten immer mit dem Makro ON_MESSAGE(...)
von Hand zur Nachrichtentabelle hinzufügen. Der Klassen-Assistent bietet für
anwenderdefinierte Nachrichten keine Unterstützung an.
 |
Wenn Sie genug geschwitzt haben kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zur anwenderdefinierten Nachricht
Definieren Sie zuerst über den Klassen-Assistenten wie folgt
den Nachrichtenbearbeiter für die anwenderdefinierte Nachricht:

Anschließend fügen Sie der Nachrichtentabelle das Makro
ON_MESSAGE(...) wie folgt hinzu:
BEGIN_MESSAGE_MAP(CWndMousePos,
CWnd)
//{{AFX_MSG_MAP(CWndMousePos)
ON_WM_PAINT()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_MMOVE,OnMMove)
END_MESSAGE_MAP() |
Im Prinzip ganz leicht, wenn man's weiß!
Ende der Lösung
|
Im Anschluss daran fügen Sie folgenden Code in die neu erstellte Methode
OnMMove(...) ein:
LONG CWndMousePos::OnMMove(UINT
wFlags, LONG lPos)
{
// X/Y Position der
Maus abspeichern
m_nXPos = HIWORD(lPos);
m_nYPos = LOWORD(lPos);
// Tastenkombination abspeichern
m_nFlags = wFlags;
// und Fensterinhalt neu
darstellen
Invalidate();
return 0L;
} |
Die Mauskoordinaten werden kodiert im lPos Parameter der Nachricht
übergeben, wobei im High-Word die X-Position und im Low-Word die Y-Position
abgelegt ist. Im Parameter wFlags werden die Tastenkombinationen übergeben.
Zum Schluss müssen noch die erhaltenen Werte im Popup-Window dargestellt
werden.
void CWndMousePos::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Code für die Behandlungsroutine für Nachrichten hier
einfügen
// Kein Aufruf von CWnd::OnPaint() für Zeichnungsnachrichten
// Puffer fuer Ausgabetexte
char acBuffer[80];
char acKey[5];
// Tastenpuffer kompl. mit
Leerzeichen fuellen
memset(acKey,' ',5);
// Tastenkombination dekodieren
if (m_nFlags&MK_LBUTTON)
acKey[0] = 'L';
if (m_nFlags&MK_MBUTTON)
acKey[1] = 'M';
if (m_nFlags&MK_RBUTTON)
acKey[2] = 'R';
if (m_nFlags&MK_CONTROL)
acKey[3] = 'C';
if (m_nFlags&MK_SHIFT)
acKey[4] = 'S';
// Mausposition fuer Ausgabe
in ASCII wandeln
wsprintf(acBuffer,"X:%d,
Y:%d",m_nXPos,m_nYPos);
// Mausposition ausgeben
dc.TextOut(10,10,acBuffer,strlen(acBuffer));
// Tastenkombinationen ausgeben
dc.TextOut(10,30,acKey,5);
} |
|
Damit ist das Popup-Fenster in seiner Funktionalität vollständig. Wenden wir
uns nach der Behandlung von anwenderdefinierten Nachrichten und der Ausgabe der
Mausdaten unserem eigentlichen Thema zu, den Mausnachrichten.
 |
Fügen Sie zunächst der Ansichtsklasse CMouseMsgView
die Nachrichtenbearbeiter für die fünf Mausnachrichten WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_RBUTTONDOWN, WM_RBUTTONUP und WM_MOUSEMOVE hinzu.

|
Damit letztendlich auch etwas im Ansichtsobjekt zu sehen ist wenn die Maus bewegt
wird, soll von dem Punkt, an dem die linke oder rechte Maustaste gedrückt wurde
bis zu dem Punkt, an dem sie wieder losgelassen wird, eine Linie gezeichnet werden.
Beim Drücken der linken Maustaste soll die Linie rot und beim Drücken der rechten
Maustaste blau dargestellt werden. Wird die Maus mit gedrückter Maustaste bewegt,
so soll die Linie bis zur jeweiligen Mausposition gezogen werden. Außerdem sind
auch noch die Mausparameter an das vorhin erstellte Popup-Fenster zu übermitteln.
 |
Erweitern Sie dazu die oben hinzugefügten Maus-Methoden wie
nachfolgend angegeben. Beachten Sie bitte, dass die modifizierten Methoden nicht
mehr die Basisklassen-Methoden aufrufen. Die in den Methoden verwendeten Membervariablen
m_CStartPos, m_CEndPos, m_bDraw und m_LineColor sind innerhalb
der Klasse bereits deklariert. Ihre Bedeutung dürfte aus ihren Namen hervorgehen.
void CMouseMsgView::OnLButtonDown(UINT
nFlags, CPoint point)
{
m_CStartPos = point;
m_CEndPos = point;
m_LineColor = RGB(255,0,0);
m_bDraw = true;
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
}
void CMouseMsgView::OnLButtonUp(UINT nFlags, CPoint point)
{
m_CEndPos = point;
m_bDraw = false;
Invalidate();
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
}
void CMouseMsgView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDraw)
{
m_CEndPos = point;
Invalidate();
}
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
}
void CMouseMsgView::OnRButtonDown(UINT nFlags, CPoint point)
{
m_CStartPos = point;
m_CEndPos = point;
m_LineColor = RGB(0,0,255);
m_bDraw = true;
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
}
void CMouseMsgView::OnRButtonUp(UINT nFlags, CPoint point)
{
m_CEndPos = point;
m_bDraw = false;
Invalidate();
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
} |
|
Beachten Sie bitte das Flag m_bDraw im obigen Listing. Diese Flag wird
immer dann gesetzt, wenn eine der Maustasten gedrückt wird. Ab diesem Zeitpunkt
wird gezeichnet! In der Methode OnMouseMove(...) wird m_bDraw
ausgewertet. Wird die Maus mit gedrückter Maustaste bewegt (m_bDraw gleich
true), so wird die aktuelle Mausposition als Endkoordinate für die zu zeichnende
Linie verwendet und der Fensterinhalt als ungültig markiert um die Linie neu darzustellen.
Die Darstellung der Linie erfolgt, wie üblich, in der Methode OnDraw(...).
OnDraw(...) muss einen Stift mit der gewünschten Farbe erstellen und die
Linie von der Koordinate m_CStartPos bis zu Koordinate m_CEndPos
zeichnen.
 |
Setzen Sie im Konstruktor des Ansichtsobjekts zunächst das
Flag m_bDraw wie angegeben auf den Wert false. Anschließend
erweitern Sie die OnDraw(...) Methode noch um die notwendigen Zeichenanweisungen
für die Linie.
CMouseMsgView::CMouseMsgView()
{
// ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,
m_bDraw = false;
}
....
void CMouseMsgView::OnDraw(CDC* pDC)
{
CMouseMsgDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten
hinzufügen
CPen CLinePen(PS_SOLID,1,m_LineColor);
pDC->SelectObject(&CLinePen);
pDC->MoveTo(m_CStartPos);
pDC->LineTo(m_CEndPos);
} |
Übersetzen und starten Sie das Beispiel. Wenn Sie die Maus nun mit gedrückter
linker Maustaste bewegen so erhalten Sie ein rote Linie die der Maus folgt.
Beim Drücken der rechten Maustaste eine entsprechende blaue Linie. Zusätzlich
werden im Popup-Window die aktuelle Mausposition sowie die gedrückten Tasten
angezeigt.
|
Sieht doch schon gar nicht schlecht aus. Doch einen 'kleinen' Fehler hat das
Beispiel noch. Haben Sie ihn schon gefunden? Wenn nicht versuchen einmal folgendes:
drücken Sie innerhalb des Fenster eine der beiden Maustasten, fahren dann mit gedrückter
Maustaste aus dem Fenster hinaus und lassen dort dann die Maustaste los. Anschließend
bewegen Sie Maus wieder ins Fenster hinein. Die Linie wird wieder der Maus folgen
obwohl keine Maustaste gedrückt ist. Was ist hier passiert? Wiederholen Sie den
Vorgang nochmals und beobachten Sie dabei die Ausgaben im Popup-Fenster. Sobald
die Maus das Fenster verlässt erhält das Fenster keine Mausnachrichten mehr. Wird
nun außerhalb des Fensters die Maustaste losgelassen, dann erfährt das Fenster nichts
davon und reagiert beim nächsten Wiedereintritt der Maus ins Fenster falsch. Um
dieses 'Fehlverhalten' zu korrigieren stehen mehrere Möglichkeiten zur Verfügung.
Eine der Möglichkeiten ist die Maus 'einzufangen'. Alle Nachrichten über Aktionen
mit der Maus werden ausschließlich an das Fenster gesendet, das die Maus eingefangen
hat, unabhängig davon ob sich die Maus innerhalb des Fensters befindet oder nicht.
Dieses Einfangen der Maus ist aber nur solange wirksam, wie eine der Maustasten
gedrückt ist. Wird die Maustaste losgelassen, so wird eine 'eingefangene' Maus automatisch
wieder freigegeben. Um die Maus einzufangen wird die CWnd-Methode SetCapture(...)
aufgerufen. Wenn eine 'eingefangene' Maus nicht mehr benötigt wird, muss
sie mit der API-Funktion ReleaseCapture(...) wieder freigegeben werden.
 |
Sollten Sie einmal vergessen, eine eingefangene Maus wieder freizugeben,
so kann es zu unerwarteten Reaktionen in der Anwendung kommen! |
Bauen wir diese Möglichkeit nun in unser Beispiel ein. Da wir nachher gleich
noch eine weitere Möglichkeit uns ansehen wollen, werden wir die Maus nur dann einfangen,
wenn die linke Maustaste gedrückt wird.
 |
Erweitern Sie jetzt OnLButtonDown(...) um den Aufruf
der Methode SetCapture(...) und die Methode OnLButtonUp(...)
um ReleaseCapture(...).
void CMouseMsgView::OnLButtonDown(UINT
nFlags, CPoint point)
{
....
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
SetCapture();
}
void CMouseMsgView::OnLButtonUp(UINT nFlags, CPoint point)
{
....
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
::ReleaseCapture();
} |
Bewegen Sie bei gedrückter linker Maustaste die Maus auch einmal links bzw.
nach oben aus dem Fenster heraus. Welche Mauspositionen erhalten Sie?
|
Sehen wir uns jetzt die zweite Möglichkeit an, den 'kleinen' Fehler von vorhin
zu beseitigen. WINDOWS bietet ebenfalls die Möglichkeit, die Bewegungsfreiheit der
Maus auf einen rechteckigen Bildschirmbereich zu begrenzen. Um die Mausbewegung
einzuschränken ist die API-Funktion ClipCursor(...) aufzurufen. Die Funktion
enthält als Parameter die Bildschirm-Koordinaten der linken oberen und rechten
unteren Ecke des zulässigen Cursor-Bereichs. Wird für den Parameter der Wert NULL
angegeben, so wird die Beschränkung der Mausbewegung aufgehoben.
Bauen wir diese Möglichkeit jetzt noch in das Beispiel ein. Die Mausbewegung
soll beim Drücken der rechten Maustaste auf den Client-Bereich begrenzt werden.
 |
Damit Sie nun endlich auch einmal wieder etwas zu tun bekommen,
versuchen Sie selbst einmal die Bewegungsfreiheit des Cursor auf den Ansichtsbereich
einzuschränken.
 |
Wenn Sie genug geschwitzt haben kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zur ClipCursor Funktion
void CMouseMsgView::OnRButtonDown(UINT
nFlags, CPoint point)
{
....
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
RECT ClientRect;
// Groesse der Client-Area
holen
GetClientRect(&ClientRect);
// Koordinaten der Client-Area
auf Bildschirm-Koord. umrechnen
ClientToScreen(&ClientRect);
// Mausbewegung auf
den umgerechneten Bereich begrenzen
::ClipCursor(&ClientRect);
}
void CMouseMsgView::OnRButtonUp(UINT nFlags, CPoint point)
{
....
m_pMPosWnd->SendMessage(WM_MMOVE,nFlags,(point.x<<16)+point.y);
// Mausbewegung
nicht mehr einschraenken
::ClipCursor(NULL);
} |
Na, haben Sie auch nicht vergessen, die Koordinaten des Ansichtsbereichs
in Bildschirm-Koordinaten umzurechnen? Die entsprechende CWnd Methode
dazu lautet ClientToScreen(...).
Und ja nicht vergessen, den Cursorbereich auch wieder freizugeben!
Ende der Lösung
|
Damit ist das Beispiel zum Thema Mausnachrichten fertig. Das fertige Beispiel
finden Sie auch unter 06Eingaben\MouseMsg.
|
Bevor wir diese Lektion beenden noch ein Hinweis:
 |
Außer den bisher genannten Mausnachrichten, die sich alle auf
die Client-Area eines Fensters beziehen, gibt es weitere Mausnachrichten die
dann auftreten, wenn sich die Maus zwar innerhalb des Fensters aber außerhalb
der Client-Area befindet. Diese Nachrichten sind vom Typ WM_NCXBUTTONY,
wobei X wieder die Maustaste beschreibt
und Y die Mausaktion (DOWN bzw.
UP). Die MFC stellt auch hierfür geeignete OnXxx(...) Methoden zur
Verfügung. Beachten Sie bitte, dass Nachrichten dieses Typs immer an das Rahmenfenster
gesendet werden und nicht an das Ansichtsobjekt. Außerdem sollten (oder besser
müssen) Sie in Ihren eigenen OnNcXxx(...) Methoden immer die Methode
der Basisklasse mit aufrufen. Ansonsten kann es Ihnen passieren, dass Sie mit
dem Fenster nicht mehr richtig arbeiten können |
So, damit beenden wir diese Lektion endgültig und wenden uns anschließend den
Tastatureingaben zu.
|