Visual C++
Visual C++
NET
Visual C++.NET
Das Buch
Ron Nanko
Z
Der Verlag hat alle Sorgfalt walten lassen, um vollständige und akkurate Informationen in diesem Buch bzw. Pro-
gramm und anderen evtl. beiliegenden Informationsträgern zu publizieren. SYBEX-Verlag GmbH, Düsseldorf, über-
nimmt weder die Garantie noch die juristische Verantwortung oder irgendeine Haftung für die Nutzung dieser
Informationen, für deren Wirtschaftlichkeit oder fehlerfreie Funktion für einen bestimmten Zweck. Ferner kann der
Verlag für Schäden, die auf eine Fehlfunktion von Programmen, Schaltplänen o.Ä. zurückzuführen sind, nicht haft-
bar gemacht werden, auch nicht für die Verletzung von Patent- und anderen Rechten Dritter, die daraus resultiert.
ISBN 3-8155-0525-9
1. Auflage 2002
Dieses Buch ist keine Original-Dokumentation zur Software der Firma Microsoft. Sollte Ihnen dieses Buch dennoch
anstelle der Original-Dokumentation zusammen mit Disketten verkauft worden sein, welche die entsprechende
Microsoft-Software enthalten, so handelt es sich wahrscheinlich um Raubkopien der Software. Benachrichtigen Sie
in diesem Fall umgehend Microsoft GmbH, Edisonstr. 1, 85716 Unterschleißheim – auch die Benutzung einer Raub-
kopie kann strafbar sein. Der Verlag und Microsoft GmbH.
Alle Rechte vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (Druck, Fotokopie, Mikrofilm oder in einem
anderen Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektroni-
scher Systeme verarbeitet, vervielfältigt oder verbreitet werden.
Printed in Germany
Copyright © 2002 by SYBEX-Verlag GmbH, Düsseldorf
Kurzübersicht
Kurzübersicht
Vorwort. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XI
1 Installation und Funktionstest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2 Ein erstes Projekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3 Grundlagen für die MFC-Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . 43
4 Entwicklung von dialogfeldbasierten Anwendungen . . . . . . . . . . . . 95
5 Entwickeln von SDI-Anwendungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
6 MDI-Applikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
7 VC++.net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
8 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
A Fensterstrukturen und zugehörige Funktionen . . . . . . . . . . . . . . . . . 363
B Werkzeugklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
C Alphabetische Übersicht der im Buch verwendeten MFC-Funktionen 425
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
V
Inhaltsverzeichnis
Inhaltsverzeichnis
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XI
VII
Inhaltsverzeichnis
VIII
Inhaltsverzeichnis
6 MDI-Applikationen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
MDI-Applikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
7 VC++.net . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
8 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
IX
Inhaltsverzeichnis
Windows-Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
CreateWindow(Ex) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
B Werkzeugklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
MFC-Werkzeugklassen . . . . . . . . . . . . . . . . . . . . . . . . . 388
MFC-Containerklassen. . . . . . . . . . . . . . . . . . . . . . . . . . 396
GDI-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
Standarddialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
X
Vorwort
Vorwort
Herzlich Willkommen in der Welt von VC++.net. Viel ist darüber gerätselt wor-
den, wie wohl die Veränderungen am Ende des Entwicklungsprozesses tatsäch-
lich aussehen würden, nachdem die ersten Betas Anfang 2001 die Runde
machten.
Bei der Konzeption des Buchs kam es uns in erster Linie auf eine sinnvolle Glie-
derung und eine behutsame Einführung in das Wirrwarr der zahllosen Klassen
und Methoden an, die dem Neueinsteiger bei seinen ersten Gehversuchen
begegnen.
Abschließend möchte ich Simone Schneider und Ute Dick vom SYBEX-Verlag
für die gute Zusammenarbeit danken und Ihnen viel Spaß beim Lesen dieses
Buches wünschen.
Ron Nanko
XII
Installation und Funktionstest
Es ist ein ungeschriebenes Gesetz, dass die Installation neuer Produkte immer
mit etwas Aufwand und Zeitbedarf verbunden ist – dieser Arbeitseinsatz
nimmt mit neuen Programmversionen für gewöhnlich immer um wenigstens
ein Viertel zu.
Der Aufwand lohnt sich allerdings. Mit der neuesten Version der Visual-Studio-
Reihe hat sich ein würdiger Thronfolger gefunden, der, ganz im neuen XP Look,
sämtliche für den Entwickler anfallenden Arbeiten in einem überschaubaren
und wohl geordneten Rahmen zur Verfügung stellt. Worum es sich hierbei im
Einzelnen handelt, würde allein ganze Bücher füllen, sodass innerhalb dieses
Werks nur auf die für den Visual C++ .NET-Entwickler relevanten Sachverhalte
eingegangen werden kann.
Die mehrere CDs – oder eventuell eine DVD – umfassende Visual Studio .NET-
Programmversion, die also nun auf Ihrem Schreibtisch liegt, sollte Sie nicht ver-
schrecken. Gerade diejenigen Entwickler, die bereits mit dem Vorgänger (Visual
Studio 6) gearbeitet haben, werden sich schnell einleben.
Rasche Eingewöh- Aber auch Neueinsteiger, die nach dem Studium der Sprache C++ nun in die
nung dank durch- visuelle Programmierung unter Windows eintauchen wollen, dürften sich
dachten Aufbaus rasch eingewöhnen – immerhin stecken die Erfahrungen vieler tausend Benut-
zer der älteren Versionen in dem neuen Produkt. Wie so oft gilt auch hier:
Übung macht den Meister – und am Ende des Buchs wird jeder in der Lage sein,
eigene Windows-Programme zu schreiben und sich, mit diesem Grundwissen
ausgestattet, die vielen weiteren Bereiche der Entwicklungswelt zu erschlie-
ßen.
Vor den Erfolg haben die Götter jedoch die Arbeit gesetzt: die folgenden Seiten
beschreiben daher den kompletten Installationsvorgang, der im Wesentlichen
zwar intuitiv, aber trotz allem allein wegen seines Umfangs gerade für den
Visual Studio-Neuling verwirrend sein mag.
2
Installation und Funktionstest
1
Mittlerweile haben sich die Wogen geglättet und Microsoft gibt eine Konfigu- Voraussetzungen für
ration ähnlich der Folgenden als sinnvolle Kombination an (sinnvoll heißt hier einen produktiven
natürlich: lauffähig, jedoch ist mit Leistungseinschränkungen in der Benut- Einsatz
zungsgeschwindigkeit zu rechnen):
PC mit Pentium II Prozessor, 450 MHz (Pentium III mit 600 MHz wird
empfohlen)
Benötigter Speicherausbau:
Festplattenplatz:
– 500 MByte auf dem Systemlaufwerk (das Laufwerk, auf dem auch
das Betriebssystem installiert ist, für gewöhnlich also C:)
3
Starten der Installation
1
Speicherbedarf des Das Gleiche gilt für den angegebenen Speicherausbau, 256 MByte sollte der
Studios ernsthafte Entwickler durchaus sein eigen nennen, nach oben sind hier keine
Grenzen gesetzt.
Abb. 1.1
Der Begrüßungsschirm
des Visual Studio .NET-
Setups
Stufenweise Das sich öffnende Assistentenfenster enthält die für eine erfolgreiche Installa-
Installation tion durchzuführenden Schritte. Genauer gesagt unterteilt sich die Einrichtung
des Visual Studio .NET in eine vorbereitende, eine Haupt- und eine nachberei-
tende Stufe, hier einfach von eins bis drei durchnummeriert.
4
Installation und Funktionstest
1
englischen Visual Studio .NET Version herausgegriffen, die unter einem deut-
schen Windows-Betriebssystem installiert werden soll:
Abb. 1.2
Fehlende Windows-
Komponenten und
Sprachkonflikte
Fehlende Komponenten
Zunächst macht Sie die Meldung darauf aufmerksam, dass die erforderlichen Verschiedene
Komponenten nicht in der passenden Sprachversion im Lieferumfang des Sprachversionen
Visual Studio .NET enthalten sind. Dieser Umstand tritt natürlich ebenso auf,
wenn Sie die deutsche Version auf einem englischen Windows-System instal-
lieren möchten und so weiter. In jedem Fall benötigen Sie dann die für das
Betriebssystem passenden Komponenten, in der jeweiligen Landessprache, zu
beziehen über die Microsoft Homepage unter www.microsoft.com – wohin Sie
der Download Components-Button auch prompt führt.
Im Normalfall jedoch dürften Sie die deutsche Visual Studio Version unter
einem deutschen Windows-Betriebssystem installieren. In diesem Fall sind die
Komponenten auf einer der Visual Studio .NET-CDs bereits enthalten.
In diesem konkreten Fall fehlt ein installiertes Windows 2000 Service Pack 2 –
das System unter dem hier installiert wird, ist Windows 2000 – sowie die neu-
este Version des Internet Explorers, der, kaum verwunderlich, eng mit dem Stu-
dio verzahnt ist.
Folgen Sie den Anweisungen auf dem Bildschirm, was in diesem Fall zunächst Installation des
nichts anderes heißt, als das aktuellste Windows 2000 Service Pack nachzuin- neuesten Service Packs
stallieren. Im Rahmen dieses Updates werden zahlreiche Sicherheitslöcher
Ihres Systems gestopft und einige Komponenten aktualisiert, sowohl was Bugs
als auch die Leistungsfähigkeit von Treibern und so weiter anbetrifft.
5
Starten der Installation
1
Ist das Service Pack erfolgreich installiert, können Sie erneut den ersten Schritt
des Installationsprozesses anwählen und erhalten in diesem konkreten Fall
dann folgende Mitteilung:
Abb. 1.3
Das Service Pack ist
installiert, doch der
Explorer ist noch nicht
up-to-date
Wieder erhalten Sie die Meldung, dass die Komponenten nicht in der zum
Betriebssystem passenden Sprache vorhanden sind. (Zur Erinnerung: Dieses
Problem tritt nur bei der Vermischung von verschiedenen Sprachversionen
bezüglich Betriebssystem und Visual Studio .NET auf.)
6
Installation und Funktionstest
1
Sie können nun selbst entscheiden, ob Sie die den Visual Studio .NET CDs beilie-
genden Komponentenversionen nutzen möchten, oder doch lieber selbst eine
Aktualisierung durchführen, beispielsweise durch das Herunterladen der Kom-
ponente in der Ihnen genehmen Sprache.
Letzteres geschieht durch Anwählen des Punkts Download Components, der Sie Herunterladen neuer
direkt auf eine passende Downloadseite – selbstverständlich im Rahmen der Komponenten
Microsoft Homepage – führt.
Wollen Sie hingegen die fremdsprachige Version verwenden, reicht ein Klick
auf die Continue-Schaltfläche aus.
Lizenzvereinbarungen
Im Laufe der Visual Studio .NET-Installation werden Sie mehr als einmal auf die
wohl bekannten Lizenzvereinbarungen zwischen Ihnen und Microsoft stoßen,
die allesamt akzeptiert werden müssen, um den Einrichtungsvorgang fortset-
zen zu können.
In diesem Fall geht es um das Zustimmen zu den Vereinbarungspunkten Annehmen der Lizenz-
bezüglich der zu installierenden Windows-Komponenten. Lesen Sie sich den vereinbarung
Text durch und bestätigen Sie, dass Sie die Vorschriften akzeptieren durch
Anwahl des Punkts I accept the agreement durch anschließenden Klick auf die
Schaltfläche Continue.
Eine Anwahl von I do not accept the agreement beendet die Installation vorzeitig.
Abb. 1.4
Die Lizenz-
vereinbarung
7
Starten der Installation
1
Abb. 1.5
Die zu installierenden
Komponenten
Microsoft Data Access Components 2.7 (kurz MDAC): Die MDAC bilden
den Kern von Microsofts UDA (Universal Data Access) Strategie, die es
erlaubt, beliebige Informationen aus beliebigen Datenquellen in eige-
nen Applikationen zugriffsbereit und verarbeitbar zu machen. Die
MDAC stellen sozusagen die Treibersoftware zwischen der Applikation
und der externen Datenquelle dar.
8
Installation und Funktionstest
1
Abb. 1.6
Möglichkeit zum auto-
matischen Login
Automatischer Login
Beim Betrachten der Liste der zu installierenden Komponenten ist Ihnen viel-
leicht aufgefallen, dass neben dem Internet Explorer ein Hinweissymbol zu
sehen war, dessen Erläuterung von einem erneuten Bootvorgang spricht. Je
nachdem, welche Komponenten auf Ihrem System noch installiert werden
müssen, könnten es durchaus eine ganze Reihe von Schritten sein, die einen
neuen Bootvorgang bedingen.
Wie schon angesprochen, braucht die Installation von Visual Studio.NET seine
Zeit, umso angenehmer fällt da das Fenster auf, das sich zeigt, sobald Sie die
Informationsseite mit Continue verlassen haben.
In dem erscheinenden Dialog finden Sie eine Maske wieder, die Sie mit Ihren Zeit sparen bei der
Login-Informationen füllen können. Das Setup trägt dann dafür Sorge, dass Installation
erforderliche Bootvorgänge voll automatisch ablaufen und Sie in der Zwischen-
zeit Kaffee trinken gehen können, während der Rechner seine Arbeit erledigt –
vergessen Sie aber nicht, auch den Erlaubnisschalter Automatically Log On zu
aktivieren, wenn Sie von dieser Möglichkeit Gebrauch machen wollen.
9
Starten der Installation
1
Im Normalfall gibt es keinen Grund, der gegen ein Ausnutzen dieser Verein-
fachung spricht, bedenken Sie jedoch, dass Sie auch dann noch einmal neu
eingeloggt werden, wenn der Installationsprozess abgeschlossen wurde –
nach dem finalen Reboot, sozusagen.
Haben Sie tatsächlich die Zeit während der Installation zu einer Kaffeepause
oder Ähnlichem genutzt und steht Ihr Rechner in einem nicht verschlosse-
nen Büro, ist er nun ungeschützt und jedermann zugänglich! Unter solchen
Voraussetzungen bietet es sich an, den automatischen Login zu untersagen
und dann lieber die etwas längere Installationszeit – bedingt durch erforder-
liche manuelle Eingaben – in Kauf zu nehmen.
Der Installationsverlauf
Der Installationsprozess wird nun durch eine Reihe von visuellen Markierungen
(Fortschrittsbalken, farbige Pfeile und Häkchen) dargestellt und dokumentiert:
Abb. 1.7
Der laufende Installa-
tionsvorgang für die
Komponenten
10
Installation und Funktionstest
1
Dieses sollte normalerweise nicht der Fall sein und deutet normalerweise auf
einen schwerwiegenden Fehler hin. Allgemein gilt, dass der Setup-Prozess des
Visual Studio .NET im Laufe der von Microsoft verteilten Test- und Zwischenver-
sionen immer ausgefeilter wurde und Probleme meist immer mit vorhandenen
früheren Testversionen zusammenhingen.
Stellen Sie bei Schwierigkeiten mit der Installation sicher, dass keine Altlasten
aus dem Betastadium des Visual Studio .NET mehr vorhanden sind oder entfer-
nen Sie diese gegebenenfalls. Prüfen Sie weiterhin, ob auf dem Systemlaufwerk
– das auch das Betriebssystem enthält – hinreichend Speicherplatz vorhanden
ist.
Abb. 1.8
Die erfolgte und
erfolgreiche Installa-
tion wird gemeldet
Jetzt kommt es darauf an, ein Werkzeug einzurichten, mit dem auf dieses Fra- Fortsetzen der
mework in einer effektiven und effizienten Weise zugegriffen werden kann. Installation
11
Die zweite Runde – Installation des eigentlichen Studios
1
Abb. 1.9
Der Installations-
assistent wartet auf
die Ausführung des
zweiten Schritts
Es präsentiert sich ein weiteres Fenster, das in drei Reiter aufgeteilt ist, die aller-
dings nicht frei auswählbar sind, sondern der Reihe nach komplett abgearbei-
tet werden müssen. „Abarbeiten“ heißt hierbei nichts anderes, als Ihre
Benutzerinformationen einzutragen und weiterführende Angaben zu machen,
die für die erfolgreiche Installation notwendig sind.
Weitere Lizenzvereinbarungen
Sie treffen nun auf die zweite Lizenzvereinbarung, die sich dieses Mal auf das
Visual Studio .NET selbst bezieht. Auch hier besteht die Wahl wiederum darin,
die Vereinbarungen zu akzeptieren oder abzulehnen. Während ein Ablehnen
zum Beenden des Setup-Vorgangs führt, erlaubt Ihnen das Annehmen das
Fortfahren des Vorgangs.
12
Installation und Funktionstest
1
Abb. 1.10
Die erste Seite der
Hauptinstallation
Neben dem Lizenzvertrag sind noch Ihr Name sowie die Produkt ID Ihrer Visual Angeben der
Studio .NET Version in die dafür vorgesehenen Felder einzutragen. Produkt ID
Die Optionsseite
Nach einem Anklicken von Continue gelangen Sie auf die zweite Seite des
Installationsdialogs:
13
Die zweite Runde – Installation des eigentlichen Studios
1
Abb. 1.11
Die Optionsseite
Auswählen von Lauf- Eigentlich ist der Name Optionsseite etwas hochtrabend für die möglichen Ein-
werken und Verzeich- stellungen, in Wirklichkeit können hier nur der Installationsumfang und die zu
nissen zur Installation verwendenden Installationspfade eingetragen werden.
Visual Studio .NET braucht ca. 300 MByte an Plattenplatz auf Ihrem System-
laufwerk (in der Regel C:) sowie bei voller Installation weitere 3 GByte, die mehr
oder weniger frei verteilt werden können – einzelne Programmpakete sind
dabei immer komplett auf einem Laufwerk zu installieren.
Festlegen der zu Auf diese Weise können Sie jeden Punkt des Baums bequem erreichen und ein-
installierenden zeln an- oder abwählen. Nur die angewählten Komponenten werden im Fol-
Komponenten genden installiert, die übrigen sind aber auch nachträglich über den
Assistenten noch hinzufügbar.
Sobald Sie ein Element des Baums auswählen, werden rechts oben Informa-
tionen über den benötigten Platzbedarf und das derzeitig eingestellte Installa-
tionsverzeichnis ausgegeben, das Sie beliebig anpassen dürfen. Dabei gelten
Einstellungen von höher eingeordneten Komponenten auch automatisch für
die untergeordneten – stellen Sie also beispielsweise bei Language Tools als
14
Installation und Funktionstest
1
Visual C++.NET
Visual C++.NET: C++ galt immer als das Schlachtross unter den Programmier- Visual C++ – alter
sprachen, da es durch seine vielfältigen Einsatzmöglichkeiten zwar zu hochop- Bekannter im neuen
timierter Programmierung einlud und performante Applikationen erlaubte, Gewand
aber bei ungünstiger Wahl der Programmkonstrukte leicht Flaschenhälse an
kritischen Programmstellen einstreuen konnte. Mit der jetzt vorliegenden Ver-
sion hat sich an dieser Situation zunächst einmal nur so viel geändert, dass
Microsoft die neue Generation als noch leistungsfähiger und noch flexibler
darstellt. Zu den neuen Features gehören Managed Extensions, Attribut-Pro-
grammierung und Webapplikationsprogrammierung, das Ganze verpackt in
einem neuen Kleid, der neuen IDE des Visual Studio .NET.
– Visual C++-Klassen & Template Bibliotheken: Wie es der Name Ergänzungen für
schon andeutet, verbergen sich hinter diesem Punkt eine Reihe Visual C++
wichtiger Bibliotheken, nämlich genau denen, die für die Entwick-
lung von Webservices, MFC-Applikationen und der Entwicklung
von COM-Komponenten erforderlich sind.
15
Eine kurze Beschreibung der installierbaren Komponen-
1
ActiveX-Steuer- ActiveX Control Test Container: Bei der Entwicklung von ActiveX-Kontrol-
elemente len kommt es insbesondere auf das Testen und Debuggen derselben an.
Hierbei unterstützt Sie der ActiveX Control Test Container, der hilfreiche
Informationen während Fehlersuche in den Kontrollen bereitstellt.
Error Lookup Tool: Ein unerlässliches Tool. Compiler, Linker und andere
Visual Studio Komponenten kommunizieren miteinander und mit dem
Benutzer durch Angabe von Zahlencodes, sobald Fehler auftreten. Diese
sind in einer Datenbank abgelegt und mit einem Prosa-Text versehen,
der eine genauere Beschreibung des Fehlers enthält, insbesondere also
einen, der vom Menschen gelesen und klar verstanden werden kann.
Das Error Lookup Tool dient nun dazu, diese Informationen automatisch
auszulesen und zu präsentieren, sobald eine Fehlermeldung auftritt.
Fensterüberwachung Spy++: Dient zum Überwachen von Fenstern. Sie können mit diesem
Tool ein (oder mehrere) Fenster selektieren, die dann auf eingehende
Nachrichten überprüft werden. Die folgenden Nachrichtentypen kön-
nen überwacht werden: DDE, Zwischenablage, Maus, Tastatur, Buttons,
Non-Client Bereich, Kombo-Kontrollen, Edit-Kontrollen, Listbox-Kontrol-
len, Statische Kontrollen, applikationsspezifische Mitteilungen, andere
Mitteilung, die nicht in eine der eben genannten Kategorien passen.
Visual C#.NET
C# – Die neue Visual C#.NET: Ein junger Spross der Sprachenfamilie ist C# (sprich: c sharp).
Programmiersprache Microsoft selbst bezeichnet diese Sprache als „logische Konsequenz aus der
16
Installation und Funktionstest
1
Entwicklung von C, C++ und Java“. Kurz gesagt, erfüllt C# alle Forderungen, die
man an eine moderne Programmiersprache stellen würde und ist komplett auf
die Entwicklung von Anwendungen auf das .NET Framework ausgerichtet.
Weitere Komponenten
.NET Framework SDK: Die Dokumentation für Entwickler, die mit dem
.NET Framework arbeiten.
SQL Server Desktop Engine: Hier hinter verbirgt sich die Microsoft Server
Desktop Engine (kurz MSDE), die einen SQL-Server darstellt. Erforderlich,
falls Sie keinen anderen SQL-Server zur Verfügung haben. MSDE unter-
stützt weiterhin XML.
17
Abschließen der Installation
1
nenten an) und die gewünschten Verzeichnisse ausgewählt haben, können Sie
den eigentlichen Installationsvorgang durch einen Klick auf die Schaltfläche
Install Now starten.
Beobachten des Es präsentiert sich ein weiteres Fenster, das den Installationsverlauf visuali-
Installationsvorgangs siert. Nach dem Erzeugen eines Setup-Skripts werden dann zunächst eine
ganze Reihe von Registrierungsschlüsseln angelegt und Dateien in die spezifi-
zierten Verzeichnisse kopiert. Dieser Vorgang kann je nach Plattengeschwin-
digkeit und ausgewählten Komponenten durchaus eine halbe Stunde und
länger dauern.
Reichlich Zeit also, um einen Blick auf die im Fenster angebotenen weiterfüh-
renden Informationen zu werfen. Sie sollten diesen Service durchaus nutzen,
da es hier allerlei Interessantes zu erfahren gibt, beispielsweise wie man an
eine MSDN Subscription (also ein Abonnement der aktuellsten Referenzen,
Dokumentationen und Artikel aus der Entwicklergemeinde) beziehen kann
oder was es an Neuerungen in der integrierten Entwicklungsumgebung (kurz
IDE des Visual Studio .NET) gibt.
Abb. 1.12
Der Bildschirm
während der
Hauptinstallation
18
Installation und Funktionstest
1
Abb. 1.13
Das Ergebnis einer
fertigen Visual Studio
.NET-Installation
In diesem Fall teilt das Setup-Programm mit, dass Fehler – oder besser: War-
nungen – während der Installation aufgetreten sind. Das sollte im Normalfall
nicht vorkommen und dient an dieser Stelle nur zur Demonstration. Sollten bei
Ihnen Fehler aufgetreten sein, können Sie dann an dieser Stelle einen Blick in
den Installations-Log werfen und sehen, was schief gegangen ist.
Können Sie den Fehler dann aus irgendeinem Grund nicht selbstständig behe- Fehler bei der
ben – zum Beispiel, weil eine Datei auf CD defekt ist – wenden Sie sich an den Installation
Microsoft Support, der sich in diesem Fall eingehend mit Ihrem Problem
beschäftigen kann und wird.
19
Phase 3 – Alles auf den neuesten Stand bringen
1
Abb. 1.14
So sieht der Startbild-
schirm im dritten
Anlauf aus
Verwirrenderweise sind nun alle drei Punkte anwählbar, doch sollte Sie dieser
Umstand nicht irritieren: Sobald der dritte Punkt aktivierbar ist, haben Sie wäh-
rend der Installation alles richtig gemacht und sind nun in der Lage, die Win-
dows-Komponenten erneut zu installieren, falls Sie durch einen Datenverlust
(zum Beispiel nach einem Virenbefall) korrumpiert wurden).
Aktualisierung Interessanter ist an dieser Stelle jetzt der Punkt 3, der es erlaubt, die gerade
einer Installation abgeschlossene Installation auf den neuesten Stand zu bringen. Dieses dient
dem Umstand, dass es im Laufe der Zeit Fehlerkorrekturen geben kann – und es
ist davon auszugehen, dass Visual Studio .NET (das ja auch als Visual Studio 7
bezeichnet wird) durchaus einige Jahre als Ihre Hauptentwicklungsplattform
dienen könnte, wie es eventuell schon die Vorgängerversion getan hat.
Innerhalb dieser Zeit werden von Microsoft immer wieder Verbesserungen und
Bugfixes nachgeliefert werden, die über diesen Punkt 3 zugriffsbereit gemacht
werden. Wählen Sie daher jetzt diesen letzten Schritt durch einfaches Ankli-
cken aus.
20
Installation und Funktionstest
1
Abb. 1.15
Die Möglichkeiten
zum Aktualisieren
der Installation
Haben Sie eine Update-CD vorliegen, können Sie hier den Punkt Installieren von
einer Service CD wählen, ansonsten prüfen Sie, ob im Internet eine neue Version
vorliegt, indem Sie den Punkt Prüfen auf neue Versionen im Internet auswählen.
Sollte einer der beiden Punkte zutreffen, führen Sie die Anweisungen aus, die
auf dem Bildschirm ausgegeben werden, wodurch Ihre Version, falls nötig, auf
den aktuellen Stand gebracht werden kann.
Herzlichen Glückwünsch, Sie haben die Installationshürden erfolgreich gemeis- Die erste Hürde
tert! Im Folgenden soll es um einen kleinen Funktionstest des gerade installier- ist genommen
ten Produkts gehen.
21
Test der Funktionalität
1
Abb. 1.16
Die unangetastete
Startseite
Einrichten eines Profils Hier bietet sich die Möglichkeiten, Ihr Profil als Entwickler einzurichten. Unter
Profil versteht das Studio ihr Haupteinsatzgebiet, also ob Sie beispielsweise
C++-Programmierer, Visual Basic-Entwickler oder vielleicht ein Allround-Talent
sind, das mit allen Sprachen gleichstark arbeiten möchte.
Sie können entweder aus einer Reihe von vorgefertigten Profilen wählen, oder
Ihre eigenen Einstellungen nach Lust und Laune festsetzen – dabei enthält
jeder Punkt eine Reihe von Einstellmöglichkeiten.
Tastatur Layout: Hier tragen Sie ein, welche Shortcuts auf der Tastatur
wie belegt sein sollen.
Fenster Layout: Gibt an, wie die Fenster und Toolbars innerhalb der IDE
angeordnet werden sollen.
Hilfe anzeigen: Hier können Sie wählen, ob Hilfstexte intern (also inner-
halb der IDE) oder extern (in einem eigenen Fenster) angezeigt werden
sollen. Dieses ist sicherlich Geschmackssache, wer einen aufgeräumten
Desktop mag, wählt die interne Variante.
22
Installation und Funktionstest
1
Beim Start: Gibt an, was Sie beim Starten des Studios auf dem Bild-
schirm sehen möchten. Voreingestellt ist Startseite, dies ist die Seite, die
Sie gerade betrachten.
Abb. 1.17
Die Startseite wurde
geeignet initialisiert
Wie Sie sehen, gibt es auf der Startseite noch eine ganze Reihe weiterer Punkte,
die hier nicht näher erläutert werden sollen und im Wesentlichen zusätzliche
Hilfestellungen zur Verwendung des Studios, Download-Links, News zum
VS.NET und so weiter anbieten.
Es sei dem Leser überlassen, an dieser Stelle frei in den einzelnen Unterpunkten
herumzustöbern.
Im Laufe des nächsten Kapitels werden Sie eine erste Anwendung mit dem Ausblick
Visual Studio erzeugen, die auf den so genannten Microsoft Foundation Classes
(MFC) basiert und bereits die vollständige Funktionalität einer Windows-
Anwendung aufweist.
23
Ein erstes Projekt
Portierung von Gerade Umsteiger vom Visual Studio 6.0 werden sich dadurch schnell zurecht-
alter Software finden und insbesondere ihre alten Projekte im neuen Studio weiterpflegen
können – es soll nicht verschwiegen werden, dass es an einigen Stellen sicher-
lich trotz allem Portierungsschwierigkeiten geben wird (das bleibt bei neuen
Versionen nie aus), doch muss man diese im Einzelfall betrachten, was im Rah-
men dieses Buches natürlich leider nicht möglich ist.
Die Entscheidung, trotz der neuen Möglichkeiten zunächst mit einer MFC-
Anwendung zu beginnen, liegt ganz einfach darin begründet, dass die MFC
immer noch zu den meist verbreiteten Klassenbibliotheken für Windows gehö-
ren und auch von Microsoft weiterhin als gültig und nützlich anerkannt wer-
den.
C# vs. C++ Während C# beispielsweise Editoren zum Anlegen von Dialogen auf Basis des
.NET Frameworks bietet, fehlen diese für VC++ – da es kaum anzunehmen ist,
dass Microsoft diesen nicht unerheblichen Umstand einfach übersehen hat,
muss vermutet werden, dass C# im Bereich der Windows-Entwicklung auf län-
gere Sicht gesehen eine echte Alternative zu VC++ werden könnte.
Letzteres verliert seinen Sinn jedoch nie wirklich, da mit ihm die Entwicklung
von so genannten unmanaged, nicht verwalteten Anwendungen möglich ist,
also Applikationen, die beispielsweise ihren Speicher frei selbst verwalten kön-
nen.
26
Ein erstes Projekt
2
Projekt anlegen
Um ein neues Projekt zu erzeugen, wählen Sie aus dem Menü Datei den Punkt
Neu > Neues Projekt, woraufhin sich ein Dialogfeld ähnlich dem folgenden öff-
net:
Abb. 2.1
Das Dialogfeld zum
Erzeugen eines neuen
Projekts
Einstellmöglichkeiten
Hier finden Sie eine ganze Reihe von Einstellungs- und Auswahlmöglichkeiten:
Projekttyp: Der zu erzeugende Projekttyp. Dieser hängt insbesondere Ein Visual C++-Projekt
davon ab, welche Sprache verwendet werden soll. Verwenden Sie hier
zunächst einfach den Punkt Visual C++ Projekte.
Name: Der Name des zu erzeugenden Dokuments. Tragen Sie hier zum
Beispiel start ein.
27
Der gute alte MFC-Application Wizard
2
Pfad: Der Pfad, in dem das Projekt eingefügt werden soll. Es wird im spe-
zifizierten Pfad automatisch ein Unterverzeichnis mit dem Namen des
Projekts angelegt. Geben Sie als Pfad also beispielsweise f:\vstudiopro-
jects\ ein und als Projektname start, werden Sie das erzeugte Projekt im
Verzeichnis f:\vstudioprojects\start wiederfinden.
Die weiteren Optionen sind an dieser Stelle nicht relevant. Tätigen Sie nun die
beschriebenen Einstellungen und bestätigen Sie sie mit OK.
Kein Datenbanksupport
Sind diese Einstellungen für Sie zufrieden stellend, können Sie jederzeit durch
Anklicken von Fertig stellen das Erzeugen des Projekts in Auftrag geben. Ande-
renfalls sind in der linken Leiste eine Reihe von Punkten vorgegeben, die ange-
klickt werden können und weitere Optionen bieten.
Es sei darauf hingewiesen, dass Sie auf dieser ersten Seite keine Möglichkeit
haben, die Einstellungen zu verändern, es handelt sich tatsächlich nur um eine
reine Informationsseite.
Abb. 2.2
Die Zusammenfassung
über die Einstellungen
28
Ein erstes Projekt
2
Als Erstes ist der eigentliche Anwendungstyp erwähnenswert. Hier wird, unter- SDI, MDI und Dialoge
schieden zwischen Single Document (SDI) und Multiple Document (MDI), dazu
kommt die Möglichkeit, eine dialogfeldbasierte Anwendung zu erzeugen, die
darüber hinaus als HTML-Dialog ausgerichtet sein kann.
Abb. 2.3
SDI-Anwendung
(hier: Editor)
29
Der gute alte MFC-Application Wizard
2
Abb. 2.4
Dialogfeldbasierte
Anwendung (hier:
Rechner)
Abb. 2.5
MDI-Anwendung
(hier: Opera)
30
Ein erstes Projekt
2
Englische Bezeichnungen
Die Sprache der Windows-Entwickler ist englisch, daran gibt es nichts zu rüt-
teln. Selbst in deutschen Büchern wird mehr und mehr mit den englischen
Originalbegriffen gearbeitet, zum einen der Einfachheit halber (es müssen
nicht künstlich wirkende Eindeutschungen vorgenommen werden), zum
anderen wegen der Konsistenz mit englischsprachigen Dokumentationen.
Auch in dem vorliegenden Werk soll mit dieser Konsistenz nicht gebrochen
werden, sodass Sie in vielen Fällen direkt mit den englischen Begriffen kon-
frontiert werden. Dabei wird immer ein deutsches Synonym, oder, in Erman-
gelung eines solchen, eine passende Erklärung abgegeben, wie oben im Fall
von Toplevel.
Weitere Einstellungsmöglichkeiten
Weiterhin auf dieser Seite befinden sich Möglichkeiten zur Angabe, ob eine an Baumstrukturen
den Explorer angelehnte Anwendung (mit einer Baumstruktur im linken Teil innerhalb von Fenstern
des Fensters) oder eine herkömmliche MFC-Applikation erzeugt werden soll
und ob die MFC in einer gemeinsamen (engl. shared) oder einer statischen
(engl. static) Bibliothek zu verwenden sind.
Der Vorteil von gemeinsam verwendeten Bibliotheken liegt auf der Hand:
Die Bibliothek ist nur einmal auf dem System vorhanden, belegt also wenig
Speicherplatz auf der Festplatte, und kann von beliebig vielen MFC-Anwen-
dungen benutzt werden.
Das ist aber auch genau der Nachteil dieses Verfahrens: die Bibliothek muss
auf dem System vorhanden sein. Erfahrungen zeigen, dass dieses bei weitem
nicht bei allen Windows-Besitzern der Fall ist, sodass bei einer Distribution
die Bibliotheken im Zweifelsfall mitgeliefert werden sollten.
31
Der gute alte MFC-Application Wizard
2
Die statische Bibliothek wird hingegen direkt mit der Applikation zu einem
EXE-Paket verschnürt. Hierdurch steigt die Größe der Anwendung nicht
unbeträchtlich, mehrere MByte können im Extremfall hinzukommen.
Als Faustregel gilt: Wenn Sie auf Nummer sicher gehen wollen, verteilen Sie
statisch gelinkte (also mit den Bibliotheken verbundene) Anwendungen.
Werden die Bibliotheken auf dem Zielsystem ohnehin verfügbar sein, sind
gemeinsam verwendete Bibliotheken die richtige Einstellungsmöglichkeit.
Abb. 2.6
Der Applikationstyp
32
Ein erstes Projekt
2
Je nach Umfang der benötigten Unterstützung können Sie hier den passenden Supporteinstellungen
Support einstellen, die Auswahl reicht dabei von Keine, über Container und Mini für Verbund-
Server hin zu Full Server und Container/Full Server. dokumente
Stellen Sie auch auf dieser Seite wieder alle für Sie interessanten und für das
Projekt notwendigen Punkte ein, für das einfache Projekt im Rahmen dieses
Buch, können Sie die Einstellungen so belassen, wie sie sind: Die Unterstützung
für Verbunddokument wird deaktiviert.
Abb. 2.7
Einstellungen für
Verbunddokumente
Zeichenkettenvorlagen
Ein Dialogfeld, das unter dem Visual Studio 6 gut versteckt war, findet sich hier Stringkonstanten
direkt als Punkt, wenn auch untergeordnet, innerhalb der Auswahlleiste: die
ehemaligen Erweiterten Optionen heißen nun Dokumentvorlagen-Strings, bie-
ten aber dieselben Einstellungsmöglichkeiten wie das alte Pendant:
Filetyp ID: ID für Dateien, die von diesem Programm angelegt werden.
33
Der gute alte MFC-Application Wizard
2
File New Short Name: Name (Kurzform), der angezeigt wird, wenn ein
neues Dokument dieses Typs erzeugt werden soll (beispielsweise über
einen Shortcut im Explorer oder Ähnliches).
Filter Name: Name für einen Filter, der Zusammen mit diesem Doku-
ment angewendet wird.
Main Frame Caption: Der Titeltext für das Hauptfenster der Applikation.
File Type Long Name: Name (Lange Form), der angezeigt wird, wenn ein
neues Dokument dieses Typs erzeugt werden soll (beispielsweise über
einen Shortcut im Explorer oder Ähnliches).
Lokalisierte Strings
Im Gegensatz zu den ersten beiden Strings sind die zuletzt beschriebenen
Zeichenketten solche, die zu lokalisieren sind.
Ein gutes Beispiel für zu lokalisierende Strings ist die Beschriftung der Titel-
zeile eines Dialogs einer Anwendung, der zum Einstellen einiger Optionen
dient. Sie als deutscher Programmierer tragen dort vielleicht Bearbeitungs-
optionen ein, während ein englischer Kollege eher Edit Options schreiben
würde.
34
Ein erstes Projekt
2
Belassen Sie auch in diesem Fall die Einstellungen wieder so, wie sie sind, es sei
denn, Sie möchten gleich konkret ein neues Projekt erzeugen, mit dem Sie wei-
ter arbeiten möchten – dieses empfiehlt sich allerdings nur für Benutzer, die
bereits mit den MFC gearbeitet haben.
Abb. 2.8
Globale Zeichenket-
ten für die Applikation
Datenbankunterstützung
Das nun folgende Fenster (zu Erreichen durch Anwahl des Schriftzugs Daten-
bankunterstützung im linken Navigationsbalken) ermöglicht Ihnen Einstellun-
gen zur Verwendung von Datenbanken innerhalb des zu erzeugenden Projekts.
Auch hier gilt wieder, dass einige Dialoge des alten Studios zusammengefasst Einstellungen für die
und erweitert wurden. Die Punkte im Einzelnen: Datenbankunter-
stützung
Datenbank Support: Gibt an, welche Art von Datenbankunterstützung
Sie wählen können. Die Einstellmöglichkeiten reichen von keine über
Nur Header Files bis hin zu Datenbankansicht ohne Dateiunterstützung
und Datenbankansicht mit Dateiunterstützung. Der Unterschied bei den
35
Der gute alte MFC-Application Wizard
2
Typ: Hier stehen Dynaset und Snapshot zur Verfügung. Der Unterschied
besteht darin, dass beim Dynaset Benachrichtigungen an einen Benut-
zer gesandt werden, wenn ein anderer gleichzeitig mit denselben Daten
arbeitet. Der Snapshottyp bietet diese Möglichkeit nicht, ist dafür deut-
lich einfacher einzusetzen.
Abb. 2.9
Datenbank-
unterstützung
36
Ein erstes Projekt
2
Aufgrund der vielzähligen Optionen, die dieses Fenster bietet, soll zur
Abwechslung der Screenshot am Anfang der Erläuterungen stehen:
Abb. 2.10
Einstellungen für die
Benutzeroberfläche
Initial Status Bar: Fenster hat beim Start eine Statuszeile. Zusätzliche Attribute
Dialog Title: Einstellung für Dialoge, enthält den Namen des Dialogs, der
in der Titelzeile dargestellt wird.
37
Der gute alte MFC-Application Wizard
2
Werkzeugleisten
None: Anwendung hat keine Werkzeugleisten
Erweiterte Einstellungen
Der vorletzte Dialog in der Reihe der Konfigurationsdialoge für die zu erzeu-
gende Anwendung beinhaltet erweiterte Einstellungen für das Verhalten und
die Features der Applikation:
Abb. 2.11
Erweiterte Optionen
38
Ein erstes Projekt
2
Kontextsensitive Hilfe: Gibt an, ob es eine Hilfsoption innerhalb der Einbinden von
Anwendung geben soll, die nähere Informationen zur Bedienung auf Hilfedokumenten
Wunsch des Benutzers liefert. Es stehen hierfür das bewährte WinHelp
und das neuer HTMLHelp-Format zur Verfügung.
Messaging API (MAPI): Diese wird benötigt, wenn die Anwendung Mail- Mailversand
nachrichten verwalten können soll. Dieses ist bei Anwendungen sinn-
voll, die häufig ihre Dokumente per E-Mail versenden müssen.
Windows Sockets: Gestattet den Zugriff per TCP/IP auf externe und
interne Netzwerke
Anzahl der Dateien in Liste der zuletzt verwendeten Dateien: Gibt die Verwendete
Anzahl der Dateien an, die im Startmenü am unteren Ende angezeigt Dokumente
werden sollen – dieses sind gerade die Dateien, die zuletzt bearbeitet
wurden.
Lassen Sie auch in diesem Fall die Einstellungen so, wie sie sind, es sei denn, Sie
haben einen Grund, sie zu ändern (zum Beispiel, um eine Option direkt auszu-
testen, die im Rahmen dieses Beispiels nicht vorkommen wird).
Ansonsten ist es jetzt an der Zeit, den letzten Punkt unter Augenschein zu neh-
men.
39
Das Projekt erzeugen
2
Abb. 2.12
Zu erzeugende Klassen
Was es mit den Ansichtsklassen auf sich hat, werden Sie im Rahmen des nächs-
ten Kapitels erfahren, wenn es darum geht, tiefer in die MFC-Entwicklung ein-
zusteigen. Für jetzt sei nur gesagt, dass hiermit die Art, in der ein Dokument
dargestellt wird, festgelegt werden kann.
Ende der Rundreise Wenn Sie an dieser Stelle angelangt sind, haben Sie sämtliche Dialogfenster
gesehen, in denen Einstellungen für das vom Assistenten zu erzeugende Pro-
jekt festgelegt werden können.
Sollten Sie schon mit dem Visual Studio 6.0 gearbeitet haben, werden Sie die
meisten Optionen wiedererkannt haben, auch wenn sich die eine oder andere
an einer vermeintlich seltsamen Position befunden haben mag.
Einige Erweiterungen sind ebenfalls dazu gekommen, die den ohnehin schon
guten MFC-Anwendungsassistenten der Vorgängerversion abzurunden verste-
hen.
Der Solution Explorer Was früher als Arbeitsbereich bekannt war, heißt jetzt Solution Explorer – stören
Sie sich für den Anfang nicht an diesem Namen, es wird in Kürze darauf einge-
gangen werden.
Der Solution Explorer selbst bietet Ihnen nun das folgende Bild, das bis auf den
ungewohnten Namen recht vertraut aussieht:
40
Ein erstes Projekt
2
Abb. 2.13
Der Solution Explorer,
der früher einmal
Arbeitsbereich hieß
Kompilieren ...
Nun soll nicht mehr lange gezögert werden, schließlich wollen Sie auch sehen,
dass die Anwendung ihren Dienst versieht und Sie sich nicht umsonst durch die
Einstellungsseiten gemüht haben.
Starten Sie den Übersetzungsvorgang durch Anwahl des Menüpunkts Project > Starten des
Erstellen – Visual Studio 6.0 Benutzer beachten an dieser Stelle insbesondere, Programms
dass das Kompilieren deutlich schneller vonstatten geht als mit der vergleich-
bare älteren Compilerversion. Natürlich erhalten Sie auch eine Ausgabe, die im
Folgenden dargestellt ist:
Abb. 2.14
Die Ausgaben wäh-
rend des Kompilier-
vorgangs
41
Das Projekt erzeugen
2
Weitere Alternativ hätten Sie die Anwendung auch außerhalb des Debuggers über den
Startmöglichkeiten passenden Menüpunkt starten können (oder auch direkt über die beim Über-
setzungsvorgang erzeugte EXE-Datei), doch das Ergebnis ist in diesem Fall das-
selbe.
Das fertige Programm tut genau das, was man von ihm erwartet: Es ist eine
vollständige Windows-Anwendung, jedoch ohne Inhalt, es tut einfach nichts.
Abb. 2.15
Das lauffähige
Programm
42
Grundlagen für die
MFC-Entwicklung
Fachbegriffe in der Das Konzept von Fenstern, Nachrichten, Semaphoren und Threads – um nur eine
Windows-Welt kleine Anzahl von wichtigen Kernpunkten zu nennen – gehört sicherlich zu den
umfangreichsten Themen, die es für einen einsteigenden Entwickler zu lösen
gilt.
Moderne Betriebssysteme
Zum Glück ist das Zerstören anderer Applikationen heutzutage kaum noch
möglich, vielfach sogar gar nicht mehr durchführbar, da die Betriebssysteme
selbst darauf achten, dass Anwendungen nur den ihnen zugehörigen Bereich
verwenden und nicht in den Adressraum anderer Programme eindringen kön-
nen.
Das erlaubt es dem Entwickler, sich mehr auf die eigentlich wichtigen Aspekte
der Windows-Programmierung zu kümmern, nämlich das Verwalten von Fens-
tern und die Interaktivität mit dem Anwender.
Fensterverwaltung
Kapselung von Wenn hier von Fensterverwaltung gesprochen wird, handelt es sich im Wesent-
API-Funktionalität lichen um eine buchführerische Tätigkeit, denn die gesamten Interna – Anle-
durch die MFC gen des Fensters, Reaktion auf Benutzereingaben wie Größenveränderungen
und so weiter – werden innerhalb der MFC weitestgehend vor dem Benutzer
versteckt und nur offen gelegt, sobald der Anwender eine von der Norm abwei-
chende Tätigkeit durchführen möchte.
Sie haben im letzten Kapitel gesehen, wie mit einigen wenigen Mausklicks eine
komplette Windows-Anwendung erzeugt wurde, die zumindest von der Bedie-
nung her eine vollständige Applikation ergab.
Unterschiedliche Es ist allerdings trotzdem notwendig, dass Sie einmal genaueres Augenmerk
Fenstertypen auf die verschiedenen Fenstertypen richten, mit denen man es unter Windows
zu tun hat.
44
Grundlagen für die MFC-Entwicklung
3
Die folgende Grafik stellt einige davon, ohne Anspruch auf Vollständigkeit,
zusammen mit weiteren wichtigen Begriffen dar:
Abb. 3.1
Verschiedene Fenster-
typen und wichtige
Begriffe
Eine interessante Eigenschaft von Fenstern ist es, dass sie, sozusagen rekursiv, Eigenschaften
aus weiteren Fenstern aufgebaut sind. Prinzipiell ist jedes Element, das sich von Fenstern
innerhalb eines Fensters befindet, wiederum ein Fenster, im obigen Beispiel
zählen beispielsweise auch die Steuerelemente oder die Bildlaufleiste dazu.
Wenn Sie also wissen, wie man ein Fenster öffnet, verschiebt oder auf seinen
Status hin überprüft, können Sie dieses Wissen auch direkt auf sämtliche
weiteren Komponenten anwenden.
Insbesondere ist auch das Konzept der Nachrichten, das im weiteren Verlauf
dieses Kapitels beschrieben wird, auf sämtliche Komponenten anwendbar.
45
Eine kurze Einführung in die Windows-Programmierung
3
Weitere Bezeichnungen
Fensterhierarchien Es gibt Hierarchien unter den Fenstern, diese werden dadurch bestimmt, wel-
che Fenster von welchen Elternfenstern erzeugt oder umschlossen werden.
Wenn Sie sich zum Beispiel das Visual Studio anschauen, sind die beiden in die-
sem Zusammenhang wichtigsten Fensterarten sichtbar, sobald Sie zum Bei-
spiel das Projekt aus dem letzten Kapitel geöffnet haben und eine
Quelltextdatei betrachten:
Abb. 3.2
Startup – Mainframe
und Kindfenster
Zum einen sehen Sie in der Abbildung das Hauptrahmenfenster (engl. Main-
frame). Es ist ein Toplevel-Fenster, da es keine übergeordneten Fenster gibt, die
diesen Mainframe enthalten.
Gleichsam existieren eine Reihe von untergeordneten Fenstern, die als Kind-
fenster oder Child Windows bezeichnet werden – in diesem Fall zwei Fenster,
die Quelltexte darstellen, sowie eine ganze Reihe von Toolbars und weiteren
Kontrollelementen.
Verwandtschaftsverhältnisse
Beachten Sie, dass diese Bezeichnungen nicht nur eine Abstammungs- son-
dern insbesondere auch eine Existenzberechtigung beinhalten: Wird ein
Elternfenster (zum Beispiel der Visual Studio .NET Mainframe) geschlossen,
endet gleichzeitig auch die Existenz der untergeordneten Kindfenster!
46
Grundlagen für die MFC-Entwicklung
3
Am einfachsten kann man sich die Materie klarmachen, indem man einmal
betrachtet, wie Windows arbeitet.
Nehmen wir zum Beispiel das Visual Studio .NET – starten Sie es und warten Auslastung des
Sie, bis sich das Hauptfenster aufgebaut hat. Wenn Sie nun keine Eingabe täti- Systems
gen und mit der Maus keine Elemente an- oder Aktionen auswählen, was wird
passieren? Richtig, nichts – und das schlägt sich auch in der Auslastung des Sys-
tems nieder.
Öffnen Sie den Task-Manager (durch Rechtsklick in die Startzeile und Auswahl Der Task-Manager
des Punkts Task-Manager oder durch gleichzeitiges Drücken von A+S+_
und nachfolgende Auswahl des Punkts Task-Manager) und schauen Sie sich
unter der Registerkarte Prozesse die CPU-Nutzung des Prozesses devenv.exe an:
Abb. 3.3
Der Task-Manager
zeigt, dass das Studio
nichts tut
Die Auslastung liegt bei ungefähr Null Prozent, was ein klares Indiz dafür ist,
dass die Entwicklungsumgebung derzeit auch versteckt im Hintergrund, unbe-
merkt von den Augen des Anwenders, nichts tut.
Wenn Sie jetzt einen Menüpunkt – zum Beispiel den zum Erzeugen eines neuen
Projekts – auswählen, reagiert das Studio aber prompt. Wie funktioniert das?
47
Eine kurze Einführung in die Windows-Programmierung
3
Ein Ereignis ist zunächst nichts anderes als eine bestimmte Aktion, die der
Benutzer ausführt. Umgangssprachlich könnte man diese Aktionen zum Bei-
spiel so beschreiben:
Verständigung Nun versteht Windows die deutsche Umgangssprache leider nicht, sodass
mit Windows anstelle der Prosatexte natürlich, wie häufig bei der Beschreibung von immer
wiederkehrenden Sachverhalten oder Werten innerhalb eines Programms, ent-
sprechende Zahlenwerte einzusetzen sind. Wie Sie später sehen werden, exis-
tieren für jedes mögliche Ereignis Konstanten, die aus Ihren Programmen
heraus eingesetzt werden können.
Über Ereignisse in Das liegt daran, dass die Anwendung zunächst noch nichts von dem eingetre-
Kenntnis setzen tenen Ereignis (engl. Event) wissen kann – lediglich das Betriebssystem hat zur
Kenntnis genommen, dass der Benutzer eine Aktion durchführen will (oder
besser: schon durchgeführt hat, auch wenn die Behandlung derselben noch
nicht vollzogen wurde).
Tritt ein Ereignis auf, hat das Betriebssystem alle Hände voll zu tun: zunächst
muss geprüft werden, um welche Art von Ereignis es sich handelt und welche
Anwendungen davon betroffen sind.
48
Grundlagen für die MFC-Entwicklung
3
Der Nachrichtenpuffer
Die Geister scheiden sich darüber, ob es korrekt ist zu sagen, dass Windows-
Anwendungen von eingehenden Nachrichten in Kenntnis setzt.
In der Tat ist es so, dass jede Applikation über einen so genannten Nachrichten- Puffer zum Zwischen-
puffer verfügt, der zur Entgegennahme von Windows-Nachrichten gedacht ist speichern von
und die Botschaften solange speichert (eben „puffert“), bis sie vom zugehöri- Nachrichten
gen Programm verarbeitet wurden. Die Gesamtheit der Puffer wird als System-
nachrichtenpuffer bezeichnet:
Abb. 3.4
Der System-
nachrichtenpuffer
Hier liegt auch der eigentliche Haken, denn die Verarbeitung liegt vollständig in Nachrichten-
den Händen der Anwendung – sie ist dafür verantwortlich, zu prüfen, ob neue verarbeitung
Botschaften vorliegen. Windows schiebt die Nachrichten zwar in den Puffer,
zieht sich damit allerdings auch schon aus der Verantwortung zurück.
Das heißt im Extremfall, dass eine Anwendung, die nie ihren Puffer auf den Ein-
gang neuer Nachrichten hin überprüft, niemals auch nur eine einzige Botschaft
zu Gesicht bekommen wird und sich gegenüber Benutzereingaben als blind
und taub darstellen wird.
49
Eine kurze Einführung in die Windows-Programmierung
3
Gerade der zweite Schritt ist es, der im Ruhezustand einer Anwendung die
ganze Zeit durchlaufen wird. Durch geschickte Betriebssystemorganisation
ist es möglich, diesen Prozess sehr arbeitssparsam durchzuführen.
Einsparen von Das liegt an drei wesentlichen Gründen. Der Erste wurde bereits weiter oben
CPU-Zeit genannt: Die Anwendung kann in eine Art Ruhezustand übergehen, bis die
nächste Botschaft eintrifft. Würde man Nachrichten direkt an Anwendungen
weiterleiten, müsste sie kontinuierlich prüfen, ob Nachrichten vorrätig sind.
Dieses Problem ließe sich gegebenenfalls lösen, indem Windows (oder ein
anderes Betriebssystem) die Anwendung zunächst aufwecken und ihr dann die
Botschaft zuweisen würde.
Schwerwiegender ist ein weiterer Grund, der mit dem eben gesagten eng
zusammenhängt: Angenommen, Windows würde Botschaften an Applika-
tionen direkt durchreichen – wie würden sie dann entgegengenommen wer-
den? Vielleicht in der Art, dass sie akzeptiert und direkt verarbeitet werden?
Gründe gegen ein Was, wenn eine Nachricht vielleicht 10ms zur Abarbeitung braucht, und Win-
direktes Weiterrei- dows jetzt so lange warten muss, bis das Programm bereit zur Entgegennahme
chen von Nachrichten weiterer Nachrichten wäre? Sagen wir hypothetisch weiter, dass der Benutzer
dabei ist, die Maus zu bewegen – pro Mausbewegung vergehen nun diese
10 ms, da es sich um ein rechenintensives Programm handelt und viele Dinge
berechnet werden müssen, wenn sich die Mausposition ändert (betrachten Sie
zum Beispiel komplexe CAD-Applikationen – Computer Aided Design -). Da die
Anwendung pixelgenaue Mausinformationen braucht, würde wir für eine Stre-
cke von zehn Bildpunkten bereits 100 ms benötigen. Bei einer Auflösung von
1.024*768 bräuchten Sie dann über zwölf Sekunden, um die Maus von der lin-
ken oberen in die rechte untere Ecke zu bewegen. Es ist leicht einsichtig, dass
dieses im heutigen Zeitalter nicht mehr tragbar wäre.
50
Grundlagen für die MFC-Entwicklung
3
Der Vorgang der Optimierungen lässt sich grafisch wie in der folgenden Abbil-
dung zusammenfassen:
Abb. 3.5
Redundanzprozess
51
Eine kurze Einführung in die Windows-Programmierung
3
Aufbau des Die eigentliche Nachricht steckt im Nachrichtencode, der normalerweise ein
Nachrichtencodes ganzzahliger positiver Ausdruck ist – hierfür stehen dann die bereits angespro-
chenen Konstanten zur Verfügung. Die Nachricht WM_PAINT, beispielsweise,
die ein Fenster zum Neuzeichnen seines Inhalts auffordert, hat den Wert 15,
sodass dieser im Nachrichtencode zu finden sein würde. Dieser Wert wird im
Verzeichnis Vc7/PlatformSDK/Include in der Datei WinUser.h definiert.
Weitere Nachrichtencodes
Das Wörtchen „normalerweise“ im oberen Abschnitt deutet es bereits an:
auch hier gibt es Ausnahmen von der Regel. Windows wäre ein ärmliches
Betriebssystem, wenn es nur die fest definierten Nachrichtencodes zur Ver-
fügung stellen würde.
Im Einzelfall müssen Sie für die Nachrichten in der Online-Hilfe von MS-VC++
prüfen, welche Bedeutung die Parameter haben. Zum praktischen Nachschla-
gen befindet sich aber eine Übersicht sämtlicher Windows-Nachrichten mit
kurzer Beschreibung im Anhang dieses Buchs.
52
Grundlagen für die MFC-Entwicklung
3
Hierbei handelt es sich um die so genannten ungarische Notation oder den Verständigung unter
ungarischen Programmierstil. Es wird dringend angeraten, dass Sie sich auch Programmierern
an diese Konventionen halten, damit Sie sich an die Schreibweisen gewöhnen
und andere Programme schneller verstehen können und insbesondere auch für
andere Entwickler verständliche Quelltexte schreiben.
Die folgende Tabelle gibt einen Überblick über die in diesem Buch verwendeten
Präfixe:
Tabelle 3.1
Kennung Bedeutung Entspricht in C++
Ungarische Notation
im Überblick
b BOOL bool
c char char
d DOUBLE double
e Enumerierungsvariable
fn Funktion Funktion
i int int
l long long
53
Ein erstes MFC-Programm
3
s String char[x]
E enum enum
S struct struct
C class Class
54
Grundlagen für die MFC-Entwicklung
3
Einige Punkte werden sicherlich noch hier und da unklar sein, doch ist dieses Unklarheiten durch
für einen einsteigenden MFC-Entwickler ganz natürlich: Der schieren Fülle an Üben beseitigen
Informationen ist nur nach und nach beizukommen, und es ist ein ungeschrie-
benes Gesetz, dass selbst gestandene Windows-Entwickler jeden Tag etwas
Neues über die API, das MFC-Klassengeflecht oder neue entstehende Technolo-
gien lernen.
Machen Sie sich also nichts daraus, wenn hin und wieder einige Teilaspekte
nicht hundertprozentig klar werden – lesen Sie statt dessen weiter und kehren
Sie zu einem späteren Zeitpunkt zu diesen Inhalten zurück.
Wenn Ihnen dieses Vorhaben als zu leicht erscheint, kann ich Sie dahingehend Arbeit ohne
beruhigen, dass wir nicht auf Assistenten zurückgreifen werden, sondern alles Assistenten
per Hand erledigen, was nötig ist, um eine MFC-Anwendungen von Grund auf
zu erzeugen.
Diese Vorgehensweise ist sinnvoll, wenn man bedenkt, dass vom Assistenten
erzeugte Quelltexte bereits mehrere hundert Zeilen Code aufweisen – da ist es
gut, wenn man bekannte Anker wiederfindet, die man schon einmal selbst per
Hand bearbeitet hat.
Beginnen werden wir mit dem Anlegen eines neuen Projekts, öffnen Sie daher
das Datei-Menü und wählen dort den Punkt Neu > Projekt. Es öffnet sich das
schon bekannte Fenster zur Auswahl des gewünschten Projekttyps.
Suchen Sie aus der Liste im rechten Teil des Dialogs den Punkt Win32 Project Anlegen eines
und tragen Sie als Namen MFCStart ein (alternativ befindet sich das in diesem Win32-Projekts
Abschnitt erzeugte Programm auch auf der Buch-CD im Verzeichnis
\Kapitel3\MFCStart).
55
Ein sanfter Einstieg
3
Abb. 3.6
Erzeugen Sie ein einfa-
ches Win32-Projekt
Sobald Sie auf OK klicken, öffnet sich ein neues Fenster, das den ersten Schritt
eines Assistenten anzeigt, der Sie beim Anlegen der Win32-Applikation unter-
stützen soll – zugegeben, ganz ohne Assistent kommen wir dann doch nicht
aus:
Abb. 3.7
Die Übersicht über
die zu erzeugende
Win32-Anwendung
Das Fenster gibt an, dass eine Windows-Anwendung erzeugt werden soll, was
prinzipiell wenig hilfreich ist, denn dazu zählen viele Dinge. Interessant wird
diese Aussage erst im entsprechenden Kontext, der über die Auswahl Einstel-
lungen im linken Navigationsrahmen angezeigt werden kann:
56
Grundlagen für die MFC-Entwicklung
3
Abb. 3.8
Die Einstellungsmög-
lichkeiten für Win32-
Anwendungen
Applikationstypen
Die folgenden Applikationstypen können ausgewählt werden (dabei erzeugt
der Assistent jeweils eine Projektdatei und je nach Programmtyp mehrere vor-
gefertigte Prototyp-Dateien, falls unter dieses unter den Zusatzoptionen nicht
deaktivert wird):
DLL: Erzeugt eine dynamische Bibliothek (engl. dynamic link library), die Bibliotheken
einen Funktionalitätssatz enthält, der von anderen Anwendungen
genutzt werden kann. Dynamische Bibliotheken werden erst zur Lauf-
zeit einer Applikation an das Programm gebunden und können von
mehreren Anwendungen gleichzeitig genutzt werden.
Statische Bibliothek: Ähnlich der DLL, ist die statische Variante zum
direkten Anbinden an Programme gedacht. Dieses ist meist dann sinn-
voll, wenn die Bibliothek sehr spezifisch ist und mit großer Wahrschein-
lichkeit nicht von anderen Anwendungen genutzt wird.
57
Ein sanfter Einstieg
3
COM-Objekte ATL: Diese Bibliothek (ATL steht für Active Template Library) dient zur
Entwicklung von COM-Komponenten. Auf sie wird im weiteren Verlauf
des Buchs noch näher eingegangen. ATL-Unterstützung kann nur für
Konsolen-Anwendungen hinzugefügt werden.
Klassenframework MFC: Die Microsoft Foundation Classes (MFC) sind ein objektorientiertes
Klassenframework zur Entwicklung von Windows-Anwendungen. Das
Aktivieren der Unterstützung ist allerdings nur für Konsolen-Anwen-
dungen und statische Bibliotheken erforderlich, bei einer Windows-
Applikation – wie in unserem Fall – ist dieser Schritt nicht nötig.
Zusätzliche Optionen
Als letzten Abschnitt der Dialogseite gibt es eine Reihe von weiteren Optionen,
die nachstehend aufgeführt sind:
Leeres Projekt: Erzeugt nur das eigentliche Projekt, aber keine Dateien,
die sich in diesem befinden. Sämtliche zum Projekt gehörenden Files
müssen per Hand hinzugefügt werden.
Export Symbols: Aktivieren Sie diesen Punkt, wenn Sie eine DLL erzeugen
und von dort aus Symbole exportieren möchten.
Beschleunigung von Precompiled Headers: Die vorkompilierten Header dienen in erster Linie
Kompiliervorgängen dazu, den Kompiliervorgang enorm zu beschleunigen. Während ohne
diese Option jedes Headerfile möglicherweise einige hundert Mal ein-
gelesen und übersetzt werden müsste, ermöglicht es der Einsatz von
vorkompilierten Headerdateien, diesen zeitraubenden Prozess nur ein-
mal durchzuführen und dann mit bereits übersetzen Headern weiterzu-
arbeiten. Die Option kann nur für statische Bibliotheken aktiviert
werden – in unserem Fall werden die vorkompilierten Header automa-
tisch verwendet.
Erzeugungsprozess einleiten
Für unser Projekt brauchen keine Einstellungen verändert zu werden, überneh-
men Sie also die vordefinierten Werte und klicken dann auf die Fertigstellen
Schaltfläche.
58
Grundlagen für die MFC-Entwicklung
3
Der Assistent bereitet das Projekt sowie eine Reihe von Dateien vor, die nun Automatische Bereit-
zum Editieren bereit stehen. Schon zu diesem Zeitpunkt existiert eine vollwer- stellung von Dateien
tige Applikation, wie Sie leicht durch Kompilieren und Ausführen der neu ent-
standenen Sources verifizieren können (Menü Build > Build Solution oder /
zum Kompilieren, dann % oder Debug > Start zum Ausführen des Programms):
Abb. 3.9
Die vom Assistenten
angelegte Anwendung
Wie schon beim MFC-Beispiel präsentiert sich also eine vielleicht nicht atembe-
raubende, aber vollständig funktionierende Anwendung, die darüber hinaus
auch noch eine Infobox mit Copyright-Informationen und zwei vorgefertigte
Menüs bereit hält.
Abb. 3.10
Der Solution Explorer
59
Ein sanfter Einstieg
3
Unterteilung Dieses ist vor allem dann nützlich, wenn Sie es mit großen Aufgaben zu tun
von Projekten haben, deren Unterteilung in kleinere Aufgaben eine Zuordnung der einzelnen
Teilgebiete in separate Projekte rechtfertig.
Abb. 3.11
Das Kontextmenü
eines Projekts
Interessant ist die Möglichkeit, neue Dateien zu erzeugen und einzufügen. Bei
Auswahl dieses Punkts bietet sich dem Entwickler ein neues Dialogfeld, das
passende Einstellmöglichkeiten zulässt:
60
Grundlagen für die MFC-Entwicklung
3
Abb. 3.12
Dialogfeld zum Hinzu-
fügen neuer Dateien
Dieses erspart den lästigen Umweg über eine externe Applikation, die dann
möglicherweise extra mühsam gestartet werden muss, wenn beispielsweise
eine neue Bitmap- oder eine HTML-Datei angelegt werden soll.
Abb. 3.13
Das Dialogfeld zum
Erzeugen neuer
Klassen
61
Ein sanfter Einstieg
3
Klassentypen Wie für die Assistenten des Studios üblich, haben Sie eine ganze Reihe von
Möglichkeiten, von welchem Typ die zu erzeugende Klasse sein soll. Welche
Auswahlmöglichkeiten es hier im Einzelnen gibt, ist an dieser Stelle nicht wei-
ter relevant, eine Beschreibung würde den Rahmen des Buchs sprengen.
Ferner können Sie spezifizieren, ob die neue Klasse von einer anderen, bereits
bestehenden Klasse abzuleiten ist, und in welchem Modus (public, protected,
private) dieses zu geschehen hat.
Abb. 3.14
Die Einstellmöglich-
keiten im Klassen-
assistenten
62
Grundlagen für die MFC-Entwicklung
3
In Ihrem aktuellen Projekt soll eine Textausgabe im Fenster getätigt werden. Zu Ein Objekt zur
Demonstrationszwecken soll das Objekt einer Klasse dafür verantwortlich sein, Textausgabe
diesen Text bereitzustellen. Das ist vielleicht in diesem Rahmen etwas übertrie-
ben, veranschaulicht aber die allgemeine Vorgehensweise beim assistentenge-
stützten Anlegen neuer Klassen mit dem Visual Studio.
Abgeleitet werden soll unsere Klasse nicht, ebenso wenig ist sie als Inline zu
spezifizieren oder mit einem virtuellen Destruktor zu versehen.
Klicken Sie auf Fertig stellen und der Assistent legt die genannten Dateien an. Anlegen der Klassen-
Sie können sich durch einen schnellen Blick in den Solution Explorer davon dateien
überzeugen, dass sowohl die Header- als auch die Implementationsdatei in den
dafür vorgesehenen Ordnern abgelegt wurden:
Abb. 3.15
Die neu hinzu-
gefügten Dateien
Öffnen Sie durch Doppelklick auf die Dateinamen die beiden Files Textaus-
gabe.cpp und Textausgabe.h. Es öffnen sich zwei neue Fenster, die die Inhalte
der eben neu angelegten Files präsentieren.
Vielleicht sagt Ihnen die Art der Fensterverwaltung beim Visual Studio.NET
nicht zu. Dieses ist aber kein Problem, denn Sie kann auf die vom Visual Studio
6 bekannte Variante umgestellt werden. Öffnen Sie hierzu das Tools-Menü und
wählen Sie dort den Punkt Optionen:
63
Ein sanfter Einstieg
3
Abb. 3.16
Unter zahlreichen
Optionen verbirgt sich
auch die Darstellungs-
art der Fenster
Darstellungsoptionen Bei den Einstelloptionen unter Umgebung > Allgemein können Sie zwischen
Tabbed Documents und einer MDI-Umgebung wählen. Den Unterschied zwi-
schen diesen beiden Formen zeigen die beiden folgenden Abbildungen auf:
Abb. 3.17
Die voreingestellte
Tabbed Documents-
Variante
Wie Sie sehen, sind die Fenster bei der Tabbed Windows-Version in einer Über-
sichtsleiste am oberen Rand aufgeführt, während immer nur eins sichtbar im
Vordergrund verweilt und dabei den gesamten zur Verfügung stehenden
Raum einnimmt.
64
Grundlagen für die MFC-Entwicklung
3
Abb. 3.18
Viele Entwickler
empfinden die MDI-
Darstellungsvariante
als übersichtlicher
class CTextAusgabe
{
public:
CTextAusgabe(void);
~CTextAusgabe(void);
private:
char m_lpszAusgabe[MAXIMALE_STRINGLAENGE];
};
Zunächst fällt auf, dass die Datei mit einem #pragma once beginnt. Diese
Direktive sorgt dafür, dass die Headerdatei nur einmal eingebunden wird.
Angenommen, Sie haben eine Datei A.cpp, welche die beiden Includefiles B.h #pragma once
und C.h einbindet. Wenn B.h nun C.h ebenfalls einbindet, würde C.h zweifach
eingelesen und damit die Definitionen als doppelt angesehen werden.
65
Ein sanfter Einstieg
3
#pragma once sorgt dafür, dass jede Headerdatei tatsächlich nur einmal einge-
lesen wird. Ist dieses bei einem Kompiliervorgang bereits der Fall gewesen, wird
das Übersetzen der Datei übersprungen.
Die Klasse selbst bietet nichts Aufregendes: zwei Memberfunktionen zum Initi-
alisieren und Auslesen eines Chararrays – diese werden im Laufe dieses Buchs
häufig salopp als Strings bezeichnet werden – sowie das Array selbst, dessen
Größe durch eine definierte Konstante festgelegt ist.
Die CTextausgabe-Implementationsdatei
Editieren Sie nun die Implementationsdatei TextAusgabe.cpp:
CTextAusgabe::CTextAusgabe(void)
{
strcpy(m_lpszAusgabe, "String nicht
initialisiert!");
}
// CTextAusgabe::StringSetzen
//
// Setzt den auszugebenden String neu.
//
// Rückgabewerte: keine
66
Grundlagen für die MFC-Entwicklung
3
// CTextAusgabe::StringAuslesen
//
// Gibt den auszulesenden String aus.
//
// Rückgabewerte: Zeiger auf den auszulesenden,
// nullterminierten String
char *CTextAusgabe::StringAuslesen()
{
return (m_lpszAusgabe);
}
CTextAusgabe::~CTextAusgabe(void)
{
}
Wenn Sie ein Projekt mit vorkompilierten Headern bearbeiten, muss jede Im- Bedingungen für
plementationsdatei diesen Ausdruck (bzw. mit dem entsprechenden zur Vor- vorkompilierte
kompilierung gehörenden Includedateinamen) als erste Zeile enthalten, da Headerdateien
sonst der Übersetzungsvorgang direkt wieder abgebrochen wird.
Im Konstruktor der Klasse wird der auszugebende String auf einen initalen
Wert gesetzt, der dafür sorgt, dass ein Entwickler es sofort bemerkt, wenn mit
einem nicht initialisierten String gearbeitet wird.
Wenn Sie jedoch bei einigen Werten von Ihnen selbst verteilte Dummysym-
bole wieder erkennen, ist es oftmals eine Sache von Sekunden, Fehler zu
berichtigen.
Es stehen weiterhin zwei Methoden zum Auslesen und Setzen des Ausgabe-
strings zur Verfügung, von denen Letztere wieder extensiven Gebrauch von
Sicherheitsmaßnahmen gegen inkorrekte Eingabeparameter enthält.
67
Ein sanfter Einstieg
3
Ressourcen MFCStart.rc: Diese Datei enthält eine Auflistung der Ressourcen, die vom
Projekt MFCStart verwendet werden. Zu den Ressourcen einer Anwen-
dung zählen unter anderem Icons, Bitmaps, Zeichensätze oder Mauszei-
ger, kurz alles, was nicht direkt im Programm als Quelltext gespeichert
wird, sondern als externe Datenquelle eingebunden wird (die dann wie-
derum statisch zur Anwendung gebunden werden kann).
Small.ico: In der Regel das gleiche Bild wie MFCStart.ico, nur in einer
kleineren (16*16) Version. Wird ebenfalls über MFCStart.rc eingebunden.
Abb. 3.19
Das Applikations-Icon
Vorkompilierte Header StdAfx.h, StdAfx.cpp: Dateien, die zum Erzeugen der vorkompilierten
Headerfiles benötigt werden. Vorkompilierte Header (precompiled hea-
ders, kurz PCH) dienen, wie bereits angesprochen, zur Beschleunigung
von Kompiliervorgängen.
68
Grundlagen für die MFC-Entwicklung
3
#include "stdafx.h"
#include "MFCStart.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst;
// current instance
TCHAR szTitle[MAX_LOADSTRING];
// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];
hAccelTable = LoadAccelerators(hInstance,(LPCTSTR)IDC_MFCSTART);
69
Ein sanfter Einstieg
3
wcex.cbSize = sizeof(WNDCLASSEX);
70
Grundlagen für die MFC-Entwicklung
3
return RegisterClassEx(&wcex);
}
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
71
Ein sanfter Einstieg
3
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst,
(LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message,
wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message,
wParam, lParam);
}
return 0;
}
72
Grundlagen für die MFC-Entwicklung
3
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK ||
LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
Trotzdem ist es sinnvoll einmal kurz zu reflektieren was eingangs dieses Kapi-
tels über die Windows-Programmierung gesagt wurde und wie die praktische
Umsetzung im Rahmen der Win32-API-Programmierung aussieht – zumal in
diesem Zusammenhang auch an einem konkreten Beispiel einige weitere Kon-
zepte erläutert werden können.
Die WinMain
Zunächst fällt auf, dass das Programm offensichtlich keine main()-Funktion Fehlende main()-
hat, wie Sie vermutlich von der Shell-Programmierung her bekannt sein dürfte. Funktion
Unter Windows heißt das passende Gegenstück WinMain, oder, wie in diesem
Fall _tWinMain. Sie stellt das Hauptprogramm jeder Windows-Applikation dar.
Tabelle 3.2
Parameter Bedeutung
Die Parameter der
Funktion WinMain
hInstance Kennzahl für die aktuelle Programminstanz, wird automa-
tisch von Windows beim Starten der Anwendung verge-
ben.
hPrevInstance NULL (stammt aus Win16-Zeiten und wird nicht mehr ver-
wendet)
73
Ein sanfter Einstieg
3
Parameter Bedeutung
lpCmdLine Ein Zeiger auf die Kommandozeile, mit der das Programm
aufgerufen wurde, ist analog zu argv aus main(int argc,
char **argv).
Wenn bei der Beschreibung oben von Instanzen geredet wird, meint man hier-
mit das im Speicher befindliche Abbild der zugrunde liegenden Applikationsda-
tei – hierbei handelt es sich normalerweise um EXE- Files.
Instanzen Sie können eine (theoretisch) unbegrenzte Anzahl von Instanzen gleichzeitig
starten, Windows kümmert sich um die Verwaltung und sorgt dafür, dass sich
die einzelnen laufenden Anwendungen nicht gegenseitig in die Quere kom-
men – eine Ausnahme bilden Programme, die dazu gedacht sind, nebeneinan-
der zu existieren und untereinander zu kommunizieren, etwa Hybridformen
von Applikationen, die gleichzeitig Client- und Server bei Netzwerk-Anwendun-
gen darstellen können.
Fensterklassen Fensterklassen charakterisieren den grundlegenden Typ eines Fensters, der für
alle Fenster dieser Klasse identisch sein soll, während bei der eigentlichen
Erzeugung eines Fensters die individuellen Eigenschaften (Größe, Position etc.)
spezifiziert werden können. Für nähere Angaben zu RegisterClassEx und der ver-
wendeten WNDCLASSEX schlagen Sie im Anhang dieses Buchs nach.
Die Nachrichtenschleife
Wenn Sie sich an den Anfang dieses Kapitels zurückerinnern, werden Sie die
nun in WinMain folgende Kontrollstruktur als Nachrichtenschleife wiederer-
kennen.
74
Grundlagen für die MFC-Entwicklung
3
Sie ist es, die das eigentliche Herzstück der Anwendung ausmacht: Solange die
Applikation läuft (also nicht auf Wunsch des Anwenders oder des Betriebssys-
tems geschlossen wurde) wird die Nachrichtenschleife ständig ausgeführt, um
eingehende Botschaften zu verarbeiten.
Nachrichten abholen
1 Entgegennehmen der Botschaft: Dieses geschieht in diesem Fall durch
ein ständiges Aufrufen von GetMessage (diese Methode liefert 0 zurück,
sobald die Anwendung geschlossen werden soll) und wartet ansonsten
solange, bis eine Nachricht eintrifft. Das Ganze passiert in Absprache
mit dem Betriebssystem und ist sehr ressourcenschonend, wie wir
bereits bei der Untersuchung des Task-Managers gesehen haben.
Nachrichten
2 TranslateMessage: Eingegangene Nachrichten müssen erst in ein für übersetzen
Windows verständliches, internes Format gebracht werden, dessen Spe-
zifikationen für uns an dieser Stelle nicht weiter interessant sind.
Nachrichten
3 DispatchMessage: Dieser Aufruf leitet die eingetroffene Botschaft an weiterleiten
eine speziell auf die Nachrichtenverarbeitung fokussierte Methode wei-
ter
Woher weiß Windows nun, wohin die Nachricht geleitet werden soll?
Nun, beim Anlegen der Fensterklasse wird unter anderem auch eine so
genannte Callbackfunktion spezifiziert, die immer dann aufgerufen wird,
sobald eine Nachricht verarbeitet werden soll (durch den DispatchMessage Auf-
ruf).
Callback-Funktionen
Funktionen dieser Art werden von Windows direkt aufgerufen, nicht von
unserer Applikation (es gibt keinen direkten Aufruf innerhalb unseres Quell-
texts).
Man spricht auch von asynchroner Abarbeitung, womit man sich auf den
Zeitpunkt des Aufrufs bezieht: dieser kann jederzeit stattfinden und ist vom
Programmierer nicht vorhersehbar.
Die Nachrichtenfunktion
Die Nachrichtenfunktion für das erzeugte Fenster heißt WndProc (Window Pro-
cedure, also Fensterprozedur) und genügt dem nachstehenden Aufbau:
75
Ein sanfter Einstieg
3
Tabelle 3.3
Parameter Bedeutung
Parameter der Nach-
richtenfunktion
hWnd Handle des Fensters, das die Nachricht empfangen hat.
Muss übergeben werden, da dieselbe Nachrichtenfunk-
tion Basis für beliebig viele Fenster sein kann.
Fensterhandles
Nachrichtenfunktionen erhalten als ersten Parameter ein Handle (zu
deutsch ungefähr Griff) auf das Fenster, das die Nachricht erhalten hat. Stel-
len Sie sich Handles einfach als einen anderen Namen für ein Objekt vor.
Handles werden von Windows automatisch ergeben, meist liefern die erzeu-
genden Funktionen – zum Beispiel CreateWindow wie in diesem Programm
– die jeweiligen Werte zurück. Handles sind ganzzahlige positive Ausdrücke
und werden von Windows in einer internen Liste verwaltet.
Spricht ein Programmierer ein Objekt über sein Handle an, prüft Windows in
dieser Liste, welches Objekt gemeint ist – oder ob es überhaupt existiert –
und gestattet somit den indirekten Zugriff.
Fensterhandles sind in der Regel im Nachhinein nicht mehr aus den Objek-
ten auszulesen, speichern Sie sie also immer, sobald sie von erzeugenden
Funktionen zurückgeliefert werden.
Der Inhalt der Nachrichtenfunktion selbst ist dann wieder einfach zu verstehen
– vorausgesetzt man kennt die Bedeutung der einzelnen Fensternachrichten
sowie die ihrer Parameter.
76
Grundlagen für die MFC-Entwicklung
3
Eine Übersicht über verfügbar Windows-Messages finden Sie im Anhang dieses Verfügbare Windows-
Buches, die konkrete Beschreibung der Parameter schlagen Sie bequem in der Messages
MSVC++-Online-Hilfe nach.
Sie können sich an dieser Stelle eine kurze Lesepause gönnen und ein wenig
mit dem Win32-Programm herumspielen. Es ist wichtig, dass Sie ein Gefühl
dafür bekommen, wie Windows-Programme intern arbeiten, wie Nachrichten
versendet werden und wie auf sie reagiert wird.
Vielleicht schlagen Sie im Anhang dieses Buchs ein paar interessante Fenster-
nachrichten nach und testen aus, ob diese vom oben erzeugten Fenster verar-
beitet werden können.
#pragma once
Achten Sie insbesondere darauf, dass die Zeile #include <windows.h> auskom-
mentiert wird, da es ansonsten Probleme mit den im Folgenden verwendeten
MFC-Klassen gibt, die ihrerseits auch bereits windows.h einbinden – da die App-
likation ursprünglich eine Win32-Anwendung ist, hat der Assistent diese Zeile
nicht schon selbst weggelassen.
77
Ein sanfter Einstieg
3
// Klassendeklarationen
class CMFCApplikation : public CWinApp
{
public:
BOOL InitInstance();
};
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
// globale Variablen
CMFCApplikation dieApplikation;
// die Nachrichtentabelle
BEGIN_MESSAGE_MAP(CMFCFenster, CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
// CMFCApplikation::InitInstance()
//
// InitInstance initialisiert die Anwendung
// (Fenstererzeugung
// und -darstellung)
//
// Rückgabewerte: true -> Kein Fehler
78
Grundlagen für die MFC-Entwicklung
3
BOOL CMFCApplikation::InitInstance()
{
m_pMainWnd = new CMFCFenster;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return (true);
}
// CMFCFenster() [Konstruktor]
//
// Der Konstruktor der CMFCFenster Klasse erzeugt ein
// neues Fenster mit dem Text "Ein kleiner MFC Test"
// in der Titelzeile
//
// Rückgabewerte: keine
CMFCFenster::CMFCFenster()
{
Create(NULL, "Ein kleiner MFC Test");
}
// CMFCFenster::OnPaint
//
// Die OnPaint Methode der Klasse CMFCFenster
// behandelt eingehende WM_PAINT Nachrichten und
// stellt den Inhalt der Fensters wieder her.
//
// Rückgabewerte: keine
void CMFCFenster::OnPaint()
{
CPaintDC dc(this);
RECT rect;
dieApplikation.m_pMainWnd->GetClientRect(&rect);
dc.DrawText("Dieser Text soll zentriert
ausgegeben werden", -1, &rect,
DT_SINGLELINE|DT_CENTER|DT_VCENTER;
}
79
Ein sanfter Einstieg
3
Sicherstellen einer Sie müssen selbst manuell dafür sorgen, dass die notwendigen Bibliotheken
erfolgreichen eingebunden werden, sonst kommt es zu Linkerfehlern.
Übersetzung
Wählen Sie im Solution Explorer das MFCStart-Projekt aus, aktivieren Sie dessen
Kontextmenü (rechte Maustaste) und wählen Sie den Punkt Einstellungen.
Abb. 3.20
Verwendung der
MFC aktivieren
Stellen Sie die Verwendung der MFC wie im Bild angezeigt auf Verwendung der
MFC in einer statischen Bibliothek und bestätigen Sie durch einen Klick auf OK.
Kompilieren Das Projekt sollte jetzt kompilierbar (/) sein. Ist das nicht der Fall, vergleichen
des Projekts Sie es mit dem auf der Buch-CD befindlichen Projekt (\Kapitel3\MFCStart), um
herauszufinden, welche Stellen falsch sind. Vermutlich handelt es sich nur um
einen kleinen Fehler, doch gerade durch die lernt man sein Handwerkszeug
richtig kennen.
Abb. 3.21
Die erste MFC-
Anwendung
80
Grundlagen für die MFC-Entwicklung
3
Am einfachsten kann man diese Inhalte erklären, wenn man versucht, Analo- Vergleich mit dem
gien zum Win32-Beispiel zu finden und anhand dieser Gemeinsamkeiten und Win32-Programm
Unterschiede herauszukristallisieren.
Das MFC-Anwendungsgerüst
Wenn Sie Ihren Blick über die MFC-Variante der Applikation schweifen lassen,
wird Ihnen auffallen, dass keine explizit angegebene WinMain-Funktion exis-
tiert.
Das heißt nicht etwa, dass im Rahmen von MFC-Anwendungen andere Regeln Verstecke WinMain
gelten, vielmehr ist es so, dass die WinMain-Funktion implizit im so genannten
MFC-Anwendungsgerüst (engl. Application Framework) verborgen ist.
Enthalten in ihm sind zahlreiche Funktionen, Klassen und Strukturen, die jede
für sich Teilkomponenten bei der Entwicklung von fensterorientierten Pro-
grammen zur Verfügung stellen.
Benötigen Sie beispielsweise einen Dialog, der eine Sicherheitsabfrage – etwas Verwenden bereit-
in der Art wie Möchten Sie den Vorgang wirklich fortsetzen? – enthält, greifen Sie gestellter Klassen
auf eine der bereits bestehenden Dialogklassen des Frameworks zurück und
ergänzen Sie lediglich um die gerade benötigten Inhalte.
Die gesamte Handhabung des Dialogs hingegen überlassen Sie den vorgefer-
tigten Klassen des MFC-Anwendungsgerüsts.
81
Beschreibung des MFC-Programms
3
Abb. 3.22
Das Anwendungs-
gerüst im Kontext
In diesem Schaubild unten beginnend finden Sie die Win32-API, mit der Sie
schon kurzen Kontakt im einführenden Beispiel dieses Kapitels hatten. Die API
umfasst sämtliche Funktionen und Strukturen, die zum Entwickeln von Win-
dows-Programmen benötigt werden.
Nachteile Soweit so gut, doch hat sie für sich genommen einen entscheidenden Nachteil:
prozeduraler APIs Sie ist als prozedurale API nur noch schwer in den heutigen Kontext objekt-
orientierter Sprachen wie C++ einzuordnen und scheint sich den modernen
Programmierformen verschließen zu wollen.
Als C++-Entwickler ist Ihnen das Prinzip sicherlich bekannt: anstatt von Grund
auf neue Methoden und Klassen mit eigenständiger Funktionalität zu entwer-
fen, nimmt man häufig ein bestehendes System und bastelt lediglich deren
Funktionalität zur Verfügung stellende Klassensysteme, die intern wiederum
auf die alte Basis zugreifen.
82
Grundlagen für die MFC-Entwicklung
3
Abstraktionsschichten
Ein gutes Beispiel für diese Abstraktionsschichten (engl. Abstraction Layers)
sieht man an Projekten, die für unterschiedliche Plattformen (zum Beispiel
Windows und X-Windows) entwickelt werden.
Konkret bedeutet dieses meist, dass eine Schnittstelle entwickelt wird, die
häufig als abstrakte Basisklasse (zum Beispiel IFenster)entworfen ist und
deren abgeleitete Klassen (in diesem Fall vielleicht CWindowsFenster und
CXWindowsFenster) die für die jeweiligen Plattformen spezifischen Metho-
den definieren.
Im Programm selbst, wenn es um den Zugriff auf ein Fenster geht, könnte
man dann einfach einen Zeiger auf ein Objekt vom Typ IFenster (Schnittstel-
len erhalten normalerweise das Präfix I für Interface) haben, das je nach
Plattform auf ein Objekt vom Typ CWindowsFenster oder CXWindows-
Fenster verweist.
Die MFC traten das erste mal im Jahre 1992 zur Erscheinung und haben seit- Kleine Geschichte
dem einen expansiven Wandel hinter sich gebracht. Ausgehend von einer der MFC
handvoll Klassen zur Verwaltung von Fenstern, stellen Sie jetzt tatsächlich ein
komplettes Anwendungsgerüst zur Verfügung, das neben Kernpunkten wie
der Dokument-/Ansichtarchitektur, auf die im weiteren Verlauf dieses Buchs
noch eingegangen wird, auch eine Vielzahl an Hilfsklassen zur Verwendung
von Zeichenketten, verketteten Listen und dergleichen mehr bietet.
Insgesamt umfassen die MFC mittlerweile über 200 Klassen, die Ihnen als Ent-
wickler zur Verfügung stehen. Die Gesamtheit der Klassen wird dann als MFC-
Anwendungsgerüst bezeichnet.
83
Beschreibung des MFC-Programms
3
welche Möglichkeiten sich hier bieten und uns von der Leistungsfähigkeit der
kleinen Helfer zu überzeugen.
Fleißige Helfer Ein Assistent erzeugt nach Angaben des Benutzers, der diese für gewöhnlich in
einer Reihe von Optionsdialogen seinen derzeitigen Bedürfnissen gemäß tätigt,
ein Grundgerüst, das eine komplett funktionierende Anwendung bereitstellt
und danach zur weiteren Bearbeitung als vollständiger Quelltext zur Verfü-
gung steht (neben Anwendungen können auch weitere Komponenten wie
DLLs mithilfe von Assistenten erzeugt werden).
In diesem Kapitel wird allerdings noch Hand angelegt, damit Sie sich später in
den automatisch erzeugten Quelltexten schneller zurechtfinden.
Wie Sie sehen, wird hier lediglich ein Aufruf von AfxWinMain getätigt, das in
der Folge eine ganze Reihe von Initialisierungen und Aktionen durchführt:
84
Grundlagen für die MFC-Entwicklung
3
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-
NULL m_pMainWnd\n");
pThread
->m_pMainWnd>DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock
!= 0)
{
TRACE1("Warning: Temp map lock count non-
zero (%ld).\n",
AfxGetModuleThreadState()
->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
Das sieht nicht nur kompliziert aus, dass ist es auch. Zum Glück ist es nicht
unbedingt erforderlich, jeden einzelnen Schritt nachvollziehen zu können, es ist
nur wichtig zu wissen, wie generell der Ablauf aussieht, vom Starten des Pro-
gramms bis zum Einstiegspunkt in das, was wir selbst geschrieben haben.
85
Beschreibung des MFC-Programms
3
Die Applikationsklasse Nun, CMFCApplikation ist von CWinApp abgeleitet worden, das wiederum von
CWinThread abgeleitet ist. Geeignete Initialisierungen durch die Konstruktoren
der Basisklassen bereiten dieses Objekt darauf vor, eine wichtige Rolle im restli-
chen Verlauf der MFC-Sources zu spielen.
Insbesondere stellt die Definition also sicher, dass AfxWinMain mit dem richti-
gen Ausgangsobjekt arbeitet und entsprechende Initialisierungen nicht im
Sande verlaufen.
dieApplikation ist abgeleitet von einer Klasse CWinThread – von dieser stammt
auch das in AfxWinMain verwendete Objekt pThread ab.
MFC-Klassenhierarchie
Dieses ist gerade der richtige Zeitpunkt, einen Blick auf die Klassenhierarchie
der MFC zu werfen, die relativ strikt ausgelegt ist. Es gibt nämlich keine Mehr-
fachvererbung und sämtliche Klassen sind von einer Oberklasse CObject abge-
leitet – dieser Umstand macht die Verwaltung von Zeigern auf Klassen
natürlich recht angenehm.
Verwendung von Die virtuellen Methoden der rein abstrakten Basisklasse CObject werden dann
virtuellen Methoden geeignet in den abgeleiteten Klassen überschrieben.
Ein Auszug aus der MFC-Klassenhierarchie stellt die folgende Abbildung dar,
hier sind insbesondere auch CWinThread und CWinApp aufgeführt. Beachten
Sie auch die klare, übersichtliche Baumstruktur der einzelnen Unterklassen:
Abb. 3.23
Ein Auszug der MFC-
Klassenhierarchie
86
Grundlagen für die MFC-Entwicklung
3
BOOL CMFCApplikation::InitInstance()
{
m_pMainWnd = new CMFCFenster;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return (true);
}
Hier haben Sie es also mit der ersten selbst geschriebenen MFC-Methode zu
tun, die vom Framework aus aufgerufen wird – mal abgesehen von der Defini-
tion des Anwendungsobjekts.
87
Beschreibung des MFC-Programms
3
Erzeugen des Anwen- Richtig, das Anwendungsfenster wurde erzeugt, zunächst über die Registrie-
dungsfensters rung einer passenden Fensterklasse, dann über den darauf abgestimmten Auf-
ruf von CreateWindow().
Initialisiert wird sie durch den Aufruf von Create – der Prototyp dieser Funktion
ist im Folgenden abgedruckt:
Tabelle 3.4
Parameter Bedeutung
Parameter
lpszClassName Der Klassenname für die zu verwendende, registrierte
Fensterklasse (siehe auch RegisterClass). Wird kein
Name angegeben, werden die CFrameWnd-Standard-
attribute für das Erzeugen des Fensters verwendet.
88
Grundlagen für die MFC-Entwicklung
3
Parameter Bedeutung
Das Erzeugen läuft also prinzipiell analog zum Win32-API-Pendant. Das ist Analogien zwischen
natürlich auch einsichtig, denn die MFC kapseln ja nur deren Funktionalität. Win32-API und MFC
Insbesondere können Sie auch direkt auf Win32-Funktionen zugreifen – also
sozusagen an den Möglichkeiten des MFC Frameworks vorbei – was allerdings
in den meisten Fällen nicht unbedingt erforderlich oder ratsam ist.
Die Nachrichtenbehandlungsmethoden
Sie haben im Win32-Beispiel gesehen, wie eine Methode zum Behandeln von
Nachrichten auszusehen hat. Dort werden eingehende Botschaften auf Ihren
Typ hin geprüft und dann in einem großen Switch-Konstrukt passende Ant-
wortbehandlungen durchgeführt.
89
Beschreibung des MFC-Programms
3
Nachrichtentabelle
Sie ahnen es sicherlich schon, bei dieser Übersicht handelt es sich um die nach-
stehend noch einmal wiedergegebenen Zeilen:
Was inhaltlich hinter den Makros steht, ist an dieser Stelle nicht weiter interes-
sant, Sie können sich bei Interesse die Implementationen in der schon ange-
sprochenen Datei afxwin.h anschauen.
MFC-Nachrichtenmakros
Festlegen der zu In der Nachrichtentabelle sind nun auch die einzelnen zu behandelnden Nach-
behandelnden richten durch Makrodefinitionen einzutragen. ON_WM_PAINT, wie im obigen
Nachrichten in Beispiel, bedeutet, wie eine WM_PAINT Nachricht behandelt werden soll.
Nachrichtentabellen
Wie? Das ist das durch das ON_WM_PAINT-Makro bereits festgelegt: es wird
eine Methode OnPaint in der Klasse CMFCFenster erwartet.
Diese Methoden- und Makronamen sind Vorgaben, die die MFC-Entwickler für
sinnvoll hielten – es bleibt nichts anderes übrig, als sich an diese Richtlinien zu
halten.
Das ist aber nicht weiter problematisch, da ein Großteil der Eintragungen in
Nachrichtentabelle später von den Assistenten selbstständig durchgeführt
werden – nur in Ausnahmefällen muss der Entwickler selbst Hand anlegen und
hat es dann meistens mit einfach zu verstehenden Makros zu tun.
Die OnPaint-Methode
Nachdem Sie festgestellt haben, dass eine OnPaint-Methode zur Behandlung
der WM_PAINT Nachricht zum Neuzeichnen eines Fensters benötigt wird, wer-
den Sie sich vielleicht daran erinnern, eine solche auch zur Verfügung gestellt
zu haben.
90
Grundlagen für die MFC-Entwicklung
3
Es ist trotzdem Sitte, bei Behandlungsmethoden dieses Kürzel vorne vor die Das afx_msg-Präfix
eigentliche Deklaration zu stellen, man erkennt damit sehr schnell, welchen
Zweck eine Methode hat – wir haben es hier also mit der gleichen zugrunde lie-
genden Überlegung zu tun, wie es allgemein auch mit der ungarischen Nota-
tion der Fall ist.
void CMFCFenster::OnPaint()
{
CPaintDC dc(this);
RECT rect;
dieApplikation.m_pMainWnd->GetClientRect(&rect);
dc.DrawText("Dieser Text soll zentriert
ausgegeben werden", -1, &rect,
DT_SINGLELINE|DT_CENTER|DT_VCENTER;
}
Was hier inhaltlich geschieht, soll an dieser Stelle nicht weiter ausgeführt wer-
den, da Sie diesbezüglich in den folgenden Kapiteln noch einiges über die hier
verwendeten Gerätekontexte erfahren werden.
Kurz gesagt, wir die Größe des Fensters ermittelt und dann ein Text exakt in
dessen Mitte ausgegeben.
Die Nachrichtenfunktion
Das gesamte MFC-Listing ist jetzt besprochen worden, doch fehlt derzeit noch Nachrichtenfunktion
ein ganz elementarer Bestandteil jeder Windows-Anwendung: die Nachrich- der Anwendung
tenfunktion zur Entgegennahme und Weiterleitung von Botschaften.
91
Beschreibung des MFC-Programms
3
Hier schließt sich der Kreis und wir kommen zur AfxWinMain-Methode zurück.
Nach der erfolgreichen Initialisierung der Anwendung, findet sich dort der fol-
gende Aufruf:
Ruhezustand einer Dazu bietet sie die Möglichkeit, die Anwendung in eine Art Ruhezustand zu ver-
Applikation setzen. Immer, wenn die Applikation keine Nachrichten zu verarbeiten hat (das
bedeutet normalerweise, dass der Benutzer gerade keine Eingaben macht), ver-
zweigt Run in eine weitere Methode namens OnIdle.
Hier kann der Entwickler Funktionalitäten unterbringen, die immer dann aus-
geführt werden sollen, wenn gerade nichts anderes anliegt. Für ein 3-D-Grafik-
programm wäre es hier zum Beispiel möglich, aufwendige Berechnungen
durchzuführen, aber auch einfachere Aufgaben wie das Aktualisieren der
Benutzeroberfläche (zum Beispiel das Darstellen von Tooltips) wird von hier aus
initiiert.
Wichtig: findet kein Rücksprung von OnIdle zu Run statt, kann die Anwendung
keine Nachrichten mehr auswerten, sie wird dem Benutzer als nicht mehr reak-
tionsfähig präsentiert. An dieser Stelle hilft dann nur noch ein Abbruch über
den Task-Manager oder (in hartnäckigen Fällen) einen Reboot.
ExitInstance
Nun würde Run ewig laufen, wenn es nicht die Möglichkeit zum Beenden der
Anwendung gebe. Diese tritt beispielsweise dann ein, wenn das Hauptfenster
einer Applikation geschlossen wird.
Beendigung eines Ähnlich wie bei der Win32-Anwendung, deren GetMessage-Aufrufe bei einer
Programms Programmbeendigung das Verlassen der Nachrichtenschleife auslösen, wird
auch die Run-Methode in einem solchen Fall verlassen, allerdings nicht, ohne
zuvor einen Aufruf der ExitInstance-Methode durchzuführen.
92
Grundlagen für die MFC-Entwicklung
3
Abb. 3.24
Übersicht über den
Ablauf einer MFC-
Anwendung
Zusammenfassung
Dieses Kapitel hat Ihnen aufgezeigt, wie grundlegende Windows-Programmie-
rung funktioniert und welches Hintergrundwissen notwendig ist, um die
ablaufenden Vorgänge zu verstehen. Nach einer Einführung in die Win32-Pro-
grammierung wurde der Wechsel zu einer MFC-Anwendung vollzogen, wobei
das elementare Basiswissen zu solchen Applikationen angeführt wurde.
Nach diesen Grundlagen wird es in den folgenden Kapiteln mit der konkreten Vorschau
Programmierung moderner Software weitergehen, dabei lernen Sie das Anle-
gen von dialogfeldbasierten , SDI- und MDI-Anwendungen, das Erzeugen von
GUIs – Menüs, Tooltips etc. –, die Auswertung von Benutzereingaben und so
weiter.
93
Entwicklung von
dialogfeldbasierten
Anwendungen
Arten von Windows- Es gibt im Wesentlichen drei Arten von Windows-Anwendungen, die sich ins-
Anwendungen besondere hinsichtlich der Darstellung und der Verwaltung von Dokumenten
unterscheiden:
Entwurf eines Zeichen- Auf den folgenden Seiten dieses Kapitels werden Sie daher ein kleines Zeichen-
programms programm entwickeln, das innerhalb eines Dialogfelds den Benutzer in die
Lage versetzt, eine Figur mittels Freihandstrichzeichnungen zu erzeugen.
96
Entwicklung von dialogfeldbasierten Anwendungen
4
Benennen Sie das Projekt ZeichenProgramm und bestätigen Sie die Auswahl
durch Anwahl von OK.
Abb. 4.1
Die korrekten Projekt-
einstellungen für das
ZeichenProgramm-
Projekt
Wählen Sie das Dialogfeld Anwendungstyp aus und stellen dort Dialogfeldba- Dialogfeldbasierter
siert ein. Ob Sie die MFC in einer statischen oder gemeinsam verwendeten Bib- Anwendungstyp
liothek verwenden wollen, bleibt im Rahmen dieses Buches Ihrem Geschmack
überlassen, bedenken Sie aber, dass die Verwendung der gemeinsamen Biblio-
thek auf Rechnern Fehler verursachen wird, auf denen die MFC-Bibliotheken
nicht installiert sind.
97
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.2
Einstellen des
Anwendungstyps
Erweiterte Einstellungen
Die nächste Registerkarte, auf der Veränderungen vorgenommen werden müs-
sen, ist die, die sich mit den erweiterten Einstellungen für die Applikation
beschäftigt.
Deaktivieren von Da innerhalb des Projekts ZeichenProgramm keine ActiveX Controls verwendet
ActiveX-Steuer- werden sollen – mit denen sich ein späteres Kapitel dieses Buchs noch beschäf-
elementen tigen wird – können Sie den zugehörigen Punkt deaktivieren.
Der Rest der Einstellungen bleibt unverändert, sodass sich das folgende Bild für
die Einstellungsseite ergibt:
Abb. 4.3
Die erweiterten
Einstellungen für das
ZeichenProgramm-
Projekt
98
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.4
Übersicht über
die gemachten
Einstellungen
Bestätigen Sie die Auswahl mit Fertig stellen und veranlassen Sie den Anwen-
dungsassistenten dadurch, ein zu den Einstellungen passendes Grundgerüst
zu erzeugen.
Kurzer Funktionstest
Kompilieren und starten Sie das vom Assistenten kreierte Projekt. Sie sehen
nun das Hauptdialogfeld des neuen Programms.
Im Systemmenü – erreichbar durch Anklicken des Applikationssymbols in der Prüfen der Ausgabe
linken oberen Ecke – finden Sie einen Punkt namens Info über ZeichenPro-
gramm. Wählen Sie ihn an. Es ergibt sich das folgende Bild 4.5.
Mehr Funktionalität – bis auf das Beenden des Programms durch Anwahl der
OK- bzw. Abbrechen-Schaltfläche des Hauptdialogfelds – existiert derzeit noch
nicht. Brechen Sie die Programmausführung ab und bringen Visual Studio .NET
wieder in der Vordergrund.
99
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.5
Das Hauptdialogeld
und das Infodialog-
feld im Überblick
Editieren von Dialogen Um sie zu editieren, wählen Sie den Reiter Ressourcenansicht aus, den Sie am
unteren Ende des Solution Explorers finden. Öffnen Sie sämtliche dort befindli-
chen Ordner, um einen Überblick über die zur Verfügung stehenden Ressour-
cen zu erhalten:
Abb. 4.6
Die Ressourcen für das
geöffnete Projekt
Sie finden hier eine kleine Anzahl von Ressourcen, die allesamt im ZeichenPro-
gramm-Projekt untergebracht sind. Wir wollen nun im Einzelnen schauen,
wofür die einzelnen Ressourcen zuständig sind. Zum näheren Betrachten, was
sich hinter einer Ressource verbirgt, ist es ausreichend, sie mit einem Dop-
pelklick anzuwählen, woraufhin sich weitere Fenster zum Editieren der enthal-
tenen Daten öffnen.
100
Entwicklung von dialogfeldbasierten Anwendungen
4
Die Versionsressource
Zu einem Projekt gehören vielfältige Informationen, wenn man es richtig ein-
und zuordnen möchte. Zu diesen Daten zählen beispielsweise der Name des
Entwicklers – gegebenenfalls einer Entwicklerfirma –, eine kurze Beschreibung
des Projektinhalts, aber auch die Versionsnummer und dergleichen mehr.
Die Versionsressource enthält genau diese Arten von Informationen und es ist Applikations-
angebracht zu Beginn der Entwicklung eines Projekts diese eben genannten informationen
Angaben in entsprechender Form zu machen.
Ein Doppelklick auf die Versionsressource öffnet ein Fenster, in dem Sie die für
Ihr Projekt notwendigen Einstellungen festlegen können:
Abb. 4.7
Die Einstellungs-
optionen der Versions-
ressource
Die Zeichenkettenressource
Eine wichtige Ressource ist die Zeichenkettenressource, da sie optimalerweise
sämtliche innerhalb eines Projekts verwendete Texte enthalten sollte. Dazu
zählen sowohl Menüeinträge als auch Beschriftungen von Schaltflächen oder
Ausgaben in einem Fehlerfall.
101
Grundlegender Entwurf einer MFC-Anwendung
4
Externe Texte Der Vorteil liegt auf der Hand: durch das Entkoppeln der Anwendung von den
Texten ist es leicht möglich, ein bestehendes Programm unkompliziert auf
andere Sprachen zu übertragen.
Wurden die Texte statt dessen fest im Quelltext verankert, ist es sehr mühsam,
diese per Hand herauszufiltern und entsprechend anzupassen – insbesondere
eine Arbeit, die ein der Programmierung unkundiger Übersetzer gar nicht
selbstständig durchführen könnte.
In der Praxis verzichtet man häufig darauf, die Beschriftungen von Schaltflä-
chen oder anderen Steuerelementen dynamisch beim Programmstart aus der
Zeichenkettenressource auszulesen und den jeweiligen Kontrollen zuzuweisen.
Das liegt jedoch einfach daran, dass es mithilfe der Dialogfeldeditoren norma-
lerweise ein Leichtes ist, die Beschriftung der Schaltflächen zu verändern.
Außerdem behält man sich dadurch den Vorteil bei, die Beschriftungen in
ihrem wirklichen Kontext zu sehen – es gibt vermutlich nichts Schlimmeres, als
schlecht übersetzte Programmversionen: man stelle sich einen Bundesligama-
nager vor, der vom Englischen ins Deutsche übertragen wird und aus Fans mal
eben Ventilatoren macht.
Abb. 4.8
Die Zeichenketten-
ressource
Die Iconressource
Die Iconressource enthält sämtliche Icons, die im Rahmen der Anwendung
benötigt werden, also Beispielsweise das Anwendungssymbol, das bei einem
Hauptfenster in der linken oberen Ecke dargestellt wird.
Editieren von Icons Das Microsoft Visual Studio .NET bietet einige rudimentäre Möglichkeiten zum
Editieren dieser Symbole, gestattet es aber auch, diese mit anderen Program-
men erzeugen zu lassen und dann in den Iconkontext zu übertragen.
102
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.9
Der Icon-Ressourcen-
editor
Die Dialogressourcen
Die für das aktuelle Projekt wichtigsten Ressourcen dürften diejenigen sein, die
das Aussehen und Verhalten der Dialoge – zum einen des Hauptdialogs, zum
anderen des Infodialogs – beschreiben.
Sie finden diese Ressourcen im Ordner Dialog. Dabei heißt der Infodialog Namen der Dialoge
IDD_ABOUTBOX, der Hauptdialog IDD_ZEICHENPROGRAMM_DIALOG:
Abb. 4.10
IDD_ABOUTBOX
103
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.11
IDD_ZEICHEN-
PROGRAMM_DIALOG
Dieses Prinzip gilt natürlich auch für Dialoge, nur dass hier die einzelnen Fens-
ter als Kontrollen (englisch Controls) oder Steuerelemente bezeichnet werden.
Hinzufügen und Sie können in eine Dialogressource jedes beliebige Steuerelement einfügen,
Editieren von Steuer- positionieren, in der Größe verändern und ihr passende Eigenschaften mit auf
elementen den Weg geben – dazu zählt auch das Festlegen einer für die Kontrolle einzigar-
tigen ID, über die dann später vom Programm aus Zugriffe auf das Steuerele-
ment stattfinden können.
104
Entwicklung von dialogfeldbasierten Anwendungen
4
Die Dialogfeldressourcen-Toolbox
Abb. 4.12
Die komplette Toolbox
mit den verfügbaren
Steuerelementen
Die Toolbox umfasst sämtliche Kontrollen, die Sie in Ihren Programmen einset-
zen können. Sie können dieses Fenster über das Menü View > Toolbox
(S+A+x) öffnen. Der folgende Abschnitt stellt die verfügbaren Steuerele-
mente vor, die Sie aus dieser Toolbox heraus in Ihre Dialoge einbauen können.
Abb. 4.13
Knopf
Der gewöhnliche Knopf (auch Schaltfläche oder englisch Button) wird in der
Regel für Aktionen benutzt, die sofort ausgeführt werden sollen. Prominentes-
tes Beispiel sind sicherlich die OK- und Abbrechen-Knöpfe, wie sie in den meis-
ten Dialogboxen vorkommen (unter anderem auch im Rahmen dieses Projekts).
Abb. 4.14
Checkbox
105
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.15
Eingabefeld
Abb. 4.16
Combobox
Die Combobox stellt sich im Ruhezustand als einfaches Editierfeld da, an des-
sen rechter Seite ein Dreieckssymbol angeordnet ist. Klickt man auf dieses, öff-
net sich eine Tabelle der für dieses Feld gültigen Optionen. Weiterhin bietet
sich die Möglichkeit, Comboboxen so anzulegen, dass auch neue Einträge hin-
zugefügt werden können. Diese neuen Einträge werden dann in der Regel beim
nächsten Öffnen des Dialogfelds in der angehängten Auswahlliste ebenfalls
angezeigt.
Es gibt eine erweiterte Combobox, die darüber hinaus die Möglichkeit bietet,
Icons neben den einzelnen Texteinträgen darzustellen.
Abb. 4.17
Listenfeld
Das Listenfeld (Listbox) stellt in einer Liste eine Anzahl an möglichen Optionen
bereit, die nicht editierbar sind. Reicht der für das Kontrollelement vorgesehene
Platz nicht aus, wird automatisch eine Scrollbar an den rechten Rand ange-
hängt, mit deren Hilfe in der Liste navigiert werden kann.
Abb. 4.18
Gruppenfeld
106
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.19
Radiobutton
Im Gegensatz zur Checkbox steht der Radio-Button für sich gegenseitig aus-
schließende Optionen zur Verfügung. Dieses Kontrollelement wird immer in
größerer Anzahl in die Dialogfelder eingestreut, um beispielsweise übersicht-
lich verschiedene Konfigurationsmöglichkeiten zur Verfügung zu stellen. Bei
einem Spiel beispielsweise könnten Sie vielleicht zwischen einer 800*600 und
einer 1.024*768 Bildauflösung wählen – beides gleichzeitig ist natürlich nicht
praktikabel.
Abb. 4.20
Statischer Text
Das vermutlich einfachste Kontrollelement ist der statische Text (engl. static
text). Er dient in erster Linie zum Beschriften der anderen, komplexeren Ele-
mente, aber auch zur einfachen Textausgabe innerhalb von Dialogfeldern.
Abb. 4.21
Grafiken
Abb. 4.22
Horizontale Scrollbar
Abb. 4.23
Vertikale Scrollbar
107
Grundlegender Entwurf einer MFC-Anwendung
4
Die von den Fenstern her bekannten Scrollbars gibt es sowohl in einer horizon-
talen wie auch in einer vertikalen Ausführung. Sie kommen immer dann zur
Anwendung, wenn größere Ausschnitte oder Listen nicht komplett in den dafür
vorgesehenen Bereich passen und die Ansicht daher verschoben werden muss.
Abb. 4.24
Slider
Der Slider (Schieberegler) findet seinen Einsatz in vielen Bereichen heutiger
Applikationen. Sei es, dass mit ihm die Zoomgröße einer Grafikverarbeitungs-
software eingestellt, oder der Kompressionsgrad eines MP3-Enkoders festge-
legt wird, seine Verwendung liegt immer dann nahe, wenn variable Werte
nicht punktgenau festgelegt werden müssen, sondern es in erster Linie auf
schnelle Bedienung ankommt. Meistens befindet sich ein Editierfeld in der
unmittelbaren Umgebung, das den exakten Wert der momentanen Schie-
bereglerstellung angibt.
Abb. 4.25
Spin
Der Spin (nicht zu verwechseln mit dem vertikalen Scrollbar), dient zum schnel-
len Durchlaufen einer Reihe von Elementen. Er wird jedoch aufgrund von Lis-
tenfeldern und Comboboxen (die diese Funktionalität bereits beinhalten) nicht
sehr häufig eingesetzt.
Abb. 4.26
Fortschrittsbalken
Abb. 4.27
Hotkeys
Hin und wieder ist es für den Benutzer eines Programms praktisch, wenn er
sich selbst aussuchen kann, über welche Tastenkombination eine bestimmte
Aktion der Applikation ausgeführt werden kann – als Beispiel sei ein Screen-
shot-Tool genannt, dass nach dem Drücken der jeweiligen Tastenkombination
eine Kopie des Bildschirminhalts anfertigt. Zum Festlegen solcher Tastenkom-
108
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.28
Listkontrollfelder
Abb. 4.29
Baumstruktuanzeige
Abb. 4.30
Reiterbox
Die Tab Control (Reiterbox) dient zum platzsparenden Anordnen großer Infor-
mationsmengen oder Einstellungsmöglichkeiten innerhalb von Applikationen.
Dieses Kontrollelement könnte als die digitale Repräsentation eines Karteikas-
tens bezeichnet werden, bei dem jeder Reiter eine einzelne Karte darstellt.
Abb. 4.31
Beispiel für ein Anima-
tionssteuerelement
109
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.32
RichEdit-Eingabefeld
Abb. 4.33
Datumsauswahl-
element
Abb. 4.34
Monatskalender
Abb. 4.35
IP-Kontrollelement
IP-Felder kommen vor allem in netzwerkorientierten Anwendungen zum Ein-
satz. Sie ermöglichen die einfache Eingabe einer IP-Adresse zur weiteren Ver-
wendung durch das Programm.
110
Entwicklung von dialogfeldbasierten Anwendungen
4
Falls noch nicht geschehen, öffnen Sie jetzt das Hauptdialogfeld der Anwen-
dung mit dem Namen IDD_ZEICHENPROGRAMM_DIALOG. Suchen Sie dann aus Hinzufügen eines
der Toolbox das Steuerelement Gruppenfeld heraus und platzieren es in etwa Gruppenfelds
so wie in der nachfolgenden Abbildung (entweder durch eine Drag-&-Drop-
Operation von der Toolbox in das Dialogfeld oder durch Auswahl des Elements
und Aufziehen eines Kastens ähnlich der Markierung eines Bereichs in einem
Zeichenprogramm):
Abb. 4.36
Das eingefügte
Gruppenfeld
Dieses Gruppenfeld soll uns als Zeichenfläche dienen, beschreibt also gerade Gruppenfeld als
den Bereich, den der Benutzer mit der Maus einfärben oder salopp gesagt, Zeichenfläche
bemalen kann. Was zunächst wie ein einfacher Rahmen mit einer Beschriftung
am oberen Rand aussieht, ist in Wirklichkeit ein komplexes Gebilde, bestehend
aus einer Vielzahl von Attributen, Eigenschaften und Methoden.
Das liegt in letzter Instanz daran, dass es sich ja wie bereits mehrfach erwähnt,
um ein eigenständiges Fenster handelt und somit eben auch entsprechende
Beschreibungsmerkmale aufweisen muss.
Einen Überblick über die Eigenschaften eines Gruppenfelds erhalten Sie durch Gruppenfeld-
einen Blick in das Properties-Fenster. Ist dieses zur Zeit nicht aktiviert, rufen Sie eigenschaften
durch einen Rechtsklick das Kontextmenü des Hauptdialogs auf und wählen
den Punkt Eigenschaften.
111
Grundlegender Entwurf einer MFC-Anwendung
4
Sie sollten nun in der linken unteren Bildschirmecke das erwähnte Fenster vor-
finden können. Klicken Sie jetzt das Gruppenfeld an und der Inhalt der Property-
Übersicht wird mit den Werten dieses Steuerelements aktualisiert:
Abb. 4.37
Eigenschaften des
Gruppenfelds
Titel des Gruppenfelds Zum einen geht es dabei um den Text, der am oberen Rand des Steuerelements
ändern ausgegeben werden soll – Sie finden den zugehörigen Eintrag unter dem Punkt
Caption. Tragen Sie hier das Wort Zeichenfläche ein und bestätigen Sie die Ein-
gabe mit M. Der Inhalt wird sofort in die Dialogansicht übernommen.
Die andere interessante Einstellung verbirgt sich hinter der lapidaren Bezeich-
nung ID und kennzeichnet den Zahlenwert, über den die zugrunde liegende
Kontrolle vom Rest des Programms aus referenziert werden kann.
Sie als Programmierer brauchen nicht direkt mit dem Zahlenwert herumzuhan-
tieren, sondern erhalten den Zugriff über Konstanten, die gerade den entspre-
chenden Wert beschreiben.
ID_STATIC Initial ist hier ID_STATIC eingetragen, was als Standardkonstante für sämtliche
fixe, unveränderliche Elemente eines Dialogfelds gilt. Das heißt aber gleichzei-
tig, dass Sie auch nicht direkt auf diese Kontrolle zugreifen können, da es prinzi-
piell beliebig viele Steuerelemente mit einer ID von ID_STATIC geben kann.
112
Entwicklung von dialogfeldbasierten Anwendungen
4
Für das Zeichenprogramm ist es wichtig zu wissen, wie groß das Gruppenfeld
ist, um dem Benutzer zu verbieten, über dessen Rand hinaus zu zeichnen.
Daher ändern Sie die ID auf IDC_ZEICHENFLAECHE1, was es Ihnen in der Folge
erlauben wird, die Position und Dimensionen des Steuerelements auszulesen.
Applikationsdateien
Nach diesen einleitenden Vorbereitungen wird es Zeit, einen Blick auf die vom
Anwendungsassistenten erzeugten Dateien zu werfen. Nachdem Sie ja bereits
eine MFC-Applikation per Hand entwickelt haben, ist es Ihnen während dieses
Vorgangs möglich, Parallelen zwischen Ihren Quelltexten und den automatisch
generierten Files zu ziehen.
Die Basis einer Anwendung ist ihr Applikationsobjekt, das, wie Sie wissen, die Implementation der
Instanz einer von CWinApp abgeleiteten Klasse ist. Die Deklaration für eben Applikationsklasse
diese Kindklasse findet sich in der Datei ZeichenProgramm.h und ist im Folgen-
den abgedruckt:
#pragma once
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this
file for PCH
#endif
// CZeichenProgrammApp:
// Siehe ZeichenProgramm.cpp für die Implementierung
// dieser Klasse
//
// Überschreibungen
public:
virtual BOOL InitInstance();
// Implementierung
113
Grundlegender Entwurf einer MFC-Anwendung
4
DECLARE_MESSAGE_MAP()
};
#include "stdafx.h"
#include "ZeichenProgramm.h"
#include "ZeichenProgrammDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CZeichenProgrammApp
BEGIN_MESSAGE_MAP(CZeichenProgrammApp, CWinApp)
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
// CZeichenProgrammApp-Erstellung
CZeichenProgrammApp::CZeichenProgrammApp()
{
// TODO: Hier Code zur Konstruktion einfügen
// Alle wichtigen Initialisierungen in
// InitInstance positionieren
}
114
Entwicklung von dialogfeldbasierten Anwendungen
4
// CZeichenProgrammApp Initialisierung
BOOL CZeichenProgrammApp::InitInstance()
{
// InitCommonControls() ist für Windows XP
// erforderlich, wenn ein Anwendungsmanifest
// die Verwendung von ComCtl32.dll Version 6
// oder höher zum Aktivieren
// von visuellen Stilen angibt. Ansonsten treten
// beim Erstellen von Fenstern Fehler auf.
InitCommonControls();
CWinApp::InitInstance();
CZeichenProgrammDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Fügen Sie hier Code ein, um das
// Schließen des Dialogfelds über OK zu
// steuern
}
else if (nResponse == IDCANCEL)
{
// TODO: Fügen Sie hier Code ein, um das
// Schließen des
// Dialogfelds über "Abbrechen" zu
// steuern
}
115
Grundlegender Entwurf einer MFC-Anwendung
4
Eine ID_HELP Botschaft wird immer dann ausgelöst, wenn der Benutzer wäh-
rend des Programmablaufs ! drückt. Sie kennen dieses Vorgehen sicherlich
aus anderen Windows-Programmen – wenn Sie an einer Stelle nicht weiter
wissen, oder beispielsweise erfahren möchten, was die einzelnen Einstellmög-
lichkeiten einer Programmoption bedeuten, öffnet das Drücken auf ! häufig
eine Hilfeseite mit weiterführenden Informationen.
Es ist klar, dass diese Informationen vom Entwickler des Programms selbst
stammen müssen und nicht vom Anwendungsassistenten oder gar dem Pro-
gramm selbst erzeugt werden können.
Abb. 4.38
Die Fehlermeldungen
bei einer vorhande-
nen Hilfedatei
116
Entwicklung von dialogfeldbasierten Anwendungen
4
Bei der Programmierung im Rahmen der MFC müssen Sie allerdings etwas
mehr Vorsicht walten lassen. Häufig drängt sich der Wunsch auf, wichtige
Fensterinitialisierungen – beispielsweise das Sichern von Größen- und Positi-
onsangaben wie im Falle des Gruppenfelds – direkt beim Programmstart vorzu-
nehmen und somit in den Konstruktor entweder der Applikations- oder der
Dialogfeldklasse zu verfrachten.
Beide Stellen sind hierfür jedoch ungeeignet, wird das benötigte Fenster – und
insbesondere die enthaltenen Steuerelemente – doch erst in einer der Initiali-
sierungsmethoden erzeugt. Es wird unweigerlich zu einem Ausnahmefehler
kommen, der gerade bei Anfängern der MFC-Programmierung häufig für viel
Verwirrung sorgt.
Die InitInstance-Methode
Nachdem der Konstruktor bereits als nur bedingt geeignet zur Initialisierung
bezeichnet wurde, geht das Augenmerk auf die InitInstance-Methode der Appli-
kationsklasse über.
Diese unterscheidet sich nun doch ein wenig von dem, was Sie zuvor per Hand
geschrieben haben, sodass eine nähere Betrachtung nötig wird.
Die ersten Zeilen beziehen sich auf ein mögliches Anwendungsmanifest, das Windows XP
im Rahmen von Windows XP die Verwendung von weiterführenden Kontroll-
elementstilen vorschreibt. Ein Weglassen dieses Aufrufs würde beim Erzeugen
der jeweiligen Fenster Fehler verursachen.
Basisklassenmethoden
Das Aufrufen von Basisklassenmethoden ist innerhalb der MFC weit verbrei-
tet. Der Grundgedanke dahinter ist, dass jede abgeleitete Klasse immer nur
die Aktionen durchführt, die speziell für sie selbst notwendig sind und für
alle globaleren Geschehnisse die Basisklassenmethode derselben, abgeleite-
ten Methode aufruft.
117
Grundlegender Entwurf einer MFC-Anwendung
4
Bei Klassen, die sich direkt von MFC-Klassen ableiten richten Sie sich am bes-
ten nach den durch den Assistenten – gleich ob Klassen-, Funktions- oder
Anwendungsassistenten – erzeugten Hinweisen. So steht normalerweise in
jeder automatisch generierten Methode an einer Stelle ein Satz ähnlich
„Hier Erweiterungen einfügen“ oder schlicht „Todo“.
Dialogkonstruktion Zunächst muss man wissen, dass jeder Dialog, der im Ressourceneditor kon-
struiert wird, zunächst nichts anderes als eben dieses ist: eine Ressource.
Ressourcen sind im Falle von Dialogen mit Grafiken vergleichbar, wobei diese
speziellen Bilder aus einem Hintergrund (dem Dialogfeldrahmen mit seiner Cli-
ent Area) und darauf platzierten weiteren Grafiken (Knöpfe, Gruppenfelder und
so weiter) bestehen.
Nun könnte man mit den Ressourcen an sich nichts weiter anfangen, daher ist
es notwendig, zu jeder Ressource eine zugehörige Klasse zu schreiben, die defi-
niert, was beim Anwählen der Steuerelemente passieren soll und wie sich der
Dialog allgemein zu verhalten hat, wenn der Benutzer des Programms mit ihm
interagiert.
118
Entwicklung von dialogfeldbasierten Anwendungen
4
Um den Dialog jetzt also im Programm zu verwenden, brauchen Sie lediglich Verwendung
ein Objekt dieser Klasse anlegen und dann über einen einfachen Funktionsauf- des Dialogs
ruf darzustellen.
Genau genommen haben Sie zum Darstellen sogar zwei Möglichkeiten. Wel-
che Sie wählen, hängt davon ab, ob der Dialog modal oder nicht-modal darzu-
stellen ist.
Das Hauptargument für ein modales Dialogfeld ist jedoch, dass es den restli-
chen Programmablauf einfriert. Das heißt, dass die aufrufende Funktion nicht
weiter abgearbeitet wird, solange der Dialog noch aktiv ist. In der Tat ist es so,
dass Sie in dieser Zeit absolut keine Möglichkeit haben, auf die restlichen Fens-
ter der Anwendung zuzugreifen.
Ein Beispiel für einen modalen Dialog wäre der von vielen Anwendungen her
bekannte Öffnen/Speichern-Dialog.
Im Gegensatz dazu erlauben es nicht-modale Dialoge, ganz normal mit der Vor- und Nachteile
Anwendung im Hintergrund weiterzuarbeiten, während sie geöffnet sind. Das nicht modaler Dialoge
Öffnen selbst findet über einen Aufruf der Methode CreateIndirect statt. Dieses
Verhalten ist bei nur recht wenigen Dialogen sinnvoll, beispielsweise bei einem
Suchen/Ersetzen-Dialog.
Das Verwenden von nicht-modalen Dialogen birgt eine ganze Reihe von
Schwierigkeiten, stellen Sie sich beispielsweise ein Aktion vor, die über ein Dia-
logfeld abgehandelt soll und mit den gerade im Dokument markierten Daten
arbeitet. Ändert sich diese Markierung – was aufgrund der weiterhin aktivier-
baren restlichen Anwendungsfenster ja ohne weiteres möglich ist – ist die
gesamte Handlung des Dialogs unter Umständen nicht mehr gültig oder
durchführbar.
119
Grundlegender Entwurf einer MFC-Anwendung
4
Auch unser Zeichenprogramm hat nach dem Schließen nichts weiter zu tun,
sodass hier keine Änderungen oder Erweiterungen einzufügen sind.
Die Hauptdialogklasse
Es ist nun schon einiges über die Klasse gesagt worden, die zur Darstellung des
Hauptdialogs herangezogen wird. Grund genug, sich einmal ihren Aufbau im
Detail anzusehen.
// CZeichenProgrammDlg Dialogfeld
class CZeichenProgrammDlg : public CDialog
{
// Konstruktion
public:
// Standardkonstruktor
CZeichenProgrammDlg(CWnd* pParent = NULL);
// Dialogfelddaten
enum { IDD = IDD_ZEICHENPROGRAMM_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV-
Unterstützung
// Implementierung
120
Entwicklung von dialogfeldbasierten Anwendungen
4
protected:
HICON m_hIcon;
Die darauf folgende Enumeration ist als Vereinfachung und Vereinheitlichung Definition von
anzusehen. Hier wird einer Konstanten IDD die ID der Dialogressource zugeord- Platzhaltern
net, die als Vorlage für die grafische Repräsentation dienen soll.
Dadurch, dass nun ein normierter Platzhalter IDD definiert wurde, können Sie
leicht unterschiedliche Implementationen einer Dialogklasse realisieren, die
vielleicht auf unterschiedlichen Dialogressourcen operieren, aber inhaltlich
recht ähnlich agieren – und insbesondere dieselben IDs umfassen.
Man denke hier zum Beispiel an die Möglichkeit, einem Anwender eine Reihe
von unterschiedlich aufgebauten Dialogfeldern zu präsentieren, die quasi als –
derzeit ja sehr beliebte – Skin-Grundlagen zwar dieselben Funktionalitäten auf-
weisen, aber halt doch ursprünglich andere Dialogressourcen als Basis haben.
Die durch DoDataExchange ermöglichte DDX/DDV (Dialog Data Exchange und Datenaustausch
Dialog Data Validation) Unterstützung dient kurz gesagt dazu, Werte zwischen mit Dialogen
dem Dialogfeld und Variablen auszutauschen und ihre Wertebereiche auf Gül-
tigkeit zu überprüfen.
121
Grundlegender Entwurf einer MFC-Anwendung
4
Als Beispiel sei ein Edit-Feld genannt, in das der Benutzer ganzzahlige Werte
eintragen darf. Diese sollen allerdings in einem Bereich zwischen 0 und 12 blei-
ben.
Bei der Konstruktion der Dialogressource würden Sie diesem Edit-Feld eine
Membervariable zuordnen, die den Wert bei einem Quittieren des Dialogs mit
OK übernehmen soll.
Angenommen, der Benutzer trägt in das Edit-Feld die Zahl 99 ein, so ist dieser
Wert natürlich höher als der maximal Erwünschte. Hier kommt DDV zum Tra-
gen. Vor dem Beenden des Dialogs werden sämtliche einstellbaren Werte auf
Gültigkeit überprüft.
Prüfen ungültiger Bei unzulässigen Werten weist eine Meldung den Anwender darauf hin, dass
Werte seine Eingabe nicht korrekt war und verändert werden muss – der Dialog wird
also an dieser Stelle nicht beendet, sondern wieder in den Vordergrund
gebracht.
Sind die Werte hingegen allesamt korrekt, tritt DDX auf den Plan und transfe-
riert die Informationen der Dialogfeld-Steuerelemente in die zugeordneten
Membervariablen.
Abb. 4.39
DDV in Aktion
Es ist wichtig zu wissen, dass dieses Übertragen tatsächlich erst beim Beenden
eines Dialogfelds automatisch durchgeführt wird, nicht etwa schon bei der Ein-
gabe. Es ist jedoch auch während der Lebenszeit eines Dialogs möglich, die
DDX/DDV-Funktionalität manuell auszulösen.
122
Entwicklung von dialogfeldbasierten Anwendungen
4
Grau ist alle Theorie, daher hier nun die tatsächliche Implementation der eben
kurz angerissenen Methoden:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// Dialogfelddaten
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
// DDX/DDV-Unterstützung
// Implementierung
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
// CZeichenProgrammDlg Dialogfeld
123
Grundlegender Entwurf einer MFC-Anwendung
4
CZeichenProgrammDlg::CZeichenProgrammDlg(
CWnd* pParent /*=NULL*/)
: CDialog(CZeichenProgrammDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CZeichenProgrammDlg::DoDataExchange(
CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CZeichenProgrammDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
// CZeichenProgrammDlg Meldungshandler
BOOL CZeichenProgrammDlg::OnInitDialog()
{
CDialog::OnInitDialog();
124
Entwicklung von dialogfeldbasierten Anwendungen
4
void CZeichenProgrammDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // Gerätekontext zum
// Zeichnen
SendMessage(WM_ICONERASEBKGND,
reinterpret_cast<WPARAM>
(dc.GetSafeHdc()), 0);
125
Grundlegender Entwurf einer MFC-Anwendung
4
// Symbol zeichnen
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
Wir wollen uns also direkt um die im Anschluss daran befindlichen Methoden
der Klasse CZeichenprogrammDlg kümmern.
126
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.40
Die Ressource
IDR_MAINFRAME
Die Methode AfxGetApp liefert dabei einen Zeiger auf das Applikationsobjekt Zugriff auf das
zurück, über dessen LoadIcon-Funktion wiederum das Ressourcenhandle ange- Applikationsobjekt
fordert werden kann.
Hier könnten Sie beispielsweise noch weitere Prüfungen einfügen, die vielleicht
über DDX/DDV nicht durchzuführen sind. Denken Sie aber in jedem Fall daran,
die Basisklassenmethode aufzurufen, um das reibungslose Übertragen der
Variablen zu gewährleisten.
Die anschließende Message Map fasst noch einmal die zu behandelnden Bot-
schaften zusammen.
Zur Erinnerung: das Systemmenü erreichen Sie durch Anklicken des Applikati-
ons-Icons in der linken oberen Ecke des Fensters.
Zunächst wird geprüft, ob sich die ID des Menüeintrags im Bereich des System- Bereich von
menü IDs befindet. Sie werden in einem späteren Kapitel noch mehr über Menüeintrags-IDs
Menüs und deren Einsatz erfahren, für jetzt nur soviel: Systemmenü-Nachrich-
ten werden über eine eigene Fensternachricht bekannt gemacht, nämlich
gerade WM_SYSCOMMAND.
127
Grundlegender Entwurf einer MFC-Anwendung
4
Damit diese Nachricht aber auch gesendet wird, muss das auslösende Ereignis
– in der Regel halt ein Menüeintrag – eine ID in einem dafür vorgesehen Werte-
bereich haben. IDs sind ja, wie Sie sicherlich noch wissen, lediglich positive
ganzzahlige Werte, die zur einfacheren Verwendung durch eine Konstante
beschrieben werden können.
Begrenzung des Diese Einschränkung im möglichen Wertebereich, der dafür sorgt, dass nicht
Systemmenüs beliebig viele Einträge in das Systemmenü vorgenommen werden können, was
allerdings auch kaum sinnvoll sein dürfte, wird durch zwei aufeinander-
folgende ASSERT-Anweisungen auf Gültigkeit geprüft. ASSERTs werden im Rah-
men dieses Buches im Kapitel Debugging noch ausführlich behandelt werden.
Die folgenden Zeilen enthalten wiederum eine Reihe von weiterführenden Auf-
rufen, die ebenfalls erst in den folgenden Kapiteln erläutert werden können.
Kurz gesagt fordert der folgende Abschnitt das Systemmenü an – was durch
einen zurückgelieferten Zeiger auf eben dieses quittiert wird – und versucht
dann, die Info Über-Menüzeile in das Systemmenü einzugliedern.
Zur besseren Übersichtlichkeit wird bei dieser Gelegenheit auch gleich noch
eine Trennzeile eingearbeitet, die den neuen Punkt von den bereits bestehen-
den Kommandos Verschieben und Schließen trennt.
Festlegen eines Die restlichen Zeilen dieser InitDialog-Methode setzen das Applikations-Icon
Applikations-Icons für die Anwendung fest, nämlich gerade auf das, welches im Konstruktor aus
dem Ressourcenbereich der Applikation angefordert wurde.
Abhängig vom zweiten booleschen Parameter wird das große oder kleine Appli-
kations-Icon festgelegt. Je nach Situation wird eines dieser Icons angezeigt.
Hier legen Sie also fest, welche Symbole beispielsweise der Windows-Explorer
anzeigen soll, wenn als Darstellungsart kleine Symbole oder große Symbole
ausgewählt sind.
Als Rückgabewert der Funktion können Sie den Wert true angeben, falls Sie kei-
nem Steuerelement des Dialogs explizit den Fokus zuweisen wollen (in diesem
Fall setzt Windows den Fokus auf das erste gefundene Steuerelement des Dia-
logs) oder false, falls Sie einem Steuerelement den Fokus mithilfe der dafür
zuständigen Funktion SetFocus, die an dieser Stelle nicht weiter beschrieben
werden soll, zugewiesen haben – Windows lässt in diesem Fall den Fokus
unverändert.
Versäumen Sie, den Fokus manuell zu setzen, geben aber false als Rückgabe-
wert an, kann es zu einer Zugriffsverletzung bei der Ausführung der Anwen-
dung kommen.
128
Entwicklung von dialogfeldbasierten Anwendungen
4
Dies geschieht analog zum Aufruf des Hauptdialogs, also insbesondere durch
einen Einsatz der Funktion DoModal.
Es ist an dieser Stelle nicht erforderlich zu prüfen, wie der Anwender das Dia-
logfeld beendet hat. Zum einen, weil es hierfür ohnehin nur eine Möglichkeit
gibt, zum anderen, weil das Dialogfeld an sich unerheblich für die weitere Aus-
führung der Anwendung ist.
Das Informationsdialogfeld könnte prinzipiell auch nicht modal dargestellt Möglichkeit der nicht-
werden, sein Informationsgehalt und seine Funktionalität sind hinreichend modalen Darstellung
unkritisch.
Stimmt die ID nicht mit der des Informationsdialogfelds überein, wird die Stan-
dardbehandlung aus der Basisklasse aufgerufen – in diesem Fall also insbeson-
dere die ordnungsgemäße Abarbeitung der Kommandos Verschieben und
Schließen.
In ihrer Rohform jedoch macht die Behandlungsmethode der WM_PAINT Nach- Ausgangsform der
richt nichts dergleichen, sie lässt die Client Area des Dialogfelds unbeschrieben. WM_PAINT-Behand-
lungsmethode
Natürlich versteht man unter dem Client-Bereich eines Fensters normalerweise
alles innerhalb des Fensters aber außerhalb von Statuszeilen, Toolbars und so
weiter. Im Falle des Dialogfelds ist dieses ähnlich, doch überlagern Steuerele-
mente den Bereich der Client Area. Diese werden also in jedem Fall dargestellt,
egal, was in der OnPaint-Methode geschrieben steht.
In OnPaint wird zunächst geprüft, ob die Anwendung minimiert ist, oder nicht.
Ist sie es, braucht nur das Applikations-Icon in die verkleinerte Titelleiste der
Anwendung gemalt zu werden.
Das ist zwar nicht sonderlich aufregend, bedingt aber trotzdem eine Reihe von
Anweisungen, die vom Löschen der Titelzeile bis hin zur Ausrichtung des Sym-
bols und schließlich dem Zeichen des Icons reichen.
Es ist an dieser Stelle nicht notwendig, weiter auf die einzelnen Zeilen einzuge-
hen, da sie bei dem Großteil der Windows-Anwendungen, die Sie schreiben
werden, nicht verändert zu werden brauchen.
129
Grundlegender Entwurf einer MFC-Anwendung
4
Es bestünde an dieser Stelle allerdings durchaus die Möglichkeit, eine Art Ani-
mation durch ständig wechselnde Applikations-Icons zu erzeugen. Inwieweit
dieser Blickfang von einem potenziellen Anwender gewünscht wird, sei dahin-
gestellt.
WM_QUERYDRAGICON
Die letzte Methode, die in der Datei CZeichenprogrammDlg.cpp definiert ist,
dient zum Festlegen des Mauszeigers, der dargestellt werden soll, wenn ein
Fenster im minimierten Zustand verschoben wird.
Reaktion auf Überlegen Sie an dieser Stelle einmal, welche Aktionen der Benutzer durchfüh-
Benutzereingaben ren muss, um im Zeichenprogramm eine freihändige Linie zu zeichnen – dabei
einmal davon ausgehend, dass das Freihandwerkzeug sowie die gewünschte
Farbe bereits ausgewählt sind und nur noch der Zeichenvorgang durchzufüh-
ren ist.
2 Der Benutzer bewegt die Maus, entweder um den Mauszeiger für eine
neu zu zeichnende Linie neu zu positionieren, oder um einen laufenden
Zeichenvorgang bei gedrückter linker Maustaste fortzuführen.
3 Der Benutzer hatte die linke Maustaste bereits gedrückt, um einen Lini-
enzug zu zeichnen und lässt die Maustaste nun wieder los.
Diese Ereignisse werden, wie es unter Windows üblich ist, im Rahmen von
Fensternachrichten an das Dialogfeld gesandt, ohne derzeit von unserer Appli-
kation bearbeitet zu werden.
130
Entwicklung von dialogfeldbasierten Anwendungen
4
Es ist lediglich so, dass wir nicht explizit bestimmte Aktionen durchführen,
sondern uns, ohne eine passende Methode zu überschreiben, auf die Basis-
klassenmethoden verlassen, die im Zuge wiederum nur eine Standardbe-
handlung abarbeiten – im Zweifelsfall also auch nichts tun.
Während es bei Mausnachrichten noch einigermaßen schlüssig zu sein scheint, Erlernen von Nachrich-
welche Möglichkeiten hier überhaupt existieren (Drücken der Maustasten, tenzugehörigkeiten
Bewegen der Maus, Drehen eines Mausrads), kann dieses bei komplexen Kon-
trollelementen, die als eigenständige Fenster ebenfalls Windows-Nachrichten
empfangen können, durchaus komplizierter sein.
Allerdings ist dieses eine der Hürden, die gemeistert werden müssen, um tief in
die MFC-Entwicklung eintauchen zu können. Es führt kein Weg daran vorbei,
dass Sie sich einmal die Zeit nehmen, die einzelnen möglichen Fensternach-
richten zu studieren und grob zu überfliegen, wofür jede einzelne zuständig ist
– hierbei sind natürlich nur die Standard-Windows-Nachrichten gemeint, nicht
etwa applikationsspezifische Botschaften.
Im Anhang dieses Buchs finden Sie eine Auflistung der meisten Windows-
Nachrichten, wobei diese keinen Anspruch auf Vollständigkeit erheben kann
oder will. Nutzen Sie diese Quelle als Basis für Ihre weiteren Recherchen, zum
Beispiel im Rahmen der Visual Studio.NET Online-Hilfe.
Für dieses Projekt (und auch die im weiteren Verlauf des vorliegenden Buchs)
werden Ihnen die benötigten Fensternachrichten allerdings jeweils explizit
genannt werden.
131
Grundlegender Entwurf einer MFC-Anwendung
4
Im erscheinenden Dialogfeld finden Sie ganz oben eine Liste mit Symbolen, wie
sie auch im nachstehenden Screenshot festgehalten sind:
Abb. 4.41
Die Symbole des
Eigenschaften-Dialogs
Es ist wichtig, dass Sie sich mit diesen Symbolen vertraut machen, um bei der
späteren Entwicklung zügig mit Ihnen arbeiten zu können und nicht erst stän-
dig grübeln müssen, welches Symbol welche Informationen verbirgt.
Es ist dann häufig möglich, diesen Wert durch eine Untersuchung der
passenden Oberkategorie zu finden. Wollen Sie beispielsweise festle-
gen, dass ein Dialog Dateien per Drag & Drop übernehmen kann, wer-
den Sie schnell auf die Kategorie Behaviour (Verhalten) stoßen, in der es
ein auf Ihre Problemstellung passendes Attribut Accept Files gibt.
Festlegen des anzuzei- Die folgenden drei Symbole regeln, welche Arten von Informationen
genden Informations- angezeigt werden sollen. Die eigentlichen Eigenschaften eines Dialogs
gehalts sind über den linken Knopf dieser Gruppe zu aktivieren. Hier werden
feste Konstanten für die einzelnen Attribute festgelegt, beispielsweise
der in der Titelzeile eines Dialogs anzuzeigende Text oder der im Dialog-
feld zu verwendende Zeichensatz.
Der mittlere Knopf listet sämtliche Steuerelemente auf, die ein Dialog
besitzt. Sie bekommen so einen schnellen Überblick darüber, woraus Ihr
Feld im Einzelnen besteht. Wählen Sie eine der Kontrollen in dieser Liste
an, erscheint eine Zusammenfassung der Fensternachrichten, auf die
dieses Steuerelement reagiert.
132
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.42
Nachrichten für
den Hauptdialog
133
Grundlegender Entwurf einer MFC-Anwendung
4
Führen Sie dieses nun für die Nachricht WM_LBUTTONDOWN durch, Sie erhal-
ten darauf eine Ansicht ähnlich der folgenden:
Abb. 4.43
Hinzufügen einer
Behandlungsmethode
Hinzufügen einer Als Option steht hier nur <Add> OnLButtonDown zur Verfügung, was nichts
neuen Methode anderes bedeutet, als dass der Klassen- und Behandlungsmethodenassistent
nach Anwahl dieses Kommandos eine Methode OnLButtonDown in das beste-
hende Anwendungsgerüst integrieren wird, die gerade zur Behandlung der
nebenstehenden Botschaft WM_LBUTTONDOWN dienen wird.
Führen Sie diese Aktion nun aus, Sie werden daraufhin automatisch in der
Datei ZeichenprogrammDlg.cpp an den neu eingefügten Quelltextbereich ver-
setzt:
CDialog::OnLButtonDown(nFlags, point);
}
Der Inhalt dieser Methode ist nun relativ nichtssagend, in der Tat tut sie nichts
weiter, als die Basisklassenmethode OnLButtonDown der Klasse CDialog aufzu-
rufen.
Somit laufen Sie nicht Gefahr, ein lauffähiges Programm zu zerstören, nur weil
Sie eine in Wirklichkeit gar nicht benötigte Behandlungsmethode aus Versehen
in Ihr Projekt eingefügt haben.
Ausgabe von Hier bietet sich ein kleiner Trick an, den man immer dann einsetzen kann, wenn
Testzeilen man prüfen möchte, ob eine Behandlungsmethode aufgerufen wird – Sie wer-
den in einem späteren Kapitel eine elegantere Möglichkeit hierzu kennen ler-
nen, doch wird sich die nun beschriebene Vorgehensweise auch häufig in
anderem Kontext einsetzen lassen.
134
Entwicklung von dialogfeldbasierten Anwendungen
4
// Standardbehandlung aufrufen
CDialog::OnLButtonDown(nFlags, point);
}
Kompilieren Sie das neue Programm und führen es aus. Klicken Sie dann mit
der linken Maustaste ein beliebigen Punkt innerhalb der Client Area an:
Abb. 4.44
Ergebnis des
AfxMessageBox-
Aufrufs
Das ist schon recht erfreulich, ermöglicht es uns, nun Schritt für Schritt eine
sinnvolle Maustastenbehandlung zu implementieren.
Um nun feststellen zu können, ob der Benutzer in einen erlaubten Bereich Verwendung der
geklickt hat, müssen Sie die Mausposition zum Zeitpunkt des Klicks abfragen. OnLButtonDown-
Praktischerweise wird diese Information der OnLButtonDown-Methode als Parameter
Parameter point direkt mitübergeben:
// Position ausgeben
AfxMessageBox(lpszAusgabe);
135
Grundlegender Entwurf einer MFC-Anwendung
4
// Standardbehandlung aufrufen
CDialog::OnLButtonDown(nFlags, point);
}
CPoint ist eine der Werkzeugklassen der MFC und ist äquivalent zum vielleicht
bekannten Struct POINT. Die Klasse enthält also insbesondere eine X- und eine
Y-Komponente als Integerwerte, die in dieser OnLButtonDown-Methode via
sprintf-Funktion in einen String transferiert werden.
Abb. 4.45
Eine Beispielausgabe
Diese Mausposition ist relativ zur linken oberen Ecke der Client Area anzusehen
(nur hier werden Mausklicks entgegen genommen).
Bestimmen der Da Sie das Gruppenfeld in jeder beliebigen Größe aufgezogen haben könnten,
Gruppenfeldgröße bietet es sich hier nicht an, mit absoluten Zahlwerten zu arbeiten und bei-
spielsweise zu sagen: Jeder Klick im Rechteck von (10, 10) bis (100, 100) relativ zur
linken oberen Ecke der Client Area ist gültig.
Vielmehr müssen die hier fixierten Werte durch die tatsächlichen Koordinaten
des Gruppenfelds ersetzt werden.
Wie Sie an diese Informationen gelangen können, zeigt die nächste Erweite-
rungsstufe von OnLButtonDown:
136
Entwicklung von dialogfeldbasierten Anwendungen
4
AfxMessageBox(lpszAusgabe);
AfxMessageBox(lpszAusgabe);
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
Diese Version sieht schon etwas komplizierter als ihre Vorgänger aus – was an
einer kleinen Problematik des Koordinatengültigkeitsbereichs liegt, wie Sie
gleich sehen werden.
Wird eine ungültige ID spezifiziert, ist der Rückgabewert NULL. Aus Gründen Prüfung auf Fehler
der Übersichtlichkeit wurde hier jedoch auf eine explizite Fehlerabfrage ver-
zichtet. Das Gleiche gilt für die weiteren Beispiele dieses Buches. Der Leser ist
angehalten, sich stets um eine vernünftige Absicherung seiner Programme zu
sorgen, sodass fehlerhafte oder unerwartete Rückgabewerte möglichst früh
abgefangen werden und nicht später im Programmablauf für Probleme sorgen.
Haben Sie erst einmal einen Zeiger auf ein Steuerelement, können Sie über die Daten aus
Membermethode GetWindowRect leicht die so genannte Boundingbox der Steuerelementen
Kontrolle auslesen. Eine Boundingbox ist der weit verbreitete englische Begriff auslesen
für ein eine Sache umschließendes Rechteck, beziehungsweise einen umschlie-
ßenden Kasten im dreidimensionalen Raum.
137
Grundlegender Entwurf einer MFC-Anwendung
4
Der Haken bei der Sache offenbart sich beim Anzeigen der ermittelten Werte:
sie sind absolut beziehungsweise relativ zur linken oberen Ecke des Bild-
schirms, nicht zu der der Client Area.
Koordinatenumrechnungen
Es muss also geeignet umgerechnet werden, was die Funktion ScreenToClient
erledigt, die als Argument einen Zeiger auf eine Rechteckstruktur übernimmt,
die wiederum im Laufe der Funktionsabarbeitung auf die relativen Clientwerte
umgerechnet wird.
Systemausrichtungen Bei der Entwicklung unter Windows müssen Sie stets darauf achten, mit den
richtigen Werteinheiten zu arbeiten, insbesondere darauf, ob sie im richtigen
System oder relativ zu einem sinnvollen Ursprung angegeben sind.
Im Falle des Mausklicks wäre es zum Beispiel wenig sinnvoll, den relativen
Mausklickpunkt mit dem absoluten Gruppenfeldpunkt zu vergleichen – das
Resultat wäre im günstigsten Fall verwirrend, aber vermutlich ohne Kenntnisse
über diese unterschiedlichen Koordinatenangaben nur schwer zu korrigieren.
Kompilieren Sie das neue Programm und führen Sie es aus. Das Ergebnis der
Werteumrechnung wird Ihnen nach einem Mausklick in die Client Area des
Dialogs präsentiert:
Abb. 4.46
Position des
Gruppenfelds
Eine mögliche Lösung für diese Fragestellung könnte wie folgt aussehen:
138
Entwicklung von dialogfeldbasierten Anwendungen
4
->GetWindowRect(&FensterRechteck);
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
Beim Überprüfen der Klickposition wird in der obigen Methode ein Offset (also
Abstand) von 8 Pixeln mit in die Überprüfung eingerichtet. Damit wird dem
Umstand entgegengebeugt, dass es sich bei der Boundingbox des Gruppen-
felds ja tatsächlich um ein umschließendes Rechteck handelt, das also auch die
Beschriftung am oberen Rand des Felds beinhaltet. Dieser Bereich soll natürlich
nicht übermalt werden können, sodass durch den Offset eine Marge von 8
Pixeln freigehalten wird. Das reicht bei den Standardzeichensatzeinstellungen
gerade aus, um den Rand des Gruppenfelds von Zeicheneinflüssen frei zu hal-
ten.
Gerätekontexte
Nun, da Sie endlich wissen, ob der Benutzer einen gültigen Punkt angeklickt
hat, ist es interessant zu wissen, wie man diesen Punkt letztendlich auch zeich-
nen kann. Hierzu ist es zunächst erforderlich, einen groben Überblick über das
Konzept der Gerätekontexte zu erlangen.
139
Grundlegender Entwurf einer MFC-Anwendung
4
Zeichenflächen in der Gerätekontexte sind inhaltlich eine Art Zeichenfläche, auf der beliebige
Windows- Informationen eingetragen werden können – also beispielsweise die von einem
Programmierung Benutzer gezeichneten Punkte oder auch eine einfache Textausgabe.
Gerätekontexte kommen innerhalb des Konzepts der GDI (Graphic Device Inter-
face, etwa: Schnittstelle für grafikfähige Ausgabegeräte) vor. Die GDI stellen
eine Abstraktionsschicht zwischen Ihnen und dem tatsächlichen Ausgabegerät
(einem Monitor etwa, oder einem Drucker) dar.
Durch diese Abstraktionsschicht ist es möglich, dass Sie als Entwickler mit
einem Gerätekontext in beliebiger Weise arbeiten können und sich nicht
darum sorgen müssen, wie die von Ihnen eingefügten Informationen auf dem
Zielgerät ausgegeben werden. Der Vorteil liegt auf der Hand: befinden sich die
gewünschten Daten erst einmal im Gerätekontext, können Sie (durch die inter-
nen Funktionen der GDI) ohne Probleme und vor allem ohne weiteren program-
miertechnischen Aufwand auf jedes beliebige Ausgabegerät weitergeleitet
werden.
Ausgabe auf Wenn Sie also ein Zeichenprogramm geschrieben haben, dass eine Grafik auf
verschiedene Medien dem Bildschirm darstellt, können Sie die zugehörige Grafik genauso gut auf
den Drucker ausgeben, ohne irgendwelche Einstellungen an Ihrem Programm
verändern zu müssen.
In den meisten Fällen ist es ausreichend, sich den Gerätekontext als eine Art
Leinwand vorzustellen und so soll es innerhalb dieses Buches auch gehandhabt
werden. Sind hiervon abweichende Betrachtungsweisen notwendig, werden
Sie an entsprechender Stelle darauf hin gewiesen.
CClientDC Das Anfordern geschieht über das Erzeugen eines neuen Objekts vom Typ CCli-
entDC. Der Name deutet es bereits an, es geht hierbei um einen Gerätekontext,
der den Client-Bereich des Dialogs virtuell repräsentiert. Der Konstruktorpara-
meter this stellt klar, dass ein Gerätekontext für die eigene Instanz, in unserem
Fall das Hauptdialogfeld, erbeten wird.
Farbwerte Letzterer wird als COLORREF-Wert spezifiziert, Sie erzeugen solche Werte durch
Verwendung des Makros RGB, das als Parameter den Rot-, Grün sowie Blauan-
teil des zu erzeugenden Farbwerts enthält. RGB (0, 0, 0) erzeugt beispielsweise
gerade den RGB-Wert für die Farbe schwarz.
140
Entwicklung von dialogfeldbasierten Anwendungen
4
Starten Sie nun das Programm und versuchen Sie, einige Punkte zu setzen:
Abb. 4.47
Eine Beispielgrafik
Mausbewegungen registrieren
Bislang wird nur die WM_LBUTTONDOWN Nachricht behandelt, ein Anwender
des Zeichenprogramms kann also nur einzelne Punkte setzen.
Diese Funktionalität soll nun dahingehend erweitert werden, dass nach dem Zeichnen von Punkten
Drücken der linken Maustaste auch nach Mausbewegungen an die neuen
Mauspositionen Punkte gemalt werden, bis die Taste wieder losgelassen wird.
// CZeichenProgrammDlg Dialogfeld
class CZeichenProgrammDlg : public CDialog
{
// Konstruktion
public:
CZeichenProgrammDlg(CWnd* pParent = NULL);
// Standardkonstruktor
141
Grundlegender Entwurf einer MFC-Anwendung
4
// Dialogfelddaten
enum { IDD = IDD_ZEICHENPROGRAMM_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV-
Unterstützung
// Implementierung
protected:
HICON m_hIcon;
bool m_bLinkeMaustasteGedrueckt;
};
Vielleicht ist Ihnen beim bisherigen Zeichnen aufgefallen, dass der eingestellte
Offset für die seitlichen und den Rand zu groß wirken. Dieser Umstand soll nun
gleich mitbehoben werden, wenn wir in OnLButtonDown das Setzen der boole-
schen Variable m_bLinkeMaustasteGedrueckt einfügen:
142
Entwicklung von dialogfeldbasierten Anwendungen
4
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
143
Grundlegender Entwurf einer MFC-Anwendung
4
CDialog::OnMouseMove(nFlags, point);
}
Wie bei OnLButtonDown wird hier einfach der Gerätekontext des Client-
Bereichs angefordert und ein schwarzer Pixel an die neue Mausposition
gesetzt.
CDialog::OnLButtonUp(nFlags, point);
}
Testen Sie das so veränderte Programm aus und zeichnen Sie einige Figuren in
das Fenster. Was fällt Ihnen dabei auf?
144
Entwicklung von dialogfeldbasierten Anwendungen
4
Fehler im Programm
Die nachstehend abgedruckt Abbildung gibt einen Überblick über die Fehler,
die sich bei diesen Versuchen offenbaren werden:
Abb. 4.48
Die derzeitigen Fehler
des Programms im
Überblick
Die drei sichtbaren Linienzüge sind auf folgende Art und Weise entstanden:
Der obere Linienzug wurde korrekt innerhalb der Zeichenfläche begon- Fehler 1: Über-
nen, doch hat der Benutzer dann den Mauszeiger aus der Fläche hinaus- schreiten gültiger
bewegt. Da in OnMouseMove keine weitere Positionsabfragen Bereiche
vorgenommen werden, konnte der Anwender so in den eigentlich nicht
erlaubten Bereich hineinzeichnen.
Der mittlere Linienzug wurde ebenfalls korrekt innerhalb der Zeichen- Fehler 2: Weiter-
fläche begonnen. Der Benutzer hat dann die Maus aus dem Fenster zeichnen nach Los-
hinaus bewegt und dort, außerhalb des Dialogs, die Maustaste losge- lassen der Maustaste
lassen. Offensichtlich wurde die OnLButtonUp-Methode nicht aufgeru-
fen, denn beim Wiedereintritt des Mauszeigers in den Client-Bereich
des Dialogs wurde der Zeichenvorgang trotz nicht gedrückter Maus-
taste fortgesetzt.
Der untere Linienzug wurde korrekt innerhalb der Zeichenfläche begon- Fehler 3: Lücken
nen und beendet, doch hat der Benutzer den Mauszeiger mit schnellen beim Zeichnen
Bewegungen geführt. Scheinbar wurden dabei nicht alle Positionen des
Mauszeigers an das Fenster geliefert, sodass zwischen den einzelnen
Punkten deutliche Abstände zu sehen sind.
145
Grundlegender Entwurf einer MFC-Anwendung
4
Das Visual Studio bietet eine Reihe von Hilfestellungen für die Arbeit mit Klas-
sen und deren Funktionen, unter anderem eine Möglichkeit, neue Methoden
mithilfe eines Assistenten zu entwerfen und automatisch in das bestehende
Klassengerüst einzufügen.
Testen, ob gültige Zei- Diese Vorgehensweise soll nun demonstriert werden, indem eine Methode
chenposition vorliegt zum Testen einer gültigen Zeichenposition geschrieben wird – scheinbar brau-
chen wir diesen Test sowohl in OnLButtonDown als auch in OnMouseMove,
sodass es wenig Sinn machen würde, die betreffenden Zeilen in beide Metho-
den einzubinden.
Natürlich kommt es dabei jeweils auf den Einzelfall an, doch sollten Sie sich
in Ihren Überlegungen diesbezüglich nie von der Sorge des Geschwindig-
keitsverlusts der Anwendung leiten lassen – definieren Sie im Zweifelsfall die
betreffenden Funktionen einfach als Inline.
In jedem Fall wird sich die Zeitersparnis – weniger Tipparbeit und vor allem
weniger Wartungszeit, falls sich die Funktionalität einmal ändert – in sehr
kurzer Zeit bemerkbar machen.
146
Entwicklung von dialogfeldbasierten Anwendungen
4
Die Solution-Klassen
Um eine neue Methode in das bestehende Klassengerüst einzufügen, öffnen Hinzufügen neuer
Sie zunächst die Klassenansicht Ihres Projekts (zu finden innerhalb des Arbeits- Methoden
bereichs, der auch den Solution Explorer enthält).
Sie finden hier eine Übersicht über die bereits im Projekt befindlichen Klassen –
erweitern Sie die Klasse CZeichenProgrammDlg und Sie erhalten eine Übersicht
ähnlich der folgenden:
Abb. 4.49
Ansicht der Solution-
Klassen
Die Liste enthält sämtliche Methoden, Variablen, Basisklassen, Enumeration, Übersicht über
Strukturen und dergleichen mehr (unter anderem auch eine Übersicht der von Klasseninhalte
der Klassen verarbeiteten Fensternachrichten), die für die ausgewählte Klasse
definiert sind.
Ein Rechtsklick auf einen Klassennamen öffnet ein Kontextmenü, aus dem Sie
nun den Punkt Add > New Function auswählen. Dieses Kommando dient zum
Einfügen einer neuen Methode in die Klasse.
147
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.50
Dialogfeld zum Einfü-
gen neuer Methoden
Festlegen eines Sie können hier sämtliche Einstellungen vornehmen, die zum Erzeugen eines
Funktionsprototyps Funktionsprototyps notwendig sind, angefangen vom gewünschten Rückgabe-
typ, über benötigte Parameter (die über Add in eine Liste aufgenommen und
mit Remove von dort wieder entfernt werden) bis hin zu Zugriffsrechten,
zusätzlichen Modifikatoren (Inline, Virtual, Pure und Static) und einem Kom-
mentar, der über dem Funktionskopf ausgegeben wird.
148
Entwicklung von dialogfeldbasierten Anwendungen
4
149
Grundlegender Entwurf einer MFC-Anwendung
4
Es handelt sich hierbei gerade um die Zeilen, die auch in OnLButtonDown schon
zum Einsatz kamen, jedoch liefert die Methode IstAufZeichenflaeche als Rück-
gabe den Wahrheitsgehalt der Aussage, ob sich die übergebene Position inner-
halb der Zeichenfläche befindet (true bedeutet dabei ein positives Beantworten
der Frage, false, dass sich der Punkt außerhalb des Bereichs befindet).
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
CDialog::OnMouseMove(nFlags, point);
}
Testen Sie die neue Variante aus, werden Sie feststellen, dass der weiter oben
beschriebene erste Fehler erfolgreich behoben wurde.
150
Entwicklung von dialogfeldbasierten Anwendungen
4
Insofern ist es klar, dass der gewählte Ansatz (Benutzer lässt Maustaste los, Probleme bei fenster-
boolesche Variable m_bLinkeMaustasteGedrueckt wird auf false gesetzt) so spezifischen Nach-
nicht funktionieren kann, wenn der Anwender sich außerhalb des Dialogfelds richten
entscheidet, die Maustaste nicht weiter zu drücken – die zugehörige
WM_LBUTTONUP-Nachricht geht dann an das Fenster, das sich zu diesem Zeit-
punkt gerade unterhalb des Mauszeigers befindet.
Die dazu verwendete Funktion heißt SetCapture und sorgt dafür, dass auch Fixieren der Maus
Nachrichten, die außerhalb des Dialogs entstehen, an ihn weitergeleitet wer-
den. Dieses geschieht so lange, bis durch einen Aufruf von ReleaseCapture die
Exklusivrechte wieder abgegeben werden.
Fügen Sie die passenden Aufrufe nun in die OnLButtonDown- und OnLButto-
nUp-Methoden ein:
// Pixel zeichnen
dc.SetPixel(point.x, point.y, RGB(0,0,0));
// Maus festhalten
SetCapture();
}
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
151
Grundlegender Entwurf einer MFC-Anwendung
4
// Maus freigeben
ReleaseCapture();
CDialog::OnLButtonUp(nFlags, point);
}
Testen Sie das Programm nun erneut aus, auch der zweite Fehler ist behoben,
verbleibt noch die Problematik der versetzten Punkte.
Tricks für verbundene Stellen Sie sich die weiter oben abgedruckte Punktgrafik mit durch Linien ver-
Zeichnungen bundenen Punkten vor. Da die einzelnen Pixel nicht allzu weit auseinander lie-
gen, käme es einem Betrachter tatsächlich so vor, als würde ein runder,
geschwungener Kurvenzug dargestellt werden.
Es soll auch für unser Projekt gelten: Linien statt Punkte, doch dafür sind
zunächst einige Vorkehrungen zu treffen, insbesondere muss nämlich der
zuletzt gezeichnete Punkt gespeichert werden, da eine Linie ja immer einen
Start- und einen Endpunkt besitzt. Der gespeicherte Punkt stellt dann den
Startpunkt dar, die bei einer Mausbewegung angesteuerte neue Position den
Endpunkt.
152
Entwicklung von dialogfeldbasierten Anwendungen
4
Öffnen Sie die Klassenansicht, klicken Sie mit der rechten Maustaste auf die
CZeichenProgrammDlg-Klasse und wählen Sie das Kommando Add > New Vari-
able aus dem erscheinenden Kontextmenü:
Abb. 4.51
Dialogefeld zum
Hinzüfügen neuer
Variablen
Dieser Dialog ähnelt dem für die Methoden und bietet wiederum sämtliche Einstellungen für
Einstellmöglichkeiten, die man sich wünschen könnte – und darüber hinaus neue Variablen
einige mehr, die Ihnen derzeit noch nichts sagen werden, und zu denen wir in
einem späteren Kapitel zurückkehren.
An dieser Stelle sei nur auf die Felder Min Value und Max Value hingewiesen,
die gerade die bei DDX/DDV-Besprechung erwähnten Grenzwerte für gültige
Daten angeben. Sie sind an dieser Stelle nicht weiter relevant.
Tragen Sie als Variablentyp int und als -namen m_nStartPunktX ein, der
Zugriffsbereich wird mit Protected angegeben. Bestätigen Sie die Angaben
durch Anklicken von Fertig stellen und wiederholen Sie den Vorgang für eine
Variable m_nStartPunktY.
153
Grundlegender Entwurf einer MFC-Anwendung
4
// Pixel zeichnen
dc.SetPixel(point.x, point.y, RGB(0,0,0));
// Maus festhalten
SetCapture();
// Startposition merken
m_nStartPunktX = point.x;
m_nStartPunktY = point.y;
}
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
154
Entwicklung von dialogfeldbasierten Anwendungen
4
// Startpunkt anfahren
dc.MoveTo(m_nStartPunktX, m_nStartPunktY);
CDialog::OnMouseMove(nFlags, point);
}
Das Zeichnen einer Linie erweist sich als relativ einfach. Zunächst fahren Sie
mit der Gerätekontextfunktion MoveTo den Punkt an, an dem die Startposition
der Linie liegen soll.
Es ist dem Benutzer nur möglich, Linien zu zeichnen, wenn er zuvor auf einen Initialisieren eines
Startpunkt durch Drücken der linken Maustaste innerhalb eines gültigen Zeichenvorgangs
Bereichs auf der Zeichenfläche gesetzt hatte.
Der eigentliche Zeichenvorgang beginnt dann durch den Aufruf von LineTo, das
eine Linie zum angegeben Punkt (gerade der neuen Mausposition) zieht.
Dieser wird dann in der Folge als neuer Startpunkt festgelegt und erlaubt so
das Kreieren geschlossener Linienzüge, wie Sie leicht nachvollziehen können,
wenn Sie das fehlerbereinigte Programm nun kompilieren und starten:
155
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.52
Ergebnis des
Linienzeichnens
Farbauswahl
Erweiterung des Das Zeichenprogramm in seiner bisherigen Form ist noch relativ trist und soll
Programms ein wenig aufgepeppt werden. Dazu bietet es sich an, eine Möglichkeit zum
Auswählen von unterschiedlichen Farben anzubieten.
Öffnen Sie in der Ressourcenansicht den Hauptdialog und fügen vier Schaltflä-
chen hinzu (dabei brauchen Sie nicht sonderlich auf eine korrekte Platzierung
achten). Ändern Sie deren Beschriftungen mithilfe der Eigenschaftentabelle
auf Schwarz, Rot, Grün und Blau und vergeben Sie in derselben Tabelle die IDs
IDC_SCHWARZ, IDC_ROT, IDC_GRUEN und IDC_BLAU an die neuen Knöpfe.
Obwohl Sie mit einer deutschen Version des Visual Studio .NET arbeiten, kann
es doch zu Problemen führen, wenn Umlaute in IDs verwendet werden – schrei-
ben Sie also statt ä, ü, ö und ß immer ae, ue, oe und ss.
156
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.53
Die neuen Buttons in
ungeordneter Form
Sie finden allerdings ein Menü namens Format, wenn Sie mit Dialogen arbei- Formatieren von
ten, das eine ganze Reihe von Formatierungshilfsmitteln zur Verfügung stellt, Dialogen
unter anderem auch solche zum Ausrichten von Steuerelementen zueinander
oder zum Verteilen von Kontrollen über einen bestimmten Bereich.
Markieren Sie eine der Schaltflächen, drücken die Taste S und markieren
dann die restlichen. Sie werden bemerken, dass alle Schaltflächen bis auf eine
weiß umrahmt sind, die letzte jedoch blau. Dieses ist die Kontrolle, nach der die
anderen ausgerichtet werden, wenn Sie eine der Formatierungsfunktionen
benutzen. Sie können bei gedrückt gehaltener Taste S auch nachträglich
noch zwischen den bereits markierten Elementen hin und her wechseln, um
das Ankerobjekt zu verändern.
Wählen Sie dann zum Beispiel das Menükommando Format > Align > Tops, um
alle Buttons auf eine Höhe zu bringen. Spielen Sie ein wenig mit den Formatie-
rungsmöglichkeiten herum und richten Sie die neuen Schaltfläche nach Ihren
Wünschen auf dem Dialogfeld aus. In meinem Fall sieht das Ergebnis so aus:
157
Grundlegender Entwurf einer MFC-Anwendung
4
Abb. 4.54
Die Buttons wurden
sinnvoll ausgerichtet
Hinzufügen neuer Gehen Sie zurück in den Ressourceneditor und wählen Sie dort eine der Schalt-
Behandlungs- flächen aus. Wählen Sie im Eigenschaften-Fenster die Übersicht über die ver-
methoden fügbaren Fensternachrichten aus und fügen Sie eine Methode für BN_CLICKED
auf die bekannte Weise hinzu.
Übrigens, wenn Sie später einmal vom Ressourceneditor direkt an die Quell-
textstelle springen wollen, die zur Behandlung einer Nachricht vorgesehen ist,
wählen Sie das betreffende Fenster (also in diesem Fall den Dialog oder ein
Steuerelement) aus und wählen in der Nachrichtenliste die betreffende aus.
158
Entwicklung von dialogfeldbasierten Anwendungen
4
Abb. 4.55
Löschen und Editieren
von Behandlungs-
methoden
In letzterem Fall werden Sie die automatisch generierten, leeren Funktions- Automatisch ange-
rümpfe der vier neuen Methoden finden: legte Behandlungs-
methoden
void CZeichenProgrammDlg::OnBnClickedRot()
{
// TODO: Add your control notification handler
// code here
}
void CZeichenProgrammDlg::OnBnClickedGruen()
{
// TODO: Add your control notification handler
// code here
}
void CZeichenProgrammDlg::OnBnClickedBlau()
{
// TODO: Add your control notification handler
// code here
}
159
Grundlegender Entwurf einer MFC-Anwendung
4
Automatische Initiali- Hier zeigt sich ein weiterer Unterschied zwischen normaler und automatischer
sierung von Variablen Hinzufügung von Variablen: Durch Assistenten eingefügte Variablen werden
automatisch mit einem Startwert initialisiert, in diesem Fall die von Ihnen ein-
gebauten Integerwerte m_nStartPunktX und m_nStartPunktY, die jeweils mit 0
initialisiert wurden.
Farbige Punkte
Um nun auch farbige Punkte zeichnen zu können, muss in OnLButtonDown das
Setzen von Pixeln überarbeitet werden:
// Maus festhalten
160
Entwicklung von dialogfeldbasierten Anwendungen
4
SetCapture();
// Startposition merken
m_nStartPunktX = point.x;
m_nStartPunktY = point.y;
}
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
void CZeichenProgrammDlg::OnBnClickedRot()
{
m_AktuelleFarbe = RGB (255, 0, 0);
}
void CZeichenProgrammDlg::OnBnClickedGruen()
{
m_AktuelleFarbe = RGB (0, 255, 0);
}
void CZeichenProgrammDlg::OnBnClickedBlau()
{
m_AktuelleFarbe = RGB (0, 0, 255);
}
Sie sind jetzt in der Lage, farbige Startpixel zu setzen, doch die Linien sind
immer noch schwarz. Editieren Sie daher nun die OnMouseMove-Methode, die
ebenfalls in der Datei ZeichenprogrammDlg.cpp zu finden ist:
161
Grundlegender Entwurf einer MFC-Anwendung
4
(IstAufZeichenflaeche(point.x, point.y)))
{
// Gerätekontext anfordern
CClientDC dc(this);
// Stift vorbereiten
CPen StandardPen(PS_SOLID, 1,
m_AktuelleFarbe);
CPen *pOldPen =
dc.SelectObject(&StandardPen);
// Startpunkt anfahren
dc.MoveTo(m_nStartPunktX, m_nStartPunktY);
CDialog::OnMouseMove(nFlags, point);
}
Vielleicht hatten Sie sich bereits darüber gewundert, dass beim Zeichnen von
Linien keine Farbe explizit angegeben werden brauchte. Das liegt daran, dass
ein Gerätekontext eben nicht nur eine Leinwand ist, sondern auch sämtliche
Hilfsmittel zur Verfügung stellt, die ein Maler benötigen würde, wenn man das
Konzept einmal in die Realität übersetzt.
Dazu gehören unter anderem auch Stifte und es ist ein Stift, mit dem Linien
über die LineTo-Methode gezeichnet werden.
GDI-Objekte
Zeichenwerkzeuge Ein solcher Stift ist eins von zahlreichen GDI-Objekten, die jeweils durch eine
eigene Klassen im Rahmen der MFC repräsentiert werden. Die Klasse zur Arbeit
mit Stiften trägt den passenden Namen CPen.
Die einzelnen Parameter, die für GDI-Objekte verwendet werden können, fin-
den Sie im Anhang dieses Buchs aufgelistet, an dieser Stelle sei nur gesagt, dass
der im obigen Listing stehende Aufruf einen Stift mit der aktuell eingestellten
Farbe erzeugt, der eine Zeichendicke von einem Pixel hat und das Ziehen unun-
162
Entwicklung von dialogfeldbasierten Anwendungen
4
terbrochener Linien erlaubt – sie könnten Ihren Stift auch so erzeugen, dass er
automatisch gestrichelte oder gepunktete Linien zeichnet.
Dem Erzeugen eines Stifts folgt meistens die Verwendung in einem Gerätekon-
text. Dort kann immer nur ein GDI-Objekt eines bestimmten Typs (zum Beispiel
ein Stift) zurzeit aktiv sein, daher liefert die überladene SelectObject-Methode
einen Zeiger auf den bis dahin aktivierten Stift zurück.
So, nun haben Sie es endlich geschafft – es können farbige Bilder gezeichnet
werden. Kompilieren und starten Sie das Programm:
Abb. 4.56
Farbige Grafiken
163
Grundlegender Entwurf einer MFC-Anwendung
4
Nun, Windows kann prinzipiell nicht wissen, was Ihre Anwendung in einem
Fenster darstellen soll oder will – das ist Aufgabe der Applikation selbst. Im bis-
herigen Beispielprogramm werden jedoch nur direkt Daten in das Dialogfeld
gezeichnet.
Zerstören von Wischt man mit einem Fenster – ähnlich einem Schwamm – über den Dialog,
Bildinhalten wird der überschriebene Hintergrund nicht wieder hergestellt. Sie ahnen es
schon: Fenster werden nicht etwa irgendwo im Speicher gesichert, wenn Sie
hin- und hergeschoben und möglicherweise überlagert werden, damit das
Betriebssystem sie später wieder herstellen könnte.
Der Grund dafür ist auch recht schnell ermittelt: gehen Sie einmal von einer
Bildschirmauflösung von 1.024*768 Pixeln bei einer Farbtiefe von 32 Bit aus. Sie
kommen dann auf ungefähr 3 MByte, die ein maximiertes Fenster an reiner
Grafikdarstellung benötigen würde. Nun sind unter Windows normalerweise
eine ganze Reihe von Fenstern geöffnet, viele von Ihnen ebenfalls maximiert –
der Speicherbedarf wäre ganz erheblich.
Aufforderung zum Deshalb geht Windows den schon skizzierten Weg, den Anwendungen die Ver-
Neuzeichnen antwortlichkeit zur Wiederherstellung ihres Fensterinhalts zu übertragen.
Dazu wollen wir uns einer der Containerklassen der MFC bedienen, die zu die-
sem Zweck ausgezeichnete Dienste leisten wird: CList.
MFC-Containerklassen
Die MFC bieten drei Arten von Containerklassen an, die jede für sich ihre eige-
nen Vorzüge bietet. Containerklassen sind, wenn man so will, eine Struktur zur
Aufnahme einer unbestimmten Anzahl von Objekten eines definierten Typs.
164
Entwicklung von dialogfeldbasierten Anwendungen
4
Sie haben sicherlich schon einmal mit verketteten Listen gearbeitet, eine ähn- Verkettete Listen
liche Funktionalität stellt die MFC-Containerklasse CList zur Verfügung. Sie
kapselt die Möglichkeiten einer verketteten Liste und stellt Sie Ihnen zur beque-
men Anwendung zur Verfügung.
Stellen Sie sich ein Objekt des Typs CList einfach als eine anfangs leere Liste von Navigieren in Listen
Funktionstypen vor. Sie können über diese Liste iterieren, indem Sie eine wei-
tere Variable vom Typ POSITION einführen, die jeweils auf ein konkretes Ele-
ment innerhalb dieser Liste zeigt.
Beim Auslesen eines Objekts aus der Liste wird die POSITIONs-Variable auf das
jeweils nächste Objekt gesetzt, können keine Werte mehr ausgelesen werden,
ist dieses auch direkt über diesen Navigationswert auszulesen.
Da wir für einen Linienzug zwei Koordinaten speichern müssen, bietet sich eine
Verwendung der ebenfalls in den MFC vorhandenen Klasse CRect an, die eine
Rechteckstruktur analog zu RECT repräsentiert.
Es sollen die Startkoordinaten in den Komponenten left und top und die Endko-
ordinaten in den Komponenten right und bottom gespeichert werden. Ferner
muss noch die Farbe festgehalten werden, in der eine Linie gezeichnet wurde.
165
Grundlegender Entwurf einer MFC-Anwendung
4
#include <afxtempl.h>
struct SLinienzug
{
CRect m_Koordinaten;
COLORREF m_Farbe;
};
// CZeichenProgrammDlg Dialogfeld
class CZeichenProgrammDlg : public CDialog
{
// Konstruktion
public:
CZeichenProgrammDlg(CWnd* pParent = NULL);
// Standardkonstruktor
// Dialogfelddaten
enum { IDD = IDD_ZEICHENPROGRAMM_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV-
Unterstützung
// Implementierung
protected:
HICON m_hIcon;
bool m_bLinkeMaustasteGedrueckt;
166
Entwicklung von dialogfeldbasierten Anwendungen
4
protected:
int m_nStartPunktX;
int m_nStartPunktY;
COLORREF m_AktuelleFarbe;
public:
afx_msg void OnBnClickedSchwarz();
afx_msg void OnBnClickedRot();
afx_msg void OnBnClickedGruen();
afx_msg void OnBnClickedBlau();
};
Die Methode ResetListe setzt die Liste auf den Anfang zurück, genauer gesagt
die Positionsvariable. GetHeadPosition ermittelt gerade die Position des ersten
Elements einer CList. Der nächste Auslesevorgang liefert dann eben dieses Ele-
ment der Liste zurück, soweit die Liste nicht leer ist.
167
Grundlegender Entwurf einer MFC-Anwendung
4
// alles OK
return (true);
}
else
{
// es existieren keine weiteren Elemente
return (false);
}
}
Das Auslesen eines Elements beginnt mit einer Prüfung, ob das POSITIONs-
Objekt gültig ist. Hat es den Wert NULL, befindet es sich zur Zeit auf keiner gül-
tigen Listenelementposition, was in der Zeichenprogrammanwendung ein
Indiz dafür ist, dass keine weiteren Elemente ausgelesen werden können. Die-
ses ist auch der Fall, wenn die Liste leer ist.
Existiert noch ein Element, wird dieses über die CList-Methode GetNext ausge-
lesen, was automatisch das POSITIONs-Objekt ein Element weiter bewegt und
auf NULL setzt, falls das Ende der Liste erreicht ist.
Ein Rückgabewert von true bedeutet, dass ein Wert ausgelesen werden konnte,
false, dass keine weiteren Elemente in der Liste existieren.
168
Entwicklung von dialogfeldbasierten Anwendungen
4
{
// übergebenes Objekt kopieren und in Liste
// einfügen
m_LinienListe.AddTail(in_Linienzug);
}
Die CList-Methode AddTail fügt ein übergebenes Element vom Datentyp der
Liste, der über die Initialisierung des CList-Objekts festgelegt wurde, an das
Ende der Liste ein. Es gibt auch weitere Methoden zum Einfügen von Objekten
wie zum Beispiel AddHead, das Objekte an den Anfang der Liste einfügt.
Die Zeichenoperationen müssen aber in der Reihenfolge nachvollzogen wer- Beachten der Reihen-
den, wie sie der Benutzer ursprünglich auch vollzogen hatte, dazu ist es sinn- folge beim Zeichnen
voll, die Liste sequenziell abzuarbeiten, weshalb ein Einfügen an das Ende
erforderlich ist.
Da wir die Positionen der Linienzüge in einer Rechteckstruktur speichern wol- Markieren von
len, OnLButtonDown aber nur einzelne Pixel setzt, verwenden wir den einfa- Punkten innerhalb
chen Trick, die gleichen Koordinaten in den left/top und den right/bottom der Liste
Komponenten der Rechteckstruktur abzulegen.
Beim Wiederherstellen des Bilds kann diese Gleichheit dann festgestellt und
gegebenenfalls statt einer Linie ein Punkt gezeichnet werden:
169
Grundlegender Entwurf einer MFC-Anwendung
4
// Maus festhalten
SetCapture();
// Startposition merken
m_nStartPunktX = point.x;
m_nStartPunktY = point.y;
Linienzug.m_Koordinaten.left = point.x;
Linienzug.m_Koordinaten.right = point.x;
Linienzug.m_Koordinaten.top = point.y;
Linienzug.m_Koordinaten.bottom = point.y;
Linienzug.m_Farbe = m_AktuelleFarbe;
LinienzugHinzufuegen(Linienzug);
}
// Standardbehandlung
CDialog::OnLButtonDown(nFlags, point);
}
Wie Sie sehen, wird eine neue Slinienzug-Strukturvariable deklariert und dann
mit den gerade aktuellen Werten gefüllt und zur Liste hinzugefügt. Die left/
top- und right/bottom-Komponentenpaare erhalten jeweils dieselben Werte.
170
Entwicklung von dialogfeldbasierten Anwendungen
4
// Stift vorbereiten
CPen StandardPen(PS_SOLID, 1,
m_AktuelleFarbe);
CPen *pOldPen =
dc.SelectObject(&StandardPen);
// Startpunkt anfahren
dc.MoveTo(m_nStartPunktX, m_nStartPunktY);
LinienzugHinzufuegen(Linienzug);
171
Grundlegender Entwurf einer MFC-Anwendung
4
CDialog::OnMouseMove(nFlags, point);
}
Zeichnen der Begeben Sie sich im Quelltext dazu zur Methode OnPaint (Datei Zeichenpro-
gespeicherten Linien grammDlg.cpp) und editieren Sie diese wie folgt:
und Punkte
SendMessage(WM_ICONERASEBKGND,
reinterpret_cast<WPARAM>(dc.GetSafeHdc()),
0);
// Symbol zeichnen
dc.DrawIcon(x, y, m_hIcon);
}
else
{
// Linienzugvariable vorbereiten
SLinienzug Linienzug;
// Liste zurücksetzen
ResetListe();
// Gerätekontext vorbereiten
CPaintDC dc(this);
172
Entwicklung von dialogfeldbasierten Anwendungen
4
// Stifte vorbereiten
CPen SchwarzerStift(PS_SOLID, 1,
RGB(0, 0, 0));
CPen RoterStift(PS_SOLID, 1,
RGB(255, 0, 0));
CPen GruenerStift(PS_SOLID, 1,
RGB(0, 255, 0));
CPen BlauerStift(PS_SOLID, 1,
RGB(0, 0, 255));
dc.SetPixel(
Linienzug.m_Koordinaten.left,
Linienzug.m_Koordinaten.top,
Linienzug.m_Farbe);
}
else
{
// passenden Stift setzen
if (Linienzug.m_Farbe ==
RGB(0, 0, 0))
{
dc.SelectObject(
&SchwarzerStift);
}
else
{
if (Linienzug.m_Farbe
== RGB(255, 0, 0))
{
dc.SelectObject(
&RoterStift);
173
Grundlegender Entwurf einer MFC-Anwendung
4
}
else
{
if
(Linienzug.m_Farbe == RGB(0, 255, 0))
{
dc.SelectObject(
&GruenerStift);
}
else
{
dc.SelectObject(
&BlauerStift);
}
}
}
// Linie zeichnen
dc.LineTo(
Linienzug.m_Koordinaten.right,
Linienzug.m_Koordinaten.bottom);
}
}
// Standardbehandlung aufrufen
CDialog::OnPaint();
}
}
Die Methode ist nun schon etwas umfangreicher geworden, aber eigentlich
nicht allzu kompliziert zu verstehen.
174
Entwicklung von dialogfeldbasierten Anwendungen
4
In diesem Fall liegen festdefinierte Farben vor, also ist dieser Weg möglich, Verwenden von unter-
wären die Farben frei definierbar gewesen, müsste man den Stift tatsächlich schiedlichen Stiften
vor jedem Zeichenvorgang neu mit den passenden Werten erzeugen – auf
diese vereinfachte Weise sparen wir also ein wenig Abarbeitungszeit.
Der Aufruf von ListeLoeschen entfernt sämtliche Elemente aus dem Listenob-
jekt. Nun muss allerdings noch das Fenster zum Neuzeichnen aufgefordert
werden, was ja bekannterweise durch eine WM_PAINT-Nachricht initiiert wird.
Dieses kann durch Verwendung der Funktion InvalidateRect erzwungen wer-
den, die den Zeiger auf eine RECT-Struktur als Parameter erhält, die wiederum
das Rechteck des Client-Bereichs angibt, das neu zu zeichnen ist. Geben Sie hier
NULL an, wird das komplette Dialogfeld neu gezeichnet.
Der zweite Parameter bestimmt, ob der Client-Bereich des Dialogfelds vor dem Löschen des
neuen Zeichenvorgang gelöscht werden soll. True sorgt für ein vorhergehendes Client-Bereichs
Löschen, false lässt den Inhalt unangetastet. In unserem Fall ist true die richtige
Alternative, da sonst die schon bestehenden Linien trotz des Löschens der
Daten bestehen bleiben würden.
175
Grundlegender Entwurf einer MFC-Anwendung
4
Weitere Features für Sie sollten sich jetzt die Zeit nehmen, das Zeichenprogramm nach eigenen Vor-
das Zeichenprogramm stellungen zu erweitern. Zum Beispiel könnten Sie eine Funktion zum Malen
von Rechtecken oder Kreisen einbauen, oder eine größere Menge an Farben zur
Verfügung stellen.
Was immer Sie tun, es ist auf jeden Fall sinnvoll genutzte Zeit, denn die Grund-
konzepte dieses Kapitels ermöglichen es Ihnen, nahezu jede beliebige Anwen-
dung zu programmieren.
Abb. 4.57
Das fertige Programm
176
Entwickeln von
SDI-Anwendungen
Unterschiede zu dialogfeldbasierenden
Applikationen
Im vorangegangenen Kapitel wurde eine Anwendung auf der Basis eines Dia-
logfelds realisiert, die mit einer geringen Anzahl an Dateien und Klassen aus-
kam.
Während viele kleinere Tools durchaus nach diesem Konzept entwickelt wer-
den können, sind doch häufig umfangreichere Projektentwürfe notwendig, um
eine gestellte Aufgabe durch ein MFC-Programm realisieren zu können.
Umfangreichere Dieses liegt zum einen darin begründet, dass umfangreiche Daten gespeichert
Projekte werden müssen – beispielsweise in einer Datenbank-ähnlichen Anwendung –
oder die Darstellung der Dokumentdaten nicht auf trivialem Wege durchführ-
bar ist. Ein Beispiel für den letztgenannten Fall ist ein Programm, dass die ein-
gegebenen Daten auf mehrere verschiedene Arten anzeigen kann, vielleicht
einmal in Textform und einmal als Balkendiagramm.
Entwicklung einer Der vorliegende Abschnitt beschäftigt sich mit der SDI-Variante und wird eine
SDI-Applikation vollständige lauffähige Anwendung hervorbringen, die neben den Möglichkei-
ten zum Speichern von Dokumentdaten auch weitere wichtige Windows-The-
men wie das Erzeugen von Menüs oder die Ausgabe von Grafik besprechen
wird.
Die Document/View-Architecture
Sie haben bereits das MFC-Anwendungsgerüst kennen gelernt, dessen Kern die
Dokument-/Ansichtarchitektur ist. Hierbei handelt es sich um ein Konzept, das
durch seine Einfachheit und Vorteile überzeugen kann.
Stellen Sie sich eine umfangreiche Anwendung vor, die ihre Daten auf eine
beliebige Weise speichert und die Ausgabe dieser Informationen durch direk-
ten Zugriff vornimmt.
Datenorganisation Nun entsteht noch in der Entwicklungsphase die Notwendigkeit, die Daten in
anderer Art und Weise zu organisieren, beispielsweise weil wichtige Schlüssel-
informationen weiter aufgeteilt oder zusammengefasst werden sollen.
Das Ergebnis ist, dass neben den Methoden zur eigentlichen Datenverwaltung
auch die zur Anzeige neu geschrieben werden müssen, da sich durch die ein-
gangs gewählte enge Verzahnung zwischen Darstellung und Informationsba-
178
Entwickeln von SDI-Anwendungen
5
sis ein roter Faden durch das komplette Projekt zieht und die Umstellung
Folgefehler in allen möglichen Unterkomponenten hervorruft.
Ein deutlich geeigneterer Weg ist es, die zu einem Projekt gehörenden Daten in
einem eigenen Bereich abzulegen und dann nur über Anforderungsfunktionen
auf diese zu zugreifen.
Man sagt, die Ansichtsfunktionen benötigen keine Kenntnis über den tatsächli- Blackbox-Prinzip bei
chen Aufbau der Informationen. Ein Geburtsdatum könnte intern als Ganzzahl Dokumentdaten
oder als String gespeichert werden, das Ansichtsobjekt fordert vom Dokument
aber immer einen bestimmten Typ an, beispielsweise eine Stringrepräsenta-
tion.
In vielen Fällen ist es nicht wünschenswert, dass ein Benutzer Daten direkt ver- Umleitung der
ändern kann – vielleicht ist die interne Repräsentation mit den Eingaben eines Benutzereingaben
Anwenders auch gar nicht kompatibel, wie es im obigen Beispiel mit dem
Geburtsdatum gezeigt wurde (dieses könnte als Ganzzahl gespeichert sein,
während der Benutzer nur Strings eingeben kann).
Die Ansichtsklasse nimmt daher zunächst nur die Eingaben des Anwenders
entgegen und überträgt diese dann in geeigneter Form an die Dokumenten-
klasse. Auch hier greift wieder das Prinzip von Zugriffsfunktionen, die eine Abs-
traktion von den tatsächlichen Daten darstellen und einen transparenten
bidirektionalen Zugriff innerhalb der Projekte erlauben.
179
Das Apfelmännchen-Projekt
5
Abb. 5.1
Zusammenhang
zwischen
Dokumentenklassen,
Ansichtsklassen und
Benutzerschnittstelle
Das vorliegende Kapitel beschäftigt sich, wie bereits angekündigt, mit einer
einfachen SDI-Anwendung, die neben einer Dokumentenklasse genau eine
Ansichtsklasse enthält.
Das Apfelmännchen-Projekt
Um das Prinzip der SDI-Anwendungen anschaulich zu erklären, sollen auch
gleich die Grundlagen der Grafikausgabe unter Windows erklärt werden.
Darstellung grafischer Viele neue Programmierer machen sich keine oder nur wenige Vorstellungen
Elemente davon, wie aufwendig die Darstellung von grafischen Elementen wirklich ist. Es
bietet sich daher gleich von Beginn an, die Grenzen der Möglichkeiten aufzuzei-
gen, um so von vornherein die Entwicklung von anspruchsvollen Anwendun-
gen in die richtige Bahn zu lenken.
Inhalt des neuen Um die Theorie nicht allzu trocken werden zu lassen, soll der geneigte Leser
Projekts trotzdem durch eine ansprechende Optik belohnt werden – konkret geht es in
diesem Beispiel daher um die Berechnung und Darstellung von so genannten
Apfelmännchen (auch Mandelbrot-Mengen genannt), deren mathematischer
Hintergrund ebenfalls grundlegend erklärt werden soll.
180
Entwickeln von SDI-Anwendungen
5
Sie finden das fertige Projekt auch auf der Buch-CD im Verzeichnis \Kapitel5\
Apfelmaennchen.
Abb. 5.2
Nur eine Einstellung
muss verändert
werden
Erzeugte Dateien
Neben den schon aus dem vorhergegangenen Projekt bekannten Dateien wer- Begutachtung der ent-
den bei einer SDI-Anwendung sechs Files erzeugt, die im Rahmen dieses Kapi- standenen Dateien
tels besondere Beachtung verdienen:
181
Das Apfelmännchen-Projekt
5
Es ist wichtig, einen Überblick über die Inhalte dieser Klassen zu erhalten, wes-
halb sie auf den folgenden Seiten kurz dargestellt werden sollen.
Die CMainFrame-Klasse
Die CMainFrame-Klasse symbolisiert das Hauptrahmenfenster der Applikation:
// Attribute
public:
// Operationen
public:
// Überschreibungen
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
// Implementierung
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
182
Entwickeln von SDI-Anwendungen
5
Die Funktionen AssertValid und Dump dienen zum Debugging, also der Fehler- Debug-Funktionen
suche, innerhalb Ihrer Programme. Sie werden im Kapitel zum Debugger noch
gesondert behandelt und detailliert besprochen.
MainFrm.cpp
Die vom Anwendungsassistenten erzeugte Implementationsdatei sieht so aus:
#include "stdafx.h"
#include "Apfelmaennchen.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMainFrame
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
183
Das Apfelmännchen-Projekt
5
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
// CMainFrame Erstellung/Zerstörung
CMainFrame::CMainFrame()
{
// TODO: Hier Code für die Memberinitialisierung
// einfügen
}
CMainFrame::~CMainFrame()
{
}
return TRUE;
}
int CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,
WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS |
CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Symbolleiste konnte nicht erstellt
werden\n");
return -1; // Fehler bei Erstellung
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
184
Entwickeln von SDI-Anwendungen
5
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Statusleiste konnte nicht erstellt
werden\n");
return -1; // Fehler bei Erstellung
}
// TODO: Löschen Sie diese drei Zeilen, wenn Sie
// nicht möchten, dass die Systemleiste
// andockbar ist
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
// CMainFrame Diagnose
#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
CFrameWnd::AssertValid();
}
#endif
Das folgende indicators-Array enthält eine Aufzählung der in der Statuszeile Statuszeilen-
des Fensters vorhandenen Komponenten und wird später in diesem Kapitel informationen
noch näher untersucht, wenn es um das Hinzufügen eigener Daten in dieser
Informationsquelle geht.
PreCreateWindow erlaubt es, spezifizierte Angaben zur Erzeugung des Fensters Einleitende Fenster-
zu machen, beispielsweise Stilflags zu manipulieren und Ähnliches. Das wird vorbereitungen
standardmäßig nicht benötigt, sodass die Methode zwar vorhanden ist, aber
selbst keine Änderungen an dem übergebenen CreateStruct-Parameter vor-
nimmt. Schauen Sie in die Online-Hilfe, um nähere Informationen über die hier
zu Verfügung stehenden Möglichkeiten zu finden.
185
Das Apfelmännchen-Projekt
5
Die OnCreate-Methode
Erzeugung eines Hier wird das eigentliche Rahmenfenster erzeugt (durch Weiterleitung des
Rahmenfensters lpCreateStruct Parameters an die Basisklassenmethode CFrameWnd::OnCreate),
sowie die beiden Leisten (Status- und Werkzeugleiste) erzeugt.
Die hierzu eingesetzten Methoden CreateEx und Create finden Sie im Anhang
dieses Buches erläutert.
Diagnosemethoden
Die beiden Diagnosemethoden AssertValid und Dump werden im Kapitel über
den Debugger noch näher besprochen. Für den Augenblick beachten Sie bitte,
dass diese Methoden nur definiert werden, wenn ein Debug-Build des Projekts
erzeugt wird.
Bedingte Kompilie- Die Präprozesseranweisungen #ifdef _DEBUG ... #endif machen die Methoden
rung von Debug- in Release-Builds für den Compiler unsichtbar und somit vom Programm aus
Funktionalitäten nicht erreichbar. Somit wird dafür gesorgt, dass Release-Builds unter Umstän-
den deutlich schneller als ihre Debug-Build-Versionen ablaufen können.
Die Dokumentenklasse
Die generierte Da das Hauptfenster der Anwendung ohne Daten noch relativ leer aussieht,
Dokumentenklasse müssen Sie eine Möglichkeit vorsehen, Informationen zu speichern. Der
Anwendungsassistent hat zu diesem Zweck bereits eine Dokumentenklasse
namens CApfelmaennchenDoc angelegt, die hierfür hervorragend geeignet ist:
// Attribute
public:
// Operationen
public:
// Überschreibungen
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
186
Entwickeln von SDI-Anwendungen
5
// Implementierung
public:
virtual ~CApfelmaennchenDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
Diese beiden Methoden jedoch, OnNewDocument und Serialize, sind für SDI- Dokumenten-
Anwendungen zwei der wichtigsten Dokumentverwaltungsfunktionen über- verwaltung
haupt. Ihre Implementation ist in der Datei ApfelmaennchenDoc.cpp zu finden:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CApfelmaennchenDoc
IMPLEMENT_DYNCREATE(CApfelmaennchenDoc, CDocument)
BEGIN_MESSAGE_MAP(CApfelmaennchenDoc, CDocument)
END_MESSAGE_MAP()
// CApfelmaennchenDoc Erstellung/Zerstörung
CApfelmaennchenDoc::CApfelmaennchenDoc()
187
Das Apfelmännchen-Projekt
5
{
// TODO: Hier Code für One-Time-Konstruktion
// einfügen
CApfelmaennchenDoc::~CApfelmaennchenDoc()
{
}
BOOL CApfelmaennchenDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
return TRUE;
}
// CApfelmaennchenDoc Serialisierung
// CApfelmaennchenDoc Diagnose
#ifdef _DEBUG
void CApfelmaennchenDoc::AssertValid() const
{
CDocument::AssertValid();
}
188
Entwickeln von SDI-Anwendungen
5
OnNewDocument dient in SDI-Anwendungen dazu, die Daten eines Doku- Neue Dokumente
ments zu reinitialisieren. Angenommen, Sie hätten bereits einige Informa-
tionen in ein Dokument eingetragen und erzeugen dann über den von
Windows-Programmen bekannten Punkt Datei > Neu ein neues Dokument,
werden die alten Daten gegebenenfalls gespeichert und dann verworfen.
Serialize, auf der anderen Seite, dient zum Laden und Speichern (Serialisierung Laden und Speichern
genannt) eines Dokuments. Was hier genau geschieht, wird im weiteren Ver- von Daten
lauf des Kapitels noch näher geklärt.
Bleibt noch eine Betrachtung der Ansichtsklasse für neue Dokumente des Typs
Apfelmännchen.
// Attribute
public:
CApfelmaennchenDoc* GetDocument() const;
// Operationen
public:
// Überschreibungen
public:
virtual void OnDraw(CDC* pDC); // Überladen, um
// diese Ansicht darzustellen
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
189
Das Apfelmännchen-Projekt
5
protected:
virtual BOOL OnPreparePrinting(CPrintInfo*
pInfo);
virtual void OnBeginPrinting(CDC* pDC,
CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo*
pInfo);
// Implementierung
public:
virtual ~CApfelmaennchenView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
Visualisierung von Neben dem Zugriff ist die Darstellung die eigentliche Hauptaufgabe der
Dokumentinhalten Ansichtsklasse. Die überschriebene Methode OnDraw dient zur Ausgabe der
ermittelten Daten auf einen Gerätekontext – dabei ist es (mehr oder weniger)
unerheblich, ob dieser einen Bildschirm oder eine Druckausgabe repräsentiert.
Es gibt jedoch noch einige zusätzliche Operationen für die konkrete Ausgabe
von Informationen auf den Drucker – namentlich sind dies OnPreparePrinting,
OnBeginPrinting und OnEndPrinting. Sie können beispielsweise genutzt wer-
den, um eine bestimmte Sortierung beim Drucken zu erreichen, oder sonstige
Einstellungen in diesem Bereich zu tätigen.
190
Entwickeln von SDI-Anwendungen
5
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CApfelmaennchenView
IMPLEMENT_DYNCREATE(CApfelmaennchenView, CView)
BEGIN_MESSAGE_MAP(CApfelmaennchenView, CView)
// Standarddruckbefehle
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT,
CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW,
CView::OnFilePrintPreview)
END_MESSAGE_MAP()
// CApfelmaennchenView Erstellung/Zerstörung
CApfelmaennchenView::CApfelmaennchenView()
{
// TODO: Hier Code zum Erstellen einfügen
CApfelmaennchenView::~CApfelmaennchenView()
{
}
BOOL
CApfelmaennchenView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Ändern Sie hier die Fensterklasse oder
// die Darstellung, indem Sie
// CREATESTRUCT cs modifizieren.
return CView::PreCreateWindow(cs);
191
Das Apfelmännchen-Projekt
5
// CApfelmaennchenView-Zeichnung
// CApfelmaennchenView drucken
BOOL
CApfelmaennchenView::OnPreparePrinting(CPrintInfo*
pInfo)
{
// Standardvorbereitung
return DoPreparePrinting(pInfo);
}
void CApfelmaennchenView::OnBeginPrinting(CDC*
/*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: Zusätzliche Initialisierung vor dem
// Drucken hier einfügen
}
// CApfelmaennchenView Diagnose
#ifdef _DEBUG
void CApfelmaennchenView::AssertValid() const
{
CView::AssertValid();
}
192
Entwickeln von SDI-Anwendungen
5
CView::Dump(dc);
}
In der Regel brauchen Sie an dieser Stelle nicht tätig zu werden, es sei denn, Sie
wünschen alternative Behandlungen dieser drei Aktionen.
Aus den CView-Methoden heraus werden dann vom Framework die Methoden
OnPreparePrinting (vorbereitende Aktionen), OnBeginPrinting (Initialisierungen
direkt vor Druckbeginn) und OnEndPrinting (Aufräumarbeiten nach Beendi-
gung des Druckvorgangs) aufgerufen.
Diese sind eingangs leer und, wie Sie später noch sehen werden, brauchen im
einfachsten Fall auch keine weitere Funktionalität aufzunehmen.
193
Ausfüllen der OnDraw-Methode
5
Zeichnen von Begonnen werden soll mit dem Zeichnen der Apfelmännchen, ohne dabei
Apfelmännchen Rücksicht auf irgendwelche Benutzereingaben oder Datenbestände zu neh-
men.
Ein wenig Schul- Es ist für die folgenden Überlegungen nicht wichtig, ob Sie wissen, wie mit
mathematik komplexen Zahlen gerechnet wird, stellen Sie sich einfach das aus der Schule
bekannte Gauss'sche Koordinatensystem vor, bei dem eine (normalerweise mit
X bezeichnete) horizontale Achse von einer (mit Y titulierten) vertikalen Achse
geschnitten wird. Der Schnittpunkt dieser beiden Geraden ist genau an der
Position (0, 0).
194
Entwickeln von SDI-Anwendungen
5
Eine komplexe Zahl C ist in unserem Fall einfach ein beliebiger Punkt innerhalb
dieses Systems. Etwas weiter in die Mathematik hineingreifend sei erwähnt,
dass die X-Achse den Realteil von C, die Y-Achse den Imaginärteil beschreibt.
Für ein Apfelmännchen wird nun jeder einzelne Punkt (vereinfacht gesprochen)
herangezogen und in die oben stehende Formel gesteckt. Weiterhin wird für X0
der Wert 0 angenommen.
Ausgehend von einem Punkt C und X0 mit dem Wert 0 lässt sich nun X1 berech-
nen, daraus dann wiederum mit demselben C X2 und so weiter. Das Ganze wie-
derholt man so lange, bis Xn eine bestimmte Grenze (für Apfelmännchen
nimmt man hier in der Regel den Wert 4.0 an) überschreitet.
Sie schauen nun auf den Index bei X und wissen dann, wie viele Iterationen
nötig waren, um den Grenzwert zu überschreiten. Bei manchen Cs wird der
Grenzwert allerdings nie erreicht, weshalb man nach einer bestimmten Iterati-
onstiefe (zum Beispiel 10) die Berechnung abbricht und diesen (höchsten) Wert
als Index annimmt.
Bei farbigen Apfelmännchen wird abhängig von der Iterationstiefe eine andere
Farbe gewählt, normalerweise ein Farbverlauf, wobei Koordinaten mit der
höchsten Iterationstiefe in der Regel schwarz gezeichnet werden.
Hier bietet es sich sicherlich an, eine Funktion zu schreiben, die für einen gege- Unterteilung der
benen Punkt genau diesen Wert berechnet. Da es hierbei um eine Verarbeitung Berechnung in
von Dokumentdaten handelt und diese nur für die Darstellung benötigt wer- Funktionsaufrufe
den (insbesondere sollen die Werte nicht gespeichert, sondern gleich geeignet
gezeichnet werden), gehört die Methode in die Ansichtsklasse.
195
Implementation der Apfelmännchen-Berechnung
5
// Variablen initialisieren
dZX = in_dX;
dZY = in_dY;
dSX = dZX * dZX;
dSY = dZY * dZY;
i = 0;
// iterieren
while ((i <= in_nMaxIter) && (dSX + dSY <= 4.0))
{
dZY = ((dZX + dZX) * dZY) + in_dY;
dZX = dSX – dSY + in_dX;
dSX = dZX * dZX;
dSY = dZY * dZY;
i++;
}
// Iterationstiefe zurückgeben
return (i);
}
Hier wird die Anzahl der Iterationen bestimmt, und als Rückgabewert an den
Aufrufer übermittelt.
Zeichnen eines ersten Für einen ersten Versuch soll ein Mandelbrot mit einer Größe von 200*200
Apfelmännchens Pixel gezeichnet werden:
196
Entwickeln von SDI-Anwendungen
5
197
Implementation der Apfelmännchen-Berechnung
5
int nIterations =
GetNumberOfIterations(dX, dY,
nMaxIter);
// nächste Zeile
Position.y++;
dY += dSY;
}
}
Erklärungen zu OnDraw
Zunächst werden hier eine Reihe von Initialisierung durchgeführt, die den
Bereich und die Auflösung des zu zeichnenden Mandelbrots bestimmt.
Der gewählte Bereich (-2.0, -2.0) bis (2.0, 2.0) ist gerade der klassische Aus-
schnitt, der auch in Büchern gerne gewählt wird. Er sieht aus wie in der nach-
stehenden Abbildung:
Abb. 5.3
Erste Ergebnisse des
Mandelbrot-
Programms
198
Entwickeln von SDI-Anwendungen
5
Im weiteren Verlauf von OnDraw gehen wir davon aus, dass pro Pixel genau ein Annahmen über
Punkt im Koordinatensystem steht. Wenn wir den Bereich von (-2.0, -2.0) – (2.0, die Darstellung
2.0) abdecken wollen, entspricht ein Pixel gerade einem 0,02*0,02 Einheiten
großen Punkt im Koordinatensystem.
Es werden zwei Zeilen- und Spaltenzähler verwendet, die jeweils die aktuelle
Position auf der Zeichenfläche und im Koordinatensystem festhalten und dann
geeignet die GetNumberOfIterations-Funktion aufrufen. Ist der Rückgabewert
größer oder gleich der Variablen nMaxIter, wird ein schwarzer Punkt gezeich-
net.
Obwohl dieses Vorgehen funktioniert, fällt doch sofort ein Problem auf: was,
wenn das Fenster kleiner als 200*200 Pixel ist? Oder deutlich größer? Wie
bringe ich das Mandelbrot auf eine ansprechende Größe, beispielsweise gerade
durch Ausnutzung der kompletten Zeichenfläche?
Bislang sind wir davon ausgegangen, dass sich der Punkt (0, 0) unserer Zei- Der Urspung der
chenfläche in der linken oberen Ecke befindet und die X- und Y-Werte beim Zeichenfläche
Zeichnen nach rechts beziehungsweise nach unten vergrößern.
Offensichtlich ist dieses auch tatsächlich der Fall, doch warum eigentlich?
Kurz gesagt, gerade diese Vorstellung von der virtuellen Leinwand (in Form
unseres Gerätekontexts und somit in zweiter Instanz des tatsächlichen Fenster-
inhalts) ist bei Windows standardmäßig voreingestellt.
Was zum schnellen Austesten durchaus Vorteile bringt, entpuppt sich bei
näherer Überlegung aber als Problem: Stellen Sie sich vor, Sie möchten ein Qua-
drat zeichnen, dass 320*200 Pixel groß ist. Ist das zugrunde liegende Fenster
genauso groß, wird es den gesamten Bereich ausfüllen.
Vergrößern Sie das Fenster nun auf 640*480 Pixel, füllt das Quadrat nur noch Zahlenspielereien
ein Viertel des Platzes aus.
Noch dramatischer wird es, wenn Sie die Grafik auf einem herkömmlichen
Drucker mit zum Beispiel 600 dpi (Dots per Inch, also Punkte pro Zoll, wobei ein
Zoll 2,54 cm sind) Auflösung aus. Das Quadrat ist gerade einmal noch ungefähr
1,25 cm2 groß.
199
Abbildungsmodi unter Windows
5
Sie können diesen Umstand jedoch durch einen einfachen Aufruf ändern und
die linke obere Ecke auf einen beliebigen anderen Punkt im kartesischen Koor-
dinatensystem umlegen.
Verschieben Hierfür gibt es die Methode SetWindowOrg, die als Parameter den Ursprung
des Ursprungs (den Wert für die linke obere Ecke) in Form von X- und Y-Parametern übergeben
bekommt.
pDC->SetWindowOrg(100, 50);
pDC->TextOut(100, 50, "Versetzte Ausgabe");
Sie könnten diese Zeilen in OnDraw einfügen, um zu sehen, was passiert. Die
folgende Abbildung veranschaulicht Ihnen dieses aber auch hinreichend genau
und erspart das umständliche Abändern der Methode des Beispielprojekts:
Abb. 5.4
Das Ergebnis von
SetWindowOrg
Wie Sie sehen können, sorgt das Verändern des Koordinatenursprungs für eine
Veränderung der Ausgabe. Man spricht hier von logischen Koordinaten. (100,
50) ist eine solche logische Koordinate, die sich nach einem Aufruf der oben ste-
henden SetWindowOrg-Zeile mit der Gerätekontext-Koordinate (0, 0) deckt.
200
Entwickeln von SDI-Anwendungen
5
Logische Einheiten
Wenn es logische Koordinaten gibt, liegt es nahe, dass auch logische Einheiten Rechnen mit
existieren, die unabhängig vom Gerätekontext sind und dort lediglich in einer logischen Einheiten
geeigneten Form übersetzt werden.
Tatsächlich gibt es eine ganze Reihe von logischen Einheiten, die in der folgen-
den Tabelle mit ihren zugehörigen Abbildungsmodi zusammengefasst sind:
Tabelle 5.1
Abbildungsmodus Logische Einheit in diesem Modus entspricht
Abbildungsmodi
MM_TEXT 1 Pixel
MM_LOMETRIC 0,1 mm
MM_HIMETRIC 0,01 mm
Danach kann einfach wie gehabt gezeichnet werden. Es ist jedoch zu beachten,
dass in allen Abbildungsmodi außer MM_TEXT die y-Koordinate nach unten hin
abnimmt. Ist der Gerätekontextursprung – seine linke obere Ecke – auf (0, 0)
gesetzt und wollen Sie einen Pixel an die Stelle (0, 10) setzen, müssen Sie als
Koordinate (0, -10) angeben.
pDC->SetMapMode(MM_HIMETRIC);
201
Abbildungsmodi unter Windows
5
pDC->SelectStockObject(WHITE_BRUSH);
pDC->Rectangle(test);
Das zweite gezeichnete Rechteck ist tatsächlich um den Faktor 10 kleiner als
das erste (schwarze) Rechteck.
Passende Ergebnisse Mehr noch: Wenn Sie zum Beispiel das schwarze Rechteck am Bildschirm mit
einem Lineal ausmessen, werden Sie feststellen, dass es tatsächlich ungefähr
3,2 cm breit und 2,0 cm hoch ist. Das liegt an der Verzerrungsungenauigkeit des
Monitors – bei einem Ausdruck erhalten Sie schon deutlich präzisere Messun-
gen.
Bei diesen Modi können Sie die Größe einer logischen Einheit frei bestimmen.
Das folgende Beispiel zeigt dieses anhand des MM_ANISOTROPIC-Abbildungs-
modus:
// Fenstergröße ermitteln
CRect Rectangle;
GetClientRect(Rectangle);
202
Entwickeln von SDI-Anwendungen
5
// Ellipse zeichnen
pDC->Ellipse(CRect(-2500, -2500, 2500, 2500));
Hier wird zunächst die Größe der Client Area des aktuellen Fensters bestimmt
und dann der Mapping Mode auf MM_ANISOTROPIC gesetzt.
Der Vorteil von MM_ANISOTROPIC und MM_ISOTROPIC besteht ja darin, dass Vorteile der frei
die Größe einer logischen Einheit frei gewählt werden kann. Genau das skalierbaren Modi
geschieht jetzt mit dem Aufruf von SetWindowExt, das die Ausmaße des Fens-
ters angibt (unabhängig von der tatsächlichen Fenstergröße).
Das heißt in diesem konkreten Fall, dass das Fenster 5.000 Einheiten in der
Breite und 5.000 Einheiten in der Höhe darstellen kann.
Der Ursprung dieses sichtbaren Bereichs wird mit der nachfolgenden Zeile
gerade auf die Mitte des Fensters gesetzt und sorgt somit dafür, dass der
Ursprung (0, 0) des Viewports mit dem Punkt (2500, 2500) des Fensters über-
eintrifft.
Das tatsächliche Zeichnen der Ellipse (die eigentlich ein Kreis sein müsste, da
beide Größenangaben identisch sind) ergibt dann das folgende Ergebnis:
Abb. 5.5
MM_ANISOTROPIC
203
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
MM_ISOTROPIC
Unterschiede Möchten Sie ein unverzerrte Abbildung erreichen, können Sie den
zwischen Anisotropic MM_ISOTROPIC-Abbildungsmodus verwenden. Wenn Sie das obige Beispiel
und Isotropic heranziehen, und lediglich die Zeile
pDC->SetMapMode(MM_ANISOTROPIC);
durch
pDC->SetMapMode(MM_ISOTROPIC);
Abb. 5.6
MM_ISOTROPIC
Es ist sehr wichtig, dass Sie sich mit den verschiedenen Abbildungsmodi aus-
kennen. Spielen Sie daher ein wenig damit herum, setzen Sie unterschiedliche
Abbildungsmodi, Viewport- und Fensterursprungskoordinaten und Ausmaße
ein, bis Sie ein Gefühl für die Möglichkeiten bekommen haben.
Vorbereiten des Während in den einfachen Beispielen weiter oben die entsprechenden Funk-
Gerätekontexts tionsaufrufe direkt in der OnDraw-Methode getätigt wurden, sollen sie im
204
Entwickeln von SDI-Anwendungen
5
Diese ist in CView definiert und wird aufgerufen, bevor das Framework die
OnDraw-Methode der Ansichtsklasse ausführt. Praktischerweise wird OnPrepa-
reDC bereits der zu verwendende Gerätekontext übergeben, sodass hier die
notwendigen Einstellungen vorgenommen werden können und der Funkti-
onsumfang von OnDraw auf die tatsächliche Ausgabe begrenzt werden kann.
Eine ähnliche Option besteht für das Anlegen so genannten Override-Funk- Override-Funktionen
tionen, also Methoden, die in einer Basisklasse definiert wurden und nun in
einer abgeleiteten Klasse überschrieben werden sollen.
Öffnen Sie dazu zunächst die Klassenansicht des Projekts und suchen dort die
Klasse CApfelmaennchenView heraus. Stellen Sie weiterhin sicher, dass das
Eigenschaften-Fenster geöffnet ist.
Sie sollten nun einen Überblick über die Klasse erhalten und können in der obe-
ren Knopfleiste des Eigenschaften-Fensters den Punkt Overrides auswählen,
woraufhin sich eine Liste aller überschreibbaren Basisklassenmethoden öffnet.
Suchen Sie in dieser Liste die Methode OnPrepareDC heraus, klicken Sie in die
rechte Spalte und wählen Sie den Punkt <Add> OnPrepareDC aus:
Abb. 5.7
Die Override-
Methoden
205
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
// Auflösung berechnen
nAufloesung = (ClientRect.Width() >
ClientRect.Height()) ?
ClientRect.Width() :
ClientRect.Height();
// Zeichenflächengröße festlegen
pDC->SetWindowExt(nAufloesung,
nAufloesung);
// Abbildungsbereich festlegen
pDC->SetViewportExt(ClientRect.Width(),
ClientRect.Height());
206
Entwickeln von SDI-Anwendungen
5
// Standardbehandlung aufrufen
CView::OnPrepareDC(pDC, pInfo);
}
Vielmehr soll der gewählte Ausschnitt stets den kompletten Client-Bereich des Ausfüllen des
Ausgabefensters ausfüllen. Daher wird mittels SetViewportExt der komplette gesamten Bildbereichs
Client-Bereich ausgewählt und als Auflösung die größere Dimension (Breite
oder Höhe) gewählt.
207
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
dYStart = -2.0;
dXEnd = 2.0;
dYEnd = 2.0;
// nächste Zeile
Position.y++;
dY += dSY;
}
}
208
Entwickeln von SDI-Anwendungen
5
Änderungen in OnDraw
Zunächst wird in OnDraw die aktuelle Fenstergröße ermittelt und danach die
Zahl der zu berechnenden Punkte (die Auflösung) kalkuliert.
Außerdem ist die Schrittweite im Koordinatensystem des Mandelbrots nun Berechnen der
ebenfalls abhängig von diesem Wert (nAufloesung). Die restlichen Berechnun- Bildauflösung
gen bleiben unverändert, lediglich die Schleifenbedingungen verändern sich.
Wenn Sie das veränderte Programm nun kompilieren und starten, werden Sie
feststellen, dass sich das Apfelmännchen der Fenstergröße anpasst. Beachten
Sie hierbei insbesondere, wie lange die tatsächlichen Zeichenvorgänge dauern.
Ein großes Fenster benötigt schon einige Sekunden, um komplett aufgebaut zu
werden.
Benutzerdefinierte Apfelmännchen
Der nächste Schritt wird es sein, dem Benutzer zu gestatten, selbstständig den Erweiterung um
darzustellenden Bereich und die maximale Iterationstiefe festzulegen. Weiter- Benutzerinteraktion
hin soll eine Option existieren, die es erlaubt zu entscheiden, ob die Man-
delbrote farbig oder schwarz-weiß darzustellen sind.
Hierfür bietet es sich an, einen eigenen Dialog anzulegen, der diese Einstell-
möglichkeiten anbietet. Sie haben bereits mit dialogfeldbasierenden Applika-
tionen gearbeitet, aber wie kann nun ein neuer Dialog in eine bestehende SDI-
Anwendung integriert werden?
Öffnen Sie hierzu die Ressourcenansicht Ihres Projekts und öffnen durch einen
Rechtsklick das Kontextmenü des Ordners Dialogs:
Abb. 5.8
Einfügen eines
neuen Dialogs
209
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Hinzufügen eines Wählen Sie hier den Menüpunkt Insert Dialog. Ein Assistent fügt nun den
neuen Dialogs neuen Dialog in Ihr Projekt ein und präsentiert ein zunächst leeres Dialogfeld,
das dem der dialogfeldbasierenden Anwendung des letzten Kapitels ähnelt:
Abb. 5.9
Der neue Dialog
Abb. 5.10
Die Edit-Felder
des Dialogs
210
Entwickeln von SDI-Anwendungen
5
Tabelle 5.2
Komponente ID
IDs für Dialogfeld-
komponenten
X-Start IDC_XSTART
Y-Start IDC_YSTART
X-Ende IDC_XEND
Y-Ende IDC_YENDE
Iterationstiefe IDC_MAXITER
Startfarbe R IDC_STARTRED
Startfarbe G IDC_STARTGREEN
Startfarbe B IDC_STARTBLUE
Endfarbe R IDC_ENDRED
Endfarbe G IDC_ENDGREEN
Endfarbe B IDC_ENDBLUE
Klicken Sie mit der rechten Maustaste in den neuen Dialog und wählen aus Ergänzen des Projekts
dem auftauchenden Kontextmenü den Punkt Add New Class aus. um eine neue Dialog-
klasse
Hiermit wird der Klassenassistent der MFC aufgerufen, der es Ihnen erlaubt,
eine neue Klasse in das Projekt zu integrieren, die auf Basis der Dialogfeldvor-
lage ein lauffähiges Dialogfeld implementiert.
211
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Abb. 5.11
Anlegen einer neuen
Dialogklasse
public:
// standard constructor
CApfelmaennchenEinstellungen(CWnd* pParent =
NULL);
virtual ~CApfelmaennchenEinstellungen();
// Dialog Data
enum { IDD = IDD_SETTINGS };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
// DDX/DDV support
212
Entwickeln von SDI-Anwendungen
5
DECLARE_MESSAGE_MAP()
};
Diese Zeilen sind mit Ihrem jetzigen Wissen leicht zu verstehen, kommen Sie
Ihnen doch sicherlich aus der Arbeit mit dem dialogfeldbasierenden Zeichen-
programm des letzten Kapitels bekannt vor.
ApfelmaennchenEinstellungen.cpp
Die Implementationsdatei ApfelmaennchenEinstellungen.cpp ist ebenfalls
leicht zu durchschauen:
#include "stdafx.h"
#include "Apfelmaennchen.h"
#include "ApfelmaennchenEinstellungen.h"
// CApfelmaennchenEinstellungen dialog
IMPLEMENT_DYNAMIC(CApfelmaennchenEinstellungen,
CDialog)
CApfelmaennchenEinstellungen::
CApfelmaennchenEinstellungen(CWnd* pParent /*=NULL*/)
: CDialog(CApfelmaennchenEinstellungen::IDD,
pParent)
{
}
CApfelmaennchenEinstellungen::
~CApfelmaennchenEinstellungen()
{
}
void CApfelmaennchenEinstellungen::
DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CApfelmaennchenEinstellungen,
CDialog)
END_MESSAGE_MAP()
213
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Variablen an Kom- Der nächste Schritt besteht also darin, Variablen an die einzelnen Komponen-
ponenten binden ten zu binden. Öffnen Sie wieder das Dialogfeld innerhalb des Ressourcenedi-
tors und klicken Sie das zu X-Start gehörende Edit-Feld mit der rechten
Maustaste an. Wählen Sie den Punkt Add Variable. Es erscheint das folgende
Dialogfeld:
Abb. 5.12
Eigenschaften
der Variablen
Echte Werte statt Deaktivieren Sie also gegebenenfalls die Checkbox Control Variable und wählen
Kontrollvariablen statt dessen als Variablentyp double aus und tragen als Variablennamen
m_dStartX ein.
Sinnvolle Werte für die Startvariable rangieren von -2.0 bis 2.0, sodass Sie diese
Werte nun bei min value, bzw. max value angeben.
Erzeugen Sie die Variable mit einem Klick auf die Finish-Schaltfläche.
Wiederholen Sie den Vorgang auf Basis der nachstehenden Tabelle und fügen
für sämtliche editierbaren Komponenten Variablen ein:
214
Entwickeln von SDI-Anwendungen
5
Tabelle 5.3
ID Variablentyp Variablenname Wertebereich
Variablen für die
Steuerelemente des
IDC_XSTART double m_dStartX -2.0 bis 2.0
Dialogfelds
IDC_YSTART double m_dStartY -2.0 bis 2.0
Durch das Angeben von Wertebereichen für die einzelnen Variablen wird auto-
matisch sichergestellt, dass nur gültige Werte durch den Benutzer eingetragen
werden können (Stichwort DDX/DDV).
#include "ApfelmaennchenEinstellungen.h"
215
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
CApfelmaennchenEinstellungen dlg;
dlg.DoModal();
CView::OnLButtonDown(nFlags, point);
}
Austesten der Kompilieren und testen Sie das neue Programm. Durch einen Linksklick in die
aktuellen Version Apfelmännchen-Grafik (oder besser: in den Client-Bereich des Hauptfensters)
wird der neue Dialog aufgerufen und dargestellt.
Allerdings nützen diese Eingaben derzeit noch nichts, da die neuen Werte nir-
gendwo gespeichert werden – sobald der Gültigkeitsbereich des Dialogs verlas-
sen wird, verfallen auch die Informationen, die möglicherweise eingetragen
wurden.
Speichern der für das In der Klasse sollen von jetzt an die zur Darstellung eines Apfelmännchens not-
Apfelmännchen wendigen Werte eingetragen werden. Dazu müssen geeignete Speicherstruk-
relevanten Werte turen sowie Zugriffsmethoden vorhanden sein.
Um das Beispiel nicht unnötig kompliziert zu machen, werden für jede abzule-
gende Information eine Variable und zwei Zugriffsmethoden bereitgestellt.
Öffnen Sie die Datei ApfelmaennchenDoc.h und fügen Sie dort die folgenden
Zeilen in die Klassendeklaration von CApfelmaennchenDoc ein:
double GetStartX();
double GetStartY();
double GetEndX();
216
Entwickeln von SDI-Anwendungen
5
double GetEndY();
int GetMaxIter();
bool IsColored();
int GetStartRed();
int GetStartGreen();
int GetStartBlue();
int GetEndRed();
int GetEndGreen();
int GetEndBlue();
private:
void InitializeValues();
double m_dStartX;
double m_dStartY;
double m_dEndX;
double m_dEndY;
int m_nMaxIter;
int m_nStartRed;
int m_nStartGreen;
int m_nStartBlue;
int m_nEndRed;
int m_nEndGreen;
int m_nEndBlue;
bool m_bColor;
CApfelmaennchenDoc::CApfelmaennchenDoc()
{
// TODO: Hier Code für One-Time-Konstruktion
// einfügen
InitializeValues();
}
Der Konstruktor von CApfelmaennchenDoc soll in erster Linie die Variablen der Variablen-
Klasse auf sinnvolle Startwerte setzen. Da dieses aber auch immer dann initialisierungen
geschehen soll, wenn ein neues Dokument erzeugt wird, macht es Sinn, die Ini-
tialisierungen in eine eigene Methode InitializeValues auszulagern, sodass sie
von mehreren Stellen des Quelltexts aus angesprungen werden können.
217
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
InitializeValues
Die InitializeValues-Methode sieht wie folgt aus:
void CApfelmaennchenDoc::InitializeValues()
{
m_dStartX = -2.0;
m_dStartY = -2.0;
m_dEndX = 2.0;
m_dEndY = 2.0;
m_nMaxIter = 10;
m_nStartRed = 0;
m_nStartGreen= 0;
m_nStartBlue = 0;
m_nEndRed = 255;
m_nEndGreen = 255;
m_nEndBlue = 255;
m_bColor = true;
}
Farbige vs. schwarz- Neben dem Einsetzen der bekannten Standardwerte (nämlich zufälligerweise
weiße Apfelmännchen gerade denen, die auch in der ersten OnDraw-Methode Verwendung fanden),
wird weiterhin festgelegt, dass farbige Apfelmännchen gezeichnet werden sol-
len (m_bColor) und dabei ein Farbverlauf von Schwarz nach Weiß einzusetzen
ist. Das sorgt dafür, dass um die innerste Schicht des Apfelmännchens (gerade
die Punkte, an denen der maximale Iterationswert überschritten wird und die
daher schwarz gezeichnet werden) ein weißer Rahmen für eine edle Optik
sorgt.
OnNewDocument
Das Dokument Das Initialisieren gehört, wie schon angesprochen, nicht nur in den Konstruk-
neu initialisieren tor, sondern vor allem auch in die OnNewDocument-Methode, die beim Erzeu-
gen eines neuen Dokuments verwendet wird:
218
Entwickeln von SDI-Anwendungen
5
BOOL CApfelmaennchenDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
return TRUE;
}
Sie bieten jeweils schreibenden beziehungsweise lesenden Zugriff auf die ein- Vereinfachter Zugriff
zelnen Variablen. Das ist nur bedingt schöner Programmierstil, vereinfacht auf Dokumentinhalte
aber das doch schon recht umfangreiche Projekt:
219
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
doubleCApfelmaennchenDoc::GetStartX()
{
return (m_dStartX);
}
doubleCApfelmaennchenDoc::GetStartY()
{
return (m_dStartY);
}
doubleCApfelmaennchenDoc::GetEndX()
{
return (m_dEndX);
}
220
Entwickeln von SDI-Anwendungen
5
double CApfelmaennchenDoc::GetEndY()
{
return (m_dEndY);
}
int CApfelmaennchenDoc::GetMaxIter()
{
return (m_nMaxIter);
}
bool CApfelmaennchenDoc::IsColored()
{
return (m_bColor);
}
int CApfelmaennchenDoc::GetStartRed()
{
return (m_nStartRed);
}
int CApfelmaennchenDoc::GetStartGreen()
{
return (m_nStartGreen);
}
int CApfelmaennchenDoc::GetStartBlue()
{
return (m_nStartBlue);
}
int CApfelmaennchenDoc::GetEndRed()
{
return (m_nEndRed);
}
int CApfelmaennchenDoc::GetEndGreen()
{
return (m_nEndGreen);
}
int CApfelmaennchenDoc::GetEndBlue()
{
return (m_nEndBlue);
}
221
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Benutzerdefiniertes Diesem Umstand soll nun Rechnung getragen werden. Öffnen Sie die Datei
Auswählen einer Farbe ApfelmaennchenEinstellungen.h und Sie fügen dort die folgende Zeile ein:
private:
bool GetColor(COLORREF &out_Color);
Standarddialogfelder
Öffnen Sie die Datei ApfelmaennchenEinstellungen.cpp und ergänzen Sie die
Includezeilen zunächst um die nachstehende Zeile:
Zur Verfügung Windows bietet eine ganze Reihe solcher Standarddialogfelder. Im Einzelnen
stehende Standard- sind dies:
dialoge
CColorDialog: Dialog zur Auswahl einer Farbe.
CFileDialog: Dialog zur Auswahl einer oder mehrerer Dateien, bzw. zum
Eingeben eines Dateinamens.
Sie finden eine Übersicht über die einzelnen Standarddialoge und ihre Verwen-
dung im Anhang dieses Buches.
222
Entwickeln von SDI-Anwendungen
5
bool CApfelmaennchenEinstellungen::GetColor(COLORREF
&out_Color)
{
// Farbdialog vorbereiten
CColorDialog dlg;
Der Dialog wird zunächst als lokales Objekt erzeugt und dann modal darge-
stellt. Wird er durch einen Druck auf die OK-Schaltfläche beendet, kann man
über die Membermethode GetColor den ausgewählten Farbwert auslesen. Die-
ser wird in Form eines COLORREF-Werts gespeichert, der, wie Sie bereits wissen,
die Komponenten einer Farbe in Form von Rot-, Grün- und Blauanteil enthält.
void
CApfelmaennchenEinstellungen::OnBnClickedButton1()
{
// Variable deklarieren
COLORREF Color;
// Farbwert ermitteln
if (GetColor(Color))
{
223
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
// Farbwert kopieren
m_nStartRed = GetRValue(Color);
m_nStartG = GetGValue(Color);
m_nStartB = GetBValue(Color);
// Farbwerte aktualisieren
UpdateData(false);
}
}
void
CApfelmaennchenEinstellungen::OnBnClickedButton2()
{
// Variable deklarieren
COLORREF Color;
// Farbwert ermitteln
if (GetColor(Color))
{
// Farbwert kopieren
m_nEndR = GetRValue(Color);
m_nEndG = GetGValue(Color);
m_nEndB = GetBValue(Color);
// Farbwerte aktualisieren
UpdateData(false);
}
Durch einen Aufruf der selbst definierten Funktion GetColor wird der vom
Benutzer ausgesuchte Farbwert an die COLORREF-Variable Color übertragen.
Die einzelnen Farbanteile werden dann in die zugehörigen Variablen mithilfe
der GetXValue-Makros übertragen.
Aktualisieren von Der abschließende Aufruf von UpdateData überträgt die neuen Variablenwerte
Dialogfeldinhalten in die Steuerelemente, da hier sonst noch die alten Daten angezeigt würden.
Testen Sie das Programm nun erneut aus und öffnen Sie den Farbdialog durch
Anwahl einer der beiden Schaltflächen. Es öffnet sich das von anderen Win-
dows-Programmen her bekannte Feld:
224
Entwickeln von SDI-Anwendungen
5
Abb. 5.13
Der Farbdialog
225
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
// Farbverlauf vorberechnen
int nRed = (pDoc->GetStartRed() -
pDoc->GetEndRed()) / pDoc->GetMaxIter();
int nGreen = (pDoc->GetStartGreen() -
pDoc->GetEndGreen()) / pDoc->GetMaxIter();
int nBlue = (pDoc->GetStartBlue() -
pDoc->GetEndBlue()) / pDoc->GetMaxIter();
226
Entwickeln von SDI-Anwendungen
5
RGB(0,0,0));
}
else
{
// befinden wir uns im
// Farbmodus?
if ((pDoc->IsColored()) &&
(nIterations > 0))
{
// Farbwerte berechnen
int nR, nG, nB;
nR = pDoc
->GetStartRed()
- (nIterations * nRed);
nG = pDoc
->GetStartGreen()
-(nIterations *
nGreen);
nB = pDoc
->GetStartBlue()
-(nIterations * nBlue);
// Farbwerte im
// gültigen Bereich
// halten
// Sicherheits-
// vorkehrung
if (nR < 0) nR = 0;
if (nR > 255) nR = 255;
if (nG < 0) nG = 0;
if (nG > 255) nG = 255;
if (nB < 0) nB = 0;
if (nB > 255) nB = 255;
// farbigen Pixel
// setzen
pDC->SetPixel(Position,
RGB(nR, nG, nB));
}
}
227
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Position.x++;
dX += dSX;
}
// nächste Zeile
Position.y++;
dY += dSY;
}
}
Berechnung Der Farbverlauf wird durch einfaches Verrechnen der Start- und Endfarben
des Farbverlaufs ermittelt, wodurch sich ein linearer Farbverlauf zwischen diesen beiden Werten
ergibt. Starten Sie das Programm, erhalten Sie nun die folgende Ausgabe:
Abb. 5.14
Zwischenstand
Testen Sie nun einmal die Auswirkungen von anderen Einstellungen innerhalb
des Optionsfelds aus. Verändern Sie den angezeigten Bildausschnitt, den Farb-
verlauf, oder die Grundeinstellung, ob farbige oder schwarz-weiße Apfelmänn-
chen zu zeichnen sind. Probieren Sie auch höhere und niedrigere Werte für die
maximale Iterationstiefe aus.
Auf den letzten Seiten dieses Kapitels wird es nun noch um einige abrundende
Veränderungen und Erweiterungen gehen.
228
Entwickeln von SDI-Anwendungen
5
Sie ist selbstverständlich auch innerhalb unseres Programms definiert und viel- Definition der
leicht erinnern Sie sich an die Deklaration des indicators Arrays, die hier zur Ver- Statuszeile
einfachung noch einmal wiedergegeben ist:
};
Die IDs, die hier angegeben werden, beschreiben die Inhalte, die sich in der Sta-
tuszeile befinden sollen. Schauen Sie sich die bisherige Statuszeile einmal an –
Sie werden sofort erkennen, welche IDs welchen Komponenten zugeordnet
sind:
Abb. 5.15
Elemente der
Statuszeile
Welche Angaben wären hier interessant? Der Benutzer kann einen Bereich des
Apfelmännchens auswählen und darstellen lassen. Dieses geht durch Eingabe
passender Werte in das Options-Dialogfeld.
Da wäre es doch nett, wenn bei einer Mausbewegung über dem Apfelmänn-
chen die aktuelle Koordinate, über der sich der Mauszeiger befindet, angezeigt
würde. So könnte man bequem interessante Stelle anfahren, die Werte notie-
ren und in das Optionsfeld eintragen.
229
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Wenn wir schon dabei sind, ergänzen wir die Statuszeile gleichzeitig noch um
die tatsächliche aktuelle Mauszeigerposition, relativ zur linken oberen Ecke,
gerechnet in Pixeln.
Ergänzen des Die IDs, die in das Array eingefügt werden sollen, können frei gewählt werden.
indicator-Arrays Ergänzen Sie das Array wie folgt:
Die neuen Informationen werden dabei hinter dem Separator aber vor den
Standardausgaben angezeigt.
Doch woher weiß das Programm nun, wie diese Werte aussehen sollen?
Die Antwort ist so einfach wie erwartet: überhaupt nicht. Der Programmierer
muss selbstständig für eine Aktualisierung der Werte sorgen. Dazu dienen die
IDs, denen ein geeigneter Anfangswert zugewiesen wird und über die später
die gewünschten Informationen in die Statuszeile eingetragen werden können.
Stringtabellen
Eintragen von Start- Bei der Arbeit mit Statuszeilen und ihren Anzeigebereichen ist es wichtig zu
werten in die neuen wissen, dass die Größe der einzelnen Felder (genauer: ihre Breite) bereits bei
Felder der Statuszeile der Initialisierung festgelegt wird.
Es ergibt sich von selbst, dass es zweckmäßig ist, hier bereits einen Starttext
anzugeben, der die maximal auftretende Textlänge impliziert und somit direkt
während der Initialisierung für ein ausreichend großes Statusleistenelement
sorgt.
230
Entwickeln von SDI-Anwendungen
5
Dieser Text wird in der Stringtabelle der Anwendung eingetragen, die zu den
Ressourcen unserer Applikation gehört. Öffnen Sie die Ressourcenansicht und
wählen Sie den String Table-Ordner aus. Dort klicken Sie auf die einzige vorhan-
dene String Table:
Abb. 5.16
Die Zeichenketten-
Ressource
ID Caption
ID_INDICATOR_XPOSCLIENT X : XXXX
ID_INDICATOR_YPOSCLIENT Y : XXXX
ID_INDICATOR_XPOSAPFEL X : XXXX.XXXX
ID_INDICATOR_YPOSAPFEL Y : XXXX.XXXX
Die automatisch erscheinenden Eintragungen im Value-Feld verändern Sie IDs für die neuen
nicht – hier wird automatisch eine noch nicht vergebene ID ausgewählt und Strings
eingesetzt.
Kompilieren und starten Sie das Programm, Sie haben nun die folgende Status-
zeile vorliegen:
Abb. 5.17
Die vorläufige
Statuszeile
231
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Dieses ist gerade der Inhalt, der in der String Table unter der spezifizierten ID
gefunden werden kann.
Gründe für die Wenn Sie mit einem nicht-größenfixierten Zeichensatz arbeiten (bei dem alle
gewählten Startwerte Zeichen dieselbe Größe belegen), werden Sie bemerkt haben, dass sämtliche
Ziffern, die als Angabe für die aktuelle Mauszeigerposition in Frage kommen,
unterschiedlich breit sind.
Hätten Sie als Starttext zum Beispiel 1111 angegeben, wäre die Breite des Felds
bei einer Ausgabe von 4444 beispielsweise gesprengt worden. Man verwendet
daher den Trick, statt einer konkreten Ziffernfolge so viele X-Zeichen anzuge-
ben, wie Ziffern zu erwarten sind. Das X-Zeichen ist nämlich breiter als jede
mögliche Ziffer und sorgt somit für ein ausreichend breites Ausgabefeld.
Der Vorteil liegt auf der Hand: Die ausgegebenen Texte sind nicht über den
kompletten Quelltext verstreut, sondern an einer zentralen Stelle zusammen-
gefasst.
Machen Sie viel Gebrauch von String Tables, ihre Programme werden es Ihnen
durch bessere Wartbarkeit und Übersichtlichkeit danken.
232
Entwickeln von SDI-Anwendungen
5
Sie benötigen also innerhalb dieser Klasse Zugriff auf die Statuszeile. Da bietet Zugriff auf die
es sich an, einen Zeiger auf die Statuszeile direkt beim Programmstart (bezie- Statusleiste
hungsweise beim Initialisieren der Ansichtsklasse) zu speichern.
Fügen Sie hierzu die folgende Zeile in die Klassendeklaration von CApfelmaenn-
chenView in der Datei ApfelmaennchenView.h ein:
private:
CStatusBar *m_pStatusBar;
CStatusBar ist gerade die Klasse, die eine Statusleiste beschreibt. Um einen Zei-
ger auf die Statuszeile des Hauptfensters zu erhalten, muss das Hauptfenster
natürlich zunächst einmal existieren.
Die MFC bietet für die meisten Klassen allerdings bereits Initialisierungsme-
thoden an, die in den Basisklassen definiert und in Ihren abgeleiteten Klas-
sen überschrieben werden können) – ein typischer Name für diese Methoden
ist OnInitialUpdate.
Wo fügt man diese Initialisierungen dann ein? Wie dem Kasten zu entnehmen Zugriff ermöglichen
ist, existiert für eine Ansichtsklasse eine Methode namens OnInitialUpdate, die
gerade zu diesem Zweck dient: Initialisierungen durchzuführen, die darauf ver-
trauen, dass sie umschließende Konstrukte – wie eben das Hauptfenster und
seine Statusleiste – bereits existieren.
Öffnen Sie die Klassenansicht für CApfelmaennchenView und wählen Sie die
überschreibbaren Methoden im Eigenschaftsfenster aus. Suchen sie hier die
Methode OnInitialUpdate und fügen eine überschreibende Methode in die
Klasse ein:
233
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
Abb. 5.18
Hinzufügen der
OnInitialUpdate-
Methode
#include "MainFrm.h"
Die Methode beschafft sich zunächst durch einen Aufruf von AfxGetApp-
Zugriff auf das Applikationsobjekt, das als Membervariable einen Zeiger auf
das Hauptfenster der Anwendung enthält.
Dieses Hauptfenster ist bei einer durch den Assistenten erzeugten Anwendung
immer vom Typ CMainFrame und enthält seinerseits ein Objekt vom Typ CSta-
234
Entwickeln von SDI-Anwendungen
5
tusBar namens m_wndStatusBar, das gerade die Statusleiste beschreibt, auf die
Sie Zugriff benötigen.
OnInitialUpdate speichert nun einen Zeiger auf dieses Objekt in der zuvor defi-
nierten CStatusBar Variablen m_pStatusBar und erlaubt so auch später den
Zugriff auf diese für uns so wichtige Zeile.
Grundlegend wurde die Wahl bereits auf eine die WM_MOUSEMOVE-Nachricht Aktualisieren der
behandelnde Funktion festgelegt. angezeigten Werte
// Text vorbereiten
strText.Format("X : %4d", point.x);
// y-Koordinate editieren
nIndex = m_pStatusBar
->CommandToIndex(ID_INDICATOR_YPOSCLIENT);
strText.Format("Y : %4d", point.y);
m_pStatusBar->SetPaneText(nIndex, strText);
235
Wahl eines Abbildungsmodus für das Apfelmännchen-
5
nIndex = m_pStatusBar
->CommandToIndex(ID_INDICATOR_YPOSAPFEL);
strText.Format("Y : %4.4f", dApfelY);
m_pStatusBar->SetPaneText(nIndex, strText);
// Standardbehandlung aufrufen
CView::OnMouseMove(nFlags, point);
}
Erläuterung zu OnMouseMove
Obwohl in der OnMouseMove-Methode einige Rechnungen durchgeführt wer-
den, handelt es sich im Großen und Ganzen doch um eine sehr geradlinig arbei-
tende Funktion.
236
Entwickeln von SDI-Anwendungen
5
Die vier Statuszeilenelemente, die innerhalb dieser Methode angepasst werden Vorgehen beim Aktua-
müssen, werden jeweils nach einem einfachen Schema editiert: lisieren der Statuszeile
Wert formatieren
Während die Pixelpositionen direkt aus dem Eingabeparameter point ausgele- Umrechnen der
sen werden können, muss die Position innerhalb der Mandelbrot-Menge Mauszeigerposition
berechnet werden. Hier kommen die gleichen Formeln zum Einsatz wie bereits
in OnDraw, sodass nicht weiter auf die Kalkulationen eingegangen werden soll.
Das Ergebnis der Bemühungen sehen Sie, wenn Sie das Programm kompilieren,
starten und dann die Maus über der Ausgabefläche hin- und herbewegen:
Abb. 5.19
Beispielwerte
Gummibänder kennt man unter Windows vor allem aus den zahlreichen Zei- Erweiterte Einfluss-
chenprogrammen. Sie arbeiten prinzipiell so, dass der Benutzer die Maustaste nahme durch den
an einer beliebigen Stelle drückt und gedrückt hält und dann durch weitere Benutzer
Mausbewegungen ein gestricheltes Rechteck aufspannen kann. Lässt er die
Maustaste wieder los, wird dieses Rechteck als Markierung aufgefasst und
kann weiter bearbeitet werden.
237
Auswahl eines Mandelbrot-Ausschnitts mit der Maus
5
Damit dieses funktionieren kann, muss allerdings zunächst die linke Maustaste
wieder „funktionslos“ gemacht werden – derzeit löst sie ja noch die Anzeige
des Optionsdialogs aus.
Der übliche Weg, Dialoge anzuzeigen, ist entweder einen Toolbar-Button oder
einen Menüpunkt für diese Aktion einzurichten. An dieser Stelle sollen beide
Möglichkeiten beschrieben werden.
Editieren der Sie finden sich im Toolbar-Editor wieder, der neben der kompletten derzeit vor-
Werkzeugleiste handenen Werkzeugleiste zwei weitere Fenster mit dem aktiven Toolbar-But-
ton und einer vergrößerten Ansicht desselben enthält.
Am rechten Rand der Werkzeugleiste finden Sie ein noch nicht belegtes Feld.
Klicken Sie darauf, wird es als neuer Button zur Toolbar hinzugefügt, direkt
daneben erscheint ein neuer Platzhalter für weitere zusätzliche Buttons.
Abb. 5.20
Die neue Ressource
erhält die ID
ID_OPTIONEN_-
EINSTELLUNGEN
238
Entwickeln von SDI-Anwendungen
5
Öffnen Sie dazu in der Ressourcenansicht das Menü IDR_MAINFRAME aus dem Neues Menü erzeugen
Menu-Ordner. Hier finden Sie, ähnlich dem Toolbar Editor, das derzeitige und mit Einträgen
Hauptfenstermenü sowie einen Platzhalter auf der rechten Seite, in den Sie füllen
eine neue Menübeschriftung eintippen können – tragen Sie hier Optionen ein.
Als Unterpunkt enthält dieses neue Menü dann ebenfalls einen Platzhalter, in
den Sie Einstellungen eintippen. Das gesamte Menü können Sie durch Drag &
Drop an eine andere Position schieben, zum Beispiel links neben das Hilfe-
menü. Es ergibt sich das folgende Bild:
Abb. 5.21
Das neue Menü
Die übrigen Einstellungen bleiben unverändert, es sei aber angeraten, dass Sie
sich kurz die Zeit nehmen, die Möglichkeiten anzutesten, um einen Überblick
über die zur Verfügung stehenden Optionen zu haben.
Abb. 5.22
Menüeigenschaften
239
Auswahl eines Mandelbrot-Ausschnitts mit der Maus
5
Durchreichen der Dieses mag Ihnen etwas seltsam vorkommen, denn sowohl Toolbar als auch
Fensternachrichten Menü gehören ja zum Hauptfenster, nicht zur Ansichtsklasse. Die Ansichts-
klasse ist allerdings ein Kindfenster des Hauptfensters und kann somit auch
eine behandelnde Funktion für die Nachrichten annehmen.
Das macht umso mehr Sinn, als dass wir, wie Sie gleich sehen werden, einige
Operationen bei der Nachrichtenbearbeitung durchführen, die Daten der
Ansichtsklasse benötigen.
Entschärfen der Doch gehen wir Schritt für Schritt vor: Zunächst muss die OnLButtonDown-
OnLButtonDown- Methode von ihrer bisherigen Aufgabe befreit werden. Schneiden Sie dazu
Methode ihren Inhalt aus (möglichst über eine Bearbeiten > Ausschneiden Operation),
sodass nur der folgende Rumpf erhalten bleibt:
dlg.m_dStartX = pDoc->GetStartX();
dlg.m_dStartY = pDoc->GetStartY();
dlg.m_dEndX = pDoc->GetEndX();
dlg.m_dEndY = pDoc->GetEndY();
dlg.m_nMaxIter = pDoc->GetMaxIter();
dlg.m_bColor = pDoc->IsColored();
dlg.m_nStartRed = pDoc->GetStartRed();
dlg.m_nStartG = pDoc
240
Entwickeln von SDI-Anwendungen
5
->GetStartGreen();
dlg.m_nStartB = pDoc->GetStartBlue();
dlg.m_nEndR = pDoc->GetEndRed();
dlg.m_nEndG = pDoc->GetEndGreen();
dlg.m_nEndB = pDoc->GetEndBlue();
// Grafik aktualisieren
InvalidateRect(NULL);
}
}
Testen Sie das Programm aus: Das Optionsfeld ist nun über das neue Menü
und den neuen Toolbar-Knopf zu erreichen, die linke Maustaste hat keine Funk-
tion mehr.
Am besten erklärt man den Einsatz direkt an einem Beispiel, daher sofort ohne
Umschweife die neue OnLButtonDown-Methode für das Apfelmännchen-Pro-
jekt:
241
Auswahl eines Mandelbrot-Ausschnitts mit der Maus
5
// Gummiband initialisieren
m_Tracker.TrackRubberBand(this, point, true);
// Bereich ermitteln
RECT Rect;
m_Tracker.GetTrueRect(&Rect);
242
Entwickeln von SDI-Anwendungen
5
// neu zeichnen
InvalidateRect(NULL);
}
Der interessante Teil dieser Methode beschränkt sich auf die ersten Zeilen. Dort
wird zunächst ein neues CRectTracker-Objekt angelegt, das bereits sämtliche
Methoden enthält, um eine Gummibandauswahl einzuleiten.
TrackRubberBand
Das Einleiten selbst geschieht durch den Aufruf von TrackRubberBand, dessen
Prototyp wie folgt aussieht:
BOOL TrackRubberBand(
CWnd* pWnd,
CPoint point,
BOOL bAllowInvert = TRUE
);
Tabelle 5.4
Parameter Beschreibung
Parameter
pWnd Zeiger auf das Fenster, in dem der Benutzer die Aus-
wahl durchführen kann.
Hier sieht man gleich den Vorteil, die Behandlungsmethode direkt in der
Ansichtsklasse implementiert zu haben: beim Aufruf von TrackRubberBand
muss das Fenster übergeben werden, in dem die Auswahl erfolgen soll (und auf
das sie dann folgerichtig beschränkt ist).
Da die Ansichtsklasse selbst von CView abgeleitet ist, kann einfach der this-Zei-
ger übergeben werden. Die aktuelle Mausposition wird aus dem OnLButton-
243
Auswahl eines Mandelbrot-Ausschnitts mit der Maus
5
GetTrueRect
Nach der Auswahl TrackRubberBand unterbricht die Programmabarbeitung, bis der Benutzer die
Maustaste wieder losgelassen hat. Danach wird mit GetTrueRect das ausge-
wählte Rechteck ausgelesen:
Parameter Beschreibung
Die ermittelten Koordinaten können dann, wie auch schon in OnDraw gesehen,
wieder in die Mandelbrot-Koordinaten umgerechnet werden, die dann im
Dokument abgelegt werden.
Letztendlich wird die Ansicht zum Neuzeichnen aufgefordert, was in der Folge
die Darstellung des gerade gewählten Ausschnitts ergibt.
Zurücksetzen des Was liegt also näher, als eine Reset-Operation auf die rechte Maustaste zu
Dokuments auf die legen.
Ausgangsansicht
Überlegen Sie zunächst einmal selbst, welche Schritte dazu notwendig sind,
wenn Folgendes erfüllt sein soll:
Zurücksetzen des Ausschnitts auf den Bereich (-2.0, -2.0) bis (2.0, 2.0).
Farb- und Iterationseinstellungen sollen unverändert bleiben.
244
Entwickeln von SDI-Anwendungen
5
Versuchen Sie, diese Schritte zu implementieren. Als kleine Hilfe sei gesagt,
dass Sie hierfür nur eine einzige Methode benötigen, deren Rumpf mit maxi-
mal zehn Zeilen echtem Code auskommen sollte.
// neu zeichnen
InvalidateRect(NULL);
// Standardbehandlung aufrufen
CView::OnRButtonDown(nFlags, point);
}
Was passiert? Zunächst wird ein Zeiger auf das Dokument geholt, da einige der
Dokumentwerte zurückgesetzt werden sollen.
Über diesen Zeiger reinitialisieren Sie dann den Ausgabebereich auf (-2.0, -2.0)
bis (2.0, 2.0).
Abschließend wird das Fenster durch einen Aufruf von InvalidateRect zum Neu- Neuzeichnen
zeichnen aufgefordert – fertig ist das Zurücksetzen der Darstellung auf die Aus- des Fensters
gangswerte.
Testen Sie nun das Programm aus und wählen Sie verschiedene Bereiche mit
dem Gummiband aus – wird die Darstellung zu grob, erhöhen Sie die maximale
Iterationstiefe für feiner aufgelöste Ergebnisse:
245
Drucken von Dokumenten
5
Abb. 5.23
Apfelmännchen in
der Vergrößerung
Der Ablauf beim Dru- Einige Kleinigkeiten müssen aber dennoch eingestellt werden, daher ist es
cken von Dokumenten sinnvoll zu wissen, wie der tatsächliche Ablauf im Framework während eines
Druckvorgangs aussieht (sprich: die Reihenfolge, in der druckrelevante Funk-
tionen aufgerufen werden, sobald aus der Anwendung heraus beispielsweise
der Menüpunkt Datei > Drucken aufgerufen wird):
Abb. 5.24
Ablauf beim Drucken
246
Entwickeln von SDI-Anwendungen
5
Wichtig zu wissen ist noch, dass der Zyklus OnPrepareDC->OnPrint->OnPrepa- Zyklen beim Drucken
reDC so lange durchlaufen wird, wie neue Seiten vorliegen. OnPrepareDC-
>OnPrint wird also einmal pro auszudruckender Seite aufgerufen.
247
Laden und Speichern
5
WindowRect.Height()) ? WindowRect.Width()
: WindowRect.Height();
// Clientbereich ...
pDC->SetWindowOrg(0,0);
pDC->SetWindowExt(nAufloesung, nAufloesung);
// Standardbehandlung aufrufen
CView::OnPrint(pDC, pInfo);
}
Analog zu OnPrepareDC wird hier der bedruckbare Bereich über Fenster- und
Viewport-Größenangaben bestimmt. Die Seitengröße stammt dabei aus der
übergebenen PrintInfo-Struktur, deren genaue Komponenten Sie der Online-
Hilfe entnehmen.
Zunächst sei gesagt, dass bei der Windows-Programmierung häufig der Begriff
der Serialisierung Verwendung findet, wobei allerdings zumeist nichts anderes
gemeint ist, als das Laden und Speichern von Dokumentinhalten.
248
Entwickeln von SDI-Anwendungen
5
Übergeben bekommt Serialize eine Referenz auf ein CArchive-Objekt, das die
eigentliche Hauptarbeit beim Durchführen der Serialisierung übernimmt.
Das CArchive-Objekte kennt die überladenen Operatoren << und >>, die bei-
spielsweise auch bei cout und cin Verwendung finden. Mit ihrer Hilfe können
einfache Datentypen problemlos in Dateien geschrieben werden.
In der Tat finden die dafür notwendigen Operationen nicht erst in Serialize
statt, sondern bereits in vorangegangenen, vom Framework durchgeführten
Aufrufen. In der Regel wurde ein Lade- oder Speicherprozess bereits durch
eine ganze Reihe von Methoden hindurchgereicht, unter anderem beispiels-
weise durch die Funktionen OnOpenDocument und OnSaveDocument.
Der Name dieser zuletzt genannten Methode ist IsStoring, die true zurücklie-
fert, wenn das Dokument (oder besser: das gerade zu serialisierende Objekt)
gespeichert werden soll, oder false, falls Daten einzulesen sind.
249
Laden und Speichern
5
Entwerfen einer Erweitern Sie nun also das Apfelmännchenprojekt um die Möglichkeit, Man-
Serialize-Methode delbrot-Ausschnitte – beziehungsweise deren Kenndaten – zu serialisieren:
Die Methode ist sehr geradlinig geraten und sollte keinerlei Verständnis-
schwierigkeiten bereiten.
Sie können nun also Dokumentdaten lesen und schreiben, Apfelmännchen dru-
cken und mit der Maus interessante Bereiche vergrößern. Weiterhin haben Sie
einiges über die unter Windows zur Verfügung stehenden Abbildungsmodi
gelernt.
250
Entwickeln von SDI-Anwendungen
5
Tooltips in Dialogfeldern
Tooltips sind in Windows-Anwendungen an vielen Stellen gang und gäbe und
sie werden auch vom MFC Framework standardmäßig unterstützt – Sie können
das zum Beispiel bei den Symbolen der Werkzeugleiste sehen, wenn Sie mit
dem Mauszeiger darauf zeigen.
Aber vielleicht ist Ihnen schon aufgefallen, dass es keine Tooltips im Options- Tooltips im Options-
Dialogfeld der Apfelmännchen-Anwendung gibt. Dialogfeld
Das liegt schlicht daran, dass diese Funktionalität nicht direkt von den MFC-
Klassen vorgesehen ist, sondern erst freigeschaltet werden muss. Das ist zwar
denkbar einfach, stellt einen anfangenden Programmierer aber vermutlich
doch vor ein schwer zu überwindende Hürde.
Der erste Schritt, um das Options-Dialogfeld um Tooltips zu erweitern, muss es Aktivierung von
daher sein, diese Ereignisse als behandlungswürdig in die Message-Map der Tooltips in benutzer-
CApfelmaennchenEinstellungen-Klasse einzubinden. definierten Dialogen
BEGIN_MESSAGE_MAP(CApfelmaennchenEinstellungen,
CDialog)
ON_BN_CLICKED(IDC_BUTTON1, OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, OnBnClickedButton2)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF,
OnToolTipNotify)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF,
OnToolTipNotify)
END_MESSAGE_MAP()
Die beiden neuen Zeilen machen inhaltlich das Gleiche, fordern nämlich für
Tooltips Texte an, die dargestellt werden sollen, sobald sich der Mauszeiger
über einem interessierenden Objekt befindet.
TTN_NEEDTEXTA sorgt für ein Auslesen der ASCII Variante eines Texts (für
Win95 Betriebssysteme), TTN_NEEDTEXTW (für WinNT Betriebssysteme) für
251
Tooltips in Dialogfeldern
5
OnToolTipNotify-Implementierung
Manuelles Editieren Durch das Eintragen der neuen Zeilen in die Message Map weiß die Anwen-
der Message Map dung, wo die Texte für die im Optionsfeld vorliegenden Steuerelemente
herzuholen sind, nämlich gerade aus der Funktion OnToolTipNotify, deren
Deklaration in die Datei ApfelmaennchenEinstellungen.h gehört:
252
Entwickeln von SDI-Anwendungen
5
Geben Sie eine inkorrekte – und somit inkompatible – Funktion an, wird es
unweigerlich zu Abstürzen kommen, da durch die ungültige Funktion Fehler
auf dem Stack verursacht werden.
Schauen Sie also jeweils in der Online-Hilfe nach, welches Ereignis welchen
Funktionsprototypen voraussetzt, um unnötige Probleme dieser Art zu ver-
meiden.
BOOL
CApfelmaennchenEinstellungen::OnToolTipNotify(UINT id,
NMHDR* pNMHDR, LRESULT* pResult)
{
// Zeiger auf Tooltiptext holen
TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
UINT nID =pNMHDR->idFrom;
// String vorbereiten
CString s;
// String holen
s.LoadString(nID);
// Tooltip geschrieben
return(FALSE);
}
253
Tooltips in Dialogfeldern
5
Weiterhin wird das auslösende Handle (gerade das Handle des Steuerelements,
über dem sich zur Zeit der Mauszeiger befindet) ausgelesen.
Bei Tooltips ist es in der Regel so, dass es sich in der Tat um ein Handle, nicht
um eine konkrete ID eines Steuerelements handelt, die an die Notifizierungs-
methode übergeben wird. Daher ist es nötig, dieses Handle in eine ID umzu-
rechnen.
Auslesen von Tooltip- Es ist jetzt ein Leichtes, diese ID aus der String Table, mit der ja auch zuvor
Texten aus der schon gearbeitet wurde, in eine Cstring-Variable zu schreiben und diese dann in
Zeichenketten- die Tooltip-Struktur zu kopieren.
ressource
Der Rückgabewert false teilt dem Aufrufer mit, dass der Tooltip-Text korrekt in
die Struktur eingetragen werden konnte – das Tooltip wird dargestellt.
Sie müssen natürlich für die einzelnen IDs passende Texte in die Zeichenket-
tentabelle ablegen, sonst können die Texte nicht gefunden werden.
Fügen Sie also die folgenden Zeilen in die String Table ein:
Tabelle 5.5
ID Text
Texte für die Tooltips
IDC_XSTART X-Start-Komponente
IDC_YSTART Y-Start-Komponente
IDC_XEND X-Ende-Komponente
IDC_YEND Y-Ende-Komponente
254
Entwickeln von SDI-Anwendungen
5
Das liegt daran, dass, wie ja schon oben gesagt, Tooltips in Dialogfeldern stan-
dardmäßig nicht aktiviert sind.
Sie können dieses aber leicht erreichen, indem Sie die OnInitDialog-Methode EnableToolTips
der CApfelmaennchenEinstellungen-Klasse überschreiben und wie folgt editie-
ren:
Diese Zeilen verändern das initiale Erzeugen des Dialogs nicht, sorgen aber
durch die EnableToolTips-Zeile dafür, dass Tooltips innerhalb des Dialogs ange-
zeigt werden können.
Abb. 5.25
Tooltip im
Dialogfenster
255
Tooltips in Dialogfeldern
5
Zusammenfassung
In diesem Kapitel haben Sie gelernt, SDI-Anwendungen zu entwickeln, um
Menüs und neue Toolbar-Buttons zu ergänzen, sowie Bedienoberflächen durch
das Hinzufügen von selbst definierten und Windows-Standard Dialogfeldern
inklusive Tooltips komfortabler zu gestalten.
Sie haben die zahlreichen Abbildungsmodi von Windows kennen gelernt und
erfahren, wie einfach eine Gummibandauswahl innerhalb eines Fensters zu
realisieren ist.
Abgerundet wurde das Kapitel durch eine Einführung in das Drucken von Doku-
menten und die Serialisierung von Dokumentdaten.
256
MDI-Applikationen
MDI-Applikationen 258
Quelltextunterschiede zwischen SDI-und MDI-Applikationen 258
Das MDI-Projekt „Bezierkurven“ 258
Erweitern der Dokumentenklasse 268
Initialisieren des Dokuments 273
Darstellen der Dokumentdaten 274
Integration von Benutzerinteraktionen 276
Anwählen von Punkten 277
Bewegen der Kontroll- und Knotenpunkte 285
Bekanntmachen der neuen Ansichtsklasse 290
Implementation der alternativen Ansichtsklasse 296
MDI-Applikationen
6
MDI-Applikationen
Unterschiede MDI-Anwendungen (Multiple Document Interface) unterscheiden sich von
zwischen SDI und MDI ihren kleineren SDI-Verwandten dadurch, dass es dem Benutzer ermöglicht
wird, mehrere Dokumente zur gleichen Zeit geöffnet zu halten, wobei diese in
einem gemeinsamen Hauptrahmenfenster zusammen gehalten werden.
Ein gutes Beispiel für diese Art von Anwendung ist auch das Visual Studio
selbst, hier können beliebig viele Quelltextfenster nebeneinander existieren.
Bei SDI-Anwendungen, auf der anderen Seite, ist immer nur ein Dokument zur
Zeit aktiv, das Neuanlegen eines neuen Dokuments oder das Öffnen von
gespeicherten Daten, führt zu einem Schließen der derzeit bearbeiteten Datei
(einhergehend mit einer Sicherheitsabfrage, falls Veränderungen an dem alten
Dokument noch nicht gespeichert wurden.
Der vorliegende Abschnitt beschäftigt sich nun mit MDI-Applikationen und ver-
tieft das Wissen über Serialisierung und Abbildungsmodi. Weiterhin wird auf-
gezeigt, wie mehrere Ansichtsklassen mit einem Dokument verknüpft werden
können, um Dokumentdaten auf verschiedene Weisen darstellen zu können.
Weitere Unterschiede beziehen sich auf die Art, wie die OnNewDocument-
Methode eingesetzt wird, aber auch auf die Anbindung von Ansichtsklassen
und das Erzeugen des Hauptrahmenfensters.
Auf diese wird zu gegebener Zeit eingegangen, sobald die betreffenden Quell-
textpassagen besprochen werden.
258
MDI-Applikationen
6
Eine Bezierkurve besteht prinzipiell aus vier einzelnen Punkten, von den zwei Einführung in
den Anfangs- und Endpunkt der Kurve beschreiben, während die anderen bei- Bezierkurven
den für die Kurvenführung selbst verwendet werden.
Eine Kurve bewegt sich dabei vom Startpunkt aus jeweils auf den ersten Kon-
trollpunkt zu, und verändert ihren Lauf dann derart, dass sie, vom zweiten Kon-
trollpunkt ausgehend, auf den Endpunkt zulaufen kann.
Am besten erklärt sich dieses Verhalten durch einige Beispielkurven. In der fol-
genden Grafik sind die eckigen Punkte jeweils Start- bzw. Endpunkt, während
die runden Punkte die Kontrollpunkte darstellen.
Abb. 6.1
Beispiele für
Bezierkurven
Das zu entwickelnde Programm soll nun den Benutzer befähigen, solche Kur-
ven selbstständig durch einfache Drag-&-Drop-Operationen anzulegen. Wei-
terhin sollen die aktuellen Positionen der Knoten- und Kontrollpunkte in einem
zweiten Fenster optional eingeblendet werden können – dieses wird durch eine
weitere Ansichtsklasse realisiert.
259
Das MDI-Projekt „Bezierkurven“
6
Austesten der gene- Kompilieren Sie daher als Erstes die entstandenden Sources und testen Sie das
rierten Anwendung Grundgerüst auf seine Funktionalität hin aus. Wie Sie sehen, besteht die Anwen-
dung neben dem Hauptfenster mit Menü und Werkzeugleiste aus einer freien
Fläche, in der sich die einzelnen Dokumentfenster frei positionieren lassen.
Erzeugen Sie einige neue Dokumente durch Anwahl des Menüpunkts Datei >
Neu beziehungsweise durch wiederholtes Anklicken des Symbols Neues Doku-
ment in der Werkzeugleiste des Programms.
Abb. 6.3
Weitere Dokumente
260
MDI-Applikationen
6
Zu nennen sind hier die beiden Files childfrm.h und childfrm.cpp, die im Folgen- childfrm.h und
den abgedruckt und erläutert sind. Begonnen wird mit der Headerdatei child- childfrm.cpp
frm.h:
// Attribute
public:
// Operationen
public:
// Überschreibungen
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
// Implementierung
public:
virtual ~CChildFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
Diese Datei entspricht inhaltlich, bis auf fehlende Status- und Werkzeugleis-
tenkomponenten, der MainFrm.h aus dem SDI-Kapitel. Das ist bei näherer
Überlegung auch einsichtig klar, denn MDI-Dokumentfenster sind, wie Haupt-
rahmenfenster auch, einfache Fenster und als solche funktionsgleich mit dem
übergeordneten Elternfenstern.
261
Das MDI-Projekt „Bezierkurven“
6
Egal, ob nun ein Hauptrahmen- oder ein Kindfenster als Klasse definiert
wird, die MFC-Entwickler haben darauf geachtet, dass die grundlegenden
Elemente bei beiden Varianten dieselben sind.
Auf diese Weise ist es einem Entwickler möglich, sich schnell in neue Klassen,
mit denen er zuvor noch nicht gearbeitet hat, einzuarbeiten.
Sicherlich vergeht einige Zeit, bis Sie die Gemeinsamkeiten ausgemacht und
zu Ihrem Vorteil nutzen können, doch sind Sie daraufhin in der Lage, sehr
effizient mit den MFC-Klassen zu arbeiten.
#include "ChildFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CChildFrame
IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd)
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd)
END_MESSAGE_MAP()
// CChildFrame Erstellung/Zerstörung
CChildFrame::CChildFrame()
262
MDI-Applikationen
6
{
// TODO: Hier Code für die Memberinitialisierung einfügen
}
CChildFrame::~CChildFrame()
{
}
return TRUE;
}
// CChildFrame Diagnose
#ifdef _DEBUG
void CChildFrame::AssertValid() const
{
CMDIChildWnd::AssertValid();
}
#endif //_DEBUG
Es besteht allerdings die Möglichkeit, das Menü entsprechend des gerade akti-
ven Dokuments zu verändern, was natürlich insbesondere bei mehreren vor-
handenen Ansichtsklassen interessant sein könnte.
263
Das MDI-Projekt „Bezierkurven“
6
Als Erstes sollte dazu aber ein passender Abbildungsmodus gewählt werden,
sodass die Bezierkurven verzerrungsfrei gezeichnet werden.
// Abbildungsmodus setzen
pDC->SetMapMode(MM_ISOTROPIC);
// Kontextausmaße setzen
pDC->SetWindowExt(300, 300);
pDC->SetViewportExt(Client.right,
Client.bottom);
CView::OnPrepareDC(pDC, pInfo);
}
Man könnte hier auch eine höhere Auflösung wählen, insbesondere um eine
feinere Positionierung der Knoten- und Kontrollpunkte zu erlauben, doch zu
Demonstrationszwecken ist diese Granularitätsstufe vollkommen ausreichend.
264
MDI-Applikationen
6
ASSERT_VALID(pDoc);
Bezier[0].x = 100;
Bezier[0].y = 100;
Bezier[1].x = 100;
Bezier[1].y = 200;
Bezier[2].x = 200;
Bezier[2].y = 200;
Bezier[3].x = 200;
Bezier[3].y = 100;
pDC->Rectangle(Bezier[3].x – 5,
Bezier[3].y – 5,
Bezier[3].x + 5,
Bezier[3].y + 5);
pDC->Ellipse(Bezier[2].x – 5,
Bezier[2].y – 5,
Bezier[2].x + 5,
Bezier[2].y + 5);
pDC->PolyBezier(Bezier, 4);
}
Nachdem die Koordinaten für Knoten- und Kontrollpunkte festgelegt wurden, Zeichenfunktionen
werden die Rechtecke für die Knoten- und Ellipsen für die Kreise gezeichnet.
Dabei kommen die Gerätekontextmethoden Rectangle und Ellipse zum Einsatz,
deren Prototypen im Folgenden abgedruckt sind:
265
Das MDI-Projekt „Bezierkurven“
6
BOOL Rectangle(
int x1,
int y1,
int x2,
int y2
);
Parameter Beschreibung
BOOL Ellipse(
int x1,
int y1,
int x2,
int y2
);
Parameter Beschreibung
266
MDI-Applikationen
6
Die Koordinaten werden jeweils um den Wert 5 erhöht beziehungsweise ver- Anpassen der
mindert, um eine ausreichend große Region um den spezifizierten Punkt zu Koordinaten
erzeugen, und somit das gezeichnete Objekte (das Rechteck beziehungsweise
die Ellipse) deutlich sichtbar im Fenster zu repräsentieren.
Angenommen, ein Rechteck soll immer 10 Pixel breit und hoch sein, so
müsste bei einer Fensterbreite von 1.000 Pixeln und 1.000 eingestellten logi-
schen Einheiten im Abbildungsmodus ein Rechteck mit einer Breite von 10
logischen Einheiten gezeichnet werden, während bei einer Fensterbreite von
500 Pixeln und gleichen logischen Einheiten ein Rechteck von 20 logischen
Einheiten gezeichnet werden muss.
Dieses Verfahren bietet sich zum Beispiel dann an, wenn, wie hier, Objekte
zur Benutzerinteraktion verwendet werden, die mit der Maus angewählt
und verschoben werden können.
Beachten Sie, dass diese Rechnungen für Höhe und Breite gegebenenfalls
separat durchzuführen sind, wenn ein anisotropischer Abbildungsmodus
eingestellt ist, oder sich die eingestellten logischen Einheiten im Fenster in
Breite und Höhe unterscheiden.
BOOL PolyBezier(
const POINT* lpPoints,
int nCount
);
267
Erweitern der Dokumentenklasse
6
Parameter Beschreibung
lpPoints Zeiger auf ein Array von POINT-Strukturen, die die einzelnen
Koordinaten der Kontroll- und Knotenpunkte enthalten. Die
Reihenfolge dabei ist jeweils: Startpunkt, Kontrollpunk1,
Kontrollpunkt2, Endpunkt. Bei mehr als einer Kurve dient
der Endpunkt einer Kurve gleichzeitig als Startpunkt der
nächsten Kurve, sodass für n Kurven ((2 * n) + 1) POINT-
Strukturen benötigt werden.
Sie können sich das Ergebnis nach einem Neukompilieren des Projekts direkt
ansehen:
Abb. 6.4
Eine erste Bezierkurve
Wenn Sie jetzt weitere neue Dokumente erzeugen, werden zusätzliche Kind-
fenster geöffnet, die jeweils die gleiche Kurvendarstellung zeigen.
Der nächste Schritt besteht nun darin, die bislang statischen Knoten- und Kon-
trollpunkt-Koordinaten in die Dokumentenklasse zu verlagern.
268
MDI-Applikationen
6
Die vier relevanten Punkte einer Bezierkurve werden jeweils über eine POINT
Struktur gesichert. Das erlaubt eine übersichtliche Objekt-Struktur und einfa-
chen Zugriff auf die tatsächlich interessierenden X-/Y-Koordinaten.
Änderungen an CBezierkurvenDoc
Die Klassendeklaration von CBezierkurvenDoc muss nun um die Aufnahme der
Datenstruktur sowie geeignete Zugriffsmethoden erweitert werden:
// Attribute
public:
// Operationen
public:
// Überschreibungen
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
269
Erweitern der Dokumentenklasse
6
// Implementierung
public:
virtual ~CBezierkurvenDoc();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
private:
SBezierKurve m_BezierKurve;
public:
POINT GetStartNode();
POINT GetEndNode();
POINT GetControlPoint1();
POINT GetControlPoint2();
Diese Änderungen sind leicht verständlich und analog zu denen des letzten
Kapitels. Es ist zu erkennen, dass der Aufbau eines MDI-Anwendungsdoku-
ments exakt dem eines SDI-Anwendungsdokuments entspricht – Sie können
sich also auch hier auf bereits bekannte Strukturen und Formalitäten verlassen.
Delegation der Im neuen Projekt wird die tatsächliche Serialisierungsarbeit an ein Strukturob-
Serialisierung jekt delegiert. Die Implementation der zugehörigen Serialisierungsmethode für
dieses Objekt gehört in die Datei BezierkurvenDoc.cpp und hat nachstehenden
Aufbau:
270
MDI-Applikationen
6
Die Methode tut das, was man erwarten sollte: Nachdem geprüft wird, ob ein
Speicher- oder Ladezugriff vorliegt, werden die Daten der Punkte elementweise
in das Archiv geschrieben beziehungsweise aus diesem ausgelesen.
271
Erweitern der Dokumentenklasse
6
POINT CBezierkurvenDoc::GetStartNode()
{
return (m_BezierKurve.m_StartKnoten);
}
POINT CBezierkurvenDoc::GetEndNode()
{
return (m_BezierKurve.m_EndKnoten);
}
POINT CBezierkurvenDoc::GetControlPoint1()
{
return (m_BezierKurve.m_KontrollPunkt1);
}
POINT CBezierkurvenDoc::GetControlPoint2()
{
return (m_BezierKurve.m_KontrollPunkt2);
}
272
MDI-Applikationen
6
Trotzdem bietet es sich an, sämtliche Initialisierungen für ein Objekt in der Koordinaten
Form innerhalb dieser Methode vorzunehmen, dass ein Dokument komplett initialisieren
durch einen einfachen Zugriff auf diese Funktion auf einen definierten Startzu-
stand gesetzt wird – die Verwendung von Konstruktoren zu diesem Zweck ist,
wie Sie in den vorangegangenen Abschnitten bereits mehrfach gesehen haben,
eher als verpönt anzusehen.
Sorgen Sie für diesen Umstand durch Hinzufügen einiger Zeilen in die OnNew-
Document-Methode:
BOOL CBezierkurvenDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_BezierKurve.m_EndKnoten.x = 200;
m_BezierKurve.m_EndKnoten.y = 100;
m_BezierKurve.m_KontrollPunkt1.x = 100;
m_BezierKurve.m_KontrollPunkt1.y = 200;
m_BezierKurve.m_KontrollPunkt2.x = 200;
m_BezierKurve.m_KontrollPunkt2.y = 200;
return TRUE;
}
273
Darstellen der Dokumentdaten
6
Magische Zahlen
Immer, wenn konstante Zahlen in Quelltexten eingesetzt werden, wie es
hier im Falle der Größenangaben für Rechtecke und Ellipsen vorkommt,
sollte sich der Programmierer Gedanken darüber machen, für diese Werte
eine Konstante einzuführen und statt mit dem Skalar mit diesem nicht ver-
änderlichen Wertespeicher zu arbeiten.
Die Vorteile liegen auf der Hand: abgesehen davon, dass Quelltexte, die mit
aussagekräftigen Konstantennamen arbeiten, deutlich einfacher zu lesen
sind (die 5 könnte in unserem Fall ja auch für ganz andere Sachverhalte ste-
hen – im englischen spricht man hier gern von einem worst- case-Szenario,
wenn aus dem Kontext überhaupt nicht hervorgeht, wofür der Wert steht.
Diese Zahlen bezeichnet man daher auch als magische Zahlen, da ihre mög-
liche Bedeutung nur durch die Vorstellungskraft des Quelltextlesers
beschränkt wird), wird die Wartung deutlich vereinfacht.
Angenommen, Sie stellen fest, dass die Größe der Rechtecke nicht adäquat
zu ihren Vorstellungen ist, können Sie bei Verwendung einer Konstanten
einfach deren Wert geeignet anpassen und müssen nicht womöglich hun-
derte von Quelltextstellen editieren – man stelle sich nur vor, wie leicht ein
Vorkommen des Werts übersehen werden kann, was dann in der Folge zu
unvorgesehenen Ergebnissen führen mag.
Als Faustregel gilt, dass für jeden Wert außer 0 und 1 eine Konstante einge-
führt werden sollte, sogar wenn absehbar ist – oder gerade dann erst recht –
dass der Wert nur einmal innerhalb des Programms verwendet werden wird.
Ausnahmen bilden hier lediglich Indizes von Arrays.
const c_iKnotenRadius = 5;
274
MDI-Applikationen
6
Bezier[0] = pDoc->GetStartNode();
Bezier[1] = pDoc->GetControlPoint1();
Bezier[2] = pDoc->GetControlPoint2();
Bezier[3] = pDoc->GetEndNode();
pDC->Rectangle(Bezier[3].x – c_iKnotenRadius,
Bezier[3].y – c_iKnotenRadius,
Bezier[3].x + c_iKnotenRadius,
Bezier[3].y + c_iKnotenRadius);
pDC->Ellipse(Bezier[2].x – c_iKnotenRadius,
Bezier[2].y – c_iKnotenRadius,
Bezier[2].x + c_iKnotenRadius,
Bezier[2].y + c_iKnotenRadius);
pDC->PolyBezier(Bezier, 4);
}
Beachten Sie bei dieser neuen Variante vor allem auch, dass sie durch die Ver-
wendung der c_iKnotenRadius-Konstante deutlich besser lesbar ist.
275
Integration von Benutzerinteraktionen
6
Der nächste Schritt muss also darin bestehen, die Benutzerinteraktion zu inte-
grieren, die ein Verschieben der einzelnen Punkte gestattet.
Statusvariablen
Aktuelle Bewegungen Damit das Programm jederzeit darüber informiert ist, welcher Punkt gerade
bewegt wird, werden in der Ansichtsklasse CBezierkurvenView vier boolesche
Variablen angelegt, die jeweils für eine der vier (nur unabhängig voneinander
durchführbaren) Bewegungswünsche vorgesehen sind.
private:
bool m_bMovingStartNode;
bool m_bMovingEndNode;
bool m_bMovingControlPoint1;
bool m_bMovingControlPoint2;
276
MDI-Applikationen
6
Wie Sie mittlerweile wissen, gehören solche Aktionen in eine Behandlungsme- Behandlungsmethode
thode für die linke Maustaste, weshalb Sie jetzt eine passende Nachrichtenver- für die linke Maustaste
arbeitungsfunktion für die WM_LBUTTONDOWN-Botschaft anlegen und dann
deren Funktionsrumpf wie folgt bearbeiten:
// Abbildungsmodus setzen
dc.SetMapMode(MM_ISOTROPIC);
// Kontextausmaße setzen
dc.SetWindowExt(300, 300);
dc.SetViewportExt(Client.right, Client.bottom);
// Koordinaten umrechnen
dc.DPtoLP(&MausPosition);
if (IsHit(MausPosition, pDoc->GetStartNode()))
{
m_bMovingStartNode = true;
SetCapture();
InvalidateRect(NULL);
}
else
{
277
Anwählen von Punkten
6
if (IsHit(MausPosition,
pDoc->GetEndNode()))
{
m_bMovingEndNode = true;
SetCapture();
InvalidateRect(NULL);
}
else
{
if (IsHit(MausPosition,
pDoc->GetControlPoint1()))
{
m_bMovingControlPoint1 =
true;
SetCapture();
InvalidateRect(NULL);
}
else
{
if (IsHit(MausPosition,
pDoc->GetControlPoint2()))
{
m_bMovingControlPoint2
= true;
SetCapture();
InvalidateRect(NULL);
}
}
}
}
CView::OnLButtonDown(nFlags, point);
}
Erklärungen zu OnLButtonDown
Die OnLButtonDown-Methode gliedert sich in zwei wesentliche Abschnitte.
Umrechnung von Zunächst wird festgelegt, welcher Mauspunkt tatsächlich getroffen wurde. Da
Koordinaten an OnLButtonDown nur Pixelkoordinaten, die relativ zur linken oberen Ecke der
Client Area des betreffenden Fensters angegeben sind, übermittelt werden, ist
es notwendig, eine geeignete Umrechnung dieser Werte in logische Koordina-
ten durchzuführen.
278
MDI-Applikationen
6
Diese Methoden gehören zur CDC-Klasse, es muss also zunächst ein Geräte-
kontext zur Verwendung der Funktionen vorliegen. Diesen erzeugt man, wie in
diesem Beispiel zu sehen, durch das Anlegen eines neuen CClientDC-Objekts,
der einen Zeiger auf das zur Ansichtsklasse gehörende Fenster erhält.
LPtoDP
void LPtoDP(
LPPOINT lpPoints,
int nCount = 1 ) const;
Parameter Beschreibung
DPtoLP
void DPtoLP(
LPPOINT lpPoints,
int nCount = 1 ) const;
void DPtoLP(
LPRECT lpRect ) const;
void DPtoLP(
LPSIZE lpSize ) const;
Parameter Beschreibung
279
Anwählen von Punkten
6
Parameter Beschreibung
Beide Methoden arbeiten dabei nach dem Prinzip, dass eine übergebene Koor-
dinate in das jeweils andere Format umgerechnet wird, wobei die ursprüngli-
chen Werte überschrieben werden.
Originalkoordinaten Es bietet sich allgemein also an, eine Kopie der ursprünglichen Daten anzule-
kopieren gen, wie es im OnLButtonDown-Fall des Bezierkurven-Projekts auch gehand-
habt wird.
Umrechnungstücken
Beachten Sie in jedem Fall, dass die Gerätekontexte in Methoden wie
OnLButtonDown nicht eine vorhergehende Abarbeitung von OnPrepareDC
durchlaufen.
280
MDI-Applikationen
6
Abb. 6.5
Die Berechnung der
Bereichskoordinaten
An dieser Stelle zeigt sich auch wieder der Vorteil, eine Konstante anstelle einer
magischen Zahl für die Größe der jeweiligen Punkte verwendet zu haben –
neben der besseren Lesbarkeit brauchen diese Zeilen nicht weiter editiert zu
281
Anwählen von Punkten
6
Neuzeichnen des Das Neuzeichnen dient zum Darstellen des nun aktiven Elements, das ja rot
Fensterinhalts gefärbt erscheinen soll. Es würde reichen, nur den tatsächlichen von dem
Knotenpunkt eingenommenen Bereich neu zu zeichnen, da wir es hier aber
ohnehin nur mit insgesamt fünf Zeichenoperationen zu tun haben, wäre die
dadurch entstehende Einsparung nur als minimal zu bezeichnen.
InvalidateRect(NULL);
ReleaseCapture();
CView::OnLButtonUp(nFlags, point);
}
Beim Beenden einer Bewegung soll außerdem die fixierte Maus wieder freige-
setzt und der Bildbereich neu gezeichnet werden, um die normale Färbung des
Knotenpunkts wieder herzustellen – diese Aufgabe übernehmen die Zeilen
InvalidateRect und ReleaseCapture.
282
MDI-Applikationen
6
Hierfür ist zunächst eine erneute Überarbeitung der OnDraw-Methode not- Aufgabe zur Übung
wendig. Sie haben an dieser Stelle erneut die Möglichkeit sich selbst zu testen,
und die fehlenden Zeilen in dieser Methode selbst einzusetzen – das ist eine
gute Gedächtnisübung, um das zu wiederholen, was Sie über GDI-Objekte und
dort insbesondere das CPen-Objekt gelernt haben.
Im Folgenden steht eine mögliche Lösung für die gestellten Aufgabenpunkte, Mögliche Lösung
ich empfehle Ihnen jedoch, diese erst anzusehen, sobald Sie Ihre eigene der Aufgabe
Methode geeignet erweitert haben oder wirklich nicht weiter wissen:
Bezier[0] = pDoc->GetStartNode();
Bezier[1] = pDoc->GetControlPoint1();
Bezier[2] = pDoc->GetControlPoint2();
Bezier[3] = pDoc->GetEndNode();
// Stifte vorbereiten
CPen BlackPen(PS_SOLID, 1, RGB(0, 0, 0));
CPen RedPen(PS_SOLID, 2, RGB(255, 0, 0));
283
Anwählen von Punkten
6
Bezier[0].y + c_iKnotenRadius);
if (m_bMovingStartNode)
pDC->SelectObject(&BlackPen);
if (m_bMovingEndNode)
pDC->SelectObject(&RedPen);
pDC->Rectangle(Bezier[3].x – c_iKnotenRadius,
Bezier[3].y – c_iKnotenRadius,
Bezier[3].x + c_iKnotenRadius,
Bezier[3].y + c_iKnotenRadius);
if (m_bMovingEndNode)
pDC->SelectObject(&BlackPen);
pDC->Ellipse(Bezier[1].x – c_iKnotenRadius,
Bezier[1].y – c_iKnotenRadius,
Bezier[1].x + c_iKnotenRadius,
Bezier[1].y + c_iKnotenRadius);
if (m_bMovingControlPoint1)
pDC->SelectObject(&BlackPen);
if (m_bMovingControlPoint2)
pDC->SelectObject(&RedPen);
pDC->Ellipse(Bezier[2].x – c_iKnotenRadius,
Bezier[2].y – c_iKnotenRadius,
Bezier[2].x + c_iKnotenRadius,
Bezier[2].y + c_iKnotenRadius);
if (m_bMovingControlPoint2)
pDC->SelectObject(&BlackPen);
pDC->PolyBezier(Bezier, 4);
284
MDI-Applikationen
6
Die Erweiterung der OnDraw-Methode beginnt damit, zwei neue Stifte zu defi- Erklärung zur
nieren, von denen einer schwarz mit einer Dicke von einem Pixel (BlackPen), der Aufgabenlösung
andere rot mit einer Dicke von zwei Pixeln (RedPen) ist. Weiterhin wird der
ursprünglich eingestellte Stift des Gerätekontexts gespeichert.
Nun wird bei jedem Zeichenvorgang geprüft, ob ein markiertes Element vor-
liegt und gegebenenfalls auf den roten Stift gewechselt. Nach der Operation
wird der Stift gegebenenfalls auf den schwarzen Stift zurückgesetzt.
Das Ende der Methode stellt den ursprünglichen Zustand wieder her, indem
der alte Stift wieder eingesetzt wird.
Was noch fehlt, ist das eigentliche Bewegen der Objekte – die Behandlung Weitere Benutzer-
dieser Aktion gehört in eine Nachrichtenbearbeitungsmethode zu WM_- interaktion
MOUSEMOVE.
285
Bewegen der Kontroll- und Knotenpunkte
6
// Abbildungsmodus setzen
dc.SetMapMode(MM_ISOTROPIC);
// Kontextausmaße setzen
dc.SetWindowExt(300, 300);
dc.SetViewportExt(Client.right,
Client.bottom);
// Koordinaten umrechnen
dc.DPtoLP(&MausPosition);
// Mausposition begrenzen
if (MausPosition.x < 0)
MausPosition.x = 0;
if (MausPosition.y < 0)
MausPosition.y = 0;
if (m_bMovingEndNode)
{
// Startknoten auf neue Position
// setzen
pDoc->SetEndNode(MausPosition);
}
if (m_bMovingControlPoint1)
{
// Startknoten auf neue Position
// setzen
pDoc
->SetControlPoint1(MausPosition);
}
286
MDI-Applikationen
6
if (m_bMovingControlPoint2)
{
// Startknoten auf neue Position
// setzen
pDoc
->SetControlPoint2(MausPosition);
}
// neu zeichnen
InvalidateRect(NULL);
}
// Standardbehandlung aufrufen
CView::OnMouseMove(nFlags, point);
}
Erklärungen zu OnMouseMove
Den Anfang von OnMouseMove bildet eine Abfrage, ob überhaupt derzeit eine
Knoten- oder Kontrollpunktbewegung vorliegt. Ist das nicht der Fall, kann die
Funktion mit einem Aufruf der Standardbehandlung verlassen werden.
Der ermittelte Wert wird dann noch auf den gültigen Wertebereich beschnit- Zuschneiden von
ten, da die Maus ja den Fensterbereich verlassen und dann entsprechend klei- berechneten
nere oder größere Argumente zurückliefern würde. Koordinaten auf
gültigen Wertebereich
Je nachdem, welcher Knoten- oder Kontrollpunkt nun gerade bewegt wird,
setzt eine passende Dokumentzugriffsfunktion die neuen Werte für gerade
dieses Objekt.
Den Abschluss bildet dann ein Aufruf von InvalidateRect, der zum Neuzeichnen
der Ansicht auffordert und somit sofort die vollzogene Veränderung visuali-
siert.
Kompilieren Sie die neue Version und testen Sie das Programm aus.
Probieren Sie, die Kontroll- und Knotenpunkte anzuwählen und verschieben Sie Austesten des bis-
die Objekte innerhalb des Fensters. Beobachten Sie, wie sich die Kurve in ihrer herigen Programms
Form verändert und beachten sie auch, dass die Rechtecke und Ellipsen am
Fensterrand festgehalten werden.
Verändern Sie die Größe des Ansichtsfensters und prüfen Sie, ob die Darstel-
lung verzerrungsfrei bleibt. An dieser Stelle ist auch schön zu sehen, dass nur je
nach Fenstergröße nur ein relativ kleiner Bereich für die tatsächliche verzer-
rungsfreie Abbildung genutzt wird: ziehen Sie das Fenster relativ niedrig aber
287
Bewegen der Kontroll- und Knotenpunkte
6
recht breit auf und versuchen Sie, einen der Knoten- oder Kontrollpunkte nach
rechts zu verschieben: Sie werden recht bald an eine unsichtbare Grenze sto-
ßen.
Bei Ihren Versuchen ergibt sich dann eine Abbildung ähnlich der folgenden,
womit die Applikation prinzipiell schon fertig ist:
Abb. 6.6
Eine veränderte
Bezierkurve
Wenn ich sage „prinzipiell schon fertig“, ist damit gemeint, dass in der Einfüh-
rung ja davon die Rede war, eine weitere Ansichtsklasse für die Dokumentan-
sicht zu implementieren.
Anlegen einer neuen Die letzten Seiten dieses Kapitels sollen sich nun mit diesem Vorhaben
Ansichtsklasse beschäftigen. Bevor es allerdings um die Details der tatsächlichen Implemen-
tierung geht – die hier und da etwas kniffelig sind – sollen zunächst vorberei-
tende Maßnahmen getätigt werden.
Als Erstes ist hier das Anlegen einer neuen Klasse zu nennen, die später als
Ansichtsklasse für die Applikation dienen soll.
Wählen Sie dazu aus dem Menü Projekt den Punkt Neue Klasse. Es öffnet sich
ein Dialogfeld, in dem Sie eintragen können, was für eine Art von Klasse in das
Bezierkurven-Projekt eingefügt werden soll.
Suchen Sie aus dem Ordner Visual C++ den Unterordner MFC und dort den Typ
MFC Class heraus:
288
MDI-Applikationen
6
Abb. 6.7
Hinzufügen einer
neuen Klasse
Bestätigen Sie die Auswahl mit einem Klick auf Open. Es öffnet sich ein weite- Klasse anlegen
res Dialogfeld, in das Sie als Klassenname CBezierkurvenInfoAnsicht eintragen via Assistent
und als Basisklasse CListView auswählen:
Abb. 6.8
Einstellen der
Klassenattribute
Da bereits eine grafische Darstellung existiert, wäre es sicherlich interessant, Fähigkeiten der
die aktuellen Koordinaten der einzelnen Punkte übersichtlich präsentiert able- neuen Klasse
sen zu können. Hierzu bietet es sich eine, eine tabellarische Übersicht zu wäh-
len.
289
Bekanntmachen der neuen Ansichtsklasse
6
Ansichtsklassen sind, wie Sie wissen, von CView abgeleitet. Es gibt allerdings
einige etwas fortschrittlichere Klassen, die schon im MFC Framework existieren
und ebenfalls von CView abgeleitet sind.
Hierzu gehört beispielsweise CListView, das gerade eine Listenansicht zur Ver-
fügung stellt, wie sie in diesem konkreten Fall für die Bezierkurvendaten nütz-
lich sein könnte.
Häufig findet man bei diesem Vorgehen eine Reihe von wertvollen Klassen,
denen nur kleine Details fehlen, um Ihrem Verwendungszweck zu entspre-
chen.
Leiten Sie eine Klasse davon ab, ergänzen Sie sie um die fehlenden Methoden
und schreiben Sie sich auf diese Weise mit der Zeit selbst eine kleine Biblio-
thek an häufig verwendeten Klassen – der Aufwand wird sich lohnen.
Bekannte Ansichts- Nun, vereinfacht gesprochen, wird der Anwendung bei der Initialisierung
und Dokumenten- gesagt, welche Arten von Dokumenten verwendet werden, mit welcher Ansicht
klassen sie verknüpft sind und in welcher Art von Fenster sie darzustellen sind.
290
MDI-Applikationen
6
Wenn Sie nun ein neues Dokument vom Typ CBezierkurvenDoc öffnen, wird ein
CChildFrame Fenster erzeugt, in dessen Client-Bereich die CBezierkurvenView-
Ansichtsklasse ihre Arbeit verrichtet.
Sie haben es vielleicht schon zwischen den Zeilen gelesen: Es ist in MDI-Anwen- Möglichkeit, mehrere
dungen nicht nur möglich, ein Dokument mit verschiedenen Ansichtsklassen Dokumentenklassen
zu verknüpfen, Sie können auch mehrere Dokumenttypen in einer MDI-Anwen- zu verwenden
dung definieren und verwenden. Dieser Fall soll im Rahmen dieses Buch aber
nicht weiter besprochen werden.
CMultiDocTemplate *m_pTemplateInfo;
Bevor die Vorlage definiert werden kann, muss zunächst die neue Ansichtsklas-
sen-Headerdatei per Include-Anweisung in das Implementationsdatei von CBe-
zierkurvenApp eingefügt werden.
#include "BezierKurvenInfoAnsicht.h"
Wir haben diese Methode bislang noch nicht näher untersucht, höchste Zeit
also, sich mit ihr vertraut zu machen (die von Ihnen einzufügenden Zeilen sind
fett hervorgehoben):
BOOL CBezierkurvenApp::InitInstance()
{
// InitCommonControls() ist für Windows XP
// erforderlich, wenn ein Anwendungsmanifest
// die Verwendung von ComCtl32.dll Version 6
// oder höher zum Aktivieren
// von visuellen Stilen angibt. Ansonsten treten
291
Bekanntmachen der neuen Ansichtsklasse
6
CWinApp::InitInstance();
// OLE-Bibliotheken initialisieren
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// Standardinitialisierung
// Wenn Sie diese Features nicht verwenden und
// die Größe
// der ausführbaren Datei verringern möchten,
// entfernen Sie
// die nicht erforderlichen
// Initialisierungsroutinen.
// Ändern Sie den Registrierungsschlüssel unter
// dem Ihre Einstellungen gespeichert sind.
// TODO: Ändern Sie diese Zeichenfolge
// entsprechend,
// z.B. zum Namen Ihrer Firma oder Organisation.
SetRegistryKey(_T("Vom lokalen Anwendungs-
Assistenten generierte Anwendungen"));
292
MDI-Applikationen
6
// Haupt-MDI-Rahmenfenster erstellen
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
Erläuterungen zu InitInstance
Die ersten Zeilen der Methode beziehen sich auf Besonderheiten unter Win-
dows XP, die Initialisierung von OLE-Containern sowie das Einladen der letztbe-
kannten Einstellungen aus der Registry (insbesondere die Namen der vier
zuletzt geöffneten Dokumente) – allesamt Themenbereiche, die den Rahmen
dieses Buches sprengen würden und daher leider nicht weiter behandelt wer-
den können. Es sei daher auf die entsprechenden Funktionsbeschreibungen in
der Online-Hilfe verwiesen.
Der interessante Teil folgt allerdings auch erst nach diesen einleitenden Zeilen, Bekanntmachen der
nämlich gerade die Initialisierung der Standardvorlage gefolgt von unserer neuen Ansichtsklasse
eigenen, zweiten Vorlage. in InitInstance
293
Bekanntmachen der neuen Ansichtsklasse
6
Das Anlegen derselben geschieht über eine new-Operation mit vier übergebe-
nen Parametern. Neben der ID des zu verwendenden Menüs (Sie erinnern sich,
dass einzelne Dokumentfenster eigenständige Menüs in der Menüleiste des
Mainframe-Fensters anzeigen können – hier wird die betreffende ID eingetra-
gen) wird der Name der Dokumentenklasse, der der Kindfensterklasse und
abschließend der Ansichtsklassenname.
Separate Verwaltung Unsere eigene neue Ansichtsklasse soll separat verwaltet werden und wird
der Ansichtsklasse daher an den in der Klasse eigens dafür angelegten Zeiger übergeben.
Sie halten jetzt also ein Vorlagenobjekt in Händen, das beim Erzeugen eines
neuen Fensters als Grundlage herangezogen werden kann.
Fügen Sie eine überschreibende Variante die Methode in die Klasse CBezierkur-
venApp ein:
int CBezierkurvenApp::ExitInstance()
{
// TODO: Add your specialized code here and/or
// call the base class
delete (m_pTemplateInfo);
return CWinApp::ExitInstance();
}
Es wäre also zweckmäßig, einen weiteren Menüpunkt in das Fenster Menü der
MDI-Anwendung einzufügen, das etwa Neues Info Fenster heißen könnte.
Öffnen Sie dazu den Ressourceneditor und editieren dort das Menü
IDR_BezierkurvenTYPE. Fügen Sie einen Menüpunkt namens Neues Info Fenster
hinzu.
294
MDI-Applikationen
6
Behalten Sie dessen zugewiesene ID bei und fügen Sie eine Behandlungsme-
thode für den neuen Punkt in die CMainFrame-Klasse ein:
Abb. 6.9
Der neue Menüpunkt
für das Bezierkurven-
Programm
In diesem Fall ist es durch die Vorgabe der CMDIFrameWnd-Klasse ein Leichtes,
zum gewünschten Ergebnis zu kommen:
void CMainFrame::OnFensterInfofenster()
{
// aktives Kindfenster ermitteln
CMDIChildWnd *pActiveChild = MDIGetActive();
CDocument* pDocument;
295
Implementation der alternativen Ansichtsklasse
6
if (pFrame == NULL)
{
AfxMessageBox("Konnte neues Fenster nicht
erzeugen!");
return;
}
pTemplate->InitialUpdateFrame(pFrame,
pDocument);
}
Im ersten Teil der Methode wird geprüft, ob derzeit überhaupt ein Kindfenster
(sprich ein Dokument) aktiv ist. Ist dem nicht so, kann natürlich auch kein zuge-
höriges Infofenster geöffnet werden.
CFrameWnd* CreateNewFrame(
CDocument* pDoc,
CFrameWnd* pOther
);
Parameter Beschreibung
pDoc Dokument, mit dem das neue Fenster verknüpft sein soll.
pOther Fenster, auf dem das neue Fenster basieren soll. Es wird
im Titel durch eine vorangestellte Nummer gekennzeich-
net.
Rückgabewert Zeiger auf das neu erzeugte Fenster, oder NULL bei Auf-
treten eines Fehlers.
Erneutes Austesten Wenn Sie das Programm in der jetzigen Form ausführen, werden Sie feststel-
len, dass die neue Ansichtsklasse noch nicht sehr viel tut – allerdings ist dieses
ein gutes Zeichen, denn offensichtlich wird hier ein Fenster mit einer neuen
Ansichtsklasse geöffnet, was ja genau das ist, was wir möchten.
296
MDI-Applikationen
6
Wie schon gesagt, basiert diese auf einer CListView-Klasse, die zur Darstellung
von Tabellen geeignet ist.
Bevor mit einer solchen Tabellendarstellung gearbeitet werden kann, ist es Initialisierung von
erforderlich, dass die Tabelle geeignet angelegt wird – so ist ihr beispielsweise Tabellenansichten
mitzuteilen, welche Arten von Spalten sie hat und wie breit diese sein sollen.
// Tabellenkopf vorbereiten
refCtrl.InsertColumn(0, "Element");
refCtrl.InsertColumn(1, "X");
refCtrl.InsertColumn(2, "Y");
// Spaltenbreiten festlegen
for (int i=0;i<3;i++)
{
refCtrl.SetColumnWidth(i, 100);
}
297
Implementation der alternativen Ansichtsklasse
6
Ergänzen der Liste um Um die Liste geeignet vorzubereiten, müssen Spalten in die Liste eingefügt
passende Spalten für werden, die in ihrer Ausgangsform leer ist.
den vorliegenden
Anwendungsfall Die Methode InsertColumn hat den folgenden Prototyp:
int InsertColumn(
int nCol,
LPCTSTR lpszColumnHeading,
int nFormat = LVCFMT_LEFT,
int nWidth = -1,
int nSubItem = -1
);
Parameter Beschreibung
Es werden also drei Spalten mit den Überschriften (von links nach rechts) Ele-
ment, X und Y in die Liste eingetragen.
Breite der Spalten Im nächsten Schritt werden die Breiten der einzelnen Spalten festgelegt. Hier-
verändern bei kommt die CListCtrl-Methode SetColumnWidth zum Einsatz:
BOOL SetColumnWidth(
int nCol,
int cx
);
Parameter Beschreibung
298
MDI-Applikationen
6
Parameter Beschreibung
Weitere Initialisierungen
Bevor es um die konkreten Implementationen der selbst geschriebenen Metho- Listentyp festlegen
den geht, muss noch die Initialisierung der Liste vollendet werden. Die rest-
lichen benötigten Zeilen gehören in die Behandlungsmethode für die
Fensternachricht WM_CREATE, die Sie an dieser Stelle in die Klasse CBezierKur-
venInfoAnsicht einfügen:
// ListView erzeugen
if (CListView::OnCreate(lpCreateStruct) == -1)
return -1;
// alles ok
return 0;
}
Hier wird die Art und Weise festgelegt, in der die Tabelle angezeigt werden soll.
Die einzelnen möglichen Flags für lpCreateStruct->style entnehmen Sie der
Online-Hilfe, an dieser Stelle sei nur gesagt, dass die Einstellungen dafür
sorgen, dass die Tabelle mit einer Überschrift in nicht sortierter Reihenfolge
dargestellt wird, was in unserem Fall prinzipiell egal ist, da die Überschriften
Element, X und Y ohnehin schon alphabetisch korrekt sortiert sind.
299
Implementation der alternativen Ansichtsklasse
6
void ShowNewValues();
AddItemToList
AddItemToList soll dazu dienen, neue Elemente in die Liste aufzunehmen. Das
sind insbesondere die vier Beschriftungselemente in der linken Spalte (dort soll
indizierend stehen: Startknoten, Kontrollpunkt 1, Kontrollpunkt 2 und Endkno-
ten). Mit dem Einfügen dieser Elemente wird gleichzeitig jeweils eine neue Lis-
tenzeile in die Liste aufgenommen.
Die Funktion erhält als Parameter eine Referenz auf das Listenkontrollobjekt,
die Zeile, in die das neue Element einzufügen ist, die passende Spalte sowie den
einzutragenden Text.
Die abschließende InsertItem-Zeile fügt das neue Feld in die Liste ein.
300
MDI-Applikationen
6
ShowNewValues
ShowNewValues aktualisiert die Anzeige dahingehend, dass die neuen Werte Angezeigte Werte
von Kontroll- und Knotenpunkten aus dem Dokument ausgelesen und in der aktualisieren
Liste dargestellt werden:
CurrentPoint = GetDocument()
->GetControlPoint1();
SetItem(refCtrl, 1, 1, CurrentPoint.x);
SetItem(refCtrl, 1, 2, CurrentPoint.y);
CurrentPoint = GetDocument()
->GetControlPoint2();
SetItem(refCtrl, 2, 1, CurrentPoint.x);
SetItem(refCtrl, 2, 2, CurrentPoint.y);
CurrentPoint = GetDocument()->GetEndNode();
SetItem(refCtrl, 3, 1, CurrentPoint.x);
SetItem(refCtrl, 3, 2, CurrentPoint.y);
}
Das Eintragen der neuen Werte in die Liste wird über die dritte und letzte der
selbst geschriebenen Funktionen realisiert: SetItem.
SetItem
SetItem aktualisiert den Inhalt eines Listenelements. Es erhält als Parameter Einen spezifischen
eine Referenz auf ein Listenkontrollobjekt, die Position (Zeile und Spalte) des zu Wert der Tabelle
aktualisierenden Felds sowie einen Integerwert, der in unserem Fall gerade die ändern
neue Koordinate des Kontroll- beziehungsweise Knotenpunkts angibt:
301
Implementation der alternativen Ansichtsklasse
6
lvItem.iItem = in_nLine;
lvItem.iSubItem = in_nColumn;
itoa(in_nValue, lpszConvert, 10);
lvItem.pszText = lpszConvert;
in_Ctrl.SetItem(&lvItem);
Der Ablauf ist analog zu AddItemToList, der Unterschied besteht lediglich darin,
dass zum Schluss statt InsertItem die Methode SetItem aufgerufen wird, die
kein neues Element in die Liste einfügt, sondern ein bestehendes verändert.
#include "BezierkurvenDoc.h"
Abschließend fehlt noch die Unterscheidung zwischen Debug und Release Ver-
sion der GetDocument-Methode. Das ist zwar nicht zwingend erforderlich,
bleibt aber konform mit den übrigen Klassen.
inline CBezierkurvenDoc*
CBezierkurvenView::GetDocument() const
{ return
reinterpret_cast<CBezierkurvenDoc*>(m_pDocument); }
#endif
302
MDI-Applikationen
6
CBezierkurvenDoc*
CBezierKurvenInfoAnsicht::GetDocument() const
// Nicht-Debugversion ist inline
{
ASSERT(m_pDocument
->IsKindOf(RUNTIME_CLASS(CBezierkurvenDoc)));
return (CBezierkurvenDoc*)m_pDocument;
}
Ansichten aktualisieren
Jetzt ist das Programm fast fertig. Wenn Sie es nun kompilieren und starten,
können Sie über den Menüpunkt Fenster > Neues Info Fenster das soeben
erzeugte Listenfenster anzeigen lassen.
Sie werden jedoch feststellen, dass sich die Werte derzeit noch nicht ändern,
wenn die Punkte bewegt werden.
Das Gleiche gilt übrigens auch, wenn Sie mit Fenster > Neues Fenster eine
zweite Kurvenansicht öffnen – bewegen Sie nun einen Punkt im ersten Fenster,
werden Sie feststellen, dass die zweite Kurvenansicht unverändert bleibt, bis
sie mit der Maus in das zweite Fenster klicken.
Das ist bei bestimmten Anwendungen, die grafisch sehr aufwendig sind viel-
leicht sogar sinnvoll, in unserem Fall wirkt es hingegen eher unschön.
void UpdateAllViews(
CView* pSender,
LPARAM lHint = 0L,
CObject* pHint = NULL
);
Parameter Beschreibung
303
Implementation der alternativen Ansichtsklasse
6
Parameter Beschreibung
Begeben Sie sich also in die Datei BezierkurvenDoc.cpp und aktualisieren Sie die
nachstehenden Methoden:
304
MDI-Applikationen
6
Das ist aber auch kaum verwunderlich, denn es wurde noch an keiner Stelle Aktualisierung des
gesagt was passieren soll, wenn eine Veränderung der Dokumentdaten ein- Fensterinhalts
tritt. einleiten
Das Aufrufen von UpdateAllViews sorgt für ein Aufrufen der CView-Methode
OnUpdate. Überschreiben Sie diese Methode in der CBezierKurvenInfoAnsicht-
Klasse wie folgt:
void CBezierKurvenInfoAnsicht::OnUpdate(CView*
/*pSender*/, LPARAM /*lHint*/, CObject* /*pHint*/)
{
// TODO: Add your specialized code here and/or
// call the base class
ShowNewValues();
}
Benutzerdefinierte Parameter
In der Funktion OnUpdate können theoretisch die benutzerdefinierten Para-
meter verarbeitet werden, die im UpdateAllViews-Aufruf übergeben wer-
den können.
305
Implementation der alternativen Ansichtsklasse
6
Die Folge davon ist, dass die Veränderung in der zweiten Ansicht überhaupt
nicht sichtbar wäre. Wenn Sie nun die 0.001 als lHint-geeignet übergeben,
könnte die OnUpdate-Methode der zweiten Ansichtsklasse diesen Umstand
feststellen, und vollständig von einer Aktualisierung, die möglicherweise
aufgrund der komplexen 3-D-Darstellung einige Zeit in Anspruch nehmen
würde, absehen.
Zusammenfassung
Nachdem das Programm nun endlich seinen kompletten angedachten Dienst
versieht, wird es Zeit zusammen zu fassen, was Sie in diesem Kapitel gelernt
haben.
Die Arbeit mit Listen bildete dann den Abschluss dieses Kapitels.
Ausblick Der nächste Abschnitt beschäftigt sich mit den Möglichkeiten, C++ im Rahmen
des .NET Frameworks sinnvoll einzusetzen und insbesondere selbst geschrie-
bene Bibliotheken und Funktionen weiterzuverwenden.
Abb. 6.10
Das fertige Programm
zur Darstellung von
Bezierkurven
306
VC++.net
.NET vs. C++ Während es dem C++-Entwickler durchaus möglich ist, .NET-Anwendungen zu
schreiben, muss doch klar gesagt werden, dass der Trend deutlich in Richtung
von C# (oder Visual Basic) als Sprachplattform für das Erzeugen solcher Pro-
gramme geht – das zeigt sich allein daran, dass es keine Designtools für Dia-
loge und Ähnliches für den C++-Anwender gibt, während diese für C# in der
gewohnt umfangreichen Manier zur Verfügung stehen.
Dieses kurze Kapitel beschäftigt sich rudimentär mit dem neuen .NET Frame-
work, zeigt beispielhaft auf, wie trotzdem eine WinForm-Anwendung erzeugt
werden kann und gibt dann eine Anleitung, wie bestehende C++-Anwendun-
gen (die im Kontext des neuen .NET-Anwendungsgerüsts als „unmanaged
code“ – also nicht verwalteter Code – bezeichnet werden) in auf .NET basieren-
den Applikationen weiterverwendet werden können.
Einsatz von WinForms Sie können die Größe dieser Felder beliebig wählen und Steuerelemente auf
in einem Beispiel- ihnen platzieren, die dann über Ereignisbehandlungsmethoden selbst defi-
projekt nierte Aktionen ausführen können.
Die beispielhafte Verwendung einer solchen Form sei im Folgenden kurz skiz-
ziert.
308
VC++.net
7
Um Forms einsetzen zu können, müssen Sie zunächst Ihr Projekt auf eine Anlegen des neuen
Verwendung des .NET Frameworks einstellen. Erzeugen Sie hierzu ein neues Projekts
Projekt und stellen als Typ Managed C++ Application ein. Nennen Sie die
Anwendung ManagedProject (oder verwenden Sie das fertige Projekt von der
Buch-CD aus dem Verzeichnis \Kapitel7\ManagedProject).
Abb. 7.1
Projekteinstellungen
// This is the main project file for VC++ application Listing 7.1
// project ManagedProject.cpp
// generated using an Application Wizard.
#include "stdafx.h"
#using <mscorlib.dll>
#include <tchar.h>
309
Manuelles Schreiben einer WinForm-Anwendung
7
Die Hierarchie hierbei ist an Java angelehnt. Beispielsweise befindet sich die
Klasse zur Handhabung der von uns gewünschten Forms unter System.Win-
dows.Forms.
Textausgabe Im ersten Beispielprogramm soll allerdings nur eine Textausgabe in die Konsole
in Konsole durchgeführt werden. Hierzu kann die Klasse System::Console verwendet wer-
den, die jedwede Ein- oder Ausgaben an die Konsole kapselt.
Eine ihrer (statischen) Methoden ist WriteLine zum Ausgeben einer Zeile mit
anschließendem Zeilenumbruch.
Intermediate Language
Eine Zwischensprache Kompilieren Sie das Projekt, wird es – als managed application, also verwaltete
Applikation – zunächst in eine Zwischensprache übersetzt (intermediate langu-
age, kurz IL), die dann von den Laufzeitkomponenten des Frameworks beim
Ausführen des Programms in Maschinencode transferiert werden.
Das hat den Vorteil, dass vom System abhängige Optimierungen durchgeführt
werden können, was bei einer maschinensprachigen Vorkompilierung nicht
möglich ist – hier richtet sich das Ergebnis immer nach dem System, auf dem
eine Applikation entwickelt wurde.
Warmlaufen von Der Nachteil ist, dass diese Kompilierung immer erst dann ausgeführt wird,
Applikationen wenn bestimmte Programmteile aufgerufen werden – es kommt also zu Verzö-
gerungen bei der anfänglichen Arbeit mit Anwendungen. Je länger Sie mit
einer Applikation arbeiten, desto schneller läuft sie ab. Das ist in gewisser
Weise mit einem Neuwagen zu vergleichen, der auch erst eingefahren werden
will, bevor er seine komplette Motorenleistung an den Tag legt.
310
VC++.net
7
Abb. 7.2
Textausgabe in
die Konsole
Wie bei den MFC werden Forms durch eine eigene Klasse repräsentiert, sodass
es sich für den Entwickler anbietet, hieraus eine eigene Klasse abzuleiten und
nach eigenen Wünschen anzupassen.
Es öffnet sich ein Dialogfeld ähnlich dem folgenden, wählen Sie dort den Typ
Header File (.h) aus und geben der neu zu erzeugenden Datei den Namen
ManagedProject.h:
311
Manuelles Schreiben einer WinForm-Anwendung
7
Abb. 7.3
Hinzufügen einer
neuen Klasse
Hier werden zunächst mittels using einige dlls spezifiziert, die zur Verwendung
der Formklasse notwendig sind. Die danach angegeben Namespaces dienen
zur einfacheren Arbeit mit den Klassen des .NET Frameworks.
Form ist die Basisklasse für WinForm-Applikationen, sodass eine Klasse hiervon
abgeleitet werden soll. Der Modifizierer __gc sorgt dafür, dass wir es mit einer
verwalteten Klasse zu tun haben.
312
VC++.net
7
„Verwaltet“ bedeutet hier beispielsweise, dass Sie sich nicht selbst um Fragen Managed Applications
des Speichermanagements kümmern müssen. Legen Sie ein Objekt mittels
new auf dem Heap an, braucht es nicht mittels delete gelöscht zu werden –
.NET erkennt automatisch, wann das Objekt nicht mehr verwendet wird und
löscht es zu gegebener Zeit selbstständig.
Neben einem Konstruktor enthält die neue Klasse noch eine Behandlungsme-
thode für die Schaltfläche, die, wie bereits angekündigt, auf der Form vorhan-
den sein soll. Nähere Details hierzu gibt es, wenn es um die Implementation
der Klasse geht.
NewWinForm::NewWinForm() : Form()
{
// Größe der Form festlegen
Width = 150;
Height = 60;
// Knopfattribute setzen
pButton->Text = „Test“;
pButton->Left = 40;
pButton->Top = 5;
// Eventhandling delegieren
pButton->Click += new EventHandler(this,
OnButtonClicked);
313
Manuelles Schreiben einer WinForm-Anwendung
7
// Hauptprogramm
int _tmain(void)
{
Application::Run(new NewWinForm());
return 0;
}
Eine Breite von 150 und eine Höhe von 60 Pixel sorgt für ein relativ kleines Aus-
gabefenster, das für dieses einfache Beispiel aber vollkommen ausreichend ist.
Auf der Form soll weiterhin eine Schaltfläche angeordnet werden. Eine für ein sol-
ches Steuerelement passende Klasse findet sich mit System.Windows.
Form.Button.
Das Anlegen geschieht unspektakulär über eine new Anweisung, das Setzen
der Attribute ist aus dem Kontext heraus verständlich – hier wird die Beschrif-
tung sowie die Position auf der Form festgelegt.
Delegierte
Behandlungsmethode Wesentlich interessanter ist jetzt die Angabe einer Behandlungsmethode für
für die Schaltfläche den Fall, dass der Knopf gedrückt wird.
Sie wissen aus der MFC-Welt, dass solche Ereignisfunktionen auch in dem
umschließenden Fenster definiert werden können. Das ist aber genaugenom-
men ein Bruch mit der objektorientierten Programmierung – schließlich sollte
der Knopf selbst für seine Behandlung notwendig sein.
Ergo wäre es sinnvoll, eine Klasse vom Steuerelement abzuleiten und die Nach-
richtenfunktion dort einzusetzen.
Obwohl dieses der saubere Weg wäre, ist er doch recht umständlich wenn man
bedenkt, dass ein Dialogfeld unter Umständen mehrere Dutzend Kontrollen
enthalten kann, was dann in der Folge das Anlegen von ebenso vielen abgelei-
teten Steuerelementklassen bedeuten würde.
314
VC++.net
7
Im Rahmen von .NET besteht nun die Möglichkeit, die Behandlungen tatsäch- Delegierte
lich an genaugenommen jeder beliebigen Stelle stattfinden zu lassen. Hierzu
wird für das betreffende Ereignis eine Delegierter bestimmt (eine Behand-
lungsfunktion), die dem Steuerelement (oder einem sonstigen Objekt, das
Ereignisse erzeugen beziehungsweise erhalten kann) mitgeteilt wird.
Immer, wenn das betreffende Ereignis auftaucht, wird der Delegierte aufgefor-
dert, seine Arbeit zu verrichten.
Im obigen Beispiel wird dem Knopf mitgeteilt, dass im Fall eines Anklickens die
Funktion OnButtonClicked der Klasse NewWinForm (über den this-Zeiger spezi-
fiziert) aufgerufen werden soll.
Es ist somit möglich, dass das Anklicken der Schaltfläche tatsächlich eine ganze
Reihe von Behandlungsmethodenaufrufen auslöst.
Abschließend wird der Knopf an die erzeugte Form angehängt – sie hat hierzu
ein eigenes Controls-Objekt, das eine Liste sämtlicher auf der Form enthaltener
Steuerelemente enthält.
Die Main-Funktion
In der Main-Funktion wird die neue Form über einen Methodenaufruf von Startablauf der
Application::Run gestartet. Application ist dabei wiederum eine Klasse von Sys- Applikation
tem.Windows.Form und übernimmt als Parameter ein Form-Objekt.
Testen Sie die Applikation nach erfolgter Kompilierung nun erneut aus:
Abb. 7.4
Die Form
315
Manuelles Schreiben einer WinForm-Anwendung
7
Die Arbeit mit dem .NET Framework entspricht von den Grundsätzen her also
dem, was Sie im Rahmen der MFC bereits gesehen haben – der große Unter-
schied beschränkt sich auf das verwendete Klassenframework.
An dieser Stelle sollen nun keine weiteren Erklärungen hierzu folgen, es sei auf
einschlägige Literatur und insbesondere die auf der Buch-CD befindlichen wei-
teren digitalen Bücher aus dem Sybex Verlag verwiesen.
Allerdings können diese nicht verwalteten Klassen nicht ohne eine kleine
Ergänzung eingesetzt werden.
Die Einfachheit der gewählten Klasse erlaubt es, die wesentlichen Umstel-
lungsvorgänge ins richtige Licht zu rücken.
Neue Dateien anlegen Erzeugen Sie zunächst zwei neue Dateien namens UnmanagedClass.h und
UnmanagedClass.cpp wie oben beschrieben (stellen Sie weiterhin die Verwen-
dung von vorkompilierten Headern in den Projekteinstellungen aus, damit es
nicht zu Problemen beim Übersetzen kommt – die vorkompilierten Header
müssten von allen CPP-Dateien genutzt werden, doch ist dieses bei alten Klas-
sen oft nicht der Fall).
316
VC++.net
7
private:
int *m_pIntArray;
};
CUnmanagedClass::~CUnmanagedClass()
{
delete [] m_pIntArray;
}
Sie sehen, inhaltlich geschieht hier nicht viel. Die Klasse erzeugt bei Instantiie-
rung ein Integer Array bestehend aus fünf Elementen, die, bei 0 beginnend,
sequenziell mit Werten beschrieben werden.
317
Manuelles Schreiben einer WinForm-Anwendung
7
#include „UnmanagedClass.h“
private:
CUnmanagedClass m_UnmanagedClass;
};
Die Kompiliierung ergibt eindeutige Fehler, die besagen, dass die nicht verwal-
tete Klasse nicht verwendet werden kann:
Abb. 7.5
Fehler bei der
Kompilierung
Anlegen einer Eine solche Kapselung enthält als Schnittstelle sämtliche Funktionen, die von
zwischengeschal- der nicht verwalteten Klasse nach außen gegeben werden sollen sowie einen
teten Klasse Zeiger auf ein Objekt der betreffenden Klasse.
Fügen Sie nun zwei weitere Dateien namens Proxyclass.h und Proxyclass.cpp in
das Projekt ein.
318
VC++.net
7
private:
CUnmanagedClass *m_pUnmanagedClass;
};
Die neue verwaltete Klasse, enthält einen Zeiger auf ein CUnmanagedClass-
Objekt sowie eine Methode GetElement, die als Relaisfunktion dienen wird.
CProxyClass::~CProxyClass()
{
delete (m_pUnmanagedClass);
}
Die Klasse erzeugt also ein neues Objekt mithilfe des new-Operators und löscht
es explizit über delete. Damit ist sichergestellt, dass durch das nicht verwaltete
Objekt keine Speicherprobleme auf dem Heap entstehen können.
319
Manuelles Schreiben einer WinForm-Anwendung
7
Die GetElement-Methode ist tatsächlich nur eine Relaisfunktion, die den Aufruf
an das CUnmanagedClass-Objekt weiterleitet und deren Rückgabewert zurück-
liefert.
Was durchschleifen – Als Faustregel gilt, dass sämtliche Methoden der nicht verwalteten Klasse, die
und was nicht? von einer .NET-Applikation aus zugänglich sein sollen, als Relaismethoden in
die Proxyklasse einzubetten sind.
Private Methoden, auf der anderen Seite, gehören hier nicht hinein.
In solchen Fällen ist die Relaisfunktion derart aufzubauen, dass Sie den .NET
Datentyp übernimmt, ihn intern in einen für die alte C++-Klasse verständlichen
umkonvertiert, diesen dann an die alte Methode übergibt und den Rückgabe-
wert wieder für .NET passend aufbereitet.
#include "ProxyClass.h"
private:
CProxyClass *m_ProxyClass;
};
320
VC++.net
7
// Zufallszahl ermitteln
Random *Zufallszahlen = new Random;
int nRandom = Zufallszahlen->Next(0, 4);
// Element auslesen
Console::WriteLine("x is {0}", __box(nElement));
}
Hier wird ein neues Objekt der CProxyClass-Klasse angelegt, eine Zufallszahl
ermittelt und dann über die Relaismethode transparent mit der dahinter lie-
genden alten C++-Klasse gearbeitet.
An dieser Stelle zeigt sich auch noch einmal der Sinn der Proxyklasse: das neu
angelegte Objekt wird zwar mit new erzeugt, allerdings nicht wieder freigege-
ben. Dieses geschieht automatisch durch das .NET Framework, da CProxyClass
eine verwaltete Klasse ist.
Die zurückgelieferte Zahl soll in die Konsole geschrieben werden, die Verwen- Ausgabe von Daten
dung der __box-Funktion sorgt dafür, dass die Daten in einer für WriteLine via WriteLine
angemessenen Art und Weise aufbereitet werden.
Abschließender Testlauf
Kompilieren Sie das Programm und starten es. Testen Sie aus, ob das Drücken
der Schaltfläche die gewünschte Wirkung erzielt.
Sie sehen, das Übernehmen von alten C++-Funktionen ist ohne große Schwie- Zeitersparnis bei der
rigkeiten möglich. Sie können also bei der Entwicklung von .NET-Anwendungen Verwendung alter
zunächst ihre alten Bibliotheken beibehalten und diese dann Schritt für Schritt Bibliotheken
umstellen.
Durch die Proxyklassen hat dieser Schritt aber prinzipiell soviel Zeit, bis Sie in
der Entwicklung Freiraum für die Umstellung haben.
321
Manuelles Schreiben einer WinForm-Anwendung
7
Ein weiterer Vorteil, der sich durch die Proxyklassen automatisch bietet ist, dass
Sie die alten C++-Klassen auch mit anderen .NET Sprachen sofort verwenden
können – sämtliche .NET-Programme werden ja zunächst in die IL übersetzt,
sodass sich nach der Kompilierung theoretisch kein Unterschied mehr zwi-
schen einem ursprünglich unter C++.net oder C#.net entwickelten Programm
finden lässt.
Abb. 7.6
Die Verwendung der
alten C++-Klasse
322
Debugging
Gründe für gutes Es mag merkwürdig erscheinen, einen solchen Abschnitt in einem Program-
Debugging mierbuch vorzufinden, doch bedenken Sie, dass es hier ja konkret um die Arbeit
mit Visual C++.net geht, das nahezu untrennbar mit dem Visual Studio verbun-
den ist.
Dieses wiederum bietet eine ganze Reihe von Hilfsmitteln, um Fehler in Pro-
grammen aufzuspüren und zu beheben.
Denn seien wir einmal ehrlich: Wie viel Zeit vergeht mit der eigentlichen Pro-
grammierung und wie viel mit der Fehlersuche? Wer hat nicht schon einmal
stundenlang seine Quelltexte durchforstet, um am Ende herauszufinden, dass
lediglich an irgendeiner Stelle ein '+' zuviel war oder eine Variable nicht oder
falsch initialisiert wurde?
Wer einmal auf älteren Computersystemen gearbeitet hat, weiß, was ich
meine: Obwohl die Hardware in ihrer Komplexität mit der heutiger Rechner
nicht zu vergleichen war und Probleme wie Multitasking oder Multiuserma-
nagement noch in den Sternen standen, gestaltete sich die Entwicklung von
Programmen um einiges schwieriger als heutzutage.
Debugging im Gerade Assemblerprogrammierer können ein Lied davon singen, wie mühsam
Wandel der Zeit es häufig war, Quelltexte zu debuggen. Umständlich zu bedienenden Monitor-
programme ermöglichten zwar den Zugriff auf aktuelle Registerinhalte oder
Speicherdumps, doch konnte das Untersuchen einer längeren Schleife durch-
aus zur Qual werden, wenn ein Fehler erst gegen Ende des Durchlaufs eintrat
und man zunächst möglicherweise hunderte von vorhergehenden, ordnungs-
gemäß arbeitenden Durchgängen per Hand überprüfen musste.
Dazu kamen dann noch zwar recht interessante Neuentwicklungen zur Unter-
stützung bei der Fehlersuche, doch stellte sich in leidvollen Erfahrungen häufig
heraus, dass diese nur neue, ursprünglich vorhandene Fehler in den Debug-Pro-
zess einführten, oder, weitaus schlimmer, Fehler verbargen, die mithilfe der
neuen Tools einfach nicht mehr auftreten wollten – diese kamen dann meis-
tens erst beim Endanwender wieder zum Vorschein.
324
Debugging
8
Heutzutage sieht die Situation deutlich besser aus. Entwicklungsumgebungen Der aktuelle Stand
wie das Visual Studio stellen mächtige Instrumente zur Verfügung, von denen der Dinge
die Programmierer vergangener Zeiten nur träumen konnten.
Manuelles Debugging von einzelnen Funktionen oder ganzer Pro- Breakpoints und Step-
gramme. Der Entwickler verlässt sich hierbei nicht auf eine der beiden by-Step Debugging
oben genannten Möglichkeiten, beziehungsweise hat über diese keine
Option, einen Fehler zu finden und zu beseitigen. Diese Methode
kommt meistens dann zum Einsatz, wenn ein Fehler zwar bekannter-
weise auftritt, aber keine ungültigen Informationen erzeugt, die durch
den Debugger oder Testroutinen abgefragt werden können. Der Ent-
wickler bedient sich der Tools des Debuggers, um schritt- oder block-
weise durch das Programm hindurchzugehen, die Abarbeitung an
bestimmten Stellen abzubrechen oder einfach nur Variablen-, Register-
und Speicherwerte zu beliebigen Zeiten zu untersuchen.
325
Anlegen eines Debug-Projekts
8
Im Folgenden werden alle drei Arten von Debugging im Rahmen des Visual Stu-
dio und den MFC genauer dargestellt.
Abb. 8.1
Das dialogfeld-
basierende Debug-
Programm
Abb. 8.2
Die Schaltflächen für
das Debug-Projekt
326
Debugging
8
Verteilen Sie ferner die IDs und Beschriftungen der nächsten Tabelle und legen Dialog mit Steuer-
für jede Schaltfläche eine eigene Behandlungsmethode an. Darin werden dann elementen und IDs
auf den folgenden Seiten einzelne Debug-Szenarien implementiert werden. versehen
Tabelle 8.1
ID Beschriftung
IDs für das Debug-
Project-Programm
IDC_TRACETEST TRACE
IDC_ASSERTTEXT ASSERT
IDC_ASSERTVALIDTEST ASSERT_VALID
IDC_VERIFYTEST VERIFY
IDC_SPEICHERLECKTEST Speicherleck
IDC_AFXDUMPTEST afxDump
IDC_BREAKPOINTTEST Breakpoints
IDC_SPEICHERTEST Speicher
327
Fehlersuche mit den MFC
8
Allgemein gilt, dass Fehler, die erst spät in einem Programm entdeckt wer-
den, wesentlich mehr Zeit zur Behebung erfordern als solche, die frühzeitig
auftauchen.
Alle fünf der oben stehenden Makros und Methoden erlauben es, bereits
frühzeitig aufzudecken, ob fehlerhafte Parameter an Funktionen übergeben
werden und dergleichen mehr – nutzen Sie diesen Vorteil.
TRACE
Ausgabe von Logzeilen Eine von DOS-Entwicklern gerne eingesetzte Möglichkeit zur Aufspürung von
Fehlern besteht darin, den Quelltext mit Ausgabezeilen zu spicken, die zwi-
schen wichtigen Anweisungen platziert sind und entweder als reine Positions-
anzeiger dienen oder auch Werte von Variablen ausgeben.
Ein mögliches Ergebnis dieser Vorgehensweise ist der Abbildung 8.3 zu entneh-
men.
Abb. 8.3
Debug-Ausgaben in
der Konsole
Allerdings enthält das Visual Studio ein eigenes Fenster namens Ausgabe, dass
für eine gleichartige Funktionalität eingesetzt werden kann.
328
Debugging
8
Mithilfe des Makros TRACE können Sie nämlich Ausgaben in das Ausgabefens-
ter schreiben, wobei auch auf eine komfortable Formatierung ähnlich den
bekannten sprintf-Anweisungen nicht verzichtet werden muss.
// Zahl definieren
int nZahl = 23;
Sie erhalten das in Abbildung 8.4 zu sehende Ergebnis, wenn Sie die Schaltflä-
che TRACE im Debug-Modus (Starten mit % beziehungsweise über das Menü
Debug > Start) betätigen.
Ist das Ausgabefenster nicht zu sehen, können Sie es mit S+A+o in den
Vordergrund bringen.
Abb. 8.4
Ausgabe von TRACE
329
Fehlersuche mit den MFC
8
Integrierte Ausgaben Über beziehungsweise unter der TRACE-Ausgabe finden Sie weitere Meldun-
gen, die automatisch eingefügt werden – auch zeigt sich hier die Begrenzung
der Möglichkeiten von TRACE: Bei vielen Ausgaben wird diese Vorgehensweise
schnell unübersichtlich.
ASSERT
Zusicherungen Häufig haben Sie es in Ihren Programmen mit Funktionen oder Strukturen zu
tun, bei denen Sie im Voraus abschätzen können, in welchem Wertebereich
sich Parameter oder Komponenten bewegen dürfen – entweder direkt durch
Angabe absoluter Werte oder indirekt durch einen Vergleich zweier oder meh-
rerer Daten (beispielsweise wissen Sie, dass Parameter A in einer Funktion nie
größer als Parameter B sein darf).
Für gewöhnlich bereitet man entsprechende Funktionen so auf, dass sie gerade
dann arbeiten, wenn die Parameter in einem zulässigen Bereich liegen – was
häufig gespart wird, ist allerdings eine explizite Abfrage, ob Parameter tatsäch-
lich gültige Werte enthalten.
Die Folge sind schwer zu lokalisierende Fehler, wenn ein Parameter aus irgend-
welchen Gründen einen ungültigen Wert enthält, die Berechnung innerhalb
der Funktion ungültig macht und somit die Weiterverarbeitung im Rest des
Programms ebenfalls inkorrekt verläuft.
Eine Zusicherung ist eine Aussage, die gültig sein muss, sobald das Programm
an einer bestimmten Stelle ankommt. Man unterscheidet in der Informatik
zwischen punktuellen und Gültigkeitsbereichs-Zusicherungen.
Während erstere tatsächlich nur bei ihrer Überprüfung gültig sein müssen,
haben die globaleren Versionen während eines gesamten Blocks ihren Wahr-
heitsgehalt zu behalten.
Das ASSERT-Makro der MFC stellen eine Implementation der punktuellen Zusi-
cherungen dar. Mit seiner Hilfe können Sie beispielsweise beim Eintritt in eine
Funktion prüfen, ob alle Parameter in einem gültigen Bereich liegen oder ob
während einer Berechnung nicht erlaubte Werte entstanden sind.
Anwendungsbeispiel Ein Beispiel hierfür implementieren Sie jetzt in der Methode OnBnClickedAs-
sertTest:
void CDebugProjectDlg::OnBnClickedAsserttest()
{
// Variablen vorbereiten
int nEineZahl = 10;
int nNochEineZahl = 25;
330
Debugging
8
// Zwischenrechnung durchführen
int nErgebnis = nNochEineZahl – nEineZahl – 15;
// Zusicherung prüfen
ASSERT (nErgebnis != 0);
Hier wird eine fehlerhafte Berechnung erzwungen, nErgebnis erhält den Wert
0, was für die nachfolgende Division fatale Folgen hat: Eine Divison durch 0 ist
nicht erlaubt.
Beim Austesten des entsprechenden Buttons erhalten Sie die folgende Fehler-
meldung:
Abb. 8.5
Eine Assertion konnte
nicht eingehalten
werden
Das Fenster sagt aus, dass eine Zusicherung nicht eingehalten werden konnte. Ergebnis des Testlaufs
Das Dialogfeld enthält ferner nähere Angaben darüber, an welcher Stelle dieses
Problem aufgetaucht ist. Wesentlich aussagekräftiger ist aber das Resultat,
wenn Sie auf die Wiederholen-Schaltfläche drücken, die es Ihnen gestattet, die
Anwendung zu debuggen – da Sie die Applikation ohnehin schon im Debugger
gestartet hatten, werden Sie nur an die betreffende Quelltextzeile gebracht,
die den Fehler auslöste, ansonsten würde nun eine neue Visual Studio-Instanz
geöffnet werden.
331
Fehlersuche mit den MFC
8
Abb. 8.6
Die gescheiterte
Zusicherung
Der zeigt Pfeil an, welche Zeile gerade ausgeführt wurde – gelbe Pfeile begeg-
nen Ihnen vor allem auch noch beim manuellen Debuggen, zu dem wir später
kommen werden.
Das ist doch schon recht aussagekräftig: nErgebnis hat den Wert 0, also darf ab
hier die weitere Programmausführung nicht fortgesetzt werden. Es liegt jetzt
am Entwickler herauszufinden, wie dieser ungültige Wert zustande gekommen
ist.
Diesen Umstand haben auch die MSVC++ Entwickler erkannt und ein weiteres
Makro in die Debug-Makro-Familie eingeführt: ASSERT_VALID.
ASSERT_VALID ist prinzipiell nur ein Relaismakro, denn es übernimmt einen Zei-
ger auf ein Objekt und ruft eine Methode namens AssertValid innerhalb dieses
Objekts auf.
332
Debugging
8
Ein Beispiel: fügen Sie die nachstehende Klassendeklaration in die Datei Debug-
ProjectDlg.h ein – die Klasse wird auf den folgenden Seiten noch hin und wieder
weitergehend erweitert werden, um andere Debugmechanismen zu erläutern.
public:
CTestObjekt();
void AssertValid() const;
};
Die Implementation der Klasse fügen Sie der Einfachheit halber in die Datei
DebugProjectDlg.cpp ein:
// Zusicherungen prüfen
ASSERT (m_nEineZahl == 13);
ASSERT (!strcmp(m_lpszEinString, "Ein anderer
Teststring"));
}
Sie können hier natürlich jede beliebige Art von Überprüfungen durchführen, Beliebige
beispielsweise auch das Aufrufen von Membermethoden des Objekts, um fest- Überprüfungen
zustellen, ob diese gültige Werte zurückliefern und dergleichen mehr.
333
Fehlersuche mit den MFC
8
Zum Austesten der Funktionalität ergänzen Sie jetzt die Methode OnBnClicked-
Assertvalidtest der CDebugProjectDlg-Klasse:
void CDebugProjectDlg::OnBnClickedAssertvalidtest()
{
// zu prüfendes Objekt erzeugen
CTestObjekt TestObjekt;
Starten Sie jetzt das Debug-Testprogramm und wählen Sie den ASSERT_VALID
Test aus:
Abb. 8.7
Das Ergebnis des
ASSERT_VALID-Tests
Ergebnis von Das Ergebnis entspricht dem von ASSERT – was auch leicht nachzuvollziehen
ASSERT_VALID ist, da AssertValid auf ASSERT-Makros aufbaut. Bei einem Betätigen der Wieder-
holen-Schaltfläche landen Sie daher praktischerweise auch nicht direkt beim
ASSERT_VALID-Aufruf, sondern tatsächlich bei der gescheiterten Zusicherung:
Abb. 8.8
Die gescheiterte
Zusicherung
334
Debugging
8
VERIFY
Eine etwas eigenartige Stellung übernimmt das VERIFY-Makro. In der Debug- Zusicherungen im
Version eines Programms legt es das gleiche Verhalten an den Tag wie ASSERT, Debug-Modus
liefert also eine Assertion Failure-Dialogbox, falls eine Zusicherung nicht gültig
war.
Dieses erklärt sich am besten anhand eines Beispiels. Editieren Sie dazu die
Methode OnBnClickedVerify:
Wenn Sie das Programm in der Debug-Version ausführen, erhalten Sie die
bekannte Meldung, da der Wert einer Zuweisung immer der Wert der rechten
Zuweisungsseite – und somit in diesem Fall 0 – ist:
Abb. 8.9
Die bekannte
Assertion Failure-Box
335
Fehlersuche mit den MFC
8
Abb. 8.10
Ungültige Zusicherung
Auswirkungen in Der Unterschied zeigt sich erst, wenn das Programm in der Releaseversion
der Releaseversion erzeugt wird. Dann nämlich stehen im Quelltext anstelle der obigen Variante
die folgenden Zeilen:
Die ursprüngliche Zusicherung selbst bleibt also erhalten. In der Regel benutzt
man das VERIFY-Makro tatsächlich für Zuweisungen, bei denen nicht 0 oder
NULL zugewiesen werden darf.
Speicherlecks
Eins der größten Ärgernisse beim Entwickeln von Applikationen sind auftre-
tende Speicherlecks (engl. Memory Leaks), die immer dann entstehen, wenn ein
Objekt dynamisch mit new angelegt, danach aber nicht wieder gelöscht wird.
Unerkannte Das größte Problem bei diesem Speicherverschnitt ist es, dass er zunächst gar
Speicherprobleme nicht erst erkannt wird – möglicherweise auch nicht, wenn ein Produkt bereits
ausgeliefert wurde. Wer frühe Versionen von Blizzards Diablo II gespielt hat,
kennt sicherlich das Phänomen, dass das Spiel im Laufe der Zeit immer langsa-
mer wurde. Das hing gerade mit einem solchen massiven Speicherleck zusam-
men, dass anfangs keine Probleme bereitete, dann aber immer stärker die
Performance schrumpfen ließ.
Zum Glück erkennt das Visual Studio bereits selbstständig, wenn Speicherlecks
in MFC-Anwendungen entstehen und gibt entsprechende Meldungen mit
Angaben der auslösenden Zeilen aus.
336
Debugging
8
Hier wird ein Speicherleck erzwungen, indem zehn Integervariablen auf dem
Heap angelegt, aber nicht wieder gelöscht werden.
Testen Sie das Programm aus. Drücken Sie die Speicherlecktaste und beenden
Sie die Applikation dann wieder), erhalten Sie im Ausgabefenster Zeilen ähnlich
der in Abbildung 8.11:
Abb. 8.11
Speicherlecks
werden angezeigt
Ein Anklicken einer solchen Zeile (in Abbildung 8.11 ist eine Meldung exempla-
risch markiert), bringt Sie sogleich in den betreffenden Quelltextbereich:
Abb. 8.12
Die verursachende
Zeile
Da das Programm zu diesem Zeitpunkt nicht mehr läuft, wird auch kein gelber
Pfeil angegeben, sondern ein gelb umrandeter schwarzer Marker, der gerade
die verursachende Zeile darstellt.
afxDump
Wie beim ASSERT-Makro in Form des ASSERT_VALID-Makros gibt es auch für Erweiterung des
TRACE eine mächtigere Variante, die nicht nur einzelne Zeilen, sondern Status- TRACE-Makros
informationen über komplette Objekte mit einem einzigen Aufruf darstellen
kann: afxDump.
337
Fehlersuche mit den MFC
8
Das afxDump-Objekt afxDump ist ein Objekt, dass ähnlich cout verwendet werden kann, und mittels
der <<-Operators übergebene Daten in den Ausgabebereich von Visual Studio
.NET transferieren kann.
Das ist allerdings noch nicht sehr spannend, interessanter ist es, selbst defi-
nierte Objekte auszugeben. Die auszugebenden Objekte müssen eine Methode
Dump enthalten, deren Prototyp Sie in der erweiterten Klassendeklaration zu
CTestObjekt wiederfinden (Datei DebugProjectDlg.h)
public:
CTestObjekt();
void AssertValid() const;
void Dump(CDumpContext &_dumpContext) const;
};
Die Dump-Methode erhält als Parameter eine Referenz auf ein CDumpContext-
Objekt – dieser ist für Sie aber nicht weiter relevant, arbeiten Sie einfach mit
dem afxDump-Objekt innerhalb der Implementation der Dumpmethode, wie
es in der Beispielimplementation von CTestObjekt::Dump aufgezeigt wird (diese
gehört in die Datei DebugProjectDlg.cpp):
Wie Sie sehen können, werden hier einfach die Daten der Reihe nach über den
<<-Operator an das afxDump-Objekt übertragen.
Inwieweit Sie sich hier um eine sinnvolle Formatierung kümmern, ist Ihnen
selbst überlassen, empfohlen wird aber, zunächst eine Objekttitelzeile auszu-
338
Debugging
8
geben und dann pro Zeile möglichst nur einen Wert, oder aber eine Reihe von
zusammengehörenden Werten (beispielsweise X- und Y-Koordinaten eines
Punkts) auszugeben .
Sie können die Ausgabe leicht forcieren, indem Sie die OnBnClickedAfxdump-
test-Methode geeignet ergänzen:
void CDebugProjectDlg::OnBnClickedAfxdumptest()
{
// Objekt anlegen
CTestObjekt TestObjekt;
Hier fällt auf, dass die Ausgabe nur dann ausgeführt wird, wenn das _DEBUG- Bedingte Verwendung
Symbol definiert ist, was in der Regel nur bei Debug-Versionen der Fall ist. von afxDump
Der Grund dafür ist einfach der, dass das afxDump-Objekt in Releasebuilds
nicht zur Verfügung steht, weshalb die Release-Kompilierung an dieser Stelle
fehlschlagen würde.
Sorgen Sie also immer dafür, dass afxDump-Operationen von einem #ifdef ...
#endif-Konstrukt umschlossen sind, so laufen Sie nicht Gefahr, im Nachhinein
eine Unmenge von Kompilierfehlern bei der Übersetzung einer Releaseversion
beheben zu müssen.
Starten Sie das Programm nun im Debug-Modus und klicken Sie die afxDump-
Schaltfläche an. Das Ergebnis entspricht der Abbildung 8.13.
Abb. 8.13
Ausgabe mittels
afxDump
Damit haben Sie den ersten Teil des Debugging-Abschnitts erfolgreich hinter
sich gebracht.
Es sei angeraten, dass Sie sich die einzelnen Möglichkeiten verinnerlichen und
auch ausgiebig einsetzen. Jeder Entwickler macht Fehler, umso wichtiger ist es,
diese auch als solche zu erkennen und vor allem frühzeitig über sie in Kenntnis
gesetzt zu werden.
339
Debuggerinterne Überprüfungen
8
Es ist allemal als Vorteil anzusehen, jeden Tag mit diesen Mitteln zehn Fehler
zu erkennen, als nach der Auslieferung eines Produkts zehn Anrufe von empör-
ten Anwendern zu erhalten, die den Fehler vor Ihnen gefunden haben.
Debuggerinterne Überprüfungen
Der zweite große Abschnitt beim Finden von Fehlern wird Ihnen praktischer-
weise ohne Kosten in Form eigener Arbeitszeit geliefert: Debuggerinterne Lauf-
zeitüberprüfungen, die in Debug-Versionen von Applikationen automatisch
durchgeführt werden.
Debug-Versionen Der Preis, den Sie hierfür zahlen müssen, ist eine geringfügig größere Ausgabe-
datei (in Form von EXE- beziehungsweise DLL-Dateien) und eine leicht niedri-
gere Performance, doch sind dieses Opfer, denen Sie sich grundsätzlich
während der Entwicklungszeit aussetzen sollten.
Gerade in der neuen Version bietet das Studio durch eine Überprüfung, ob Vari-
ablen vor einer Verwendung definiert wurden, einen weiteren Grund an, über-
haupt nicht mit Releaseversionen zu arbeiten, solange nicht der Großteil an
Funktionalität erfolgreich implementiert wurde.
Undefinierte Variablen
Mit diesem Problemfall soll dann auch direkt eingestiegen werden. Editieren
Sie die Methode OnBnClickedUndefvariabletest in der Datei DebugPro-
jectDlg.cpp:
340
Debugging
8
Hier wird die Integervariable nEineZahl nicht definiert, da die Bedingung im if-
Konstrukt nicht erfüllt ist. Die Folge ist, dass die Berechnung hinter dem Block
ein undefiniertes Ergebnis erhalten würde.
Starten Sie die Debuganwendung und wählen die Sie Undef. Var.-Schaltfläche
aus – Sie erhalten umgehend eine Meldung des Debuggers:
Abb. 8.14
Eine nicht definierte
Variable wurde ent-
deckt
Der Debugger moniert hier die Verwendung einer nicht definierten Variable
namens nEineZahl. Das sind schon hilfreiche Informationen, die durch das
Anklicken der Break-Schaltfläche noch sinnvoll ergänzt werden können:
Abb. 8.15
Die fehlerhafte Zeile
Sie landen direkt im Quelltexteditor an der Stelle, an der die undefinierte Vari-
able eingesetzt wurde. Es ist nun in der Regel einfach festzustellen, wo der
Wertespeicher deklariert aber nicht initial definiert wurde.
Das Hauptproblem bei der Behebung dieses Fehlers besteht nun darin, einen Undefinierte Variablen
geeigneten Initialisierungswert zu finden, doch sollte sich dieser im Normalfall belegen
in den meisten Fällen von selbst ergeben.
Überlegen Sie jedoch einmal, wie lange man einen solchen Fehler ohne dieses
nützliche Feature vergeblich suchen könnte.
341
Debuggerinterne Überprüfungen
8
Bedeutung des Der Laufzeitstack eines Programms ist im Wesentlichen für zwei Dinge zustän-
Laufzeitstacks dig: Zum einen werden auf ihm sämtliche lokalen Variablen angelegt, zum
anderen führt er Buch darüber, welche Parameter an eine Funktion übergeben
werden und wohin eine aufgerufene Routine nach Beendigung derselben
zurückspringen soll.
Stellen Sie sich den Stack einfach als eine Sammlung von Blättern vor, die auf
einem Schreibtisch liegen. Immer, wenn Sie ein Datum auf den Stack legen,
wächst der Stapel. Das erste Blatt, das Sie auf ihm platzieren, wird aber erst als
Letztes wieder heruntergenommen, wenn beispielsweise zehn Werte auf dem
Stack abgelegt und danach wieder ausgelesen werden.
Die genauen Details der Stacks sind an dieser Stelle nicht wichtig, wichtig ist
aber zu wissen, dass Sie die ganze Zeit mit einem solchen Anwendungsstapel
arbeiten, wenn lokale Variablen innerhalb einer Funktion angelegt werden.
Beispiel eines Dabei kann es zu Stackfehlern kommen, die in der Methode OnBnClickedStack-
Stackfehlers corruptiontest, die Sie jetzt entsprechend auffüllen, demonstriert werden:
// Bereich überschreiben
strcpy(lpszText, "Ein zu langer Text");
}
Hier wird ein Chararray von fünf Zeichen Länge angelegt – und zwar auf dem
Stack, wie wir gerade eben gelernt haben.
Die nächste Zeile kopiert eine deutlich längeren String in dieses Array und zer-
stört damit eben diesen Stack.
342
Debugging
8
Der Debugger moniert dieses Problem aber sofort mit einer zutreffenden Aus-
sage, wenn Sie das Programm jetzt starten und die passende Schaltfläche aus-
wählen:
Abb. 8.16
Anzeigen des
Stackfehlers
Neben der generellen Aussage, dass der Laufzeitstack beschädigt wurde, wird
auch gleich noch gesagt, im Bereich welcher Variablen dieses Problem aufge-
treten ist – in diesem Fall gerade zufälligerweise beim Chararray lpszText.
Auch hier führt Sie das Anklicken der Break-Schaltfläche an die passende Quell-
textzeile:
Abb. 8.17
Der Fehler im
Quelltext
Hier wird allerdings interessanterweise nicht die Zuweisung des Strings oder
besser der entsprechende Kopiervorgang angezeigt, sondern die letzte Zeile der
Methode, die gerade aus der schließenden geschweiften Klammer besteht.
Auf dem Stack angelegte lokale Variablen werden beim Verlassen einer Funk- Abräumen des Stacks
tion wieder vom Stack geräumt – das ist vergleichbar mit dem Entnehmen der
obersten Blätter vom weiter oben beschriebenen Blattstapel.
Erst hier kann der Debugger bemerken, dass eine Korruption in Form von über-
schriebenem Stackbereich durchgeführt wurde.
Lassen Sie sich also nicht von der seltsam anmutenden Quelltextposition irri-
tieren – irgendwo innerhalb dieser Funktion wurde nicht ordnungsgemäß auf
dem Stack gearbeitet, was in 95 Prozent aller Fälle auf eine unsachgemäße
Stringkopieraktion zurückzuführen sein dürfte.
343
Debuggerinterne Überprüfungen
8
Hier landen sämtliche Daten, die beispielsweise mittels new angelegt werden –
ein Problem des Stacks ist es nämlich, dass er nicht unendlich groß ist, sondern
eine fest vorgegebene Größe hat (die allerdings über geeignete Compilerein-
stellungen verändert werden kann).
Beliebte Fehler, die bei der Arbeit mit dem Heap auftreten bestehen darin, dort
erzeugte Objekte mehrfach zu löschen. Aber auch dieses Problem wird vom
Visual Studio Debugger abgefangen.
Dazu ein weiteres Beispiel, das dieses Mal in die Methode OnBnClickedHeaptest
gehört:
// Objekt abräumen
delete (pNewObject);
Die Methode legt ein Objekt mittels new auf dem Heap an und löscht es
danach zweimal. Dieses führt, nach Kompilieren und Starten der neuen Pro-
grammversion, zu der Benachrichtigung über eine unbehandelte Ausnahme
durch den Debugger:
Abb. 8.18
Probleme auf
dem Heap
Diese Fehlermeldung ist für den Laien vielleicht nur schwer lesbar, deutet aber
doch darauf hin, dass eine nicht korrekte Aktion im Speicher durchgeführt
wurde.
Zum Glück wird diese Annahme durch einen Blick auf die Quelltextzeile, zu der
Sie durch Anklicken der Break-Schaltfläche geführt werden, bestätigt:
344
Debugging
8
Abb. 8.19
Das zweite Löschen
ist ungültig
Es ist nun klar ersichtlich, dass aufgrund des zweiten Löschvorgangs offensicht- Speicherkorruption
lich eine Beschädigung des Heaps aufgetreten ist. durch Löschvorgänge
Im besten Fall merken Sie hiervon nichts, doch kann es so leicht geschehen,
dass die Verwaltungstabellen, die Buch darüber führen, welche Speicherberei-
che verwendet werden und welche noch frei sind, ebenfalls in Mitleidenschaft
gezogen werden.
Die Folge davon kann sein, dass das Neuanlegen von weiteren dynamischen
Objekten fehlschlägt. Sie erkennen dieses in der Regel daran, dass ein Aufruf
von new NULL zurückliefert – nur in den wenigsten Fällen hängt dieses damit
zusammen, dass tatsächlich kein Speicher mehr zur Verfügung steht.
Die Arbeit mit dem Heap, oder besser: Das Aufspüren von mit dem Heap direkt
zusammenhängenden Fehlern gehört zu den zeitaufwendigsten Debug-Tätig-
keiten eines jeden Programmierers.
Achten Sie stets darauf, immer sorgfältig Buch zu führen, welche Objekte Ordnung bei der Arbeit
erzeugt wurden und welche bereits zerstört sind – ein laxes Umgehen mit die- mit dem Heap halten
ser Thematik ist definitiv fehl am Platze.
Ebenso mit Vorsicht zu genießen sind die Worte eines unbekannten Program-
mierers, der zu dieser Thematik einmal sagte: „Im Zweifelsfall ist ein Speicher-
leck alle mal einem Heap-Fehler vorzuziehen. Das Auslassen von delete-
Operationen kann hier manchmal Wunder wirken“.
Lassen Sie sich nicht zu einer solchen Programmiertechnik verleiten, sorgen Sie
jedoch auf der anderen Seite auch dafür, dass stets geflissentlich mit dynami-
schen Objekten umgegangen wird.
345
Debuggerinterne Überprüfungen
8
Standardwerte für Bei nicht initialisierten Variablen – und hier insbesondere bei Zeigern – gibt es
nicht initialisierte aber noch eine weitere Nettigkeit, die durch den Debugger des Visual Studios
Variablen realisiert wird: die Initialisierung mit Standardwerten.
Bei der Releaseversion eines Programms ist dieses nicht der Fall, dort stehen
beliebige Werte in den jeweiligen Variablen, insbesondere nämlich die, die
gerade an der Stelle im Speicher standen, an der die neue Variable gerade
angelegt wurde.
// Funktion aufrufen
pObject->AssertValid();
}
Kompilieren und starten Sie das Programm, erhalten Sie die schon bekannte
Ausgabe bezüglich einer nicht initialisierten Variablen, die verwendet werden
soll:
Abb. 8.20
Nicht initialisierter
Zeiger wird verwendet
Anzeige der relevanten Brechen Sie die Programmausführung durch Anwahl des Punkts Break ab. Die-
Variablen ses Mal ist jedoch nicht die auslösende Zeile interessant, sondern vielmehr die
Informationen, die Sie in einem weiteren Fenster der Debug-Umgebung fin-
den: die Anzeige der gerade aktiven Variablen.
346
Debugging
8
Sie können hier umstellen zwischen sämtlichen lokalen Variablen sowie einer
automatischen Ansicht, die alle bislang verwendet Variablen auflistet, die im
lokalen Kontext genutzt wurden oder noch in Benutzung sind.
Schalten Sie die Ansicht auf die Anzeige aller lokalen Variablen um, Sie finden
dort eine Ausgabe ähnlich der folgenden:
Abb. 8.21
Ausgabe der
Variableninhalte
Sie finden hier einen Eintrag namens pObject, dem ein Inhalt von 0xcccccccc
zugeordnet ist. Das es sich hierbei um einen hexadezimalen Wert handelt, ist
nebensächlich, interessant ist die Art des Werts, der kaum zufällig entstanden
sein kann, sondern eher den Eindruck erweckt, künstlich erzeugt worden zu
sein.
Es gibt weitere Werte dieser Art, wie zum Beispiel 0xcdcdcdcd oder 0xefefefefef, Die Standardwerte
auf die Sie bei der Arbeit mit nicht initialisierten Daten stoßen werden. für Variablen
Stack Overflow
Weiter oben wurde angedeutet, dass die Größe des Stacks nicht unbegrenzt ist. Größenbeschränkung
Obwohl sie zur Zeit der Kompilierung (man spricht hier von Compile Time) ver- des Stacks
ändert werden darf, ist die sie doch stets einer oberen Grenze unterworfen.
Das bedeutet im Klartext, dass es nicht möglich ist, beliebig viele – oder besser:
beliebig große – Objekte auf dem Stack zu erzeugen.
Ebenso heißt es aber, dass die Anzahl der Funktionsaufrufe, die sukzessiv ohne
Rücksprünge durchgeführt werden können, begrenzt ist.
Wann immer der Laufzeitstack in seiner Größe gesprengt wird, gibt der Visual
Studio .NET Debugger eine passende Fehlermeldung aus, die im Folgenden
erzwungen werden soll.
347
Debuggerinterne Überprüfungen
8
public:
CTestObjekt();
void AssertValid() const;
void Dump(CDumpContext &_dumpContext) const;
void NichtTerminieren(int in_nZahl);
};
Die neu eingefügte Methode soll sich selbst kontinuierlich aufrufen. Wie Sie
inzwischen wissen, werden für jeden Funktionsaufruf die Parameter und die
Rücksprungadresse auf dem Stack abgelegt.
Das heißt in der Folge, dass durch das sukzessive Aufrufen einer Funktion, die
sich selbst ständig aufruft, der Stack ständig erweitert wird, bis er schließlich
seine maximale Größe sprengt.
Diese Methode macht nichts anderes, als sich selbst aufzurufen, und den ihr
dabei übergebenen Parameter um eins zu erhöhen.
Das initiale Aufrufen der Methode gehört in ebenfalls in die Datei DebugPro-
jectDlg.cpp und ist in der Behandlungsmethode OnBnClickedStacktest abzule-
gen:
348
Debugging
8
Die Methode erzeugt zunächst ein neues Objekt vom Typ CTestObjekt auf dem
Stack – es handelt sich ja um eine lokale Variable – und ruft dann ihre Methode
NichtTerminieren mit einem Startwert von 0 auf.
In anderen, nicht trivialen Fällen, kann der Compiler die Problematik nicht
erkennen, beispielsweise wenn sich zwei Methoden wechselseitig aufrufen,
oder zwar eine Abbruchbedingung gegeben ist, diese aber niemals erreicht
wird.
Kompilieren und starten Sie das Debug-Programm erneut und wählen Sie den
Knopf zum Auslösen des Stack-Overflow-Fehlers.
Abb. 8.22
Ein Stack Overflow
ist eingetreten
Der Stacküberlauf wurde erkannt, drücken Sie die Break-Schaltfläche und wer- Anzeige eines
fen einen Blick in den so genannten Call Stack. Der Call Stack enthält eine Über- Stacküberlaufs
sicht über die letzten Aufrufe, die bis hin zu diesem Fehler geführt haben.
Im Falle der nicht terminierenden Funktion ist zu sehen, dass sie offensichtlich
mehrfach hintereinander aufgerufen wurde, bis es zum Stacküberlauf gekom-
men ist.
349
Manuelle Fehlersuche
8
Insbesondere wurde die Methode über 4.500 Mal aufgerufen – eine gewaltige
Zahl, wenn Sie bedenken, dass der Fehler quasi sofort ausgegeben wurde:
Abb. 8.23
Call Stack im Überblick
Abb. 8.24
Abbruchstelle
Zusammenfassung
Damit ist der zweite Abschnitt des Debugging-Kapitels abgeschlossen. Sie
haben wiederum einige hilfereiche Funktionen des Debuggers kennen gelernt
und erfahren, dass eine Reihe von Fehlern automatisch ohne Ihr Zutun erkannt
werden.
Außerdem haben Sie zwei wichtige Fenster entdeckt, nämlich den Callstack
und das Variablenausgabefenster. Sie werden diese beiden Fenster im Laufe
der Entwicklungsarbeit mit dem Visual Studio .NET noch sehr zu schätzen ler-
nen, genauso wie die anderen Möglichkeiten, denen sich der nächste Abschnitt
widmen will.
Manuelle Fehlersuche
In gewissen Situationen helfen die besten Tools nichts oder nur wenig: Fehler,
die nur in bestimmten Situationen eintreten oder nicht durch ungültige Vari-
350
Debugging
8
Hier muss man sich selbst behelfen und manuell durch die vermutlich fehler- Mühsames Debug-
haften Portionen eines Quelltexts hindurcharbeiten – das kann im Rahmen von ging per Hand
Windows-Anwendungen schon recht mühsam werden, Sie haben ja bereits
festgestellt, dass einige Applikationen relativ umfangreich sind, insbesondere
wenn man bedenkt, welche Quellcodeanteile die Anwendungsassistenten
automatisch bereitstellen und wie viele Zeilen sich erst noch in den Basisklas-
sen des MFC Frameworks verbergen.
Doch auch in solchen Situation kann der Visual Studio Debugger wertvolle
Hilfe leisten.
Zu diesem Zweck wurden vor langer Zeit die so genannten Breakpoints in die Breakpoints
meisten Debugger integriert, die zu genau diesem Zweck die dienen. Mit ihnen
beschäftigt sich ein Großteil dieses dritten und letzten Abschnitts.
Breakpoints
Ein Breakpoint (zu deutsch etwa: Abbruchpunkt) kann auf jede beliebige gül-
tige Quellcodezeile gesetzt werden. Gültig heißt hierbei, dass die entspre-
chende Zeile auch noch eine gültige Repräsentation im kompilierten
Programm aufweisen können muss.
Wenn Sie ein Programm mit dem Debugger starten, läuft es solange in höchs- Wann treten Break-
ter Geschwindigkeit durch, bis es über einen Breakpoint läuft. Das heißt für den points in Aktion?
Debugger zunächst zu prüfen, ob er es mit einem bedingten oder einem unbe-
dingten Abbruchpunkt zu tun hat.
351
Manuelle Fehlersuche
8
int a = 10;
CTestObjekt *pNewObject;
// eine Schleife
for (int i=0;i<20;i++)
{
a = a + 5;
TRACE(_T("%d\n"), a);
}
if (NULL == pNewObject)
{
AfxMessageBox("Konnte Objekt nicht
anlegen");
return;
}
// Objekt abräumen
delete pNewObject;
}
Diese Methode ist relativ übersichtlich aufgebaut, in ihrer Funktion aber höchst
ominös. Nach dem Initialisieren zweier Variablen, wird in einer Schleife ein
Wertespeicher kontinuierlich um 5 erhöht.
Danach wird ein neues Objekt der Klasse CTestObjekt erzeugt, geprüft, ob das
Anlegen erfolgreich war und direkt danach wieder gelöscht.
352
Debugging
8
Egal, ob sinnvoll oder nicht, die Methode ist hervorragend dafür geeignet, die
wichtigsten Möglichkeiten beim manuellen Debuggen zu erklären.
char lpszTemp[10];
...
a = a + 5;
...
pNewObject = new CTestObjekt;
Abb. 8.25
Eingefügte Break-
points in der
Beispielmethode
353
Manuelle Fehlersuche
8
Erreichen eines Die Ausführung des Programms wird mehr oder weniger sofort beendet – Sie
Breakpoints landen im Quelltexteditor, der erste von Ihnen gesetzte Breakpoint wurde
scheinbar eine Zeile nach unten verschoben, liegt somit nicht mehr auf einer
Variablendeklarationszeile, und enthält nun einen gelben Pfeil.
Dieser gelbe Pfeil gibt, wie bereits mehrfach angesprochen, die Zeile an, die als
Nächstes ausgeführt werden soll. Eine erste Regel bei der Arbeit mit Abbruch-
punkten ist also die, dass eine Zeile, die einen Breakpoint enthält nicht mehr
abgearbeitet wird, bevor das Programm angehalten wird.
Abb. 8.26
Die Programmaus-
führung wurde
abgebrochen
Werfen Sie einen Blick in das Anzeigefenster mit den Variablen, Sie finden dort
die derzeitig aktuellen Werte. Dabei ist zunächst eine automatische Übersicht
aktiviert:
Abb. 8.27
Automatische
Variablenauswahl
Abb. 8.28
Die lokalen Variablen
Betrachten Sie nun die Inhalte der einzelnen Variablen. Wie nicht anders zu
erwarten, sind sie allesamt nicht initialisiert und tragen vom Debugger vorge-
gebene Werte. Beispielsweise findet sich für pNewObject der bekannte Wert
0xcccccccc wieder.
354
Debugging
8
%: Setzt den Programmablauf fort. Dieses entspricht dem Starten des Fortsetzen des
Programms, nur dass dieses Mal die Ausführung an der Stelle weiterge- Programmablaufs
führt wird, an der sie soeben abgebrochen wurden. Das Programm wird
weiterlaufen, bis es terminiert oder der nächste Breakpoint erreicht
wurde.
=: Führt die nächste Anweisung komplett aus und bricht die Program- Einzelschritt-
mausführung danach ab. Hat den gleichen Effekt, als wäre in der nächs- ausführung
ten Zeile wieder ein Breakpoint gesetzt und die Programmabarbeitung
würde mit % fortgesetzt werden. Mithilfe diesen Kommandos ist es
möglich, Schritt für Schritt durch ein Programm hindurch zu gehen
(man bezeichnet diesen Vorgang häufig mit steppen).
Bedingte Abbruchpunkte
Bislang sind sämtliche Breakpoints unbedingte Abbruchpunkte. Jeder Break- Bedingungen für
point, den Sie neu in einem Programm setzen, zählt zu dieser Kategorie. Breakpoints definieren
Abb. 8.29
Übersicht aller
Breakpoints
355
Manuelle Fehlersuche
8
Der Kasten links neben den Abbruchpunkten (die mit Datei und Zeilennummer
tituliert sind) gibt an, ob der Breakpoint gerade aktiv ist oder ignoriert werden
soll. Die weiteren Angaben beziehen sich darauf, ob ein bedingter oder unbe-
dingter Abbruchpunkt vorliegt.
Die Eigenschaften eines Abbruchpunkts können Sie verändern, indem sie einen
Breakpoint anklicken und aus seinem Kontextmenü den Punkt Eigenschaften
auswählen:
Abb. 8.30
Eigenschaften eines
Breakpoints
Weiterhin können Sie hier Bedingungen angeben, die beschreiben, wann ein
Abbruchpunkt greifen soll und wann der Debugger ihn zu ignorieren hat.
Beispielhaftes Setzen Wählen Sie aus dem Quelltext den zweiten gesetzten Breakpoint aus – den im
einer Breakpoint- for-Konstrukt – und öffnen durch Anklicken der Bedingungsschaltfläche einen
Bedingung weiteren Konfigurationsdialog:
Abb. 8.31
Bedingungen für
einen Breakpoint
356
Debugging
8
In diesem können Sie spezifizieren, ob eine Bedingung für den Breakpoint fest-
gelegt werden soll, und wie diese auszusehen hat. Im Wesentlichen haben Sie
für die Beschreibung einer Bedingung sämtliche Möglichkeiten, die Ihnen die
C++-Syntax auch bietet.
Schauen Sie für die Beschreibung einiger Ausnahmen und Ergänzungen in Ihre
Online-Hilfe.
In letzterem Fall speichert der Debugger das letzte Ergebnis der Abfrage und
vergleicht es mit dem aktuellen. Hat sich hier eine Änderung ergeben, wird das
Programm angehalten.
Beim ersten Erreichen des Breakpoints wird das Programm entsprechend nie
angehalten, da keine Vergleichsmöglichkeiten zu vorhergehenden Durchläufen
besteht.
Setzen Sie als Bedingung für den zweiten Breakpoint die Zeile
a > 60
Führen Sie dann die Programmausführung fort – wenn Sie sich nicht mehr auf
dem ersten Breakpoint befinden, starten Sie das Programm beziehungsweise
die Behandlung der OnBnClickedBreakpointtest-Methode – neu.
Abb. 8.32
Die Bedingung
ist erfüllt
Wie zu erwarten wurde der Breakpunkt erst aktiviert, nachdem die Variable a
einen Wert größer als 60 angenommen hatte. Drücken Sie erneut %, hält die
Programmausführung wieder an der gleichen Stelle an, a hat dann den Wert 70
und so weiter.
357
Manuelle Fehlersuche
8
Für einen kleinen Test wählen Sie nun den dritten Abbruchpunkt aus, öffnen
seinen Eigenschaften-Dialog und wählen den Punkt Hit Count.
Hier haben Sie die Möglichkeit festzulegen, wann der Breakpoint aktiv sein soll
– als Auswahl bieten sich hier die folgenden Möglichkeiten:
Immer abbrechen immer abbrechen: der Debugger hält den Programmablauf jedes Mal
an, wenn der Breakpoint erreicht wird.
Zahl der Aufrufe ist gleich: Sie können einen Wert angeben, der spezifiziert, nach wie vie-
ist gleich x len Anläufe der Breakpoint aktiv sein soll. Tragen Sie beispielsweise den
Wert 3 ein, stoppt der Debugger das ablaufende Programm erst, sobald
der Abbruchpunkt exakt zum dritten Mal erreicht wird. Erreicht das Pro-
gramm den Abbruchpunkt zum vierten oder einem weiteren Mal, wird
das Programm nicht mehr angehalten.
Zahl der Aufrufe ist ist ein Vielfaches von: Sie können einen Wert angeben, der spezifiziert,
ein Vielfaches von x nach wie vielen Durchläufen der Breakpoint aktiv sein muss. Der aktu-
elle Zählstand wird dabei daraufhin geprüft, ob er ein Vielfaches des
eingetragenen Werts ist. Haben Sie als Zahl 3 eingetragen, stoppt der
Debugger das Programm beim dritten Erreichen des Breakpoints, beim
sechsten Mal, beim neunten Mal und so weiter.
Zahl der Aufrufe ist gleich oder größer als: Sie können einen Wert angeben, der spezifi-
ist gleich oder ziert, nach wie vielen Durchläufen der Breakpoint aktiv sein soll. Der
größer als x aktuelle Zählstand wird beim Erreichen des Breakpoints daraufhin
geprüft, ob er größer oder gleich dem eingetragenen Wert ist. Haben
Sie hier zum Beispiel fünf eingestellt, wird der Debugger den Pro-
grammablauf ab dem fünften Erreichen des Breakpoints jedes Mal stop-
pen, sobald er wieder erreicht wird.
Tragen Sie für den dritten Breakpoint als Abbruchbedingung ist gleich ein, als
Wert 3:
Abb. 8.33
Bedingter Breakpoint
358
Debugging
8
Testen Sie das Verhalten aus. Prüfen Sie, ob der Breakpoint in den ersten Austesten des Break-
Methodendurchläufen ignoriert wird, was im dritten und was in den darauf fol- point-Verhaltens
genden Durchläufen geschieht.
Sie werden bemerken, dass der Breakpoint das erste Mal tatsächlich erst im
dritten Durchlauf greift:
Abb. 8.34
Der Breakpoint wird
im dritten Durchlauf
das erste und einzige
Mal aktiviert
Die durch den Abbruchpunkt angegebene Zeile ist gerade die zum Anlegen
eines neuen Objekts:
Abb. 8.35
Anlegen eines
neuen Objekts
Drücken Sie einmal auf =, um die aktuelle Zeile auszuführen und beobachten
Sie, was mit der lokalen Variable pNewObject im Variablenanzeigefenster
geschieht.
Sie werden sehen, dass ihr eine Speicheradresse zugewiesen wird, die ungleich
NULL (also ungleich 0x00000000) ist.
Daraus ergibt sich sofort, dass das nachfolgende if-Konstrukt nicht greifen son-
dern überspringen wird.
Doppelklicken Sie jetzt im Variablenanzeigefenster auf den Wert des pNewOb- Verändern eines
ject-Objekts. Es erscheint ein Eingabefeld, in das Sie einen neuen Wert eintra- Werts zur Laufzeit
gen können.
359
Manuelle Fehlersuche
8
Stellen Sie hier den Wert 0x00000000 ein, wie es die folgende Abbildung
zeigt:
Abb. 8.36
Verändern eines
Werts zur Laufzeit
Das dieser Wert übernommen wird, können Sie sehen, wenn Sie nun erneut
mit = die Programmausführung schrittweise fortsetzen: Die Bedingung im if-
Konstrukt wird als wahr ausgewertet, die Fehlermeldung wird ausgegeben.
Anzeige des Der Speicher (insbesondere der Heap-Bereich des Speichers) ist zunächst nichts
Speicherinhalts anderes als eine nur durch den physikalisch vorhandenen Speicher begrenzte
Größe, bestehend aus zahllosen Bits und Bytes, die irgend einer Bedeutung
haben – oder auch nicht. Es sei an dieser Stelle nicht darauf eingegangen, dass
Windows den zur Verfügung stehenden Speicher durch die Verwendung einer
Auslagerungsdatei enorm erweitern kann.
Die Bedeutung der einzelnen Speicherstellen hängt von den Programmen ab,
die mit diesen Speicherbereichen arbeiten. So sind die einzelnen Segmente viel-
leicht mit sinnvollen Daten belegt, vielleicht aber auch Überbleibsel längst
beendeter Applikationen.
Tatsächlich ist es in den meisten Fällen nicht notwendig, einen Blick auf den
physikalisch tatsächlich verwendeten Speicher zu werfen.
Hin und wieder ist es jedoch trotzdem ratsam, und einmal gesehen zu haben,
wie Daten in den Speicher geschrieben werden, ist in jedem Fall für jeden Soft-
wareentwickler sinnvoll.
360
Debugging
8
// Speicher beschreiben
for (int i=0;i<16;i++)
{
pChars[i] = i;
}
// Speicher freigeben
delete [] pChars;
}
Diese Methode erzeugt ein Chararray mit sechzehn Einträgen und füllt diese
nacheinander mit den Zahlen 0 bis 15. Danach wird der angeforderte Speicher
wieder freigegeben.
Setzen Sie einen Breakpoint auf die erste Zeile der Methode und starten Sie das
Programm dann über den Debugger. Wählen Sie den Punkt Speichertest aus
dem Dialogfeld aus – sie finden sich jetzt im Quelltext wieder, gerade auf der
Zeile, die den new Operator enthält.
Drücken Sie einmal auf die =-Taste, Sie sehen, dass der Variablen jetzt eine Speicheransichts-
Speicheradresse zugewiesen wurde. fenster öffnen
Abb. 8.37
Der neue Speicher-
bereich
361
Manuelle Fehlersuche
8
Steppen Sie nun weiter mit der Taste = durch das Programm: Die Speicher-
stellen werden der Reihe nach beschrieben, wobei sich die gerade veränderten
Zellen rot färben und so den neuen Wert deutlich anzeigen:
Abb. 8.38
Die veränderten
Speicherstellen
Zusammenfassung
Sie sind jetzt am Ende dieses Kapitels angelangt und haben einiges über die
Arbeit mit dem Debugger und den zur Verfügung stehenden Hilfsmitteln
gelernt.
Zum Abschluss Abschließend hoffen wir, dass Sie Gefallen an dem vorliegenden Buch gefun-
den und einen für Ihre Bedürfnisse guten ersten Einblick in die Entwicklung
unter Windows mit den MFC und dem neuen Visual Studio .NET erhalten
haben.
Auf Ihrem weiteren Weg als Windows-Entwickler wünschen ich und der
gesamte Sybex Verlag Ihnen viel Spass und allseits guten Erfolg!
362
Fensterstrukturen und
zugehörige Funktionen
Windows-Messages 364
Die WNDCLASSEX-Struktur 378
CreateWindow(Ex) 381
Standardstile beim Erzeugen von Fenstern 382
Erweiterte Stile beim Erzeugen von Fenstern 383
Windows-Messages
A
Windows-Messages
Die folgende Tabelle gibt einen Überblick über die verfügbaren Windows-Mes-
sages (Nachrichten), die Sie innerhalb Ihrer Applikationen verwenden können.
Sie sind alphabetisch sortiert, die Tabelle umfasst die Namen der Botschaften,
sowie den Prototyp ihrer Behandlungsmethoden und eine kurze Beschreibung,
wann die Nachricht versendet wird.
Die Bedeutung der einzelnen Parameter entnehmen Sie der MSVC++ beiliegen-
den Online-Hilfe.
Tabelle A.1
Nachricht Behandlungsmethode
Die Windows-Nach-
richten im Überblick
WM_ACTIVATE afx_msg void OnActivate (UINT,
CWnd*, BOOL);
Diese Nachricht wird an ein Fenster gesendet, das gerade im Begriff ist, den
MouseCapture zu verlieren.
364
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Wird versandt, wenn der Benutzer innerhalb eines Fensters die rechte
Maustaste gedrückt hat.
365
Windows-Messages
A
Nachricht Behandlungsmethode
WM_CREATE wird versendet, sobald eine Applikation ein neues Fenster mit-
tels CreateWindowEx oder CreateWindow erzeugen will.
Teilt einem Fenster mit, dass es zerstört wird. In diesem Augenblick ist es
bereits vom Desktop verschwunden.
Teilt dem Besitzer der Zwischenablage mit, dass diese durch einen Aufruf
von EmptyClipboard gelöscht wurde.
366
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Diese Nachricht wird an das erste Fenster in der Liste der Zwischenablagen-
anzeigenden Fenster gesendet, um es darüber zu informieren, dass eine
Veränderung derselben stattgefunden hat. Diese Nachricht sollte per Send-
Message an das nächste Fenster in der Liste weitergeleitet werden.
Die Mitteilung wird an ein Fenster geschickt, wenn eins seiner untergeord-
neten Steuerelemente (insbesondere Buttons, Comboboxes, Listboxes oder
Menüs) verändert wurde.
Wird an ein Fenster verschickt, wenn der Benutzer ein File per Drag & Drop
auf dieses Fenster zieht. Es muss als Fenster eingetragen sein, dass solche
Aktionen unterstützt.
Aktiviert oder deaktiviert eine Applikation ein ihr zugehöriges Fenster, wird
diese Nachricht verschickt. Der übergebene boolesche Parameter zeigt an,
ob das entsprechende Fenster deaktiviert oder aktiviert werden soll.
Die Nachricht wird an eine Applikation verschickt, nachdem das System das
Ergebnis einer WM_QUERYENDSESSION-Nachricht ermittelt hat.
WM_ENDSESSION informiert die Applikation darüber, ob die Sitzung been-
det wird.
Diese Nachricht wird an ein Elternfenster eines modalen Dialogs oder eines
Menüs gesendet, sobald dieses seine gesamte Nachrichten abgearbeitet
hat.
367
Windows-Messages
A
Nachricht Behandlungsmethode
Diese Nachricht wird an ein Fenster verschickt, dessen Größe oder Position
geändert wird. Die übergebene Struktur enthält Informationen über
erlaubte maximale und minimale Größe bzw. die Position des Fensters.
Durch Anpassen der Strukturkomponenten können eigene Werte für die fol-
gende Vergrößerungs- bzw. Repositioierungsaktion gültig erklärt werden.
368
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Wird an ein minimierte Fenster geschickt, wenn der Hintergrund eines Icons
gefüllt werden muss, bevor dass eigentliche Icon gezeichnet werden darf.
WM_INITMENU wird verschickt, bevor ein Menü aktiviert wird (also wenn
der Benutzer gerade auf einen Eintrag in der Menüleiste geklickt hat). Das
Abfangen dieser Nachricht wird benutzt, um das Erscheinungsbild eines
Menüs zu verändern, bevor es dargestellt wird.
Die Botschaft wird immer dann versendet, wenn eine Taste gedrückt wird,
die keine Systemtaste ist.
369
Windows-Messages
A
Nachricht Behandlungsmethode
Diese Nachricht wird an ein Fenster verschickt, in dem ein Button, eine
Combo- oder Listbox oder ein Menüeintrag erzeugt werden soll.
370
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Die Nachricht wird an das Elternfenster eines Menüs geschickt, sobald der
Benutzer einen Menüeintrag ausgewählt hat.
Das Bewegen der Maus sorgt für ein Erzeugen dieser Nachricht, anhand
dessen die Applikation die aktuelle Mauszeigerposition ermitteln kann.
Diese Nachricht wird generiert, wenn die Größe und Position der Client Area
eines Fenster neu berechnet werden muss.
371
Windows-Messages
A
Nachricht Behandlungsmethode
Diese Nachricht signalisiert, dass eine Non-Client Area zerstört werden soll.
Sie wird in der Regel nach der WM_DESTROY-Botschaft verschickt.
372
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Wird analog zur WM_MOUSEMOVE beim Bewegen der Maus verschickt, die-
ses Mal jedoch nur, wenn sich der Mauszeiger über einen Non-Client Area
befindet.
WM_NCPAINT wird verschickt, wenn die Non-Client Area eines Fenster neu
gezeichnet werden muss.
WM_PAINT signalisiert, dass die Client Area eines Fenster neu gezeichnet
werden muss.
373
Windows-Messages
A
Nachricht Behandlungsmethode
Diese Nachricht teilt einem Fenster mit, dass es gleich den Eingabefokus
erhalten wird. Es kann daraufhin seine Palette vorbereiten.
374
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Sorgt dafür, dass ein bestimmter Mauszeiger für ein Fenster dargestellt
wird, wenn sich der Cursor darüber befindet.
Diese Nachricht wird an ein Fenster verschickt, das angezeigt oder verdeckt
werden soll.
375
Windows-Messages
A
Nachricht Behandlungsmethode
Diese Nachricht wird an ein Fenster geschickt, nachdem ein oder mehrere
seiner Stilflags verändert wurden. Dias geschieht normalerweise durch Auf-
ruf der Funktion SetWindowLong.
376
Fensterstrukturen und zugehörige Funktionen
A
Nachricht Behandlungsmethode
Die Nachricht wird durch das Drücken der Taste = oder einer Kombination
aus A und einer weiteren Taste erzeugt.
Die WM_TIMER-Botschaft wird immer dann ausgelöst, wenn ein durch Set-
Timer gesetzter Timer feuert.
Entspricht WM_HSCROLL, bezieht sich jedoch auf die Benutzung einer verti-
kalen Bildlaufleiste.
377
Die WNDCLASSEX-Struktur
A
Nachricht Behandlungsmethode
Diese Nachricht wird an ein Fenster geschickt, dessen Größe, Position, oder
Anordnung auf dem Schirm (vor und hinter anderen Fenstern) sich geändert
hat.
Diese Nachricht wird an ein Fenster geschickt, dessen Größe, Position oder
Anordnung auf dem Schirm gleich verändert wird.
Die WNDCLASSEX-Struktur
Um ein Fenster unter der reinen Win32-API-Programmierung zu öffnen, muss
zunächst eine dazu passende Fensterklasse registriert werden. Die hierzu aus-
zufüllende WNDCLASSEX Struktur besitzt die folgenden Komponenten:
Komponente Bedeutung
378
Fensterstrukturen und zugehörige Funktionen
A
Komponente Bedeutung
379
Die WNDCLASSEX-Struktur
A
Flag Bedeutung
380
Fensterstrukturen und zugehörige Funktionen
A
CreateWindow(Ex)
HWND CreateWindowEx
(
DWORD dwExStyle
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hInstance,
LPVOID lpParam
);
Parameter Bedeutung
381
Standardstile beim Erzeugen von Fenstern
A
Stilflag Bedeutung
382
Fensterstrukturen und zugehörige Funktionen
A
Stil Bedeutung
383
Erweiterte Stile beim Erzeugen von Fenstern
A
Stil Bedeutung
384
Fensterstrukturen und zugehörige Funktionen
A
Stil Bedeutung
CFrameWnd::Create
Erzeugt ein neues Rahmenfenster. Dient zum Initialisieren eines Objekts der
CFrameWnd oder einer davon abgeleiteten Klasse.
Parameter Bedeutung
385
Erweiterte Stile beim Erzeugen von Fenstern
A
Parameter Bedeutung
CCreateContext
Fortgeschrittene Struktur, die zur Verknüpfung zwischen Dokument und
Ansichten verwendet wird. Braucht nur spezifiziert zu werden, falls eigene Initi-
alisierungsprozeduren eingesetzt werden.
Komponente Bedeutung
386
Werkzeugklassen
MFC-Werkzeugklassen 388
MFC-Containerklassen 396
GDI-Objekte 400
Standarddialoge 418
MFC-Werkzeugklassen
B
MFC-Werkzeugklassen
Innerhalb der MFC stehen die folgenden Werkzeugklassen zur Verfügung:
CRect – Die vier Elementvariablen top, bottom, left und right speichern
die Eckpunkt-Koordinaten eines Rechtecks.
CString – Eine sehr interessante Klasse zur Arbeit mit Strings. Es werden
Vergleichsoperatoren, Zuweisungen usw. angeboten, außerdem ist eine
dynamische Größenveränderung von Strings möglich. Die von C++ her
bekannten Befehle wie strcpy und strcmp werden nicht weiter benötigt.
Außerdem kann ein CString-Objekt problemlos als const char* oder
LPCTSTR-Funktionsargument verwendet werden.
CSize size2;
size2.cx = 100;// Alternative
size2.cy = 50;
388
Werkzeugklassen
B
CString string3;
string3 = string1 + string2; // ergibt
// 'Teststring1 Teststring2'
389
MFC-Werkzeugklassen
B
if (!(string3 == string1))
{
string3 = string1.Left(4); // ergibt Test'
string3.MakeUpper();// ergibt 'TEST'
}
Die folgenden Tabellen geben einen Überblick über die Elementmethoden der
einzelnen Werkzeugklassen und sollen zur schnellen Referenz bei der täglichen
Arbeit dienen:
CPoint
Methode Beschreibung
CRect
Methode Beschreibung
390
Werkzeugklassen
B
Methode Beschreibung
IsRectEmpty Gibt an, ob das CRect-Objekt leer ist. Das ist der Fall,
wenn Breite und Höhe gleich 0 sind.
IsRectNull Gibt an, ob alle vier Werte für linken, rechten, oberen und
unteren Rand gleich 0 sind.
391
MFC-Werkzeugklassen
B
Methode Beschreibung
CSize
Methode Beschreibung
CString
Methode Beschreibung
392
Werkzeugklassen
B
Methode Beschreibung
SpanExcluding Gibt einen Teilstring zurück, der nur Zeichen enthält, die
der Methode nicht übergeben wurden.
393
MFC-Werkzeugklassen
B
Methode Beschreibung
ReleaseBuffer Gibt den durch GetBuffer erlangten Zugriff auf die Zei-
chenkette wieder frei.
CTime
Methode Beschreibung
394
Werkzeugklassen
B
Methode Beschreibung
<<, >> Ein- und Ausgabe eine CTime-Objekts in oder aus einem
CArchive.
CTimeSpan
Methode Beschreibung
395
MFC-Containerklassen
B
MFC-Containerklassen
Die MFC stellen drei Containerklassen zur Verfügung, die zur Verwaltung von
eigenen Daten dienen. Die Methoden, die jeweils verwendet werden können,
sind im Folgenden aufgeführt.
CList
CList entspricht der klassischen doppelt verketteten Liste mit entsprechenden
Zugriffsfunktionen. Der Funktionsprototyp sieht so aus:
Dabei ist TYPE der Typ der aufzunehmenden Elemente, ARG_TYPE der Typ der
verwendet werden soll, wenn Elemente dieser Liste per Parameter übergeben
werden. In der Regel ist ARG_TYPE eine Referenz auf den TYPE-Typ.
Methode Beschreibung
GetHead Gibt das erste Element der Liste zurück (Liste darf nicht
leer sein).
GetTail Gibt das letzte Element der Liste zurück (Liste darf nicht
leer sein).
AddHead Fügt ein Element am Anfang der Liste ein. Dieses Ele-
ment kann selbst wieder eine CList mit Inhalten des glei-
chen Typs sein.
AddTail Fügt ein Element am Ende der Liste ein. Dieses Element
kann selbst wieder eine CList mit Inhalten des gleichen
Typs sein.
GetHeadPosi- Gibt das POSITIONs-Objekt für das erste Element der Liste
tion zurück.
396
Werkzeugklassen
B
Methode Beschreibung
GetCount, Get- Gibt die Anzahl der Elemente in der Liste zurück.
Size
IsEmpty Gibt true zurück, falls die Liste leer ist, sonst false.
CMap
Ein CMap-Objekt speichert Elemente, die jeweils aus einem Schlüsselwert und
dem eigentlichen Inhalt bestehen. Mögliche Paare sind also beispielsweise (1,
„Hallo“), (57, 1) oder (113, 1.567), falls der Schlüssel jeweils als Integertyp dekla-
riert ist.
Dabei sind KEY und ARG_KEY der Datentyp, der als Schlüsselwert bzw. als
Schlüsselwert in Parameterübergaben verwendet wird (in der Regel ist
ARG_KEY eine Referenz vom KEY-Typ) und VALUE bzw. ARG_VALUE das eigentli-
che Element, wobei ARG_VALUE in der Regel eine Referenz vom VALUE-Typ ist.
397
MFC-Containerklassen
B
Die Wertepaare werden in einer Hashtable abgelegt, es ist möglich, die Größe
dieser Hashtable anzugeben, wie aus der folgenden Methodenübersicht her-
vorgeht:
Methode Beschreibung
PGetNextAssoc Gibt einen Zeiger auf das nächste Element zur Itera-
tion zurück. Es ist nicht sichergestellt, in welcher Rei-
henfolge die Elemente zurückgeliefert werden, insbe-
sondere in der Regel nicht nach Schlüsselwerten
sortiert!
RemoveKey Entfernt einen Wert aus der Liste, der zu einem über-
gebenen Schlüssel gehört.
IsEmpty Liefert true zurück, wenn die Hashtable leer ist, sonst
false.
398
Werkzeugklassen
B
CArray
Die CArray-Klasse ist analog zu herkömmlichen Arrays zu betrachten, kann ihre
Größe jedoch dynamisch während der Laufzeit eines Programms an die jewei-
lige Situation anpassen.
Dabei ist TYPE der Typ der aufzunehmenden Elemente, ARG_TYPE der Typ der
verwendet werden soll, wenn Elemente dieser Liste per Parameter übergeben
werden. In der Regel ist ARG_TYPE eine Referenz auf den TYPE-Typ.
Methode Beschreibung
IsEmpty Gibt true zurück, falls das Array leer ist, sonst false.
SetSize Legt die Anzahl der Elemente fest, die in das Array
eingeordnet werden können.
FreeExtra Gibt den Speicher frei, der für nicht belegte Elemente
oberhalb des letzten gültigen Elements reserviert ist.
Add Hängt ein Element an das Ende des Arrays an, vergrö-
ßert das Array bei Bedarf.
399
GDI-Objekte
B
Methode Beschreibung
SetAtGrow Wie SetAt, vergrößert das Array jedoch, falls die über-
gebene Position zu groß ist.
GDI-Objekte
Windows stellt eine kleine Zahl an nützlichen Zeichenwerkzeugen zur Verfü-
gung, die im Rahmen von Gerätekontexten zum Einsatz kommen können.
Die folgenden Seiten zeigen die wichtigsten Methode der jeweiligen Objekte
auf.
CBitmap
CBitmap repräsentiert eine einfache Bitmap-Grafik.
Methode Beschreibung
400
Werkzeugklassen
B
Methode Beschreibung
LoadMapped- Lädt eine Bitmapgrafik ein und biegt die Farben auf
Bitmap die derzeit eingestellten Systemfarben um.
GetBitmapDimen- Gibt Breite und Höhe der Bitmap zurück. Muss vorher
sion durch die SetBitmapDimension-Methode gesetzt wer-
den.
Konstanten, die mit OBM_OLD beginnen, stellen Grafiken da, die vor der Win-
dows-Version 3.0 verwendet wurden.
401
GDI-Objekte
B
BITMAP-Struktur
Die Windows-BITMAP-Struktur enthält Informationen über den Aufbau einer
Bitmap. Sie können diese durch die CBitmap-Methode GetBitmap auslesen.
Komponente Bedeutung
CBrush
Ein CBrush ist eine Art Stempel, mit dem Flächeninhalte von geometrischen
Figuren ausgefüllt werden können. Ein Brush kann dabei eine einfache Farbe,
ein Muster, oder auch eine Bitmap sein.
Methode Beschreibung
402
Werkzeugklassen
B
Methode Beschreibung
LOGBRUSH
Ein CBrush-Objekt definiert sich über eine Struktur namens LOGBRUSH, die wei-
tere Informationen über den Stil des Brushes enthält:
Komponente Beschreibung
lbStyle Gibt den Stil des Brushes an. Für mögliche Stile, siehe
unten.
403
GDI-Objekte
B
Wert Beschreibung
Werte Beschreibung
Ist lbStyle auf BS_HOLLOW oder BS_PATTERN gesetzt, wird der lbColor-Wert
ignoriert.
Ist lbStyle auf BS_DIBPATTERN gesetzt, enthält lbHatch ein Handle auf eine
gepackte DIB.
404
Werkzeugklassen
B
Ist lbStyle auf BS_DIBPATTERNPT gesetzt, enthält lbHatch einen Zeiger auf eine
gepackte DIB.
Ist lbStyle auf BS_PATTERN gesetzt, enthält lbHatch ein Handle auf die zugrunde
liegende Bitmap.
Ist lbStyle auf BS_SOLID oder BS_HOLLOW gesetzt, wird lbHatch ignoriert.
Ist lbStyle auf BS_HATCHED gesetzt, kann lbHatch einen der folgenden Werte
annehmen:
Werte Beschreibung
CFont
Ein CFont-Objekt repräsentiert einen Windows-Font, der über geeignete
Methoden manipuliert werden kann:
Methode Beschreibung
405
GDI-Objekte
B
Methode Beschreibung
LOGFONT-Struktur
Die LOGFONT-Struktur beschreibt einen Zeichensatz:
Komponente Beschreibung
406
Werkzeugklassen
B
Komponente Beschreibung
407
GDI-Objekte
B
FW_DONTCARE 0
FW_THIN 100
FW_EXTRALIGHT 200
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_NORMAL 400
FW_REGULAR 400
FW_MEDIUM 500
FW_SEMIBOLD 600
FW_DEMIBOLD 600
FW_BOLD 700
FW_EXTRABOLD 800
FW_ULTRABOLD 800
FW_HEAVY 900
FW_BLACK 900
Wert Beschreibung
408
Werkzeugklassen
B
Wert Beschreibung
Wert Beschreibung
409
GDI-Objekte
B
Wert Beschreibung
Wert Beschreibung
410
Werkzeugklassen
B
Wert Beschreibung
CPen
Ein CPen ist im Kontext der GDC als Stift zu verstehen, mit dem beispielsweise
Linien gezeichnet werden können. Er wird auch als Umrandungsinstrument für
Rechtecke und andere geometrische Figuren benutzt.
Methode Beschreibung
411
GDI-Objekte
B
Stil Beschreibung
PS_DASHDOT Stift zieht eine Linie die abwechselnd aus kurzen Stri-
chen und Punkten besteht.
PS_DASHDOTDOT Stift zieht eine Linie, die aus einer Folge „Strich-
Punkt-Punkt“ zusammengesetzt wird.
Die LOGPEN-Struktur
Die LOGPEN-Struktur enthält Informationen über den Stil, Breite und Farbe
eines Stifts und genügt dem folgenden Prototyp:
Komponente Beschreibung
Die EXTLOGPEN-Struktur
Die EXTLOGPEN-Struktur enthält im Vergleich zu LOGPEN eine ganze Reihe
erweiterte Informationen:
412
Werkzeugklassen
B
ULONG_PTR elpHatch;
DWORD elpNumEntries;
DWORD elpStyleEntry[1];
} EXTLOGPEN, *PEXTLOGPEN;
Komponente Beschreibung
Wert Beschreibung
413
GDI-Objekte
B
Wert Beschreibung
Werte Beschreibung
Ist elpBrushStyle auf BS_HOLLOW oder BS_PATTERN gesetzt, wird der elpColor
Wert ignoriert.
Ist elpBrushStyle auf BS_DIBPATTERN gesetzt, enthält elpHatch ein Handle auf
eine gepackte DIB.
Ist elpBrushStyle auf BS_PATTERN gesetzt, enthält elpHatch ein Handle auf die
zugrunde liegende Bitmap.
Ist elpBrushStyle auf BS_SOLID oder BS_HOLLOW gesetzt, wird elpHatch igno-
riert.
414
Werkzeugklassen
B
Ist elpBrushStyle auf BS_HATCHED gesetzt, kann elpHatch einen der folgenden
Werte annehmen:
Werte Beschreibung
CPalette
CPalette stellt eine Windows-Farbpalette dar, die als Schnittstelle zwischen
einer Applikation und dem Ausgabemedium dient. So ist es möglich, Farben,
die in der Anwendung vorkommen, möglichst originalgetreu auf anderen Gerä-
ten abzubilden.
Methode Beschreibung
415
GDI-Objekte
B
Methode Beschreibung
Die PALETTEENTRY-Struktur
Die PALETTEENTRY-Stuktur beschreibt gerade einen Eintrag in der Farbtabelle:
Methode Beschreibung
peFlags Gibt an, wie die übrigen Werte der Struktur zu verwenden
sind. Ist peFlags gleich PC_EXPLICIT, handelt es sich bei die-
sem Eintrag um einen Hardwarepalettenindex. PC_NO-
COLLAPSE gibt an, dass dieser Eintrag beim Einsortieren in
eine bestehende Liste nicht gegen andere Farbwerte vergli-
chen, sondern direkt eingefügt werden soll. Sind keine
freien Plätze für den Eintrag vorhanden, wird die Farbe nor-
mal gegen die bereits vorhandenen Farben verglichen.
PC_RESERVED markiert einen Farbeintrag als einen, gegen
den Farben nicht verglichen werden sollen, zum Beispiel
weil der Eintrag im Rahmen einer Palettenrotation ohne-
hin ständig verändert wird und somit zu Farbfehlern füh-
ren könnte.
416
Werkzeugklassen
B
CRgn
Ein CRgn-Objekt beschreibt einen elliptischen oder polygonalen Bereich inner-
halb einer GDI Region.
Methode Beschreibung
417
Standarddialoge
B
Methode Beschreibung
Standarddialoge
Sie haben im Verlauf des Buchs bereits mit einigen Standarddialogen gearbei-
tet. Die folgenden Seiten fassen die wichtigsten Informationen dieser kleinen
praktischen Klassen zusammen.
CColorDialog – Eine Farbtabelle wird präsentiert, aus der eine Farbe aus-
gewählt werden kann.
CColorDialog
Methode Beschreibung
418
Werkzeugklassen
B
Methode Beschreibung
CFileDialog
Methode Beschreibung
419
Standarddialoge
B
Methode Beschreibung
CFindReplaceDialog
Methode Beschreibung
420
Werkzeugklassen
B
Methode Beschreibung
CFontDialog
Methode Beschreibung
CPageSetupDialog
Methode Beschreibung
421
Standarddialoge
B
Methode Beschreibung
CPrintDialog
Methode Beschreibung
422
Werkzeugklassen
B
Methode Beschreibung
423
Alphabetische Übersicht der im
Buch verwendeten
MFC-Funktionen
AfxGetApp
C
AfxGetApp
Gibt einen Zeiger auf das globale CwinApp-Objekt zurück.
Parameter Beschreibung
Parameter Beschreibung
Create [CFrameWnd]
Erzeugt ein Objekt der CFrameWnd-Klasse.
BOOL Create(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle =
WS_OVERLAPPEDWINDOW,
const RECT& rect = rectDefault,
CWnd* pParentWnd = NULL,
LPCTSTR lpszMenuName = NULL,
DWORD dwExStyle = 0,
CCreateContext* pContext = NULL
);
Parameter Beschreibung
426
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
Create [CStatusBar]
Erzeugt ein neues Statuszeilenobjekt.
Parameter Beschreibung
427
CreateEllipticRgnIndirect [CRgn]
C
CreateEllipticRgnIndirect [CRgn]
Erzeugt eine elliptische Region, die durch eine übergebene Rechteckstruktur
verwendet wird. Durch weitere Methoden wie PtInRegion kann dann beispiels-
weise geprüft werden, ob sich ein Punkt innerhalb dieser Region befindet.
Parameter Beschreibung
CreateEx [CToolBar]
Erzeugt eine neue Werkzeugleiste.
BOOL CreateEx(
CWnd* pParentWnd,
DWORD dwCtrlStyle = TBSTYLE_FLAT,
DWORD dwStyle = WS_CHILD | WS_VISIBLE |
CBRS_ALIGN_TOP,
CRect rcBorders = CRect( 0, 0, 0, 0 ),
UINT nID = AFX_IDW_TOOLBAR
);
Parameter Beschreibung
rcBorders Gibt die Größe der Ränder der Werkzeugleiste an. (0,
0, 0, 0) erzeugt eine Toolbar ohne Ränder.
428
Übersicht der im Buch verwendeten MFC-Funktionen
C
CreateFontIndirect [CFont]
Initialisiert ein CFont-Objekt mit den durch eine LOGFONT-Struktur übergebe-
nen Charakteristika.
Parameter Beschreibung
CreateNewFrame [CDocTemplate]
Erzeugt ein neues Fenster, verknüpft mit einem anderen Fenster und einem
Dokument.
CFrameWnd* CreateNewFrame(
CDocument* pDoc,
CFrameWnd* pOther
);
Parameter Beschreibung
Rückgabewert Zeiger auf das neu erzeugte Fenster, oder NULL bei
Auftreten eines Fehlers.
DPtoLP
Rechnet Gerätekoordinaten in logische Koordinaten um.
void DPtoLP(
LPPOINT lpPoints,
int nCount = 1 ) const;
void DPtoLP(
LPRECT lpRect ) const;
void DPtoLP(
LPSIZE lpSize ) const;
429
DrawText [CDC]
C
Parameter Beschreibung
DrawText [CDC]
Druckt einen Text im Gerätekontext aus.
Parameter Beschreibung
nFormat Gibt die Art an, in welcher der Text formatiert werden
soll. Siehe unten.
Flag Beschreibung
430
Übersicht der im Buch verwendeten MFC-Funktionen
C
Flag Beschreibung
Delete [CRecordset]
Löscht einen Datensatz aus einem Recordset
DockControlBar [CFrameWnd]
Dockt eine Toolbar an einer Fensterseite an.
void DockControlBar(
CControlBar* pBar,
UINT nDockBarID = 0,
LPCRECT lpRect = NULL
);
Parameter Beschreibung
431
Ellipse [CDC]
C
Parameter Beschreibung
Ellipse [CDC]
Zeichnet eine Ellipse im Gerätekontext.
Parameter Beschreibung
EnableDocking [CFrameWnd]
Aktiviert andockbare Toolbars innerhalb eines Hauptfensters.
432
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
GetClientRect [CWnd]
Kopiert die Rechteckstruktur der Client Area des Fensters (insbesondere somit
auch dessen Größe) in ein als Parameter übergebenes Rechteck.
Parameter Beschreibung
GetRValue [Makro]
Liest den Rotanteil aus einer COLORREF-Struktur aus.
GetRValue(COLORREF Color);
Parameter Beschreibung
GetGValue [Makro]
Liest den Grünanteil aus einer COLORREF-Struktur aus.
GetGValue(COLORREF Color);
Parameter Beschreibung
433
GetBValue [Makro]
C
Parameter Beschreibung
GetBValue [Makro]
Liest den Blauanteil aus einer COLORREF-Struktur aus.
GetBValue(COLORREF Color);
Parameter Beschreibung
GetTrueRect [CRectTracker]
Gibt die tatsächliche Auswahl eines CRectTracker-Objekts zurück.
Parameter Beschreibung
InsertColumn [CListCtrl]
Fügt eine Spalte in eine Liste ein.
int InsertColumn(
int nCol,
LPCTSTR lpszColumnHeading,
int nFormat = LVCFMT_LEFT,
int nWidth = -1,
int nSubItem = -1
);
Parameter Beschreibung
434
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
InvalidateRect [CWnd]
Erklärt einen rechteckigen Bereich in der Client Area für ungültig und sorgt
somit für ein Neuzeichnen desselbigen.
void InvalidateRect(
LPCRECT lpRect,
BOOL bErase = TRUE );
Parameter Beschreibung
LineTo [CDC]
Zeichnet eine Linie von der aktuellen Stiftposition zu den angegebenen Koordi-
naten unter Verwendung des aktuell eingestellten Stifts.
Parameter Beschreibung
435
LPtoDP [CDC]
C
Parameter Beschreibung
LPtoDP [CDC]
Wandelt logische Einheiten in Geräteeinheiten um. Es existieren drei Prototy-
pen für diese Methode:
void LPtoDP(
LPPOINT lpPoints,
int nCount = 1 ) const;
Parameter Beschreibung
MessageBox [CWnd]
Öffnet eine Messagebox, die ein Icon und einen Text enthalten kann.
Parameter Beschreibung
436
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
nType Gibt den Typ der Box an, was sich in erster Linie auf
das verwendete Icon bezieht (siehe nachfolgende
Tabelle).
Parameter Beschreibung
MoveFirst [CRecordset]
Setzt den ersten Eintrag eines Datensatzsets als aktuellen Datensatz.
void MoveFirst( );
MoveLast [CRecordset]
Setzt den letzten Eintrag eines Datensatzsets als aktuellen Datensatz.
void MoveLast( );
MoveNext [CRecordset]
Bewegt sich im Datensatzset um einen Satz vorwärts.
void MoveNext( );
MovePrev [CRecordset]
Bewegt sich im Datensatzset um einen Satz zurück.
437
MoveTo [CDC]
C
void MovePrev( );
MoveTo [CDC]
Setzt die aktuelle Position des Stifts auf die übergebenen Parameterangaben.
Parameter Beschreibung
Parameter Beschreibung
Parameter Beschreibung
438
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
Parameter Beschreibung
439
OnMouseMove [CWnd, Behandlungsmethode]
C
Parameter Beschreibung
Parameter Beschreibung
Parameter Beschreibung
440
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
Parameter Beschreibung
441
OnRButtonUp [CWnd, Behandlungsmethode]
C
Parameter Beschreibung
Parameter Beschreibung
OnInitDialog [CDialog]
Enthält Initialisierungen für ein Dialogfeld.
Parameter Beschreibung
442
Übersicht der im Buch verwendeten MFC-Funktionen
C
OnPrint [CView]
Wird vom Framework aufgerufen, um eine Seite zu drucken oder eine Druckvor-
ansicht zu erzeugen.
void OnPrint(
CDC* pDC,
CPrintInfo* pInfo
);
PolyBezier [CDC]
Zeichnet eine oder mehrere Bezierkurven im Gerätekontext.
BOOL PolyBezier(
const POINT* lpPoints,
int nCount
);
Parameter Beschreibung
PreCreateWindow [CWnd]
Wird vom Anwendungsgerüst vor der tatsächlichen Erzeugung eines Fensters
aufgerufen.
Parameter Beschreibung
443
PtInRegion [CRgn]
C
Parameter Beschreibung
PtInRegion [CRgn]
Prüft, ob sich ein Punkt innerhalb der Region befindet.
Parameter Beschreibung
Rectangle [CDC]
Zeichnet ein Rechteck in das Gerätekontext-Objekt.
BOOL Rectangle(
int x1,
int y1,
int x2,
int y2
);
Parameter Beschreibung
444
Übersicht der im Buch verwendeten MFC-Funktionen
C
ReleaseCapture
Gibt die Maus wieder frei, nachdem sie mit SetCapture fixiert wurde.
BOOL ReleaseCapture ( );
Parameter Beschreibung
SelectObject [CDC]
Wählt ein Objekt (Stift, Pinsel, Bitmap, Region oder Zeichensatz) in den Geräte-
kontext. Zu diesem Zweck gibt es eine Reihe von verschiedenen Funktionspro-
totypen. Beachten Sie, dass am Ende von Zeichenoperationen die ursprünglich
eingestellten Objekte wieder eingerichtet werden sollten.
Parameter Beschreibung
SelectStockObject [CDC]
Wählt ein Standardobjekt, das nicht erst erzeugt werden muss, in den Geräte-
kontext.
445
Serialize [CObject]
C
Parameter Beschreibung
NULL_BRUSH NULL-Pinsel
NULL_PEN NULL-Stift
Serialize [CObject]
Methode zur Serialisierung (Laden/Speichern) von Dokumenten und Daten.
Parameter Beschreibung
446
Übersicht der im Buch verwendeten MFC-Funktionen
C
SetCapture [CWnd]
Fixiert die Maus auf ein Fenster und stellt sicher, dass auch Mausnachrichten
an das Fenster geleitet werden, wenn sich der Mauszeiger außerhalb des Fens-
ters befindet.
CWnd* SetCapture( );
Parameter Beschreibung
SetColumnWidth [CListCtrl]
Setzt die Breite einer Spalte einer Liste in Pixeln.
BOOL SetColumnWidth(
int nCol,
int cx
);
Parameter Beschreibung
SetCursor
Verändert den Mauszeiger und setzt ihn auf einen übergebenen Cursor.
Parameter Beschreibung
447
SetMapMode [CDC]
C
SetMapMode [CDC]
Setzt den Abbildungsmodus.
Parameter Beschreibung
SetPaneText [CStatusBar]
Setzt den Inhalt eines Elements in der Statuszeile auf einen neuen Wert.
BOOL SetPaneText(
int nIndex,
LPCTSTR lpszNewText,
BOOL bUpdate = TRUE
);
Parameter Beschreibung
SetPixel [CDC]
Setzt einen Bildpunkt an die angegebene Stelle und in der angegebenen Farbe
in den Gerätekontext.
COLORREF SetPixel(
int x,
int y,
COLORREF crColor );
Parameter Beschreibung
448
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
SetScrollSizes [CScrollView]
Aktualisiert die Größe der Bildlaufleisten. Wird verwendet, wenn sich die Größe
des Fensters oder der Umfang des Dokuments verändert hat.
void SetScrollSizes(
int nMapMode,
SIZE sizeTotal,
const SIZE& sizePage = sizeDefault,
const SIZE& sizeLine = sizeDefault );
Parameter Beschreibung
SetTextColor [CDC]
Setzt die Farbe für auszugebenden Text.
Parameter Beschreibung
449
SetTimer [CWnd]
C
SetTimer [CWnd]
Richtet einen Timer ein.
Parameter Beschreibung
SetViewportExt [CDC]
Setzt Breite und Höhe des Viewports des Gerätekontexts.
Parameter Beschreibung
SetViewportOrg [CDC]
Setzt den Ursprung des Viewports.
450
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
SetWindowExt [CDC]
Setzt Breite und Höhe des Fensters in logischen Einheiten.
CSize SetWindowExt(
int cx,
int cy );
Parameter Beschreibung
SetWindowOrg [CDC]
Setzt den Fensterursprung.
Parameter Beschreibung
ShowWindow [CWnd]
Verändert die Anzeigeart eines Fensters.
451
TrackRubberBand [CRectTracker]
C
Parameter Beschreibung
nCmdShow Gibt an, wie das Fenster dargestellt werden soll. (Für
eine komplette Übersicht der möglichen Werte
schauen Sie in Anhang A.)
TrackRubberBand [CRectTracker]
Erlaubt dem Benutzer eine Gummibandauswahl durchzuführen. Funktion
kehrt erst zurück, sobald der Benutzer die Aktion beendet (Loslassen der linken
Maustaste) oder abbricht (Drücken der E-Taste).
BOOL TrackRubberBand(
CWnd* pWnd,
CPoint point,
BOOL bAllowInvert = TRUE
);
Parameter Beschreibung
pWnd Zeiger auf das Fenster, in dem der Benutzer die Aus-
wahl durchführen kann
UpdateAllViews
Aktualisiert alle geöffneten Ansichten eines Dokuments.
void UpdateAllViews(
CView* pSender,
LPARAM lHint = 0L,
CObject* pHint = NULL
);
452
Übersicht der im Buch verwendeten MFC-Funktionen
C
Parameter Beschreibung
UpdateData [CWnd]
Aktualisiert Daten in Dialogfeldern oder zugehörigen Membervariablen.
Parameter Beschreibung
453
Index
Index
CMultiDocTemplate 291
! B CObject 86, 332
COLORREF 140, 159, 223
#using 310 Basisklassenmethoden 117
Combobox 106
.NET Framework SDK 17 Baumstrukturanzeige 109
COMMAND 240
__box 321 Bedingte Abbruchpunkte 355
CommandToIndex 235, 426
__gc 312 BEGIN_MESSAGE_MAP 90
Common Control Manifest 39
_DEBUG 339 Beim Start 23
Compound Documents 32
_T 329 Benutzerdefinierte
Container 33
Parameter 305
Container/Full Server 33
Benutzerinteraktionen 276
Containerklassen 164
Benutzeroberfläche 37
Controls 104
A Bezierkurven 258, 267
CPageSetupDialog 222, 418, 421
Bibliothek 57
Abbildungsmodus 199, 200 CPaintDC 91
BITMAP 402
Abbruchpunkt 351 CPalette 400, 415
BN_CLICKED 158
Abstraction Layers 83 CPen 162, 283, 400, 411
Boundingbox 137
Abstraktionsschichten 83 CPoint 136, 388, 390
Breakpoint Bedingung 357
Active Accessibility 39 CPrintDialog 222, 418, 422
Breakpoints 351
Active Template Library 58 Create 88, 186, 385, 426, 427
Button 105
ActiveX Control Test CreateEllipticRgnIndirect 428
Container 16 CreateEx 186, 428
ActiveX Controls 39, 98 CreateFontIndirect 429
AddDocTemplate 294 CreateIndirect 119
C CreateNewFrame 296, 429
AddHead 169
AddTail 169 Callbackfunktion 75 CreateStruct 185
Afx 85 Caption 112 CreateWindow 76, 381
afx_msg 91 CArchive 249 CRect 165, 388, 390
afxDump 327, 337, 338 CArray 399 CRgn 400, 417
AfxGetApp 127, 234, 426 CBitmap 400 Crystal Reports 17
afxwin.h 90 CBrush 400, 402 CSize 388, 392
AfxWinMain 86, 92 CChildFrame 258, 291 CStatusBar 233, 235
Animation Control 109 CChildFrm 262 CString 237, 329, 388, 392
Animationssteuerelement 109 CClientDC 140, 279 CTextausgabe 66
Ansichtsklassen 179, 194, 288 CColorDialog 222, 418 CTime 388, 394
Anwendungsobjekt 86 CCreateContext 386 CTimeSpan 388, 395
Apfelmännchen 180 CDC 279 CView 193, 205, 243, 305
Application 315 CDialog 126, 134 CWinApp 86, 113
Application Framework 81 CDumpContext 338 CWinThread 86, 92
Applikationsklasse 114, 116 CFileDialog 222, 418, 419
Arbeitsbereich 40 CFindReplaceDialog 222, 418,
ASSERT 128, 327, 330, 335, 337 420
ASSERT_VALID 327, 332, 337 CFont 400, 405 D
AssertValid 183, 332 CFontDialog 222, 418, 421
Data Access Components 8
Assistenten 83 CFrameWnd 88
Datenaustausch 121
ATL 58 Checkbox 106
Datenbankunterstützung 35
ATOM 74 Child Windows 46
Datum/Zeitauswahl-
Attribut-Programmierung 15 Clientarea 118
Steuerelement 110
Aufbau von Nachrichten 51 CList 164, 165, 396
DDV 121, 153, 215
CListCtrl 298
DDX 121, 153, 215
CListView 289, 297
Debug 42
CMainFrame 182, 295
Debugger 324
CMap 397
CMDIChildWnd 263
456
Index
Debuggerinterne
Überprüfungen 340 F I
Debugging 325
Fenster Layout 22 Iconressource 102
DECLARE_DYNCREATE 183
Fensterhandles 76 ID_FILE_PRINT 193
DECLARE_MESSAGE_MAP 89
Fenster 44 ID_FILE_PRINT_DIRECT 193
Delegierte 314
Fensterverwaltung 44 ID_FILE_PRINT_PREVIEW 193
Delete 431
FILO 342 ID_HELP 115
Device Point to Logic Point 279
FILO-Stapel 342 ID_INDICATOR_CAPS 229
Dialog Data Exchange 121
Fortschrittsbalken 108 ID_INDICATOR_NUM 229
Dialog Data Validation 121
Full Server 33 ID_INDICATOR_SCRL 229
Dialogfeldbasierte
ID_SEPARATOR 229
Anwendungen 96
ID_STATIC 112
DispatchMessage 75
IDCANCEL 119
DLL 57
G IDD_ABOUTBOX 103
DockControlBar 431
IDOK 119
Document/View- GDI 140 IDR_MAINFRAME 102
Architecture 178 GDI-Objekte 162, 283, 400 IL 310
DoDataExchange 121, 127 Generic C++ Class 62 IMPLEMENT_DYNCREATE 185
Dokument/Ansicht Gerätekontext 139, 194 indicators Array 229
Architektur 31, 178 GetBValue 223, 434 InitDialog 128
Dokumentenklasse 186, 194, 216 GetClientRect 91, 203, 433 InitialUpdateFrame 296
Dokumentvorlagen-Strings 33 GetDlgCtrlID 254 InitInstance 87, 114, 116, 117, 122,
DoModal 119, 129 GetDlgItem 137, 176 291
Download Components 5 GetDocument 190, 302 Inline 63
DPtoLP 279, 429 GetGValue 223, 433 InsertColumn 298, 434
DrawText 91, 430 GetHeadPosition 167 InsertItem 300
Dump 183 GetMessage 75, 92 Installation 4
dynamic link library 57 GetNext 168 Instanz 74
Dynaset 36 GetRValue 223, 433 intermediate language 310
GetTrueRect 244, 434 InvalidateRect 245, 282, 287,
GetWindowRect 137 435
Graphic Device Interface 140 IP-Felder 110
E Groupbox 107 IsStoring 249
Gruppenfeld 107
Editbox 106
Gummibandfunktion 181
Editcontrol 106
Gummibands 237
Eigenschaften 111
Eingabefeld 106 K
Einzeldokumentschnittstelle 96
Kindfenster 46
Ellipse 266, 432
H Konsolen Applikation 57
Elternfenster 46
Kontextsensitive Hilfe 39
EnableDocking 432 Handles 76 Kontrollelement 46
END_MESSAGE_MAP 90 Hauptdialogklasse 120 Kontrollen 104
Enumeration 121 Hauptrahmenfenster 46 Kontrollkästchen 106
Ereignisse 48 Heap 344 Koordinatenursprung 200
Error Lookup Tool 16 Hilfe 22
Erweiterte Stile 383 Hilfe anzeigen 22
Event 48 Hotkeys 109
ExitInstance 92
EXTLOGPEN 412 L
Laufzeitstack 342
LineTo 155, 162, 435
Listbox 106
457
Index
458
Index
459
Index
Z
Zeichenkettenressource 101
Zwischensprache 310
460