C++
und MFC
erstellt
von: Dr. Erhard Henkes (e.henkes@gmx.net)
Stichworte für diese Page:
MoveTo()
und LineTo()
Member-Variable
hinzufügen
Vereinfachung von
OnPaint()
Wofür ist
eigentlich diese Funktion OnPaint() genau da?
Invalidate()
Maus-Ereignisse
Buttons starten
einfache Ausgaben
Message Beep()
PlaySound()
Ungarische Notation
WinExec()
ShowWindow-Parameter
SW_...
1.4 Zeichnen mit der Maus mit MoveTo() und LineTo()
In diesem Abschnitt werden wir unsere kleine Dialogfeld-Anwendung auf einfachste Weise zum Zeichnen - sprich Linien ziehen - verwenden. Wir verwenden unser Programm aus Abb. 1.22, verändern jedoch die Geometrie des Dialogfeldes auf ein breites Fenster. Dies erledigen wir im Ressourcen-Editor durch in-die-Breite-Ziehen des Dialogfeldes. Den Titel ändern wir in Dialog-Eigenschaften auf "Zeichenfenster". Nach dem Kompilieren/Starten funktioniert unsere Textausgabe an der Stelle des linken Mausklicks natürlich immer noch. Da wir zeichnen wollen, benötigen wir nach wie vor den Gerätekontext. Die Textausgabe kann gelöscht werden. An dieser Stelle geben wir eine Funktion zum Ziehen von Linien ein. Wechseln Sie im Arbeitsbereich auf Klassen und öffnen Sie OnLButtonDown(...). Ändern Sie den Programmcode bitte wie folgt ab:
void
CDialogEinsDlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
CClientDC
dc(this);
dc.LineTo(point.x,
point.y);
CDialog::OnLButtonDown(nFlags,
point);
}
Nach dem Kompilieren/Starten beantwortet unsere Anwendung das Drücken der linken Maustaste nicht mehr mit einer Textausgabe, sondern zieht von der linken oberen Ecke des Fensters eine schwarze Linie zur Mausspitze. Nach mehrmaligem Einsatz der Maus sieht das dann etwa so aus:
Abb. 1.24: In OnLButtonDown(...) wirkt jetzt die Funktion LineTo(...)
Die Funktion LineTo(...) zieht eine gerade Linie vom aktuellen Ausgangspunkt zu dem in der Funktionsklammer angegebenen Punkt. Der Ausgangspunkt ist momentan bei jedem Einsprung in diese Funktion die linke obere Fensterecke mit den Koordinaten x=0,y=0.
Wenn wir wollen, daß die Linie immer vom letzten Mausklick zum neuen Mausklick gezogen wird, dann benötigen wir noch eine Funktion zum Positionieren, ohne eine Linie zu ziehen. Dies ist die Funktion MoveTo(...).
Die Koordinaten des letzten Mausklicks werden wir in zwei Variablen m_Xold und m_Yold speichern und an MoveTo(m_Xold, m_Yold) übergeben. Diese Variablen müssen ihren Wert auch beim Verlassen der Funktion OnLButtonDown(...) beibehalten. Daher wählen wir hierfür Member-Variablen der Klasse CDialogEinsDlg, die folglich innerhalb der gesamten Klasse gültig sind.
Zum Festlegen der beiden Member-Variablen m_Xold und m_Yold zur Klasse CDialogEinsDlg führen Sie im Arbeitsbereich in der Klassenansicht bitte einen Rechtsklick auf CDialogEinsDlg aus.
Daraufhin öffnet sich das nachstehende Auswahlmenü:
Abb. 1.25: Auswahlmenü zum
Hinzufügen
von Member-Variablen, Member-Funktionen etc.
Wir wählen jetzt "Member-Variable hinzufügen". Daraufhin öffnet sich folgendes Dialog-Fenster:
Abb. 1.26: Eingabedialog zum Hinzufügen von Member-Variablen.
Bei Variablentyp geben wir int für Integer ein. Als Variablenname geben wir m_Xold ein. Den Zugriffsstatus setzen wir auf privat. Dann bestätigen wir mit OK. Sie sehen sofort die neue Variable in der Klassenansicht. Nun lernen Sie auch das "Vorhängeschloß" als Symbol für den Zugriffsstatus "private" kennen.
Wir wählen jetzt erneut Member-Variable
hinzufügen,
geben entsprechend int und m_Yold ein und setzen auf "privat".
OK-Button
drücken. Nun doppelklicken wir auf die Klasse CDialogEinsDlg. In
der
Header-Datei CDialogEinsDlg.h finden wir jetzt:
// Implementierung
protected:
HICON m_hIcon;
// Generierte Message-Map-Funktionen
//{{AFX_MSG(CDialogEinsDlg)
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnLButtonDown(UINT
nFlags,
CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
int m_Yold;
int m_Xold;
In der Klassendarstellung finden sich ebenfalls
die
beiden Member-Variablen (mit Vorhängeschloß).
Doppelklicken Sie bitte auf die Funktion
OnLButtonDown(UINT
nFlags, CPoint point). Wir ändern diese wie folgt ab:
void
CDialogEinsDlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
CClientDC
dc(this);
//Gerätekontext erstellen
dc.MoveTo(m_Xold,
m_Yold); //an letzten Mausklick positionieren
dc.LineTo(point.x,
point.y); //Linie ziehen zum aktuellen Mausklick
m_Xold=point.x;
//Mausposition X speichern
m_Yold=point.y;
//Mausposition Y speichern
CDialog::OnLButtonDown(nFlags,
point);
}
Nach Speichern und Kompilieren/Starten arbeitet unsere Anwendung wie gewünscht. Von Mausklick zu Mausklick wird eine schwarze Linie gezogen:
Abb. 1.27: Einfaches Zeichnen mit
MoveTo(...)
/ LineTo(...)
Unsere Anwendung zeigt jedoch noch eine Merkwürdigkeit: Beim jeweils ersten Mausklick stellen wir fest, daß der Ausgangspunkt (m_Xold, m_Yold in unserem Programm) zu Beginn rein zufällig gewählt wird. Das liegt daran, daß wir keinen exakten Ausgangszustand für diese beiden Variablen definiert haben. Dies werden wir jetzt nachholen. Klicken Sie in der Klassenansicht auf die Funktion OnPaint() unserer Klasse CDialogEinsDlg und ergänzen Sie bitte wie untenstehend ab. Achten Sie hierbei auf folgende Eingabehilfe:
Geben Sie in der ersten Zeile zunächst nur m_X ein. Führen Sie dann eine rechten Mausklick durch. Hierauf öffnet sich ein Menü, aus dem Sie "Wort vervollständigen" wählen. Der Assistent vervollständigt den Rest des Variablennamens. Wiederholen Sie dies bitte in der nächsten Zeile nach Eingabe von nur "m_". Jetzt klappt Ihnen eine große Auswahl entgegen. Tippen Sie einfach das "Y". Schon sind Sie bei der gewünschten Variable "m_Yold". Doppelklick darauf und die Eingabe steht. Insbesondere bei längeren Namen ist dies sehr hilfreich.
void
CDialogEinsDlg::OnPaint()
{
m_Xold=0;
m_Yold=0;
if (IsIconic())
{
CPaintDC
dc(this);
// Gerätekontext für Zeichnen
SendMessage(WM_ICONERASEBKGND,
(WPARAM) dc.GetSafeHdc(), 0);
// Symbol in
Client-Rechteck zentrieren
int cxIcon =
GetSystemMetrics(SM_CXICON);
int cyIcon =
GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x =
(rect.Width()
- cxIcon + 1) / 2;
int y =
(rect.Height()
- cyIcon + 1) / 2;
// Symbol
zeichnen
dc.DrawIcon(x,
y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
Nun startet unsere Anwendung mit der ersten Linie immer in der oberen linken Ecke.
Die Funktion OnPaint enthält überflüssigen Code, den wir nur in Win 3.x brauchen. Vereinfachen Sie bitte auf:
void
CDialogEinsDlg::OnPaint()
{
m_Xold=0;
m_Yold=0;
CDialog::OnPaint();
}
Achten Sie bitte bei Änderungen darauf, daß nicht versehentlich geschweifte Klammern gelöscht werden bzw. zur Hälfte übrig bleiben. Ansonsten überschüttet der Compiler Sie mit Fehlermeldungen. Wir testen dies zur Übung gezielt. Löschen Sie bitte die letze geschweifte Klammer von OnPaint, also:
void
CDialogEinsDlg::OnPaint()
{
m_Xold=0;
m_Yold=0;
CDialog::OnPaint();
//hier fehlt
die abschließende Klammer
Nach dem Kompilierversuch meldet sich VC++ wie folgt in der Ausgabe:
--------------------Konfiguration:
DialogEins - Win32 Debug--------------------
Kompilierung läuft...
DialogEinsDlg.cpp
C:\Eigene
C-Programme\DialogEins\DialogEinsDlg.cpp(77)
: error C2601: 'OnQueryDragIcon' : Lokale Funktionsdefinitionen sind
unzulaessig
C:\Eigene
C-Programme\DialogEins\DialogEinsDlg.cpp(82)
: error C2601: 'OnLButtonDown' : Lokale Funktionsdefinitionen sind
unzulaessig
C:\Eigene
C-Programme\DialogEins\DialogEinsDlg.cpp(95)
: fatal error C1004: Unerwartetes Dateiende gefunden
Fehler beim Ausführen von
cl.exe.
DialogEins.exe - 3 Fehler, 0
Warnung(en)
Das Unangenehme hierbei ist, daß der Compiler Ihnen nicht sagt, welchen Fehler Sie genau gemacht haben, sondern er zeigt oft nur die Symptome. Bei einer plötzlichen Häufung von Fehlermeldungen sollte man daher zunächst nach den Klammern der vorstehenden Funktion schauen, bevor man den Fehler dort sucht, wo der Compiler ihn direkt feststellt.
Es kann nie schaden, ab und zu gezielt Fehler zu
simulieren, um die Reaktion des Compilers auszutesten. Geben Sie die
schließende
geschweifte Klammer (AltGr+0) bitte wieder korrekt ein.
Wofür ist eigentlich diese
Funktion
OnPaint() genau da?
Geben Sie bitte Strg+W ein. Der Klassen-Assistent
meldet sich sofort zur Stelle.
Unter Nachrichten finden Sie (fett dargestellt)
WM_PAINT. Wenn Sie WM_PAINT anklicken, wird die Member-Funktion OnPaint
im
unteren Feld ausgewählt. Als Beschreibung lesen
wir: "Zeigt an, dass der Rahmen eines Fensters gezeichnet werden muss."
OnPaint wird folglich aufgerufen, wenn ein Neuzeichnen des Fensters notwendig wird, z.B. wenn ein Teil des Dialogfensters für ungültig erklärt wird (nach Überdecken durch ein anderes Window, Verschieben über den Bildschirmrand, Minimieren etc.). Man kann die Ausführung der Funktion OnPaint mit folgender Anweisung (erklärt das Fenster für ungültig) aus anderen Member-Funktionen unserer Dialog-Klasse heraus erzwingen:
Invalidate(); // erklärt den Fensterinhalt für ungültig
Abb. 1.28: Member-Funktion OnPaint
Schauen Sie sich an dieser Stelle auch die Beschreibungen der anderen Member-Funktionen an. Der Klassen-Assistent ist ein leistungsfähiges Informations- und Aktionszentrum zur Verwaltung der Klassen mit ihren Member-Funktionen und Nachrichten.
Wenn wir schon einmal im Klassen-Assistent sind (falls geschlossen mit Strg+W wieder öffnen), klicken wir auch gleich die Nachricht WM_MOUSEMOVE an:
Abb. 1.29: Nachricht WM_MOUSEMOVE wird hinzugefügt
Mit Funktion hinzufügen erzeugen Sie die Member-Funktion OnMouseMove. Dies erfolgt auch, wenn Sie beim Anklicken von WM_MOUSEMOVE gleich doppelklicken. Mit dem Button "Code bearbeiten" werden wir direkt in die neue Funktion einspringen. In der Klassendarstellung (im Arbeitsbereich) wird die Funktion auch sofort angezeigt. Den TODO-Kommentar innerhalb der Funktion können Sie sofort löschen. Jetzt schneiden wir den Programmcode ab ClientDC... bis ... m_Yold=point.y; in OnLButtonDown aus und kopieren diesen nach OnMouseMove. Es sollte dann wie folgt aussehen:
void
CDialogEinsDlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
CDialog::OnLButtonDown(nFlags,
point);
}
void
CDialogEinsDlg::OnMouseMove(UINT
nFlags, CPoint point)
{
CClientDC
dc(this);
//Gerätekontext erstellen
dc.MoveTo(m_Xold,
m_Yold); //an letzten Mausklick positionieren
dc.LineTo(point.x,
point.y); //Linie ziehen zum aktuellen Mausklick
m_Xold=point.x;
//Mausposition X speichern
m_Yold=point.y;
//Mausposition Y speichern
CDialog::OnMouseMove(nFlags,
point);
}
Wie Sie sehen, habe ich die Befehlszeilen kommentiert, um auch später noch die Zusammenhänge nachvollziehen zu können. Gewöhnen Sie sich das Kommentieren bitte möglichst umfangreich an. Löschen können Sie jederzeit. Nach dem Speichern und Kompilieren können Sie mit (hoffentlich) eleganten Mausbewegungen zeichnen.
Was noch fehlt, ist z.B. eine Funktion zum einfachen Löschen der gesamten Zeichnung. Mit der bisherigen Anwendung können Sie nur umständlich löschen, indem Sie minimieren (in der Taskleiste ablegen) und anschließend wieder aufrufen.
Inzwischen wissen Sie, daß beim Neuzeichnen OnPaint ausgelöst wird, und Sie wissen auch, wie wir das Neuzeichnen durch die Programmierung erzwingen können, nämlich mit Invalidate(). Was uns jetzt noch fehlt, ist eine Nachrichtenbehandlungsfunktion, in der wir den Befehl Invalidate() einbauen. Aber wir haben doch gerade den linken Mausklick ausgeräumt. Also nutzen wir ihn für diesen Befehl:
void
CDialogEinsDlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
Invalidate();
CDialog::OnLButtonDown(nFlags,
point);
}
Abb. 1.30: Die Parameterinfo hinter "Invalidate"
Wenn Sie die öffnende Klammer hinter Invalidate eingeben (Abb. 1.30), erkennen Sie, daß der Parameter bErase nicht unbedingt eingegeben werden muß, also optional ist. Das liegt daran, daß in der Deklaration der Funktion bereits der Inhalt "TRUE" als Standard vorgegeben wurde. Daher ist es ausreichend, wenn Sie "Invalidate()" eingeben. Sie sollten jedoch wissen, daß die Funktion einen Parameter besitzt.
Funktioniert wie erwartet; Linksklick löscht die Grafik (siehe Abb. 1.31):
Abb. 1.31: Mit Linksklick kann man jetzt dank Invalidate() die Grafik löschen
Störend ist, daß immer wieder eine Linie von der linken oberen Ecke zur Mausposition gezogen wird. Das liegt daran, daß wir in OnPaint die beiden Variabalen m_Xold und m_Yold gleich null gesetzt haben. Wir streichen diese Zeilen in OnPaint und übergeben in OnLMouseButton die Mauskoordinaten an diese Variablen:
void
CDialogEinsDlg::OnPaint()
{
CDialog::OnPaint();
}
void
CDialogEinsDlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
Invalidate();
m_Xold=point.x;
m_Yold=point.y;
CDialog::OnLButtonDown(nFlags,
point);
}
Anschließend arbeitet unsere Anwendung nach dem ersten Mausklick wie gewünscht.
Abb. 1.32: Ein rudimentäres
Zeichenprogramm
mit OnLButtonDown und OnMouseMove
Da es bereits ausreichend professionelle
Zeichenprogramme
auf dem Markt gibt, sollten wir jetzt kein ausgeklügeltes
Dialogfeld-Zeichenprogramm
entwicklen.
Wir werden das Programm jedoch dazu nützen, mehr Erfahrung mit den Nachrichten und Nachrichtenbehandlungsfunktionen für Mausereignisse zu gewinnen.
Zunächst einmal stellen wir einige
Nachrichten
und zugehörige Member-Funktionen für die Maus-Ereignisse
zusammen:
Nachricht | Member-Funktion | Bedeutung |
WM_LBUTTONDOWN | OnLButtonDown | linke Maus im Clientbereich gedrückt |
WM_RBUTTONDOWN | OnRButtonDown | rechte Maus im Clientbereich gedrückt |
WM_LBUTTONUP | OnLButtonUp | linke Maus im Clientbereich losgelassen |
WM_RBUTTONUP | OnRButtonUp | rechte Maus im Clientbereich losgelassen |
WM_LBUTTONDBLCLK | OnLButtonDblClk | linke Maus im Clientbereich doppelgeklickt |
WM_RBUTTONDBLCLK | OnRButtonDblClk | rechte Maus im Clientbereich doppelgeklickt |
WM_MOUSEMOVE | OnMouseMove | Maus wurde im Clientbereich bewegt |
WM_MOUSEWHEEL | OnMouseWheel | Das "Mausrad" wurde gedreht |
Da heute die Zwei-Tasten-Maus, wie z.B. die Original-Microsoft-Maus, standardmäßig an Computern angeschlossen ist, macht es keinen großen Sinn, der Nachricht WM_MBUTTONDOWN (mittlere Maustaste gedrückt) eine einmalige Aufgabe im Programm zuzuordnen. Das gleiche gilt für das "Mausrad", das noch nicht weit genug verbreitet ist.
Kommen wir zu unserem rudimentären Zeichenprogramm zurück. Wir werden nun die Funktion OnRButtonDown in unsere kleine Anwendung einbinden. Sie wissen ja bereits wie es geht:
In unserem Dialogfeld-Zeichenprogramm fehlt die Möglichkeit, sich mit der Maus zu bewegen, ohne eine Linie zu ziehen. Wie können wir dies erreichen? Die Linie wird in der Member-Funktion OnMouseMove mittels LineTo(...) gezogen. Wir benötigen in dieser Funktion eine Kontrollstruktur, um im einem Fall LineTo(...) auszuführen, im anderen Fall jedoch nicht. Eine typische Kontrollstruktur hierfür ist die if-Anweisung. Wir werden daher in OnRMouseDown eine Flag-Variable setzen und diese in der Member-Funktion OnMouseMove mit der if-Anweisung abfragen.
Zunächst müssen wir jedoch die Flag-Variable einbauen:
In der Klassenansicht Rechtsklick auf die Klasse CDialogEinsDlg. "Member-Variable einfügen" auswäh-len. Im Feld "Variablentyp" geben wir BOOL ein und unter "Variablenname" m_DrawFlag. Bei Zugriffsstatus entscheiden wir uns für "Privat" (private).
In OnPaint setzen wir das Flag auf TRUE. Daher fügen wir folgendes hinzu:
void
CDialogEinsDlg::OnPaint()
{
m_DrawFlag=TRUE;
CDialog::OnPaint();
}
Jetzt wechseln wir zur Member-Funktion OnRMouseDown. Hier implementieren wir zu Ihrer Übung eine switch-Kontrollstruktur. Wir geben folgendes ein:
void
CDialogEinsDlg::OnRButtonDown(UINT
nFlags, CPoint point)
{ switch
(m_DrawFlag)
{
case TRUE:
m_DrawFlag=FALSE;
break;
case FALSE:
m_DrawFlag=TRUE;
}
CDialog::OnRButtonDown(nFlags,
point);
}
Nun müssen wir noch die if-Kontrollstruktur in die Member-Funktion OnMouseMove einbauen:
void
CDialogEinsDlg::OnMouseMove(UINT
nFlags, CPoint point)
{
CClientDC
dc(this);
dc.MoveTo(m_Xold,
m_Yold);
if
(m_DrawFlag==TRUE)
dc.LineTo(point.x, point.y);
m_Xold=point.x;
m_Yold=point.y;
CDialog::OnMouseMove(nFlags,
point);
}
Sie sehen, daß man unter Einsatz von Flags, if- und switch-Kontrollstrukturen bereits mit relativ einfachen Mitteln ein funktionierendes Programm erzeugen kann.
Abb. 1.33: Unser Zeichenprogramm
benutzt
OnLButtonDown, OnRButtonDown und OnMouseMove
Wenn Ihnen die Zuordnung der Funktionen zu den
Mausereignissen
nicht zusagt, können Sie die Inhalte der Funktionen leicht
verändern.
Vertauschen Sie zur Übung doch einmal die von uns
eingefügten
Inhalte der beiden Member-Funktionen OnLButtonDown und OnRButtonDown.
1.5. Buttons starten einfache Ausgaben
Im vorhergehenden Beispiel haben wir Aktionen programmiert, die durch Mausereignisse ausgelöst wurden. In der nachfolgenden Praxisübung werden wir ein einfaches Dialogfenster erstellen, daß mit sogenannten "Buttons" (Schaltflächen) arbeitet, um Aktionen auszulösen.
Zunächst benötigen wir ein einfaches Dialog-Fenster:
Erstellen Sie bitte mit dem Menübefehl Datei | Neu mit Unterstüzung des MFC-Anwendungs-Assistenten (exe) ein neues Projekt mit Namen "EinfacheAusgaben".
Wählen Sie bitte im Schritt 1 "Dialogfeldbasierend". Im Schritt 2 deaktivieren Sie die Option ActiveX-Steuerelemente. Die Schritte 3 und 4 akzeptieren Sie unverändert. Entfernen Sie das statische Textfeld "ZU ERLEDIGEN ...", fügen Sie bitte vier neue Buttons (Schaltflächen) ein und ordnen Sie den OK- und Abbrechen-Button wie folgt ab:
Abb. 1.34: Wir setzen das Steuerelement "Schaltfläche" (Button) ein
Benutzen Sie zur Anordnung der Buttons die im visuellen Ressourcen-Editor links unten angebotenen Anordnungs-/Ausrichtungs-Werkzeuge. Hier hilft langes Erklären weniger als eigenes Ausprobieren. Mit Strg+T (oder dem Kippschalter links unten) können Sie sich übrigens jederzeit einen optischen Eindruck von der erzeugten Ressource machen. Zum Vergleich überprüfen Sie bitte mit dem Texteditor die von Ihnen erzeugte Dialog-Ressource. Es sollte ungefähr wie folgt aussehen:
IDD_EINFACHEAUSGABEN_DIALOG
DIALOGEX
0, 0, 320, 75
STYLE DS_MODALFRAME | DS_3DLOOK |
WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Einfache Ausgaben"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON
"Message-Beep",IDC_BUTTON1,7,7,72,37,0,WS_EX_CLIENTEDGE
PUSHBUTTON
"Soundausgabe",IDC_BUTTON2,85,7,72,37,0,WS_EX_CLIENTEDGE
PUSHBUTTON
"MessageBox",IDC_BUTTON3,163,7,72,37,0,WS_EX_CLIENTEDGE
PUSHBUTTON "Taschenrechner
",IDC_BUTTON4,241,7,72,37,0,WS_EX_CLIENTEDGE
DEFPUSHBUTTON
"OK",IDOK,211,54,50,14,0,WS_EX_CLIENTEDGE
PUSHBUTTON
"Abbrechen",IDCANCEL,263,54,50,14,0,WS_EX_CLIENTEDGE
END
Sie sehen, daß das Ressourcen-Script den Begriff PUSHBUTTON für die Schaltfläche verwendet. Wichtig für unsere Übung ist im Moment, daß wir vier Buttons haben, denen wir nachfolgend Funktionen zuordnen können.
Wenn Sie nun speichern (Strg+S) und kompilieren/starten (Strg+F5), haben Sie ein Dialogfenster, das man mit Mausklick auf "OK" oder "Abbrechen" schließen kann, die vier hübschen Schaltflächen sind betriebsbereit aber noch funktionslos.
Dies werden wir nun ändern. Sie ahnen bereits wie? Jawohl wir pfeifen den Klassen-Assistenten herbei. Ein leicht einprägsamer Weg ist folgender: Sie befinden sich im Resourcen-Editor und doppelklicken einfach auf den Button, dem Sie eine Funktion zuordnen wollen.
Daraufhin öffnet sich nachstehendes Dialog-Fenster, das den Namen der neu zu erstellenden Member-Funktion abfragt. Hier sollten Sie gewissenhaft nachdenken, da spätere Namensänderungen nicht einfach durchzuführen sind. Für unsere Übung können Sie "OnButton1" akzeptieren. Für spätere eigene Anwendungen sollte man hier einen Namen wählen, der aussagekräftiger ist.
Abb. 1.35: Dem Steuerelement "Schaltfläche" (Button) wird eine Member-Funktion zugeordnet
Nach dem Klick auf den OK-Button landen Sie direkt in der neuen Member-Funktion und können mit der Programmierung beginnen:
void
CEinfAusgTest1Dlg::OnButton1()
{
// TODO: Code
für die Behandlungsroutine der Steuerelement-Benachrichtigung hier
einfügen
}
Diese einfache Vorgehensweise zur Aktivierung eines Buttons (hier noch einmal im Überblick):
Unsere beiden ersten Knöpfe mit der Aufschrift "Message-Beep" und "Soundausgabe" werden wir zur Ausgabe von Tönen, genauer zum Abspielen von WAV-Dateien, einsetzen. Geben Sie folgenden Programmcode in die beiden ersten Funktionen ein:
void
CEinfacheAusgabenDlg::OnButton1()
//Systemklänge
{
::MessageBeep(0xFFFFFFFF);
::Sleep(1000);
::MessageBeep(MB_OK);
::Sleep(1000);
::MessageBeep(MB_ICONASTERISK);
::Sleep(2000);
::MessageBeep(MB_ICONEXCLAMATION);
::Sleep(2000);
::MessageBeep(MB_ICONHAND);
::Sleep(2000);
::MessageBeep(MB_ICONQUESTION);
}
void
CEinfacheAusgabenDlg::OnButton2()
//Soundausgabe
{
::PlaySound("C:\\Windows\\media\\The
Microsoft Sound.wav", NULL, SND_FILENAME | SND_ASYNC );
//Wichtig:
//#include
<mmsystem.h>
in StdAfx.h einbinden
//Menü:Projekt-Einstellungen-Linker-"Objekt-/Bibliothek-Module":winmm.lib
}
Betrachten wir zunächst OnButton1():
Hier verwenden wir die API-Funktion BOOL MessageBeep( UINT uType ). Diese Information erhalten Sie, wenn Sie mit der rechten Maustaste in die Funktionsklammer klicken und Parameterinfo auswählen. Für einen Einsteiger in die Windows-Programmierung sind die vielen Abkürzungen für die verschiedensten Typen in der Regel sehr verwirrend. Sie sollten im Moment folgendes verstehen:
BOOL ist der Rückgabewert der Funktion, das heißt, wenn wir z.B. programmieren würden "ReturnValue=MessageBeep(MB_OK);", dann sollten wir ReturnValue vorher als BOOL-Variable deklarieren. Der Wert von ReturnValue wird dann entweder TRUE oder FALSE sein. Wann TRUE und wann FALSE zurückgegeben wird, das erfährt man in der Online-Hilfe (siehe unten). Innerhalb der Klammer erwartet die Funktion einen Wert vom Typ UINT, das heißt unsigned integer. Die entsprechenden Makro-Bezeichnungen für verschiedene Werte erfahren wir aus der Online-Hilfe. Die beiden Doppelpunkte vor dem Funktionsnamen können Sie übrigens weglassen. Sie zeigen an, daß es sich hier nicht um eine MFC-Member-Funktion handelt, sondern um eine API-Funktion. Sie können in Visual C++ jederzeit direkt auf die API-Funktionen zugreifen. Geben Sie zur Übung nur "::" ein und überzeugen Sie sich in der aufklappenden Liste von der überwältigenden (vielleicht auch ein wenig erdrückenden) Vielfalt, die Ihnen zur Programmierung für MS Windows zur Verfügung steht.
Lassen Sie uns zur Übung in die Hilfe zum Thema "MessageBeep" schauen. Wir finden folgende Informationen:
___________________________________________________________________________________________
BOOL MessageBeep(
UINT uType // sound type
);
Value | Sound |
0xFFFFFFFF | Standard beep using the computer speaker |
MB_ICONASTERISK | SystemAsterisk |
MB_ICONEXCLAMATION | SystemExclamation |
MB_ICONHAND | SystemHand |
MB_ICONQUESTION | SystemQuestion |
MB_OK | SystemDefault |
Für die Praxis wichtig ist zunächst der
Abschnitt über "Parameters" und über "Return Values", da man
hier die Auflistung der möglichen "Value"-Bezeichnungen für
den
Parameter uType findet und die Information erhält, wann die
Funktion
den Wert TRUE ("nonzero") und wann sie den Wert FALSE ("zero")
zurückgibt.
Verwenden Sie die Online-Hilfe sooft wie möglich als
ausführlich
Informationsquelle insbesondere zu den zahlreichen API-Funktionen.
Windows-Knowhow
entsteht durch das praktische Wissen über die API-Funktionen. Die
Hilfe ist durch ihren abstrakten Stil nicht praxisgerecht, dafür
jedoch
informativ.
Nach dem Speichern/Kompilieren/Starten gibt unsere
Funktion OnButton1() nacheinander die verschiedenen
Windows-Systemtöne
aus. Wie Sie hören, können wir trotz Soundkarte auch den
Lautsprecher
(soweit vorhanden) mit dem Parameter 0xFFFFFFFF
zum "Tönen" bringen. Das "0x" zeigt dem Compiler an, daß der
Wert in hexadezimaler Schreibweise angegeben wird. Die
API-Funktion
"VOID Sleep (DWORD dwMilliseconds)" haben wir zur Erzeugung
einer
Pause eingefügt, damit auch wirklich alle Systemklänge zu
hören
sind.
Nun können Sie bereits akustisch mit dem Anwender Ihrer Programme Kontakt aufnehmen. Wenn Ihnen die Windows-Systemklänge nicht genügen, können Sie - wie in OnButton2() erfolgt - auch eigene WAV-Files abspielen. Wir benützen hier eine angenehme Standard-WAV von Windows.
::PlaySound("C:\\Windows\\media\\The Microsoft Sound.wav", NULL, SND_FILENAME | SND_ASYNC );
Die API-Funktion BOOL PlaySound(...) erlaubt das "Abspielen" von WAV-Files. Wir müssen jedoch noch einige Nebenarbeiten verrichten. Hier läßt der Assistent uns hemmungslos im Stich! Also, zunächst fügen wir die Zeile #include <mmsystem.h> in die Header-Datei StdAfx.h ein. Seien Sie vorsichtig, damit Sie nichts zerstören. Anschließend wählen Sie im Menü "Projekt-Einstellungen (Alt+F7)" die Seite "Linker" und geben im Feld "Objekt-/Bibliothek-Module" den Namen "winmm.lib" ein. Wenn Sie dies erledigt haben, sollten Sie nach dem Speichern/Kompilieren/Linken/Starten einen netten Windows-Klang vernehmen. Wenn nicht, prüfen Sie bitte, ob das angegebene WAV-File auf Ihrem Rechner im Laufwerk C: überhaupt existiert.
Nun schauen wir in die Hilfe zum Thema PlaySound (wir führen der Kürze halber hier nur die von uns verwendeten Parameter sowie SND_LOOP und SND_SYNC auf):
PlaySound
The PlaySound function plays a sound specified by the given filename, resource, or system event. (A system event may be associated with a sound in the registry or in the WIN.INI file.)
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,
DWORD fdwSound
);
Wie Sie sehen (und hoffentlich hören), ist die Ausgabe von Sound kein Hexenwerk, sondern eine für den Einsteiger leichte "einzeilige" Angelegenheit (abgesehen von den lästigen Nebenarbeiten). Testen Sie die in der Hilfe angegebenen Möglichkeiten bitte aus, damit Sie Erfahrung in der Umsetzung von Informationen aus der Hilfe in die Praxis gewinnen. Benutzen Sie die Hilfe sooft wie möglich, da dies auf längere Sicht wahrscheinlich Ihre wichtigste Quelle zum Auffinden von Detail-Infos wird.
Nach den Soundausgaben kommen wir zu den per Knopfdruck startbaren Anwendungen, die ihr eigenes Fenster mitbringen. Hierzu gehören z.B. die Message-Box und externe Windows-Anwendungen.
Geben Sie in OnButton3() und OnButton4() bitte folgenden Programmcode ein:
void CEinfacheAusgabenDlg::OnButton3()
//MessageBox
{
MessageBox("Hallo Welt
!","MessageBox
mit MB_OK",MB_OK);
MessageBox("Hallo Welt
!","MessageBox
mit MB_OKCANCEL",MB_OKCANCEL);
MessageBox("Hallo Welt
!","MessageBox
mit MB_RETRYCANCEL",MB_RETRYCANCEL);
MessageBox("Hallo Welt
!","MessageBox
mit MB_YESNO",MB_YESNO);
MessageBox("Hallo Welt
!","MessageBox
mit MB_YESNOCANCEL",MB_YESNOCANCEL);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ABORTRETRYIGNORE",MB_ABORTRETRYIGNORE);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ICONEXCLAMATION",MB_ICONEXCLAMATION);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ICONINFORMATION",MB_ICONINFORMATION);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ICONQUESTION",MB_ICONQUESTION);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ICONSTOP",MB_ICONSTOP);
MessageBox("Hallo Welt
!","MessageBox
mit MB_ICONEXCLAMATION und
MB_RETRYCANCEL",MB_ICONEXCLAMATION
| MB_RETRYCANCEL);
}
void
CEinfacheAusgabenDlg::OnButton4()
{
::WinExec("calc.exe",SW_NORMAL);
}
In der Member-Funktion OnButton3() geben wir nacheinander verschiedene Typen der MessageBox aus. Das sind eigene Fenster, die dem Anwender eine Nachricht hinterlassen und entsprechende Eingaben über den Rückgabewert (integer) abfragen. Die abstrakte Funktion lautet:
int MessageBox ( LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK );
An dieser Stelle möchte ich auf die
sogenannte
"ungarische Notation" (benannt nach
der Nationalität des Erfinders Charles Simonyi) hinweisen.
Diese Notation vereinbart Kurzformen für verschiedene Datentypen.
Diese dem Namen einer Variable vorgestellten Buchstaben haben z.B.
folgende
Bedeutung:
Kurzform | Datentyp |
b | BOOL |
c | counter (Zähler) |
ch | char |
clr | COLORREF |
cx, cy | x- bzw. y-Längen / Größen |
dw | DWORD (32-bit unsigned integer) |
h | Handle |
l | LONG (32-bit signed integer ) |
n | int (size dependent on operating system) |
p | pointer (Zeiger) |
sz | string (terminated by zero) |
w | word (16-bit unsigned value) |
Bezüglich lpsz gibt es folgende Typen, die Ihnen immer wieder begegnen werden:
LPSTR 32-bit pointer to character string
LPCSTR 32-bit pointer to constant character
string
LPCTSTR 32-bit pointer to constant character
string if _UNICODE is defined
Unicode:
Ein 16-Bit-Zeichensatz, der
die Verschlüsselung aller bekannten Zeichen ermöglicht
und als weltweiter Standard
zur Zeichenverschlüsselung genutzt wird.
Vergleichen Sie z.B. die abstrakte Beschreibung
von
MessageBox, dann verstehen Sie nun die vorgestellten Kurzformen:
lpszText
bedeutet z.B. LONG pointer string Text; nType bedeutet int Type.
Member-Variablen
von MFC-Klassen sollen übrigens zusätzlich ein "m_" als
Präfix
erhalten.
Sie finden die Funktion MessageBox übrigens
in der Hilfe unter dem Eintrag "CWnd::MessageBox", da es sich hier
nicht
um eine API-Funktion, sondern eine Member-Funktion der Klasse CWnd
handelt.
Unser Dialogfenster kann diese Funktion als ein von der Klasse CWnd
abgeleitetes
"Window" natürlich nutzen. Eine Zusammenfassung der
Member-Funktionen
der wichtigen MFC-Klasse CWnd finden Sie in der Hilfe unter "CWnd
Member
Functions".
Die Message-Box ist ein idealer Haltepunkt zum
Austesten
eigener Programme, da man mittels Ausgabe-String Informationen, z.B.
über
den Wert von Variablen, übermitteln kann.
In OnButton4() starten wir eine Anwendung mittels
WinExec(...).
Hier eilt z.B. auf Knopfdruck der Windows-Taschenrechner (calc.exe)
herbei.
Testen Sie diese Funktion mit verschiedenen Exe-Programmen. Sie
können
auch (mit "\\") komplexe Pfadangaben verwenden. Interessant ist hier
auch
der zweite Parameter. Er entscheidet über die Erscheinung des
Fensters
beim Start der Anwendung:
::WinExec("calc.exe",SW_NORMAL);
SW_SHOW aktiviert ein Fenster in seiner aktuellen
Größe
und Position
SW_SHOWNORMAL aktiviert ein Fenster in seiner ursprünglichen
Größe
SW_SHOWMINIMIZED aktiviert und das Fenster minimiert
SW_SHOWMAXIMIZED aktiviert das Fenster maximiert
SW_SHOWNOACTIVATE zeigt ein Fenster in seiner neuesten
Größe
und Position. Das gegenwärtig
aktuelle Fenster bleibt aktiv.
SW_SHOWMINNOACTIVE zeigt ein Fenster minimiert. Das gegenwärtig
aktuelle Fenster bleibt aktiv.
SW_SHOWNA zeigt ein Fenster in seinem aktuellen Zustand. Das
gegenwärtig
aktuelle Fenster bleibt aktiv.
SW_NORMAL verändert das Fenster auf
normale
Größe
SW_MINIMIZE verändert das Fenster auf
minimierte Anzeige (Taskleiste)
SW_MAXIMIZE verändert das Fenster auf
maximierte Anzeige
SW_HIDE versteckt ein Fenster
Mit SW_HIDE können Sie obige Anwendung
unsichtbar
ablaufen lassen. Der Taschenrechner wird dann z.B. unsichtbar geladen.
Sie können dies über den Windows-Taskmanager (Strg+Alt+Entf)
überprüfen. Sinnvoller als mit einem unsichtbaren
Taschenrechner
kann man dieses Stilattribut z.B. für kleine Kopieraufgaben mit
dem
Befehl "Xcopy32" einsetzen.
1.6 Zusammenfassung
Bei unserem hoffentlich lockeren und dennoch informativen Einstieg in die Windows-Programmierung mit MFC haben Sie bisher folgendes kennengelernt: