C++ und Microsoft Foundation Classes (MFC) mit MS VS Community 2015
Kapitel 1

Dr. Erhard Henkes  (Stand: 21.07.2015)



Zurück zum Inhaltsverzeichnis
 

Vorwort

Wenn Sie dieses Tutorial gewählt haben, sind Sie offensichtlich entschlossen, Windows-Programme mit Hilfe der objektorientierten Sprache C++ und den Microsoft Foundation Classes (MFC) zu erstellen. Sie sind vielleicht noch verunsichert, da es für diesen Zweck z.B. auch andere Sprachen wie C#, Java und Visual Basic gibt. Mit C++ stehen Ihnen von Konsolen- bis zu Windows-Anwendungen alle Programmier-Wege offen. C++ unterstützt durch die Programmierumgebung MS Visual C++ ist ein mächtiges Werkzeug, das sich seit nun vielen Jahren bewährt hat und man im Internet viele Tricks, Tipps und Code Snippets findet. Der entscheidende Nachteil bei den MFC ist die Bindung an die Plattform MS Windows. Wenn dies allerdings kein Punkt mit hoher Priorität ist und Sie C++ beherrschen, dann ist die bewährte MFC-Bibliothek immer noch sehr gut einsetzbar.

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++
 Nur in der Ruhe liegt die Kraft.

 


Einführung

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.  

Was macht eigentlich Windows aus?

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". 



   

Kapitel 1 - Einstieg in MFC und Windows

1.1 Dialogbasierende Anwendung

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:

MFCApplication1.sln (lesbare MS VS Solution Datei) MFCApplication1.sdf (temporäre Datei, kryptisch) In dem Verzeichnis "MFCApplication1" finden sich die zum Projekt gehörigen Dateien.

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>  stdafx.cpp
1>  MFCApplication1Dlg.cpp
1>  MFCApplication1.cpp
1>  Code wird generiert...
1>  MFCApplication1.vcxproj -> C:\Users\oem\Documents\Visual Studio 2015\Projects\MFCApplication1\Debug\MFCApplication1.exe
========== Alles neu erstellen: 1 erfolgreich, 0 fehlerhaft, 0 übersprungen ==========

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.h       --- C++ Compiler ---------> xxx.obj
 xxx.cpp

 xxx.rc      --- Resources Compiler  --> xxx.res

 xxx.obj
 xxx.res     --- Linker ---------------> xxx.exe
 yyy.lib


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:

nameID: Unique name or a unique 16-bit unsigned integer value that identifies the dialog box
Wie ist dies nun konkret in unserer Anwendung? Sie kennen die Antwort bereits. In der vom Assistenten angelegten Datei resource.h:
Für unsere Dialog-Anwendung steht dort:

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

Wir haben jetzt gesehen, daß die Ressourcen unserer Anwendung in der Ressourcenskriptdatei (.rc) und der Datei resource.h erzeugt werden. Der Explorer zeigt Ihnen im Projekt die Datei MFCApplication1.res, eine binäre Datei, die vom Ressourcen-Compiler aus dieser Ressourcenskriptdatei (.rc) erzeugt wurde.

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. 

Sie müssen diese Begriffe natürlich nicht auswendig lernen, sollten sich aber an die englischen Begriffe gewöhnen, da Sie diese in der Hilfe bzw. in der (englischen) Fachliteratur finden. Sie finden weitere Styles in der Hilfe (MSDN): Fensterstile und Erweiterte Fensterstile Aufgabe: Bauen Sie in unser Fenster wieder das Systemmenü, einen Minimize- und Maximize-Button ein und schaffen Sie einen akzeptablen Rand.

1.2 Unser Dialog empfängt und verarbeitet Nachrichten (Meldungen)

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. 
 Klassen enthalten Member-Funktionen (Methoden) und  Member-Variablen (Attribute). 
 Der "Bau" der Objekte erfolgt in der Konstruktor-Funktion (kurz. Konstruktor). 
 Der Konstruktor hat den gleichen Namen wie die Klasse. 

 Der Zugriff auf Funktionen und Variablen wird bei deren Deklaration erledigt: 
 public, protected oder private. 

 Bei private kann nur von innerhalb der Klasse zugegriffen werden. 
 Bei public kann man auch von außen auf die Daten der Klasse zugreifen. 
 Bei protected können abgeleitete Klassen von außen zugreifen.  

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.
OnPaint reagiert z.B. auf WM_PAINT, etc.

Links stehen alle möglichen Meldungen zur Auswahl. Wir wollen nun den Linksklick mit der Maus hinzufügen. Dazu wählen wir die Meldung WM_LBUTTONDOWN aus und wählen rechts Handler hinzufügen.
Anschließend hat die Klasse "CMFCApplication1Dlg" eine weitere Member-Funktion: OnLButtonDown
Gehen Sie zurück zur Klassenansicht und wählen Sie diesen handler aus:

void CMFCApplication1Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
   
 
// TODO: Fügen Sie hier Ihren Meldungsbehandlungscode ein, und/oder benutzen Sie den Standard.
   
  CDialogEx::OnLButtonDown(nFlags, point);

}


Nun ist es vorbei mit dem assistenten-gestützten Geklicke. Wir müssen schreiben, was passieren soll, wenn jemand mit der Maus in den Client-Bereich unseres Dialogfeldes klickt.
Wir wollen einfach einen Text ausgeben. Dies erfolgt mit
TextOutW.

1.3 Textausgabe mit TextOutW()

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.

Sie sehen, VisualC++ läßt uns nicht im Stich, sondern stellt kontextbezogene Informationen zur Verfügung.

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.  

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 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. 

Bauen Sie sich zur Übung ein völlig neues Projekt auf.
Achten Sie auf Folgendes:
- Datei - Neu - Projekt - MFC-Anwendung
- im Wikkommen-Fenster auf Weiter drücken (nicht Fertig stellen!)
- bei Anwendungstyp: "Auf Dialogfeldern basierend"
- bei Benutzeroberflächenfunktionen: "Minimieren- und maximieren-Schaltfläche" und auf den Dialogfeldtitel "Zeichenfenster"
- nach dem Fertigstellen: bei der grafischen Ressourcenansicht: Ziehen Sie das Dialogfenster etwas breiter und löschen Sie den OK-, Abbrechen-Button und das Static-Feld (TODO: ...)

Nun müssen wir noch den Linksklick der Maus einbauen:

Wissen Sie noch, wie das geht? Jawohl, mit dem (Projekt - ) Klassen-Assistent (Strg+Shift+X).

Meldungen auswählen: WM_LBUTTONDOWN doppelt anklicken, der Handler OnLButtonDown erscheint

Wählen Sie OnLButtonDown an und drücken auf "Code bearbeiten"

Ä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

class CMFCApplication2Dlg : public CDialogEx
{
// Konstruktion
public:
    CMFCApplication2Dlg(CWnd* pParent = NULL);    // Standardkonstruktor

// Dialogfelddaten
    enum { IDD = IDD_MFCAPPLICATION2_DIALOG };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV-Unterstützung


// Implementierung
protected:
    HICON m_hIcon;

    // Generierte Funktionen für die Meldungstabellen
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
private:
    int m_Xold;
    int m_Yold;
};


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

 

Wenn Sie die öffnende Klammer hinter Invalidate eingeben, 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 "1", bedeutet bei bool "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.

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:

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. 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)
{
    switch (m_DrawFlag)
    {
    case true:
        m_DrawFlag = false;
        break;
    case false:
        m_DrawFlag = true;
        break;
    }

    CDialogEx::OnRButtonDown(nFlags, 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);
}
 
 
 

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 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:
1) Auswahl (Anklicken) des Buttons "Message-Beep"
2) Klassen-Assistent (Strg+Shift+X) starten, er öffnet Befehle mit der Auswahl IDC_BUTTON1
3) BN_CLICKED auswählen
4) Klick auf Button "Händler hinzufügen", er wählt "OnClickedButton1"
5) Mit OK bestätigen

Das führen Sie bitte mit allen vier Buttons durch. 

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.
}

 

Jetzt warten vier Member-Funktionen (OnClickedButton1 ... OnClickedButton4) der Klasse CMFCApplication3Dlg auf unsere Ideen. Bisher kennen Sie bereits die grafischen Funktionen TextOutW, 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 CMFCApplication3Dlg::OnClickedButton1()
{
    ::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 CMFCApplication3Dlg::OnClickedButton2()
{
    ::PlaySound(L"C:\\Windows\\media\\tada.wav", NULL, SND_FILENAME | SND_ASYNC);
    //Wichtig: #include <mmsystem.h> einbinden und winmm.lib linken
}

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
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 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.
 
 

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
 

Zurück zum Inhaltsverzeichnis