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, ...
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.
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.
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).
Abbildung 3.1:
Mit der Maus im Fenster zeichnen
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.
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.
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
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 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.
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.
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.
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.
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.
Abbildung 3.7:
Mit dem Sanduhrzeiger zeichnen
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.
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!");
In Windows sind für alle Sondertasten spezielle virtuelle Tastencodes definiert. Mit diesen Codes kann man auf spezielle Tasten prüfen, ohne sich um die OEM-Scancodes oder andere Tastenfolgen kümmern zu müssen. Die virtuellen Tastencodes kann man in der Funktion
::GetKeyState
verwenden und sie an die FunktionOnKeyDown
als ArgumentnChar
übergeben. Eine Liste der virtuellen Tastencodes finden Sie in der Dokumentation zu Visual C++.
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?
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: