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.

Lösung zur anwenderdefinierten Nachricht

Definieren Sie zuerst über den Klassen-Assistenten wie folgt den Nachrichtenbearbeiter für die anwenderdefinierte Nachricht:

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.

Die Mausnachrichten

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.

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.



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