Dr. Erhard Henkes (Stand: 21.07.2015)
In Zusammenhang mit dem Betriebssystem UNIX entstand 1972 die Programmiersprache C. Das Buch von Kernighan und Ritchie "The C Programming Language" von 1977 (mit dem berühmten Gruß "hello, world") und die Schaffung eines Standards durch das ANSI (American National Standards Institute) im Jahre 1987 waren Meilensteine. Unter Einbeziehung von Elementen der objektorientierten Programmierung (OOP) entwickelte sich hieraus die Sprache C++, die in Verbindung mit den Bibliotheken Win32-API (objektbasiert, aber nicht objektorientiert) und MFC (objektorientiert) bereits viele Jahre eine gute Basis für die Windows-Programmierung bildet.
Die
Windows-Programmierung startete 1987 mit C und WinAPI (damals noch 16
Bit) und verwendete hierbei
das Konzept der Message Loop (Nachrichtenpumpe). WinAPI ist
objektbasiert und bietet bereits Kapselung. Man interagiert mittels
eines
Handle und einer definierten Oberfläche. Das Innere der Funktion ist
verborgen.
1992 kam die objektorientierte Umhüllung durch C++ Klassen hinzu,
anfangs als
Application Frameworks (AFX),
die zu den MFC führten. Das von der alten Bezeichnung
herrührende Kürzel afx_ werden Sie im frei zugänglichen MFC Code noch
häufig vorfinden.
Nun wissen Sie woher es kommt.
Der Vorteil besteht darin, dass die MFC neben der Kapselung auch Vererbung (Klassenhierarchie) und Polymorphismus (z.B. Überladung und virtuelle Funktionen) bietet. Die WinAPI kann innerhalb der MFC im globalen Namensraum direkt aufgerufen werden. Teilweise müssen zusätzliche Header und Biliotheken eingebunden werden, z.B. winmm.lib (Windows Multimedia API) bei PlaySound.
Dieses MFC Tutorial fand seinen Anfang im Jahr 2001 mit der schlagkräftigen Version 6 von MS VisualC++. Heute sind wir schon bei Version 14 angekommen, ein weiter Weg. Neuerdings werden die MFC von MS kostenlos zur Verfügung gestellt. Was mich begeistert, ist der Umstand, dass man alte Projekte nehmen kann, diese importiert und mit kleinen Änderungen rasch zu einem lauffähigen exe-Programm gelangt. Die aktuelle IDE fordert die Umstellung von Strings auf Unicode ("xyz" ==> L"xyz"). Dies sollte man befolgen und nicht mittels Tricks mit den alten Zöpfen weiter arbeiten.
Ansonsten findet man Hinweise wie diese hier:
_WIN32_WINNT not defined. Defaulting to
_WIN32_WINNT_MAXVER (see WinSDKVer.h)
'CWinApp::Enable3dControls': CWinApp::Enable3dControls is no longer
needed. You should remove this call.
Die neue
Projektdatei hat die Endung sln, und es gibt vcxproj und
vcxproj.filters
sowie eine temporäre Datei sdf. Darum kümmert sich aber die
hervorragende IDE. Diese großartige
Abwärtskompatibilität
spricht ganz klar für die MFC, mit denen man professionelle
Windows-Programme erstellen kann. Man kann
dadurch auch ältere Code Snippets, die man in Massen im Internet
findet, in seine aktuellen Anwendungen einfügen. "Legacy" hat auch
seine Vorteile, nämlich Stabilität und Reife. C++ und MFC bilden eine
hervorragende Partnerschaft, von der Sie enorm profitieren können.
Zwei
Sprüche sollen
Ihren Weg begleiten:
MFC: Macht für C++
|
Für das Betriebssystem MS Windows bietet Microsoft seit einigen Jahren das Programmierwerkzeug Visual C++ (z.Z. in der Version 14). Sie können hiermit von Assistenten und grafischen Editoren unterstützt die Sprachelemente sowohl von C als auch von C++ einsetzen.
Wesentlicher Bestandteil von Visual C++ sind die Microsoft Foundation Classes (MFC), Microsofts inzwischen in die Jahre gekommene objektorientierte Klassenbibliothek für die Windows-Programmierung mit C++. Die MFC "umhüllen" die Funktionalität der Windows Application Programming Interface (WinAPI), die Sie in der MFC-Programmierung zusätzlich direkt einsetzen können und teilweise müssen. Mit dem hervorragenden MS Visual Studio Community 2015 erhalten Sie eine hervorragende IDE inclusive WinAPI und MFC inzwischen kostenlos.
Das vorliegende Tutorial unternimmt den Versuch, eine praktische Anleitung für Einsteiger oder Gelegenheits-Programmierer zu sein. An hoffentlich überschaubaren und leicht nachvollziehbaren Beispielen werden Sie ohne Umwege die Möglichkeiten der MFC-Programmierung kennenlernen. Basiskenntnisse in C++ und OOP sind notwendig. Für die Windows-Programmierung in C++ sollten Sie zumindest die grundlegenden Sprachelemente, den Klassenaufbau und die Kontrollstrukturen von C und C++ verstehen, um nicht ständig in Fehlersuche zu verharren.
Machen
Sie sich bitte mit den Prinzipien der Objektorientierung vertraut:
1. Klassen als Bauvorlage für
Objekte („Objekttypen“)
2. Kapselung:
Objekte besitzen "private" Eigenschaften in ihrem Innenleben, die nur
mit den dafür vorgesehenen Methoden
verändert werden können.
3. Vererbung:
Klassenhierarchien, Subklassen. Kindklassen erben die Methoden der
Elternklassen, zumeist über "protected", und werden nach unten immer
weiter spezialisiert.
4. Polymorphismus:
Subklassen können geerbte Funktionen überschreiben ("Überladung").
Virtuelle Funktionen und späte Bindung sind hier wichtige Stichworte.
Die MFC verwenden die ungarische Notation, die heute aus der Mode gekommen ist. Man gewöhnt sich allerdings rasch daran, genau wie an die windows-spezifischen Typenbezeichnungen. Kompatibilität hat eben auch ihren Preis.
MFC in Verbindung mit der grafischen Erstellung von Ressourcen erleichtern die Erstellung von Programmen und ermöglichen es, daß Einsteiger direkt für das Betriebssystem MS Windows programmieren können. Wir werden dennoch auch in die Texte der Ressourcen-Skripte schauen und dort Veränderungen vornehmen, damit Sie das gesamte Codefeld beherrschen und nicht nur von dem "Klicki-Bunti" der IDE abhängig sind.
Lassen Sie uns beginnen.
In Windows-Programmen ist der Ablauf nicht streng vorgeben.
Nehmen wir folgendes Beispiel: Der Anwender soll zwei Zahlen eingeben, die das Programm multipliziert. Das Ergebnis wird auf dem Bildschirm ausgegeben.
Konsolen-Programme fragen über Tastatur die beiden Zahlen nacheinander ab, kontrollieren die Eingaben auf Plausibilität, führen die mathematische Verknüpfung durch und geben das Ergebnis anschließend auf dem Bildschirm aus, etwa wie folgt:
Computer über
Bildschirm: Benutzer
mittels
Tastatur:
------------------------------------------------------------------------------------------------------------------
Bitte erste Zahl eingeben: (Eingabe 1)
Bitte zweite Zahl eingeben: (Eingabe 2)
Das Ergebnis ist (Eingabe 1 * Eingabe 2)
Der Benutzer kann nicht Eingabe 2 vor Eingabe 1 eingeben. Wird gerade Eingabe 2 abgefragt, kann man nur diese Eingabe durchführen und nicht noch rasch Eingabe 1 korrigieren. Dies nennt man ablauforientierte Programmierung. Solche Anwendungen kann man z.B. mit C oder C++ (auch unter Windows) im DOS-Fenster erstellen (MS Visual C++ bietet solche Möglichkeiten mittels Win32-Konsolenanwendungen).
In MS Windows erfolgen Abfragen dagegen nicht streng nacheinander. In sogenannten Dialogfenstern werden ohne strenge Abfolge die notwendigen Eingaben entgegen genommen, nicht systematisch abgefragt. Die Verknüpfung erfolgt nicht automatisch nach der letzten Eingabe, sondern der Anwender leitet den nächsten Schritt jeweils selbst ein (typischerweise mit OK, WEITER oder FERTIG). Er kann Eingaben einer Stufe durch "Abbrechen" zunichte machen. Korrekturen sind in der Regel leicht möglich.
Windows-Programmierer haben den Anwender (User) inzwischen an gewisse Darstellungen und Routinen gewöhnt, die er nun auch von Ihren Programmen erwartet. Bei der Erstellung von Windows-Anwendungen sollte man daher bezüglich der Benutzerführung nicht zuviel Kreativität walten lassen, sondern die gewohnte Windows-Umgebung bieten. Damit kann der Anwender intuitiv reagieren, und das Programm erweckt Vertrauen.
Hier bietet Ihnen Visual C++ die notwendigen professionellen Standards. Im ersten Kapitel werden Sie zwei wesentliche klassische Elemente von MS Windows erforschen, das "Fenster" (Window) und die Bedienung mit der "Maus".
Wesentliche Bestandteile von Windows sind die auf dem Bildschirm angezeigten Fenster (Windows) und die z.B. durch Benutzeraktionen ausgelösten internen Nachrichten (Messages). Daher beginnen wir mit diesen beiden Elementen der Windows-Programmierung. Sie werden die sogenannte Integrierte Entwicklungsumgebung (IDE) und den mitgelieferten MFC-Anwendungs-Assistenten von Anfang an einsetzen. Gleichzeitig werden wir den Direkteinstieg in den hiermit erzeugten Code wagen. Sie sollen das Zusammenspiel von Ressourcen, Source-Dateien und MFC-Klassen verstehen lernen, damit Sie gezielt in der Fülle von Dateien und Klassen agieren können. In Visual C++ erstellen Sie keine einfache Datei, sondern ein ganzes Projekt, das in einem gleichnamigen Ordner mehrere Dateien umfaßt.
Sie starten nun Ihr erstes Projekt, eine sogenannte "dialogfeldbasierende" Anwendung. Diese besitzt eine relativ einfache Datei- und Klassenstruktur und ist daher zum Einstieg ideal geeignet.
Starten Sie bitte MS Visual C++ und zur unabhängigen Datei-Überwachung den MS Explorer.
Vorbereitender Schritt:
Nach dem Start
wählen Sie den
Menü-Befehl
"Datei
/ Neu / Projekt" (Strg + Shift + N).
Hinweis: Bitte nicht mit dem Befehl "Neue Datei" verwechseln.
Es erscheint der Startbildschirm zur Erstellung
neuer Projekte.
Wählen Sie "MFC-Anwendung".
Es meldet sich der MFC-Anwendungs-Assistent. Nun folgt "Weiter".
Wählen Sie den Anwendungstyp "Auf
Dialogfeldern basierend". Nun folgt "Weiter".
Im Eingabefeld Dialogfeldtitel
fügen Sie
"DialogEins" ein. Nun folgt zwei Mal "Weiter".
"Fertig stellen".
Sie befinden sich nun in der Arbeitsumgebung zur
Windows-Programmierung.
Der Bildschirm zeigt neben dem Menü zwei
Bereiche:
den Projektmappenexplorer mit
den Dateien und das Dialogfenster.
Der Projektmappenexplorer (linkes Feld) dient zur Übersicht über Dateien, Klassen, Ressourcen und Eigenschaften. Im Arbeitsbereich trifft man die Auswahl, die Fensterliste (rechtes Feld) zeigt dann die entsprechenden Inhalte. Wir wollen eine Dialog-Anwendung entwickeln.
In der Fensterliste sehen Sie eine Standard-Dialog-Vorlage mit einem Textfeld, einem OK- und Abbrechen-Button, die Sie verändern/ergänzen sollen. Hierzu steht ein Werkzeugkasten mit "Steuerelementen" nach dem Klick auf die entsprechende Auswahl (rechts außen) bereit. Positionieren Sie das neu geöffnete Fenster "Werkzeugkasten" an eine geeignete Stelle.
An dieser Stelle erkennen Sie deutlich, was das "Visual" in Visual Studio für Ihre Programmierarbeiten bedeutet. Sie müssen nicht alles durch Tastatureingaben ("Codieren") festlegen, sondern können zumindest beim Editieren von Ressourcen im Wesentlichen visuell mit der Maus agieren, also grafisch unterstützt programmieren.
Wichtiger Hinweis: Üben Sie vorsorglich das Wiederfinden des "Werkzeugkastens" mit den Steuerelementen (bei Verlust). Klicken Sie rechts oben auf das "X". Nun ist der Werkzeugkasten weg. Ein Mausklick, der Zeit und Nerven kosten kann.
Das Wiederfinden der Steuerelemente ist nämlich deutlich schwieriger: Bewegen Sie die Maus in die oberste Menüleiste auf Ansicht. Ein Klick, und das Auswahlmenü erscheint. Wählen Sie "Werkzeugkasten", und die Steuerelemente sind wieder da. Die Kurzform lautet: Strg + Alt + X
Das Erscheinungsbild der aktuellen Version 14:
Abb. 1.1: Die Arbeitsumgebung zur
Erstellung
von Dialogfeldern
Wir
könnten jetzt leicht eine Dialog-Anwendung
mit einer Vielzahl von Steuerelementen entwerfen und diese zum
Funktionieren
bringen.
Dies ist für einen Einsteiger jedoch nicht
der richtige Weg zur sicheren Beherrschung der Materie.
Bevor wir den Werkzeugkasten munter einsetzen,
nutzen
wir zunächst die Möglichkeit, genauer zu analysieren,
was der hilfsbereite und flinke Assistent bisher
erzeugt hat. Hierzu
setzen wir den Windows-Explorer ein. In
Pfad "... Visual Studio 2015 \ Projects" hat der Assistent ein
Verzeichnis mit dem
Projektnamen MFCApplication1 angelegt. Schauen Sie sich dort alle
Verzeichnisse und Dateien genauer an:
Sie sehen, daß
selbst unsere vom Design her
einfache Anwendung bereits zu einer großen Zahl von Dateien
führt.
Dies ist sicher einer der Gründe, warum viele
Autoren objektorientiertes Programmieren mit C++ nicht mit
Windows-Programmen
einüben, sondern einfache Konsolenanwendungen vorziehen. Auf diese
Weise läßt sich der Programmcode leicht in ein oder zwei
Dateien
unterbringen. Da wir jedoch MFC-Windows-Programme erstellen wollen,
müssen
wir uns sofort mit dieser Dateivielfalt anfreunden. Nachdem der
Assistent
seine Aufbauarbeit geleistet hat, die wir jederzeit rasch wiederholen
könnten,
werden wir einen kleinen Ausflug in die erzeugten Dateien unternehmen.
Lassen wir MS Visual C++ selbst sprechen. Wir beginnen mit der
Textdatei
ReadMe.txt.
Sie finden hier folgendes (auszugsweise):
MFCApplication1.vcxproj
Dies ist die
Hauptprojektdatei für VC++-Projekte, die mit dem Anwendungs-Assistenten
generiert werden. Sie enthält Informationen über die Version von Visual
C++, mit der die Datei generiert wurde, sowie über die Plattformen,
Konfigurationen und Projektfunktionen, die im Anwendungs-Assistenten
ausgewählt wurden.
MFCApplication1.vcxproj.filters
Dies ist die
Filterdatei für VC++-Projekte, die mithilfe eines
Anwendungs-Assistenten erstellt werden. Sie enthält Informationen über
die Zuordnung zwischen den Dateien im Projekt und den Filtern. Diese
Zuordnung wird in der IDE zur Darstellung der Gruppierung von Dateien
mit ähnlichen Erweiterungen unter einem bestimmten Knoten verwendet (z.
B. sind CPP-Dateien dem Filter "Quelldateien" zugeordnet).
MFCApplication1.h
Dies ist die Hauptheaderdatei für die Anwendung. Sie enthält
andere projektspezifische Header (einschließlich Resource.h) und
deklariert die CMFCApplication1App-Anwendungsklasse.
MFCApplication1.cpp
Dies ist die
wichtigste Anwendungsquelldatei, die die Anwendungsklasse
CMFCApplication1App enthält.
MFCApplication1.rc
Dies ist eine
Auflistung aller vom Programm verwendeten Microsoft Windows-Ressourcen.
Sie enthält die Symbole, Bitmaps und Cursor, die im Unterverzeichnis
"RES" gespeichert werden.
Diese Datei kann direkt in Microsoft Visual
C++ bearbeitet werden.
res\MFCApplication1.ico
Dies ist eine
Symboldatei, die als Anwendungssymbol verwendet wird. Dieses Symbol ist
in der Hauptressourcendatei MFCApplication1.rc enthalten.
res\MFCApplication1.rc2
Diese Datei
enthält Ressourcen, die nicht von Microsoft Visual C++ bearbeitet
werden. Sie sollten alle Ressourcen, die nicht mit dem
Ressourcen-Editor bearbeitet werden können, in dieser Datei platzieren.
Der Anwendungs-Assistent erstellt
eine Dialogfeldklasse:
MFCApplication1Dlg.h,
MFCApplication1Dlg.cpp -
das Dialogfeld
Diese Dateien
enthalten Ihre CMFCApplication1Dlg-Klasse. Diese Klasse definiert das
Verhalten des Hauptdialogfelds der Anwendung. Die Vorlage für das
Dialogfeld befindet sich in MFCApplication1.rc und kann in Microsoft
Visual C++ bearbeitet werden.
...
Andere Standarddateien:
StdAfx.h, StdAfx.cpp
Mit diesen
Dateien werden eine vorkompilierte Headerdatei (PCH) mit dem Namen
MFCApplication1.pch und eine vorkompilierte Typendatei mit dem Namen
StdAfx.obj erstellt.
Resource.h
Dies ist die
Standardheaderdatei, die neue Ressourcen-IDs definiert. Die Datei wird
mit Microsoft Visual C++ gelesen und aktualisiert.
...
Zunächst prüfen wir, ob der Assistent in seiner Auflistung auch alle Dateien beschreibt, die der Explorer zeigt. Neben den oben beschriebenen finden wir folgende Dateien, die er nicht in "ReadMe.txt" aufführt. Alle drei Dateien lassen sich problemlos mit einem Texteditor (z.B. dem Notizblock oder WordPad im Windows-Zubehör) öffnen:
MFCApplication1.aps: Binäre Version der aktuellen Ressourcenskriptdatei; wird zum schnellen Laden verwendet. Hier haben wir nichts verloren.
Das Unterverzeichnis .../Debug bzw. .../Release wird erst später erzeugt und wird die binären Erzeugnisse von Compiler und Linker aufnehmen.
Im Unterverzeichnis .../res findet man zur Zeit zwei Dateien:
MFCApplication1.ico: Icon mit MFC-Darstellung
MFCApplication1.rc2: Öffnet man diese Datei mit einem Texteditor, so liest man folgendes:
//
// MFCApplication1.RC2 -
Ressourcen, die von Microsoft Visual C++ nicht direkt bearbeitet werden
//
#ifdef APSTUDIO_INVOKED
#error Diese Datei kann nicht in Microsoft Visual C++ bearbeitet werden.
#endif //APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
// Fügen Sie hier manuell
bearbeitete Ressourcen hinzu...
Nachdem Sie jetzt über die vom Assistenten erzeugte Dateistruktur einen ersten Überblick erhalten haben, kehren Sie zur grafischen Arbeitsumgebung von Abb. 1.1 zurück.
Wenn Sie die Projektmappe geschlossen haben, können Sie das Projekt über die Datei MFCApplication1.sln im Pfad "...\Visual Studio 2015\Projects\MFCApplication1" erneut öffnen. Dies geht natürlich auch direkt im Visual Studio, aber es ist sicher nützlich, wenn man weiß, wo die erzeugten Dateien landen.
Bleiben Sie bitte neugierig, und untersuchen Sie im Arbeitsbereich alle bereits jetzt vorhandenen (also noch nicht von uns erzeugten) Ressourcen. Folgende Ressourcen finden Sie in Resource.h:
#define
IDR_MAINFRAME
128
#define IDM_ABOUTBOX
0x0010
#define IDD_ABOUTBOX
100
#define IDS_ABOUTBOX
101
#define IDD_MFCAPPLICATION1_DIALOG 102
IDR_MAINFRAME zeigt auf das 48*48-Pixel-4-Bit-Icon MFCApplication1.ico (Sie können dieses Icon mit dem Ressourceneditor öffnen und bearbeiten.), das die Buchstaben MFC (Microsoft Foundation Class) grafisch verarbeitet (siehe oben). Probieren Sie an diesem Icon den mitgelieferten einfachen Grafik-Editor aus (es befindet sich unter Ressourcendateien).
Dieses Icon wird an dieser Stelle im Code verwendet: m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
Das finden Sie heraus, indem sie mit Rechtsklick auf IDR_MAINFRAME "Alle Verweise suchen" testen. Prägen Sie sich diesen Rechtsklick gut ein, da sie ihn gut brauchen können, um Verweise oder Deklarationen/Definitionen rasch zu finden.
IDD_MFCAPPLICATION1_DIALOG ist der Bezeichner des Dialogfeldes. Die Verwendung finden Sie über "Alle Verweise suchen": enum { IDD = IDD_MFCAPPLICATION1_DIALOG };IDD_ABOUTBOX ist der Bezeichner der About-Box. Mittels Verweise finden Sie: enum { IDD = IDD_ABOUTBOX };
In Resource.h werden also per #define sprechende Bezeichner für Zahlenwerte (damit arbeitet Visual Studio intern) festgelegt.
IDS_ABOUTBOX ist ein String. Mittels Verweise finden Sie:
BOOL bNameValid;
CString strAboutMenu;
bNameValid =
strAboutMenu.LoadString(IDS_ABOUTBOX);
IDM_ABOUTBOX ist ein Menüeintrag. Mittels Verweise finden Sie:
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,
strAboutMenu);
}
Wenn Sie nun wissen wollen, wie AppendMenu funktioniert, finden Sie dies über Rechtsklick (Verweise, Definionen) recht schnell heraus:
BOOL WINAPI AppendMenuW(_In_ HMENU hMenu,_In_ UINT uFlags,_In_ UINT_PTR uIDNewItem,_In_opt_ LPCWSTR lpNewItem);
Auf diese Weise orientieren Sie sich über Rückgabewerte, Parameter und Typen von Funktionen.
Momentan müssen Sie dort nichts ändern, das erledigt Visual Studio für Sie. Seien Sie froh darüber, aber bleiben Sie neugierig!
Sie werden nun probeweise kompilieren und linken: Dies erfolgt mittels Taste F7 bzw. Strg+Alt+F7 oder über das Menü mit "Erstellen / Projektmappe (neu) erstellen". Nur Mut! Es sollte nichts schiefgehen, da Sie bisher keine eigenen und damit evtl. fehlerhaften Dinge eingebaut haben. Compiler und Linker melden sich:
1>------ Neues Erstellen gestartet: Projekt: MFCApplication1, Konfiguration: Debug Win32 ------1 erfolgreich, 0
fehlerhaft, 0 übersprungen
Das klingt nach Erfolg! MFCApplication1.exe wurde im Unterverzeichnis Debug erzeugt.
Die in das Unterverzeichnis Debug kompilierten Programme sind nicht alleine lauffähig. Dies erfolgt erst durch Kompilierung in das Verzeichnis Release. Die Umstellung zwischen Dateiausgabe in das Projekt-Unterverzeichnis Debug bzw. Release erfolgt über das Menü Erstellen / Konfigurations-Manager. Dort findet man die Varianten Debug und Release. Probieren Sie beides aus. Praktische Verwendung findet die Release-Variante.
Hinweis: Zur
Erstellung einer exe-Datei
bedarf es
typischerweise folgender
Arbeitsvorgänge, die hier kurz skizziert werden:
resource.h
xxx.rc --- Resources Compiler --> xxx.res xxx.obj
|
Durch Kompilieren und Linken ist nundas ausführbare Programm MFCApplication1.exe entstanden. Starten Sie dieses Programm im Unterverzeichnis Debug oder Release. Auf dem Bildschirm sehen Sie die vom Assistenten erzeugte Dialog-Anwendung:
Abb. 1.2: Die erste Anwendung stellt sich vor
Sollten Sie das MFC-Icon bereits verändert haben, so sehen Sie jetzt den in Windows vorgesehenen Einsatzort: Links neben dem Titel "DialogEins" findet sich dieses Icon, das auch in der Taskleiste eingesetzt wird. Betrachten Sie bitte das erzeugte Fenster mit all seinen Elementen und Funktionen im Detail, da wir zum besseren Verständnis gezielt Änderungen an diesem Fenster vornehmen werden.
Nach dem Start wird unser Dialogfenster in der Bildschirmmitte plaziert. Durch Festhalten der Titelleiste mit der linken Maustaste kann man das Fenster beliebig verschieben (ziehen). Die Anwendung schließt direkt durch Anklicken des OK-Buttons, des Abbrechen-Buttons und des "X" rechts oben. Durch Anklicken des Icons links oben öffnet sich ein Popup-Menü, das mehrere aktive Menü-Punkte hat: Verschieben, Größe ändern, Schließen. Rechnet man noch Alt + F4 (das frühere Strg + F4) hinzu, so können Sie das Fenster mehrere Arten schließen, um damit die Anwendung zu beenden.
Das Fenster kann (inzwischen, das war in Version 6 noch fix) in der Größe verändert, (bei Erlaubnis von uns) nach unten geklickt (minimiert) oder auf volle Größe (maximiert) gebracht werden.
Öffnen Sie im Projektmappenexplorer die Datei MFCApplication1.rc mit Rechtsklick mit "Öffnen mit" mit dem Quellcode-Editor. Schauen Sie sich dort den Anschnitt Dialogfeld an:
/////////////////////////////////////////////////////////////////////////////
//
// Dialogfeld
//
IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
| WS_SYSMENU
CAPTION "Info über MFCApplication1"
FONT 8, "MS Shell Dlg"
BEGIN
ICON
IDR_MAINFRAME,IDC_STATIC,14,14,21,20
LTEXT
"MFCApplication1, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
LTEXT
"Copyright (C) 2015",IDC_STATIC,42,26,114,8
DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP
END
IDD_MFCAPPLICATION1_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SHELLFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME
| WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "DialogEins"
FONT 8, "MS Shell Dlg"
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON
"Abbrechen",IDCANCEL,263,179,50,14
CTEXT
"TODO: Dialogfeld-Steuerelemente hier
positionieren.",IDC_STATIC,10,96,300,8
END
Geben Sie bitte einen anderen Titel ein, um die Wirkung zu beobachten. Ändern Sie also bitte den Titel (Caption) unseres Dialogfeldes in diesem Ressourcencode-Bereich ab:
CAPTION "DialogEins" ==> CAPTION "Mein erster Dialog"Erstellen Sie erneut die exe-datei und öffenen Sie diese. Das Dialogfeld hat nun einen neuen Titel.
Wir üben nun das Aufrufen
der
Hilfe ,
indem Sie den Begriff DIALOGEX (entweder hinter IDD_ABOUTBOX oder
IDD_MFCAPPLICATION1_DIALOG auswählen und F1 drücken.
Früher musste man hier die MSDN lokal aufspielen, heute findet man dieses MS Developer Network in jeweils aktueller Form im Internet.
Man findet unter "DIALOGEX resource" ausführliche Hinweise auf die erweiterten Möglichkeiten. Wesentlich ist in jedem Fall das Verständnis der notwendigen Syntax eines Befehls, in unserem Fall:
nameID DIALOGEX x, y, width, height [ , helpID] [optional-statements] {control-statements}
Die nameID ist IDD_.... In der Hilfe findet man diesbezüglich:
#define IDD_ABOUTBOX
100
#define IDD_MFCAPPLICATION1_DIALOG 102
Diese "unique names" helfen nur uns. Visual Studio könnte genauso gut mit 100 oder 102 arbeiten, was es intern (über die #define Anweisung des Präprozessors macht).
Diesen Code Style kennt wohl jeder C-Programmierer, bei C++ ist das eher durch Konstanten ersetzt. Die MFC sind eher in der Mitte zwischen diesen beiden Sprachen positioniert. C ist aber eine Untermenge von C++. Sie sollten einfach alles akzeptieren und auch kennen, wenn Sie mit den MFC arbeiten: C, C++, WinAPI und die diese Schnittstelle umhüllenden MFC.
Benutzen Sie die Hilfe F1, die zum MSDN führt, möglichst oft, damit Sie sich an den etwas umständlichen Erklärungsstil gewöhnen, denn Sie finden hier detaillierte Ausführungen und entsprechende Querverweise.
Gehen wir im Ressourcenskript weiter:
IDD_MFCAPPLICATION1_DIALOG
DIALOGEX 0, 0, 320, 200
STYLE
DS_SHELLFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME |
WS_SYSMENU
EXSTYLE
WS_EX_APPWINDOW
CAPTION
"Mein erster Dialog"
FONT 8, "MS Shell Dlg"
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Abbrechen",IDCANCEL,263,179,50,14
CTEXT
"TODO: Dialogfeld-Steuerelemente
hier
positionieren.",IDC_STATIC,10,96,300,8
END
Hier werden verschiedene Window Styles (WS), das heißt Eigenschaften des Fensters festgelegt. WS_CAPTION bedeutet, daß das Fenster eine Titelzeile hat. WS_SYSMENU liefert das Menü links oben und das "X" rechts oben, mit dem man die Anwendung einfach beenden kann. Die senkrechten Striche sind bitweise ODER-Verknüpfungen der verschiedenen Window Styles.
EXSTYLE ist die erweiterte Version des veralteten STYLE und definiert WS_EX_... .
CAPTION legt die Titelzeile des Fensters fest.
Die zu verwendende Schrift wird durch FONT vorgegeben, hier die Standardschrift für Windows-Dialoge.
Zwischen BEGIN und END erhält unser Dialog seine innere Dekoration:
Nachdem Sie den Ausgangszustand der Ressourcenskriptdatei mit dem Texteditor ausführlich untersucht haben, können Sie ein wenig experimentieren. Insgesamt geht es darum, ein erstes grundlegendes Verständnis für die Konstruktion von Fenster-Ressourcen und den in diesem Zusammenhang verwendeten Styles zu erhalten.
Da wir an einer möglichst einfachen Anwendung interessiert sind, entfernen wir den OK- und Abbrechen-Button und ändern das Textfeld in der Mitte auf "Hallo Welt !!!".
Dies ist ein notwendiges Anfänger-Erfolgserlebnis. Das muß aus historischen Gründen (vgl. das Buch von Kernighan und Ritchie "The C Programming Language" von 1977 mit dem berühmten Gruß "hello, world") zumindest einmal sein. Das könnte dann in DialogEins.rc folgendermaßen aussehen:
IDD_MFCAPPLICATION1_DIALOG DIALOGEX 0, 0, 320,
200
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Mein erster Dialog"
FONT 8, "MS Shell Dlg"
BEGIN
CTEXT "Hallo Welt
!!!", IDC_STATIC, 10, 96, 300, 8
END
Wir haben einen neuen Titel, die beiden Buttons sind weg, und CTEXT bedeutet zentrierter Text. Jetzt "grüßt" Ihr Rechner die "Welt".
Im nächsten Schritt vereinfachen wir das Dialog-Fenster weiter: Das "X" oben rechts soll verschwinden. Also zurück zum Ressourcen-Editor.
Bitte WS_SYSMENU streichen. Das erledigen wir durch Auskommentieren im C++-Style mit //
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION // | WS_SYSMENU
Zur Übung bitte alles speichern und starten. Schon ist das "X" (SYSMENU bzw. Systemmenü) beseitigt.
Unser weiter vereinfachtes Dialogfenster sieht nun - hoffentlich auch bei Ihnen - wie folgt aus:
Immerhin können wir das Window noch mit der linken Maustaste verschieben. Vorhin hatten wir doch noch mehr Möglichkeiten zum Schließen, und nun?
Mit dem "X" ist auch das Icon (und damit auch das Menü zum Verschieben/Schließen) verschwunden.
Aber es bleibt noch die altbekannte Möglichkeit: Alt+F4. Das ist der bekannte Tastatur-Befehl zum Schließen von Windows-Anwendungen.
Wie Sie sehen ist nur WS_SYSMENU verschwunden. Jetzt entfernen Sie auch noch WS_CAPTION (Titelleiste).
STYLE WS_POPUP | WS_VISIBLE // | WS_CAPTION | WS_SYSMENU
und
// CAPTION "Mein erster Dialog"
Nach dem Kompilieren und Starten verbleibt, wie erwartet, ein ziemlich trostloses "Hallo Welt"-Fenster. Dieses Fenster können Sie mit Alt+F4 schließen.
Nun haben Sie bereits eine Menge über den Aufbau von Fenstern gelernt. Aus Interesse ein erneuter Blick in DialogEins.rc:
IDD_DIALOGEINS_DIALOG
DIALOGEX
0, 0, 320, 200
STYLE WS_POPUP
| WS_VISIBLE
EXSTYLE WS_EX_APPWINDOW
...
Trotz des bereits autistischen Fensters finden wir noch den Eintrag "STYLE WS_POPUP | WS_VISIBLE". Sie können nun auch noch "Sichtbar" (WS_VISIBLE) deaktivieren:
IDD_MFCAPPLICATION1_DIALOG DIALOGEX 0, 0, 320, 200 STYLE WS_POPUP // | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW //CAPTION "Mein erster Dialog" FONT 8, "MS Shell Dlg" BEGIN CTEXT "Hallo Welt !!!", IDC_STATIC, 10, 96, 300, 8 END
Wir stellen keine Veränderung fest. Das Fenster startet nach wie vor sichtbar (visible). WS_VISIBLE legt fest, ob ein bestimmtes Fenster von Anfang an sichtbar ist oder nicht. Ist dies nicht der Fall, muß man das Fenster im Programm mit ShowWindow(...) bzw. bei Dialogen mit DoModal() explizit sichtbar machen. Da es sich bei unserem Dialogfeld gleichzeitig um das Hauptfenster handelt erledigt der Assistent dies für uns:
BOOL CMFCApplication1App::InitInstance()
{
...
CMFCApplication1Dlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
...
}
Hinweis:
Bitte beachten Sie
die
objektorientierte
Programmierung. Man erstellt ein Objekt der Klasse CMFCApplication1Dlg
namens
dlg
mittels:
CMFCApplication1Dlg dlg;
Die
eigentliche Darstellung dieses Objektes erfolgt durch die
Member-Funktion
(Methode) CDialog::DoModal() :
dlg.DoModal();
Als letzter Rest bleibt "WS_POPUP" und "EXSTYLE WS_EX_APPWINDOW". Wenn Sie diese beiden Styles im Ressourcenskript streichen würden und erneut starten, sehen Sie unser Dialogfenster (hier unsere Hauptanwendung) immer noch, sogar mit einfachem Rand.
Nachfolgend finden Sie für eigene Experimente eine Auswahl von Windows Styles und ausgewählte WS_EX Styles, die es schon seit Version 6 gibt.
Im Abschnitt 1.1 beschäftigten wir uns tiefergehend mit Ressourcen-Skripten bei der MFC-Programmierung. Nun kommen Klassen, Meldungen (Nachrichten) und Handler ins Spiel. Windows war und ist ein Nachrichtensystem. Hierdurch werden die parallelen Abläufe am Laufen gehalten und mit dem Anwender über die klassische grafische Schnittstelle und die Human Interface Devices, dazu gehören heute nicht nur Tastatur und Maus, kommuniziert. Tausende solcher Meldungen tauchen intern in kürzester Zeit auf. Nicht alle finden ihren Handler. Wir wollen in diesem Abschnitt auf einen Mausklick reagieren.
Nachdem wir jetzt ein "brauchbares" Dialog-Fenster in MS Windows in der gewünschten Erscheinungsform (WS_... und WS_EX...) erstellen können, wenden wir uns dem nächsten Schritt zu, nämlich der in Windows so wichtigen Nachrichtenverarbeitung. Schauen wir uns unsere Dialoganwendung diesbezüglich genauer an. Worauf reagiert unser Fenster? Bisher reagiert unsere Anwendung für uns erkennbar nur auf das Anklicken der von uns verwendeten Fenster-Elemente. Beispiele hierfür sind SYSMENU, MINIMIZEBOX oder MAXIMIZEBOX.
Klickt man in die graue Fläche unseres Fensters, dann passiert nichts Erkennbares. Was müssen wir unternehmen, damit hier eine sichtbare oder hörbare Aktion erfolgt?
Zunächst müssen wir die Nachricht "linke Maustaste gedrückt" (man kann dies auch Event nennen) in unser Programm einbinden. Dieser Nachricht (Message) müssen wir anschließend objektorientiert eine Member-Funktion zuordnen, die als Reaktion auf die Nachricht ausgelöst wird. Zum Schluß füllen wir diese Funktion mit Quellcode, damit auch wirklich etwas passiert.
Bisher haben wir uns mit den Ressourcen beschäftigt. Nun wechseln wir in die Klassenansicht zu den mittels MFC erstellten Klassen unserer Anwendung.
Abb. 1.3: Klassenansicht der beiden DialogEins-Klassen
Das Projekt umfasst zwei von uns mittels Assistent geschaffene Klassen: CMFCApplication1App und CMFCApplication1Dlg.
Die Klasse CMFCApplication1App
besitzt zwei
Member-Funktionen
(Methoden):
den Konstruktor CMFCApplication1App() und
die Funktion InitInstance().
Die Klasse CMFCApplication1Dlg
besitzt fünf
Member-Funktionen
und eine Member-Variable (Attribut):
den Konstruktor CMFCApplication1Dlg(...),
DoDataExchange(...),
OnInitDialog(),
OnPaint(),
OnQueryDragIcon()
OnSysCommand(...) und
m_hIcon.
Die Klassenansicht zeigt symbolhaft den Zugriffsstatus auf Member-Variablen bzw. Member-Funktionen an:
Hinweis zur objektorientierten Programmierung (OOP): Klassen sind nichts anderes als
"Baupläne"
für Objekte. Der Zugriff auf Funktionen und
Variablen
wird bei deren Deklaration erledigt: Bei private kann nur von innerhalb
der Klasse
zugegriffen werden. |
Hier müssen wir in
die richtige Klasse eine
Funktion zur Reaktion auf den Mausklick einfügen.
Wie machen wir das?
Es gibt hier den sogenannten Klassen-Assistenten, den man mit Projekt - Klassen-Assistent erreicht (Strg+Shift+X).
Wählen Sie bei Klassenname: "CMFCApplication1Dlg"
Unter Meldungen (auch genannt: Messages, Nachrichten, Events) sehen Sie rechts die bereits vorhandenen Member-Funktionen, die auf Meldungen/Nachrichten reagieren. Man nennt diese Funktionen auch Handler.
Geben Sie bitte
nach der Kommentarzeile
// TODO: ...
folgenden Code
ein:
CClientDC dc(this);
dc.TextOutW(point.x, point.y, L"linke Maustaste");
Mit "CClientDC
dc(this);" definieren wir
einen
sogenannten Gerätekontext (Device Context), der die
Rolle der Schnittstelle zwischen Programm und Bildschirmausgabe
übernimmt.
Das erzeugte Objekt dc (der Name ist frei wählbar aber üblich)
besitzt
eine große Menge von Funktionen (natürlich automatisch durch
MFC vorgegeben), die wir sofort einsehen können, sobald wir in der
zweiten Zeile den Punkt hinter dc eintippen.
Ein Listenfeld klappt auf, das uns eine breite
Auswahl
bietet (auch hier spürt man etwas vom "Visual" des MS VC++).
Wir wollen Text
ausgeben und wählen daher
per
Doppelclick die Funktion "TextOutW" aus.
Sobald wir anschließend eine öffnende
runde Klammer hinter TextOutW eingeben, erhalten wir die Information
über die erwarteten Parameter und deren Typen.
Wir
geben in dieser Zeile folgendes ein:
dc.TextOut( point.x, point.y, L"linke Maustaste" ); |
Achten Sie bei der Eingabe von Programmcode bitte auf alle Klammern, Kommas und Semikolons. Da ist C/C++ besonders empfindsam. Wir benützen als x- und y-Ausgabe für den Text die von der Funktion übergebenen Mauskoordinaten, die in point als Elemente point.x und point.y enthalten sind. Wenn Sie alles richtig gemacht haben, dann erhalten Sie nach dem Speichern und Kompilieren eine Anwendung, die auf das Niederdrücken der linken Maustaste im sogenannten Client-Bereich des Fensters wie folgt reagiert:
Abb. 1.4: Unser "Fenster" reagiert auf die Nachricht WM_LBUTTONDOWN mit einer Unicode-Textausgabe
Dieses Beispiel demonstriert Ihnen, daß
es
nicht schwierig ist, eigene Nachrichten-Behandlungs-Funktionen (modern:
Handler für Meldungen) in eine
Klasse einzubauen.
In welche Klasse haben wir eigentlich unsere
Funktion
eingebaut?
Ein Blick in die Klassenansicht beantwortet diese
Frage sofort:
CMFCApplication1Dlg
Das TODO können wir nun streichen, denn wir haben bereits etwas getan:
void
CMFCApplication1Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
dc.TextOutW(point.x, point.y, L"linke Maustaste");
CDialogEx::OnLButtonDown(nFlags, point);
}
Hier können Sie etwas Wichtiges beobachten: Unsere mittels Assistent erzeugte Klasse CMFCApplication1Dlg erbt von der MFC-Klasse CDialogEx:
class CMFCApplication1Dlg : public CDialogEx
Daher ruft unsere
Application auch noch die entsprechende Funktion
CDialogEx::OnLButtonDown(...) auf.
OnLButtonDown(...) ist eine Funktion der Klasse CMFCApplication1Dlg. Wenn Sie mehr über diese Klasse wissen wollen,bauen Sie in der Klassenansicht die Klassehierarchie mit den jeweiligen Basistypen auf. Die oberste Klasse ist CObject, und die niedrigste Klasse ist die von uns erstellte Klasse in unserem programm, die von den "Elterklassen" alles Wichtige bereits erbt, was man in einem Windows-Programm benötigt.
Die gesamte (Vererbungs-)Hierarchie der Klassen sieht in unserem Fall wie folgt aus:
CObject
CCmdTarget
CWnd
CDialog
CDialogEx
CMFCApplication1Dlg
Abb. 1.5: Wichtige Arbeitsumgebung: Klassenansicht zeigt die Klassenvererbungshierarchie, die Member (Methoden und Attribute) einer Klasse, und rechts daneben stellen wir den Code dar
Wer sich für die gesamte Hierarchie der MFC-Klassen interessiert, wird hier fündig.
Die Klasse CMFCApplication1Dlg enthält im Bereich "Generierte Funktionen für die Meldungstabellen" (Version 6: Generierte Message-Map-Funktionen) die Deklaration der von uns hinzugefügten Funktion OnLButtonDown(...). Der Assistent hat dies für uns hier eingefügt.
Das Fenster mit all seinen Ausstattungen ist bis zum Schließen der Anwendung stabil. Wenn man das Fenster mit der Maus z.B. teilweise über den Bildschirmrand hinaus schiebt und dann wieder zurück zieht, dann gehen keine Details oder Funktionen verloren. Auch nach dem Minimieren (Ablegen) und Wiederaufrufen besitzt das Fenster noch all seine Details (Rahmen, Titel, Buttons, Farbe). Anders ist dies mit dem von uns erzeugten Text. Dieser ist nicht stabil gespeichert, sondern vergänglich! Schieben Sie nach einigen Mausklicks (über das gesamte Fenster verteilt) das Fenster zur Hälfte über den Bildschirmrand hinaus (dafür brauchen Sie WS_SYSTEMMENU). Alternativ können Sie ein anderes Window zur Hälfte darüber ziehen. Nach dem Zurückziehen (bzw. Freilegen) könnte Ihr Fenster dann wie folgt aussehen:
Abb. 1.6: Der in OnLButtonDown(...) ausgegebene Text ist vergänglich (Fenster aus der Version MSVC++ 6, ca. Jahr 2000).
Unser Gerätekontext leitet seine Ausgaben direkt an das Fenster. Nach dem Verschwinden dieser Informationen können diese beim Wiederaufbau (Methode: OnPaint) des Fensters nicht mehr berücksichtigt werden. Machen Sie sich dies klar, indem Sie die Methode OnPaint, die auf WM_PAINT reagiert, analysieren.
In diesem Abschnitt
werden wir unsere kleine
Dialogfeld-Anwendung
auf einfachste Weise zum Zeichnen - sprich Linien ziehen - verwenden.
Wir
verwenden ein dialogfeldbasierendes Programm und verändern die
Geometrie
des Dialogfeldes auf ein breites Fenster. Den Titel ändern wir
auf "Zeichenfenster". Bitte auch Systemmenü und Minimize/Maximize
einbauen.
Wir wollen nun beim
Linksklick keine Textausgabe,
sondern wir bauen in diesem Beispiel eine Funktion zum Ziehen von
Linien auf.
Ändern Sie den Programmcode von OnLButtonDown(...) bitte wie folgt ab:
void
CMFCApplication2Dlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
CClientDC
dc(this);
dc.LineTo(point.x,
point.y);
CDialogEx::OnLButtonDown(nFlags,
point);
}
Nach dem Kompilieren/Starten beantwortet unsere Anwendung das Drücken der linken Maustaste nicht mehr wie oben mit einer Unicode-Textausgabe, sondern zieht von der linken oberen Ecke des Fensters eine schwarze Linie zur Mausspitze. Nach mehrmaligem Einsatz des Mausklicks sieht das dann - hoffentlich auch bei Ihnen - etwa so aus:
Abb. 1.7: In OnLButtonDown(...) wirkt jetzt die Funktion LineTo(...)
Was passiert da genau? 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 unserer Klasse CMFCApplication2Dlg, die folglich innerhalb der gesamten Klasse gültig sind.
Zum Festlegen der
beiden Member-Variablen m_Xold
und m_Yold zur Klasse CMFCApplication2Dlg führen Sie in der
Klassenansicht bitte einen Rechtsklick
auf CMFCApplication2Dlg aus und wählen Hinzufügen - Variable hinzufügen
aus. Daraufhin öffnet sich der Assistent zum Hinzufügen von
Membervariablen.
Beim Zugriff wählen wir "private" und beim Variablentyp "int" (für Integer). Als Variablenname geben wir m_Xold ein. Das m_ steht für Member-Variable. Dann bestätigen wir mit Fertigstellen. 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 CMFCApplication2Dlg:
// CMFCApplication2Dlg-Dialogfeld
Doppelklicken
Sie bitte auf die Funktion
OnLButtonDown(UINT
nFlags, CPoint point). Wir ändern diese wie folgt ab:
void
CMFCApplication2Dlg::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, Kompilieren und Starten (Strg+F5) arbeitet unsere Anwendung wie gewünscht. Von Mausklick zu Mausklick wird eine schwarze Linie gezogen:
Abb. 1.8: 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 CMFCApplication2Dlg 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. Sie sehen eine Liste , aus dem Sie m_Xold
wählen.
Wiederholen Sie dies bitte in der nächsten Zeile nach Eingabe von "m_Y". Schon sind Sie bei der gewünschten Variable "m_Yold". Insbesondere bei längeren Namen ist dies sehr hilfreich.
void
CMFCApplication2Dlg::OnPaint()
{
m_Xold = 0;
m_Yold = 0;
if (IsIconic())
{
CPaintDC dc(this); // Gerätekontext zum Zeichnen
SendMessage(WM_ICONERASEBKGND,
reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Symbol in Clientrechteck 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
{
CDialogEx::OnPaint();
}
}
Nun startet unsere Anwendung mit der ersten Linie immer in der oberen linken Ecke.
Die Funktion OnPaint() enthält obsoleten Code, den man in Win 3.x brauchte. Diese Zöpfe schneiden wir nun ab. Vereinfachen Sie bitte auf:
void
CMFCApplication2Dlg::OnPaint()
{
m_Xold = 0;
m_Yold = 0;
CDialogEx::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 Visual Studio:
1>------ Erstellen gestartet: Projekt:
MFCApplication2, Konfiguration: Debug Win32 ------
1> MFCApplication2Dlg.cpp
1>...\mfcapplication2dlg.cpp(135): error C2601:
"CMFCApplication2Dlg::OnQueryDragIcon": Lokale Funktionsdefinitionen
sind unzulässig
1>...\mfcapplication2dlg.cpp(125): note: Diese Zeile enthält eine
"{", die keine Entsprechung hat.
1>...\mfcapplication2dlg.cpp(142): error C2601:
"CMFCApplication2Dlg::OnLButtonDown": Lokale Funktionsdefinitionen sind
unzulässig
1>...\mfcapplication2dlg.cpp(125): note: Diese Zeile enthält eine "{",
die keine Entsprechung hat.
1>...\mfcapplication2dlg.cpp(125): fatal error C1075: Keine Entsprechung für das
linke Element Klammer "{" am Dateiende gefunden.
========== Erstellen: 0 erfolgreich, 1 fehlerhaft, 0 aktuell, 0
übersprungen ==========
Das Unangenehme hierbei ist, daß der Compiler Ihnen oft 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. Das aktuelle Visual Studio gibt Ihnen hier aber einen guten Hinweis.
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+Shift+X ein. Der Klassen-Assistent
meldet sich sofort zur Stelle.
Unter Nachrichten finden Sie (fett dargestellt)
WM_PAINT. Als Beschreibung zu
dieser Meldung 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
Schauen Sie sich an dieser Stelle auch die Beschreibungen der anderen Member-Funktionen an. Der Klassen-Assistent (Strg+Shift+X) ist ein zentrales Informations- und Aktionszentrum zur Verwaltung der Klassen mit ihren Beziehungen zwischen Handler und Meldung.
Wenn wir schon einmal im Klassen-Assistent sind, klicken wir auch gleich die Nachricht WM_MOUSEMOVE an:
Mit "Handler 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" springen wir direkt in die neue Funktion ein. In der Klassenansicht 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
CMFCApplication2Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialog::OnLButtonDown(nFlags, point);
}
void CMFCApplication2Dlg::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
CDialogEx::OnMouseMove(nFlags, point);
}
Wie Sie sehen, habe ich die Befehlszeilen kommentiert, um auch später noch die Zusammenhänge nachvollziehen zu können. Verwenden Sie das Kommentieren bitte möglichst umfangreich, gerade bei Übungen. Löschen können Sie jederzeit. Nach dem Speichern und Kompilieren können Sie mit (hoffentlich) eleganten Mausbewegungen zeichnen (Start mit Strg+F5).
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
CMFCApplication2Dlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
Invalidate();
CDialogEx::OnLButtonDown(nFlags,
point);
}
Funktioniert wie erwartet; Linksklick löscht die Grafik.
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
CMFCApplication2Dlg::OnPaint()
{
CDialogEx::OnPaint();
}
void CMFCApplication2Dlg::OnLButtonDown(UINT
nFlags, CPoint point)
{
Invalidate();
m_Xold=point.x;
m_Yold=point.y;
CDialogEx::OnLButtonDown(nFlags,
point);
}
Anschließend
arbeitet unsere Anwendung nach
dem ersten Mausklick wie gewünscht, aber immer noch nicht wie man sich
das so vorstellt.
Da es bereits ausreichend professionelle
Zeichenprogramme
auf dem Markt gibt, sollten wir jetzt kein ausgeklügeltes
Dialogfeld-Zeichenprogramm
entwickeln.
Wir werden das Programm jedoch dazu nützen, mehr Erfahrung mit den Meldungen und Nachrichtenbehandlungsfunktionen (Handler) 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 |
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. Das ist echt
übel.
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 unsere Dialog-Klasse. "Hinzufügen -
Variable hinzufügen" auswählen.
Bei Zugriffsstatus
entscheiden wir uns für "Privat" (private).
Im
Feld
"Variablentyp" geben wir "bool" ein.
Als "Variablenname" wählen wir "m_DrawFlag".
In OnPaint setzen wir das Flag auf "true". Daher fügen wir folgendes hinzu:
void
CMFCApplication2Dlg::OnPaint()
{
m_DrawFlag = true;
CDialogEx::OnPaint();
}
Jetzt wechseln wir zur Member-Funktion OnRButtonDown. Hier implementieren wir zu Ihrer Übung eine switch-Kontrollstruktur. Wir geben folgendes ein:
void CMFCApplication2Dlg::OnRButtonDown(UINT nFlags, CPoint point)Nun müssen wir noch die if-Kontrollstruktur in die Member-Funktion OnMouseMove einbauen:
void
CMFCApplication2Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
CClientDC dc(this);
dc.MoveTo(m_Xold, m_Yold);
if (m_DrawFlag)
dc.LineTo(point.x, point.y);
m_Xold = point.x;
m_Yold = point.y;
CDialogEx::OnMouseMove(nFlags, point);
}
Sie sehen, dass man unter Einsatz von Flags, if- und switch-Kontrollstrukturen bereits mit relativ einfachen Mitteln ein funktionierendes Programm erzeugen kann.
Abb.
1.9: 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, oder
benutzen Sie die mittlere Maustaste, falls vorhanden (oft mit Mausrad
kombiniert).
Hier zur Übersicht der gesamte Code:
//
MFCApplication2Dlg.cpp: Implementierungsdatei
//
#include
"stdafx.h"
#include
"MFCApplication2.h"
#include
"MFCApplication2Dlg.h"
#include
"afxdialogex.h"
#ifdef _DEBUG
#define new
DEBUG_NEW
#endif
//
CAboutDlg-Dialogfeld für Anwendungsbefehl "Info"
class
CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
//
Dialogfelddaten
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); //
DDX/DDV-Unterstützung
//
Implementierung
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg()
: CDialogEx(CAboutDlg::IDD)
{
}
void
CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg,
CDialogEx)
END_MESSAGE_MAP()
//
CMFCApplication2Dlg-Dialogfeld
CMFCApplication2Dlg::CMFCApplication2Dlg(CWnd*
pParent /*=NULL*/)
: CDialogEx(CMFCApplication2Dlg::IDD, pParent)
, m_Xold(0)
, m_Yold(0)
, m_DrawFlag(false)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void
CMFCApplication2Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCApplication2Dlg,
CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()
//
CMFCApplication2Dlg-Meldungshandler
BOOL
CMFCApplication2Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Hinzufügen des Menübefehls
"Info..." zum Systemmenü.
// IDM_ABOUTBOX muss sich im
Bereich der Systembefehle befinden.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Symbol für dieses Dialogfeld
festlegen. Wird automatisch erledigt
// wenn das Hauptfenster der Anwendung kein Dialogfeld ist
SetIcon(m_hIcon, TRUE); // Großes Symbol verwenden
SetIcon(m_hIcon, FALSE); // Kleines Symbol verwenden
// TODO: Hier zusätzliche
Initialisierung einfügen
return TRUE; // TRUE
zurückgeben, wenn der Fokus nicht auf ein Steuerelement gesetzt wird
}
void
CMFCApplication2Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
//
Wenn Sie dem Dialogfeld eine Schaltfläche "Minimieren" hinzufügen,
benötigen Sie
//
den nachstehenden Code, um das Symbol zu zeichnen. Für
MFC-Anwendungen, die das
//
Dokument/Ansicht-Modell verwenden, wird dies automatisch ausgeführt.
void
CMFCApplication2Dlg::OnPaint()
{
m_DrawFlag = true;
CDialogEx::OnPaint();
}
//
Die System ruft diese Funktion auf, um den Cursor abzufragen, der
angezeigt wird, während der Benutzer
//
das minimierte Fenster mit der Maus zieht.
HCURSOR
CMFCApplication2Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void
CMFCApplication2Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
Invalidate();
m_Xold = point.x;
m_Yold = point.y;
CDialogEx::OnLButtonDown(nFlags, point);
}
void
CMFCApplication2Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
CClientDC dc(this); //Gerätekontext
erstellen
dc.MoveTo(m_Xold, m_Yold); //zu
letzter Mausposition navigieren
if (m_DrawFlag)
dc.LineTo(point.x, point.y); //Linie ziehen zur aktuellen
Mausposition
m_Xold = point.x; //Mausposition
X speichern
m_Yold = point.y; //Mausposition
Y speichern
CDialogEx::OnMouseMove(nFlags, point);
}
void
CMFCApplication2Dlg::OnRButtonDown(UINT nFlags, CPoint point)
{
switch (m_DrawFlag)
{
case true:
m_DrawFlag = false;
break;
case false:
m_DrawFlag = true;
break;
}
CDialogEx::OnRButtonDown(nFlags, point);
}
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 dialogbasierendes Projekt mit dem Dialog-Titel "EinfacheAusgaben".
Wählen Sie nur den Minimize-Button, aber nicht den
Maximize-Button. Fertigstellen.
Fügen Sie im visuellen Resourcen-Editor mittels Werkzeugkasten - Dialog-Editor (im Werkzeugkasten nach unten scrollen!) vier neue Buttons (Schaltflächen) mit untenstehenden Titeln ein und ordnen Sie den OK- und Abbrechen-Button wie folgt an:
Abb. 1.10: Wir setzen das Steuerelement "Schaltfläche" (Button) ein
Benutzen Sie zur Anordnung der neuen Buttons entweder den grafischen Editor oder den Code-Editor für die Ressourcen. Letzteres geht hier durch Einkopieren der Zahlen am schnellsten:
IDD_MFCAPPLICATION3_DIALOG
DIALOGEX 0, 0, 320, 99
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE
| WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "Einfache Ausgaben"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,76,50,14
PUSHBUTTON
"Abbrechen",IDCANCEL,263,76,50,14
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
END
In der Datei Resource.h finden Sie die vier neuen Button-Bezeichner:
#define
IDC_BUTTON1
1000
#define
IDC_BUTTON2
1001
#define
IDC_BUTTON3
1002
#define
IDC_BUTTON4
1003
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.
Abb. 1.11: Einfügen des
Steuerelements
"Schaltfläche" (Button) - Ausrichtung gelingt grafisch oder direkt im
Ressourcenskript
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:
Nach dem Klick auf den OK-Button im grafischen Ressourcen-Editor landen Sie direkt in der jeweiligen neuen Member-Funktion und können mit der Programmierung beginnen:
void
CMFCApplication3Dlg::OnClickedButton1()
{
// TODO: Fügen
Sie hier Ihren Kontrollbehandlungscode für die Benachrichtigung ein.
}
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 CMFCApplication3Dlg::OnClickedButton1()Für die WinAPI-Funktion ::PlaySound habe ich folgenden Code am Anfang von MFCApplication3Dlg.cpp eingefügt:
//
MFCApplication3Dlg.cpp: Implementierungsdatei
//
#include
"stdafx.h"
#include
"MFCApplication3.h"
#include
"MFCApplication3Dlg.h"
#include
"afxdialogex.h"
#ifdef
_DEBUG
#define
new DEBUG_NEW
#endif
#include
<mmsystem.h>
#pragma
comment(lib, "winmm.lib")
Betrachten wir zunächst OnClickedButton1(). Hier verwenden wir die WinAPI-Funktion BOOL MessageBeep( UINT uType ). Diese Information erhalten Sie, wenn Sie den Cursor über den Funktionsnamen führen. Mit F1 landen Sie in der MSDN bei MessageBeep function. Für einen Einsteiger in die Windows-Programmierung sind die vielen Abkürzungen für die verschiedenen Typen in der Regel 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 WinAPI-Funktion. Sie können in Visual C++ jederzeit direkt auf die WinAPI-Funktionen zugreifen. Geben Sie zur Übung nur "::", "::Get" oder "::Set" 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 MSDN zum
Thema
"MessageBeep" schauen. Für die Praxis wichtig ist zunächst der
Abschnitt "Parameters" und "Return Value", 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 WinAPI-Funktionen.
Windows-Knowhow
entsteht durch das praktische Wissen über die API-Funktionen.
Nach dem Speichern/Kompilieren/Starten gibt unsere
Funktion OnClickedButton1() nacheinander verschiedene
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
WinAPI-Funktion
"void Sleep (DWORD dwMilliseconds)" haben wir zur Erzeugung
einer
Pause eingefügt, damit 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 OnClickedButton2() erfolgt - auch eigene WAV-Files abspielen. Wir benützen hier eine WAV von Windows Media.
::PlaySound("C:\\Windows\\media\\tada.wav", NULL, SND_FILENAME | SND_ASYNC );
Die WinAPI-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> ein. Seien Sie vorsichtig, damit Sie nichts zerstören. Das Bibliotheks-Modul "winmm.lib" binden wir über eine pragma-Anweisung ein. Wenn Sie dies erledigt haben, sollten Sie nach dem Speichern/Kompilieren/Linken/Starten den erwarteten Windows-Klang vernehmen. Wenn nicht, prüfen Sie bitte, ob das angegebene WAV-File auf Ihrem Rechner im Laufwerk C: überhaupt existiert.
Noch einige Infos zu PlaySound:
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
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 möglichst oft, 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 vorhandene andere Windows-Anwendungen.
Geben Sie in OnClickedButton3() und OnClickedButton4() bitte folgenden Programmcode ein:
void
CMFCApplication3Dlg::OnClickedButton3()
{
MessageBox(L"Hallo Welt !", L"MessageBox mit MB_OK",
MB_OK);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_OKCANCEL", MB_OKCANCEL);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_RETRYCANCEL", MB_RETRYCANCEL);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_YESNO", MB_YESNO);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_YESNOCANCEL", MB_YESNOCANCEL);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ABORTRETRYIGNORE", MB_ABORTRETRYIGNORE);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ICONEXCLAMATION", MB_ICONEXCLAMATION);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ICONINFORMATION", MB_ICONINFORMATION);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ICONQUESTION", MB_ICONQUESTION);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ICONSTOP", MB_ICONSTOP);
MessageBox(L"Hallo Welt !", L"MessageBox mit
MB_ICONEXCLAMATION und MB_RETRYCANCEL",MB_ICONEXCLAMATION |
MB_RETRYCANCEL);
}
void CMFCApplication3Dlg::OnClickedButton4()
{
::WinExec("calc.exe", SW_NORMAL);
}
In
der Member-Funktion OnClickeButton3() 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.
int MessageBox( LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK );
Sie finden die Funktion MessageBox übrigens in der Hilfe unter dem Eintrag "CWnd::MessageBox", da es sich hier nicht um eine WinAPI-Funktion, sondern eine Member-Funktion der MFC-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-Klasse".
Die Message-Box ist
ein idealer Haltepunkt zum
Austesten
eigener Programme, da man mittels eines Strings Informationen, z.B.
über
den Wert von Variablen, übermitteln kann.
In OnClickedButton4() 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 Hintergrundaufgaben
einsetzen, die sich erst nach Ausführung mittels MessageBox melden.
Testen Sie
die für Sie interessanten Vorgehensweisen und Abläufe aus.
Bei unserem hoffentlich lockeren und dennoch informativen Einstieg in die Windows-Programmierung mit MFC haben Sie bisher folgendes kennengelernt: