vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 3

Maus und Tastatur

Je nach Art der zu erstellenden Anwendung muß man überwachen, welche Aktionen der Benutzer mit der Maus unternimmt - wann und wo er mit der Maus klickt, welche Taste er dabei betätigt und wann er die Taste losläßt. Außerdem muß man darüber informiert sein, was der Benutzer bei gedrückter Maustaste unternimmt.

Weiterhin ist es gegebenenfalls erforderlich, Tastaturereignisse zu verarbeiten. Wie bei der Maus braucht man die Angaben, wann der Benutzer eine Taste drückt, wie lange er sie niederhält und wann er sie wieder losläßt.

In der heutigen Lektion lernen Sie, ...

Mausereignisse

Wie Sie gestern erfahren haben, bietet der Klassen-Assistent für die meisten Steuerelemente nur eine ausgewählte Anzahl von Ereignissen. Bei Mausereignissen beschränkt sich das auf Klicken und Doppelklicken. Ein kurzer Blick auf die Maus macht aber deutlich, daß zum Auffangen von Mausereignissen mehr gehören muß, als das Erkennen der beiden genannten Aktionen. Wie steht es zum Beispiel mit der rechten Maustaste? Wie läßt sich herausfinden, ob sie gedrückt ist? Und was passiert in Zeichenprogrammen? Wie können diese Programme der Spur folgen, die man mit dem Mauszeiger vorgibt?

Wenn Sie den Klassen-Assistenten in einem Ihrer Projekte öffnen, das Dialogfeld in der Liste der Objekt-IDs markieren und dann durch die Liste der verfügbaren Nachrichten blättern, finden Sie eine Reihe von Ereignissen, die sich auf die Maus beziehen. Mit diesen Ereignissen kann man alle Aufgaben erledigen, die in einer Anwendung anfallen. Tabelle 3.1 bringt eine Übersicht dieser Ereignisse.

Tabelle 3.1: Nachrichten für Mausereignisse

Nachricht

Beschreibung

WM_LBUTTONDOWN

Die linke Maustaste wurde gedrückt.

WM_LBUTTONUP

Die linke Maustaste wurde losgelassen.

WM_LBUTTONDBLCLK

Mit der linken Maustaste wurde ein Doppelklick ausgeführt.

WM_RBUTTONDOWN

Die rechte Maustaste wurde gedrückt.

WM_RBUTTONUP

Die rechte Maustaste wurde losgelassen.

WM_RBUTTONDBLCLK

Mit der rechten Maustaste wurde ein Doppelklick ausgeführt.

WM_MOUSEMOVE

Die Maus wird über den Fensterbereich der Anwendung verschoben.

WM_MOUSEWHEEL

Das Mausrad wird bewegt.

Mit der Maus zeichnen

Heute erstellen Sie ein einfaches Zeichenprogramm, mit dem der Benutzer einfache Figuren in einem Dialogfeld zeichnen kann. Das Programm verwendet einige der verfügbaren Mausereignisse. Im wesentlichen stützt sich die Anwendung auf die Nachricht WM_MOUSEMOVE, die signalisiert, daß die Maus verschoben wird. Dabei erfahren Sie, wie man innerhalb der Behandlungsfunktion ermittelt, ob die linke Maustaste gedrückt oder losgelassen wurde. Weiterhin lernen Sie, wie man die Position der Maus im Fenster bestimmt. Das klingt alles ganz einfach. Führen Sie also die folgenden Schritte aus:

1. Legen Sie mit dem MFC-Anwendungs-Assistenten einen neuen Projekt-Arbeitsbereich an, und nennen Sie das Projekt Maus.

2. Legen Sie im ersten Schritt des Anwendungs-Assistenten für das Projekt fest, daß Sie eine dialogfeldbasierende Anwendung erstellen.

3. Übernehmen Sie die Standardeinstellungen des Anwendungs-Assistenten. Im zweiten Schritt geben Sie einen passenden Titel für das Dialogfeld wie zum Beispiel Maus und Tastatur an.

4. Nachdem das Anwendungsgerüst erstellt ist, entfernen Sie alle Steuerelemente aus dem Dialogfeld. Damit steht die gesamte Fensterfläche des Dialogfelds zum Zeichnen zur Verfügung. Dieser Schritt ist auch erforderlich, damit die Anwendung alle Tastaturereignisse auffangen kann.

Wenn in einem Dialogfeld irgendwelche Steuerelemente vorhanden sind, gelangen alle Tastaturereignisse zum Steuerelement, das momentan den Eingabefokus hat. (Ein Steuerelement, das den Eingabefokus hat, ist hervorgehoben oder enthält den Cursor zur Eingabe.) Damit sich Tastaturereignisse in einem Dialogfeld auffangen lassen, muß man alle Steuerelemente aus dem Dialogfeld löschen.

5. Öffnen Sie den Klassen-Assistenten. Wählen Sie WM_MOUSEMOVE aus der Liste der Nachrichten, und klicken Sie auf die Schaltfläche Funktion hinzufügen, um eine neue Funktion zu erstellen. Klicken Sie auf die Schaltfläche OK, um den vorgeschlagenen Funktionsnamen zu übernehmen.

6. Klicken Sie auf die Schaltfläche Code bearbeiten, um in die gerade erstellte Funktion OnMouseMove den Code aus Listing 3.1 einzugeben.

Listing 3.1: Die Funktion OnMouseMove

1: void CMausDlg::OnMouseMove(UINT nFlags, CPoint point)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Linke Maustaste gedrückt?
10: if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
11: {
12: // Gerätekontext holen
13: CClientDC dc(this);
14:
15: // Pixel zeichnen
16: dc.SetPixel(point.x, point.y, RGB(0, 0, 0));
17: }
18:
19: ///////////////////////
20: // EIGENER CODE, ENDE
21: ///////////////////////
22:
23: CDialog::OnMouseMove(nFlags, point);
24: }

Sehen Sie sich die Funktionsdefinition am Beginn des Listings an. An die Funktion werden zwei Argumente übergeben. Das erste ist eine Gruppe von Flags, über die man ermitteln kann, ob und welche Maustaste niedergedrückt ist. Das geschieht in der ersten Codezeile mit der if-Anweisung:

if ((nFlags & MK_LBUTTON) == MK_LBUTTON)

Der erste Teil der auszuwertenden Bedingung filtert das Flag heraus, das kennzeichnet, ob die linke Maustaste gedrückt ist. In der zweiten Hälfte werden die gefilterten Flags mit dem Flag verglichen, das für das Drücken der linken Maustaste steht. Bei einer Übereinstimmung ist die linke Maustaste gedrückt.

Das zweite Argument an diese Funktion gibt die Position der Maus an. Dieses Argument enthält die Bildschirmkoordinaten der aktuellen Mausposition. Mit Hilfe dieser Angaben läßt sich ein Punkt im Dialogfenster zeichnen.

Bevor man aber Punkte im Dialogfeld zeichnen kann, muß man den Gerätekontext für das Dialogfeld holen. Dazu wird eine neue Instanz der Klasse CClientDC erzeugt. Diese Klasse kapselt den Gerätekontext und die meisten darauf ausführbaren Operationen, einschließlich aller Operationen, die auf den Bildschirm zeichnen. Praktisch ist der Gerätekontext die Leinwand, auf der Sie mit Ihrer Anwendung zeichnen. Keine Leinwand - kein Gemälde. Nachdem das Gerätekontextobjekt erzeugt ist, können Sie dessen Funktion SetPixel aufrufen. Diese Funktion färbt Pixel (Bildpunkte) an der Position, die in den beiden ersten Argumenten angegeben ist, mit der im dritten Argument spezifizierten Farbe. Wenn Sie Ihr Programm kompilieren und ausführen, können Sie sich davon überzeugen, daß man mit der Maus in die Fensterfläche des Dialogfelds zeichnen kann (siehe Abbildung 3.1).

In Windows spezifiziert man Farben in Form eines Zahlenwertes, der eine Kombination aus drei Zahlen darstellt. Diese drei Zahlen bezeichnen die Helligkeit der roten, grünen und blauen Pixel auf dem Computerbildschirm. Die Funktion RGB ist ein Makro, das die drei separaten Werte in eine einzige Zahl umwandelt. Diese Zahl läßt sich an die Funktion SetPixel oder eine andere Funktion, die einen Farbwert erfordert, übergeben. Die Werte für die Farben Rot, Grün und Blau können im Bereich zwischen 0 und 255 (jeweils einschließlich) liegen.

Abbildung 3.1:
Mit der Maus im Fenster zeichnen

Die binären Verknüpfungen AND und OR

Falls Sie ein Einsteiger in C++ sind, sollten Sie sich mit der Arbeitsweise der verschiedenen Typen von AND (UND) und OR (ODER) vertraut machen. Man unterscheidet logische und binäre Verknüpfungen mit AND und OR. Die erste Kategorie verwendet man in logischen oder Bedingungsanweisungen, beispielsweise if- oder while-Anweisungen, die den Programmfluß steuern. Die binären AND- und OR-Verknüpfungen kombinieren zwei Werte auf Bitebene.

Die AND-Verknüpfung wird durch das kaufmännische Und-Zeichen (&) repräsentiert. Ein einzelnes Und-Zeichen (&) steht für ein binäres AND, ein doppeltes Und-Zeichen (&&) kennzeichnet ein logisches AND. Die Arbeitsweise der logischen AND-Verknüpfung entspricht dem Schlüsselwort AND in Visual Basic oder PowerBuilder. Diese Verknüpfung verwendet man beispielsweise in einer if-Anweisung, um auszudrücken »Wenn (IF) diese Bedingung UND (AND) die andere Bedingung erfüllt sind, dann ...«. Beide Bedingungen müssen erfüllt (TRUE) sein, damit die gesamte Anweisung das Ergebnis TRUE liefert. Eine binäres AND setzt man dagegen ein, um Bits zu setzen oder rückzusetzen. Wenn man zwei Werte mit einem binären AND verknüpft, behalten nur diejenigen Bitpositionen eine 1, die in beiden Werten auf 1 gesetzt waren. Alle anderen Bits werden auf 0 gesetzt. Sehen wir uns zur Verdeutlichung zwei 8-Bit-Werte an:

Wert1 01011001
Wert2 00101001

Verknüpft man diese beiden Werte mit einem binären AND, erhält man folgendes Ergebnis:

Wert1 AND Wert2 00001001

Alle Bitpositionen, auf denen in mindestens einem Wert eine 0 steht, sind im Ergebnis auf 0 gesetzt. Bits, die in beiden Werten auf 1 stehen, bleiben auch im Ergebnis auf 1.

Die OR-Verknüpfung wird durch das Pipe-Zeichen (|) symbolisiert. Analog zur AND-Verknüpfung steht ein einzelnes Pipe-Zeichen (|) für ein binäres OR, während ein doppeltes Pipe (||) ein logisches OR kennzeichnet. Das logische OR kann man ebenfalls in Bedingungsanweisungen wie if- oder while-Anweisungen einsetzen, um den logischen Programmfluß zu steuern. Es verhält sich genau wie das Schlüsselwort OR in Visual Basic oder PowerBuilder. Diese Verknüpfung verwendet man beispielsweise in einer if- Anweisung, um auszudrücken »Wenn (IF) diese Bedingung ODER (OR) die andere Bedingung erfüllt ist, dann ...«. Wenn eine der beiden Bedingungen TRUE ist, liefert auch die gesamte Anweisung das Ergebnis TRUE. Mit dem binären OR lassen sich Werte auf Bitebene kombinieren. Ist in einer OR-Verknüpfung die Bitposition in einem der Werte gleich 1, ist auch das entsprechende Bit im Ergebniswert auf 1 gesetzt. Eine 0 im Ergebnis tritt bei einer binären OR-Verknüpfung nur dann auf, wenn beide Bits in den Operanden gleich 0 sind. Mit den beiden obigen Beispielwerten

Wert1 01011001
Wert2 00101001

erhält man das Ergebnis einer binären OR-Verknüpfung zu:

Wert1 OR Wert2 01111001

Hier wird jedes Ergebnisbit zu 1, das in mindestens einem der beiden Werte gleich 1 ist. Nur die Bitpositionen im Ergebnis sind 0, die in beiden Ausgangswerten gleich 0 sind.

Binäre Attributflags

Die binären Verknüpfungen mit AND und OR verwendet man in C++ unter anderem, um Attributflags zu setzen und zu lesen. Attributflags sind Werte, bei denen jedes einzelne Bit angibt, ob eine bestimmte Option ein- oder ausgeschaltet ist. Damit kann der Programmierer definierte Flags verwenden. Unter einem definierten Flag versteht man einen Wert, bei dem nur ein Bit auf 1 gesetzt ist, oder eine Kombination von anderen Werten, bei denen eine bestimmte Bitkombination auf 1 gesetzt ist, so daß sich mehrere Optionen mit einem einzelnen Wert ausdrücken lassen. Die Flags, mit denen man verschiedene Optionen steuert, werden OR-verknüpft. Damit erhält man einen zusammengesetzten Wert, der angibt, welche Optionen eingeschaltet und welche ausgeschaltet sind.

Wenn zwei Flags, die bestimmte Bedingungen spezifizieren, auf zwei verschiedenen Bitpositionen in einem Byte definiert sind, lassen sich diese beiden Flags häufig mit einer OR-Verknüpfung wie folgt zusammenfassen:

Flag1 00001000
Flag2 00100000
OR-Kombination 00101000

Auf diese Weise kombiniert man Flags, um eine Anzahl von Einstellungen in einem begrenzten Speicherbereich festzulegen. Praktisch passiert das gleiche mit den meisten Einstellungen von Kontrollkästchen in den Fenster- und Eigenschaftsdialogfeldern von Steuerelementen. Diese Ein-/Aus-Einstellungen werden mit OR verknüpft und bilden dann eine oder zwei Gruppen von Flags, aus denen Windows ablesen kann, wie das Fenster oder das Steuerelement anzuzeigen ist und wie es sich verhalten soll.

Um nun zu ermitteln, ob ein bestimmtes Flag in der Kombination enthalten ist, kann man die Flagkombination mit dem gesuchten Flag in einer AND-Verknüpfung folgendermaßen testen:

Kombination 00101000
Flag1 00001000
Ergebnis 00001000

Das Ergebnis dieser Operation läßt sich mit dem zum Filtern der Flagkombination verwendeten Flag vergleichen. Handelt es sich um dasselbe Ergebnis, ist das Flag in der Kombination enthalten. Bei einer anderen Lösung prüft man, ob das gefilterte Kombinationsflag ungleich von Null ist. Wenn das zur Filterung - man spricht auch von Maskieren - verwendete Flag nicht in der Kombination enthalten ist, liefert das Ergebnisflag den Gesamtwert 0. Nach diesem Verfahren kann man sich den Vergleich in der if-Anweisung des obigen Codes sparen. Übrig bleibt eine if-Anweisung, die folgendermaßen aussieht:

if (nFlags & MK_LBUTTON)

Man kann diese Lösung auch modifizieren und testen, ob das interessierende Flag nicht in der Kombination enthalten ist:

if (!(nFlags & MK_LBUTTON))

Funktionell sind beide Versionen gleich. Vielleicht bevorzugen Sie die eine oder andere Version, oder verwenden beide in Ihrem Code.

Das Zeichenprogramm verbessern

Bei der Ausführung Ihres Programms haben Sie vielleicht ein kleines Problem bemerkt. Um eine durchgehende Linie zu zeichnen, mußten Sie die Maus sehr langsam bewegen. Wie lösen andere Zeichenprogramme dieses Problem? Sie zeichnen einfach eine Linie zwischen die beiden Punkte, die mit der Maus gesetzt werden. Auch wenn das ein wenig nach Mogelei aussieht, arbeiten die gängigen Zeichenprogramme nach diesem Verfahren.

Während Sie die Maus über den Bildschirm verschieben, prüft der Computer die Position der Maus alle paar Taktzyklen. Da der Computer auf diese Weise nicht ständig darüber informiert ist, wo sich die Maus gerade befindet, muß er diesbezügliche Annahmen treffen. Das geschieht dadurch, daß der Computer die bekannten Punkte verwendet und Linien zwischen ihnen zieht. Wenn Sie mit dem Freihandwerkzeug in Paint Linien zeichnen, übernimmt der Computer die Verbindung der einzelnen Punkte.

Wir wissen nun, daß die üblichen Zeichenprogramme Linien zwischen jedem Punktpaar ziehen. Was brauchen wir, um unsere Zeichenanwendung mit dieser Technik auszustatten? Zuerst einmal muß man die vorherige Position der Maus festhalten. Das bedeutet, daß man dem Dialogfeld zwei Variablen spendieren muß, um die vorherigen x- und y-Koordinaten zu speichern. Das läßt sich in folgenden Schritten realisieren:

1. Gehen Sie im Arbeitsbereich auf die Registerkarte Klassen.

2. Markieren Sie die Dialogfeldklasse - in diesem Fall CMausDlg.

3. Klicken Sie mit der rechten Maustaste, und wählen Sie Member-Variable hinzufügen aus dem Kontextmenü.

4. Geben Sie im Dialogfeld Member-Variable hinzufügen als Variablentyp int und als Variablenname m_iPrevY ein, und legen Sie den Zugriff mit Privat fest, wie es Abbildung 3.2 zeigt.

Abbildung 3.2:
Das Dialogfeld Member-Variable hinzufügen

5. Klicken Sie auf OK, um die Variable hinzuzufügen.

6. Wiederholen Sie die Schritte 3 bis 5, wobei Sie als Variablenname m_iPrevX für die zweite Variable eingeben.

Nachdem Sie die Variablen zum Speichern der vorherigen Mausposition hinzugefügt haben, können Sie die notwendigen Änderungen an der Funktion OnMouseMove gemäß Listing 3.2 vornehmen.

Listing 3.2: Die überarbeitete Funktion OnMouseMove

1: void CMausDlg::OnMouseMove(UINT nFlags, CPoint point)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Linke Maustaste gedrückt?
10: if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
11: {
12: // Gerätekontext holen
13: CClientDC dc(this);
14:
15: // Linie vom letzten zum aktuellen Punkt zeichnen
16: dc.MoveTo(m_iPrevX, m_iPrevY);
17: dc.LineTo(point.x, point.y);
18:
19: // Aktuellen Punkt als letzten Punkt speichern
20: m_iPrevX = point.x;
21: m_iPrevY = point.y;
22: }
23:
24: ///////////////////////
25: // EIGENER CODE, ENDE
26: ///////////////////////
27:
28: CDialog::OnMouseMove(nFlags, point);
29: }

Der Code zum Zeichnen der Linie vom vorherigen Punkt zum aktuellen Punkt sieht folgendermaßen aus:

dc.MoveTo(m_iPrevX, m_iPrevY);
dc.LineTo(point.x, point.y);

Es ist also zunächst eine Bewegung zur ersten Position auszuführen (MoveTo) und dann eine Linie zum zweiten Punkt zu ziehen (LineTo). Der erste Schritt ist deshalb wichtig, da man sonst Windows nicht mitteilen könnte, wo der Startpunkt liegt. Wenn Sie jetzt Ihre Anwendung kompilieren und ausführen, läßt es sich etwas besser zeichnen. Allerdings tritt nun ein eigentümliches Verhalten zutage. Sobald Sie die linke Maustaste drücken, um etwas mehr zu zeichnen, zieht die Anwendung zu diesem Punkt eine Linie vom Endpunkt der letzten gezeichneten Linie, wie es Abbildung 3.3 verdeutlicht.

Abbildung 3.3:
Das Zeichenprogramm mit einem eigentümlichen Verhalten

Letzte Anpassungen

Die Anwendung realisiert sämtliche Zeichenfunktionen auf das Ereignis »Verschieben der Maus«, wenn die linke Maustaste gedrückt ist. Initialisiert man die Variablen für die vorherige Position der Maus mit den Koordinaten, an der sich die Maus gerade befindet, wenn der Benutzer die linke Taste drückt, sollte sich das Verhalten der Anwendung korrigieren lassen. Probieren Sie die Lösung mit den folgenden Schritten aus:

1. Nehmen Sie mit dem Klassen-Assistenten eine Funktion für die Nachricht WM_LBUTTONDOWN in das Dialogfeldobjekt auf.

2. Fügen Sie in die eben erstellte Funktion OnLButtonDown den Code von Listing 3.3 ein.

Listing 3.3: Die Funktion OnLButtonDown

1: void CMausDlg::OnLButtonDown(UINT nFlags, CPoint point)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Aktuellen Punkt als Anfangspunkt setzen
10: m_iPrevX = point.x;
11: m_iPrevY = point.y;
12:
13: ///////////////////////
14: // EIGENER CODE, ENDE
15: ///////////////////////
16:
17: CDialog::OnLButtonDown(nFlags, point);
18: }

Wenn Sie die Anwendung kompilieren und ausführen, sollten Sie schon fast so zeichnen können, wie man es von einem Zeichenprogramm erwartet (siehe Abbildung 3.4).

Abbildung 3.4:
Das fertiggestellte Zeichenprogramm

Tastaturereignisse auffangen

Tastaturereignisse lassen sich fast genauso lesen wie Mausereignisse. Analog zur Maus werden Nachrichten verschickt, wenn man eine Taste drückt und wenn man sie wieder losläßt. Diese Ereignisse sind in Tabelle 3.2 aufgeführt.

Tabelle 3.2: Nachrichten für Tastaturereignisse

Nachricht

Beschreibung

WM_KEYDOWN

Eine Taste wurde gedrückt.

WM_KEYUP

Eine Taste wurde losgelassen.

Offensichtlich gibt es für die Tastatur weniger Nachrichten als für die Maus, und man kann mit der Tastatur aus programmtechnischer Sicht dementsprechend weniger anfangen. Die Nachrichten sind im Dialogfeldobjekt verfügbar und werden nur ausgelöst, wenn keine aktivierten Steuerelemente im Fenster vorhanden sind. Alle aktivierten Steuerelemente im Fenster haben den Eingabefokus, so daß alle Tastaturereignisse zu ihnen gelangen. Aus diesem Grund sollten Sie für die Zeichenanwendung alle Steuerelemente aus dem Hauptdialogfeld entfernen.

Den Mauszeiger zum Zeichnen ändern

Um eine Vorstellung davon zu bekommen, wie man Nachrichten in bezug auf Tastaturereignisse nutzen kann, legen wir bestimmte Tasten fest, um den Mauszeiger in der Zeichenanwendung zu verändern. Die Taste (A) ändert den Cursor in den Standardpfeil, mit dem die Anwendung auch startet. Die Taste (B) verwenden wir, um den Mauszeiger in den I-Balken-Mauszeiger zu verwandeln, während die Taste (C) für den Sanduhrzeiger vorgesehen ist. Die dementsprechende Funktionalität realisieren Sie in folgenden Schritten:

1. Nehmen Sie mit dem Klassen-Assistenten eine Funktion für die Nachricht WM_KEYDOWN in das Dialogfeldobjekt auf.

2. In die eben erstellte Funktion OnKeyDown fügen Sie den Code gemäß Listing 3.4 ein.

Listing 3.4: Die Funktion OnKeyDown

1: void CMausDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: char lsChar; // Zeichen der gedrückten Taste
10: HCURSOR lhCursor; // Handle zum anzuzeigenden Cursor
11:
12: // Code der gedrückten Taste in Zeichen umwandeln
13: lsChar = char(nChar);
14:
15: // Ist Zeichen ein "A"?
16: if (lsChar == 'A')
17: {
18: // Pfeilcursor laden
19: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
20: // Bildschirmcursor setzen
21: SetCursor(lhCursor);
22: }
23:
24: // Ist Zeichen ein "B"?
25: if (lsChar == 'B')
26: {
27: // Balkencursor laden
28: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
29: // Bildschirmcursor setzen
30: SetCursor(lhCursor);
31: }
32:
33: // Ist Zeichen ein "C"?
34: if (lsChar == 'C')
35: {
36: // Sanduhrzeiger laden
37: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_WAIT);
38: // Bildschirmcursor setzen
39: SetCursor(lhCursor);
40: }
41:
42: // Ist Zeichen ein "X"?
43: if (lsChar == 'X')
44: {
45: // Pfeilcursor laden
46: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
47: // Bildschirmcursor setzen
48: SetCursor(lhCursor);
49: // Anwendung beenden
50: OnOK();
51: }
52:
53: ///////////////////////
54: // EIGENER CODE, ENDE
55: ///////////////////////
56:
57: CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
58: }

Die Funktionsdefinition zeigt, daß die Funktion OnKeyDown drei Argumente übernimmt. Das erste gibt die gedrückte Taste an. Es handelt sich hierbei um den Zeichencode, der in der ersten Codezeile in das Zeichen umgewandelt wird. Nach der Umwandlung des Zeichens kann man direkte Vergleichsoperationen ausführen, um die gedrückte Taste zu bestimmen:

void CMausDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

Das zweite Argument an die Funktion OnKeyDown liefert die Anzahl, wie oft die Taste gedrückt wurde. Normalerweise ist dieser Wert gleich 1, wenn man die Taste drückt und wieder losläßt. Hält man die Taste dagegen nieder, tritt ein Wiederholungszähler für diese Taste in Kraft. Der entsprechende Wert sagt also aus, wie oft Windows in diesem Fall ein wiederholtes Drücken der Taste annimmt.

Das dritte Argument an die Funktion OnKeyDown ist ein Kombinationsflag, aus dem sich ermitteln läßt, ob die (Alt)-Taste gleichzeitig mit der anderen Taste gedrückt wurde oder ob es sich bei der gedrückten Taste um eine erweiterte Taste handelt. Über die Tasten (ª) oder (Strg) gibt dieses Argument keine Auskunft.

Nachdem Sie ermittelt haben, daß eine bestimmte Taste gedrückt wurde, können Sie endlich den Cursor in die Form ändern, die für die jeweilige Taste vorgesehen ist. Dieser Vorgang besteht aus zwei Teilen. Im ersten Schritt wird der Cursor in den Speicher geladen. Das realisieren Sie mit der Funktion LoadStandardCursor. Diese Funktion lädt einen der Standardcursor von Windows und gibt einen Handle auf den Cursor zurück.

Die verwandte Funktion LoadCursor übernimmt den Datei- oder Ressourcennamen eines benutzerdefinierten Cursors, so daß man eigene Cursor erzeugen und laden kann. Wenn Sie mit dem Ressourcen-Editor von Visual C++ einen Cursor entwerfen, können Sie den Cursornamen als einziges Argument an die Funktion LoadCursor übergeben. Haben Sie zum Beispiel einen Cursor unter dem Namen IDC_MYCURSOR erzeugt, können Sie ihn mit der folgenden Codezeile laden:

lhCursor = AfxGetApp()->LoadCursor(IDC_MYCURSOR);

Nachdem Sie diesen Cursor geladen haben, können Sie den Mauszeiger mit der Funktion SetCursor auf Ihren Cursor setzen, wie Sie es auch bei einem Standardcursor tun.

Nachdem die Funktion den Cursor in den Speicher geladen hat, wird der Handle auf diesen Cursor an die Funktion SetCursor übergeben. Damit nimmt der Mauszeiger die Form des Cursors an, auf den der Handle zeigt. Wenn Sie die Anwendung kompilieren und ausführen, können Sie eine der festgelegten Tasten drücken, um die Form des Cursors zu ändern, wie es Abbildung 3.5 zeigt. Sobald Sie aber die Maus verschieben, um etwas zu zeichnen, nimmt der Cursor wieder die Form des Standardpfeils an. Der folgende Abschnitt beschreibt, wie man die Änderung dauerhaft machen kann.

Abbildung 3.5:
Die Form des Cursors läßt sich mit festgelegten Tasten ändern.

Änderung beibehalten

Dem Zeichenprogramm haftet momentan der Mangel an, daß sich der Cursor sofort wieder in den Standardpfeil zurückverwandelt, wenn man die Maus bewegt. Es muß eine Möglichkeit geben, dieses Verhalten zu unterdrücken.

Windows schickt immer eine WM_SETCURSOR-Nachricht an Ihre Anwendung, wenn der Cursor neu zu zeichnen ist - weil die Maus bewegt wurde, weil ein über der Anwendung liegendes Fenster geschlossen wurde oder aus welchen Gründen auch immer. Wenn Sie das native Verhalten Ihrer Anwendung für dieses Ereignis überschreiben, bleibt der von Ihnen festgelegte Cursor erhalten, bis Sie ihn ausdrücklich wieder ändern. Führen Sie dazu die folgenden Schritte aus:

1. Nehmen Sie in die Klasse CMausDlg eine neue Variable auf, wie Sie es für die Variablen der vorherigen Position ausgeführt haben. Dieses Mal deklarieren Sie den Typ als BOOL und nennen die Variable m_bCursor, wie es Abbildung 3.6 zeigt.

Abbildung 3.6:
Eine Member-Variable definieren

2. Initialisieren Sie die Variable m_bCursor in der Funktion OnInitDialog mit dem Code in Listing 3.5.

Listing 3.5: Die Funktion OnInitDialog

1: BOOL CMausDlg::OnInitDialog()
2: {
3: CDialog::OnInitDialog();
4:
5: .
6: .
7: .
8: // Symbol für dieses Dialogfeld festlegen. Wird automatisch erledigt
9: // wenn das Hauptfenster der Anwendung kein Dialogfeld ist
10: SetIcon(m_hIcon, TRUE); // Großes Symbol verwenden
11: SetIcon(m_hIcon, FALSE); // Kleines Symbol verwenden
12:
13: // ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen
14:
15: ///////////////////////
16: // EIGENER CODE, ANFANG
17: ///////////////////////
18:
19: // Cursor als Pfeil initialisieren
20: m_bCursor = FALSE;
21:
22: ///////////////////////
23: // EIGENER CODE, ENDE
24: ///////////////////////
25:
26: return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement soll den ÂFokus erhalten
27: }

3. Ändern Sie die Funktion OnKeyDown gemäß Listing 3.6, um das Flag m_bCursor auf TRUE zu setzen, wenn Sie den Cursor wechseln.

Listing 3.6: Die Funktion OnKeyDown

1: void CMausDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: char lsChar; // Zeichen der gedrückten Taste
10: HCURSOR lhCursor; // Handle auf anzuzeigenden Cursor
11:
12: // Code der gedrückten Taste in Zeichen umwandeln
13: lsChar = char(nChar);
14:
15: // Ist Zeichen ein "A"?
16: if (lsChar == 'A')
17: // Pfeilcursor laden
18: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
19:
20: // Ist Zeichen ein "B"?
21: if (lsChar == 'B')
22: // Balkencursor laden
23: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
24:
25: // Ist Zeichen ein "C"?
26: if (lsChar == 'C')
27: // Sanduhrzeiger laden
28: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_WAIT);
29:
30: // Ist Zeichen ein "X"?
31: if (lsChar == 'X')
32: {
33: // Pfeilcursor laden
34: lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
35: // Cursorflag setzen
36: m_bCursor = TRUE;
37: // Bildschirmcursor setzen
38: SetCursor(lhCursor);
39: // Anwendung beenden
40: OnOK();
41: }
42: else
43: {
44: // Cursorflag setzen
45: m_bCursor = TRUE;
46: // Bildschirmcursor setzen
47: SetCursor(lhCursor);
48: }
49:
50: ///////////////////////
51: // EIGENER CODE, ENDE
52: ///////////////////////
53:
54: CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
55: }

4. Fügen Sie mit dem Klassen-Assistenten eine Funktion für die Nachricht WM_SETCURSOR in das Dialogfeldobjekt ein.

5. Nehmen Sie den Code aus Listing 3.7 in die eben erstellte Funktion OnSetCursor auf.

Listing 3.7: Die Funktion OnSetCursor

1: BOOL CMausDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
2: {
3: // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen Âund/oder Standard aufrufen
4:
5: ///////////////////////
6: // EIGENER CODE, ANFANG
7: ///////////////////////
8:
9: // Wenn Cursor gesetzt, TRUE zurückgeben
10: if (m_bCursor)
11: return TRUE;
12: else
13:
14: ///////////////////////
15: // EIGENER CODE, ENDE
16: ///////////////////////
17:
18: return CDialog::OnSetCursor(pWnd, nHitTest, message);
19: }

Die Funktion OnSetCursor muß immer TRUE zurückgeben oder ansonsten die Funktion der Basisklasse aufrufen. Die Funktion der Basisklasse setzt den Cursor zurück und muß aufgerufen werden, wenn die Anwendung das erste Mal startet. Aus diesem Grund müssen Sie die Variable mit FALSE initialisieren, damit die Standardverarbeitung von OnSetCursor abläuft, bis der Benutzer eine Taste drückt, um den Cursor zu wechseln. Nachdem der Benutzer den Cursor geändert hat, übergehen Sie die Standardverarbeitung und liefern statt dessen den Wert TRUE zurück. Damit kann der Benutzer mit dem Cursor, den er ausgewählt hat, zeichnen. Dazu gehört auch der Sanduhrzeiger, wie es Abbildung 3.7 zeigt.

Am häufigsten ändert man in einer Anwendung die Form des Mauszeigers in eine Sanduhr, um darauf hinzuweisen, daß das Programm eine länger dauernde Berechnung erledigt. In MFC sind für diese Aufgabe eigentlich zwei Funktionen verfügbar. Die erste heißt BeginWaitCursor. Diese Funktion zeigt den Sanduhrzeiger für den Benutzer an. Die zweite Funktion, EndWaitCursor , stellt den Standardzeiger wieder her. Beide Funktionen sind Elemente der Klasse CCmdTarget, von der alle MFC-Fenster- und Steuerelementklassen abgeleitet sind.

Haben Sie in einer einzigen Funktion alle Aufgaben zusammengefaßt, für die Sie den Sanduhrzeiger anzeigen müssen, und der Sanduhrzeiger muß nach Abschluß dieser Funktion nicht mehr angezeigt werden, gibt es eine einfachere Methode, um den Sanduhrzeiger auf den Bildschirm zu bringen. Deklarieren Sie eine Variable der Klasse CWaitCursor am Beginn der Funktion. Daraufhin zeigt Windows automatisch den Sanduhrzeiger an. Sobald das Programm die Funktion verläßt, wird der Cursor wieder in den vorherigen Cursor zurückverwandelt.

Abbildung 3.7:
Mit dem Sanduhrzeiger zeichnen

Zusammenfassung

In diesem Kapitel haben Sie gelernt, wie man Nachrichten von Mausereignissen auffängt und basierend auf diesen Ereignissen einfache Verarbeitungen durchführt. Mit Hilfe von Mausereignissen haben Sie ein einfaches Zeichenprogramm erstellt, mit dem sich Freihandfiguren im einem Dialogfeld zeichnen lassen.

Weiterhin wurde gezeigt, wie man Tastaturereignisse auffängt und die gedrückte Taste ermittelt. Anhand dieser Information haben Sie festgelegt, welcher Cursor zum Zeichnen zu verwenden ist. In diesem Zusammenhang haben Sie sich mit dem standardmäßigen Zeichnen des Cursors in MFC-Anwendungen beschäftigt und gelernt, wie man dieses Verhalten in den eigenen Code integriert, damit sich die Anwendung in der gewünschten Weise verhält.

Aufbauend auf diesen Kenntnissen erfahren Sie in der nächsten Lektion, wie man den Windows-Timer einsetzt, um Ereignisse in regelmäßigen Abständen auszulösen. Tag 5 zeigt dann, wie sich mit zusätzlichen Dialogfeldern Rückmeldungen vom Benutzer einholen lassen, so daß man auf der Basis dieser Eingaben das Verhalten der Anwendung steuern kann. Schließlich geht die sechste Lektion darauf ein, wie man Menüs für eine Anwendung erstellt.

Fragen und Antworten

Frage:
Wie kann ich die Art der zu zeichnenden Linie ändern? Ich möchte eine dickere Linie mit einer anderen Farbe zeichnen.

Antwort:
Wenn man mit einem der Standardbefehle für Gerätekontexte auf den Bildschirm zeichnet, kommt ein sogenannter Stift zum Einsatz, genau wie wenn man mit einem Stift auf Papier zeichnet. Um dickere Linien oder andere Farben darzustellen, muß man einen neuen Stift auswählen. Das läßt sich realisieren, indem man den Code in der Funktion OnMouseMove an der Stelle anpaßt, wo der Gerätekontext geholt wird. Der folgende Code bewirkt, daß die Linie mit einem großen roten Stift gezeichnet wird:

// Gerätekontext holen
CClientDC dc(this);

// Einen neuen Stift erzeugen
CPen lpen(PS_SOLID, 16, RGB(255, 0, 0));

// Den neuen Stift verwenden
dc.SelectObject(&lpen);

// Eine Linie vom letzten zum aktuellen Punkt zeichnen
dc.MoveTo(m_iPrevX, m_iPrevY);
dc.LineTo(point.x, point.y);

Frage:
Wie kann man ermitteln, ob die Tasten (ª) oder (Strg) gedrückt sind, wenn man die Nachricht WM_KEYDOWN empfängt?

Antwort:
Zu diesem Zweck ruft man die Funktion ::GetKeyState mit einem bestimmten Tastencode auf, um die gedrückten Tasten zu ermitteln. Wenn der Rückgabewert der Funktion ::GetKeyState negativ ist, wird die Taste niedergehalten. Bei einem nicht negativen Rückgabewert ist die Taste nicht gedrückt. Zum Beispiel können Sie mit dem folgenden Code ermitteln, ob die (ª)-Taste gedrückt ist:

if (::GetKeyState(VK_SHIFT) < 0)
MessageBox("Umschalt-Taste gedrückt!");

Workshop

Kontrollfragen

1. Für welche Mausnachrichten kann man Funktionen hinzufügen?

2. Wie kann man ermitteln, ob bei der Nachricht WM_MOUSEMOVE die linke Maustaste gedrückt ist?

3. Wie kann man verhindern, daß sich der Mauszeiger in den Standardzeiger zurückverwandelt, nachdem man einen anderen Zeiger festgelegt hat?

Übungen

1. Modifizieren Sie das Zeichenprogramm, so daß beim Drücken der linken Maustaste in Rot und bei der rechten Maustaste in Blau gezeichnet wird.

2. Erweitern Sie die Funktion OnKeyDown, um einige der folgenden Standardcursor aufzunehmen:



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1