C++ und MFC
erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net)



Zurueck zum Inhaltsverzeichnis
Zurueck zum vorhergehenden Kapitel

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:

Bevor wir hier in OnRButtonDown Programm-Code eingeben, möchte ich kurz skizzieren, was wir als nächstes planen:

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):

ist für Sie hoffentlich ein Beweis, daß der Einsatz der MFC-Assistenten insbesondere für den Anfänger eine deutliche Erleichterung darstellt. Fügen Sie jetzt nicht sofort Programmcode ein, sondern öffnen Sie im Arbeitsbereich erneut Ihre Dialog-Ressource (Sie sehen dort übrigens auch die IDD_ABOUTBOX) und wiederholen Sie den obigen Vorgang für die anderen drei Schaltflächen. Jetzt warten vier Member-Funktionen (OnButton1 ... OnButton4) der Klasse CEinfacheAusgabenDlg auf unsere Ideen. Bisher kennen Sie bereits die grafischen Funktionen TextOut, MoveTo und LineTo, die Sie hier natürlich gerne ausprobieren können, wenn Sie wollen. Da unser Dialogfenster jedoch nicht leer ist, sondern Schaltflächen (als untergeordnete Fenster) beherbergt, sind grafische Ausgaben hier nicht sonderlich sinnvoll.

Daher wollen wir an dieser Stelle einfache Ausgaben/Aufrufe kennenlernen, die kein eigenes Fenster unseres Programmes benötigen, das heißt die Ausgaben/Aufrufe benötigen entweder überhaupt kein Fenster oder sie bringen ihr eigenes "Window" mit.

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:

___________________________________________________________________________________________

MessageBeep

The MessageBeep function plays a waveform sound. The waveform sound for each sound type is identified by an entry in the [sounds] section of the registry.
BOOL MessageBeep(
  UINT uType   // sound type
);

Parameters

uType
Specifies the sound type, as identified by an entry in the [sounds] section of the registry. This parameter can be one of the following values:
Value Sound
0xFFFFFFFF Standard beep using the computer speaker
MB_ICONASTERISK SystemAsterisk
MB_ICONEXCLAMATION SystemExclamation
MB_ICONHAND SystemHand
MB_ICONQUESTION SystemQuestion
MB_OK SystemDefault

Return Values

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero.
...

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    
);

Parameters

pszSound
A string that specifies the sound to play. If this parameter is NULL, any currently playing waveform sound is stopped. To stop a non-waveform sound, specify SND_PURGE in the fdwSound parameter. Three flags in fdwSound (SND_ALIAS, SND_FILENAME, and SND_RESOURCE) determine whether the name is interpreted as an alias for a system event, a filename, or a resource identifier. If none of these flags are specified, PlaySound searches the registry or the WIN.INI file for an association with the specified sound name. If an association is found, the sound event is played. If no association is found in the registry, the name is interpreted as a filename.
hmod
Handle of the executable file that contains the resource to be loaded. This parameter must be NULL unless SND_RESOURCE is specified in fdwSound.
fdwSound
Flags for playing the sound. The following values are defined:
...
SND_ASYNC
The sound is played asynchronously and PlaySound returns immediately after beginning the sound. To terminate an asynchronously played waveform sound, call PlaySound with pszSound set to NULL.
SND_FILENAME
The pszSound parameter is a filename.
SND_LOOP
The sound plays repeatedly until PlaySound is called again with the pszSound parameter set to NULL. You must also specify the SND_ASYNC flag to indicate an asynchronous sound event.
...
SND_SYNC
Synchronous playback of a sound event. PlaySound returns after the sound event completes.
___________________________________________________________________________________________

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:


 
 
 
 
 

Zum nächsten Kapitel
 

Zurueck zum Inhaltsverzeichnis