148 4 46MB
German Pages 464 Year 1999
Managementwissen für Studium und Praxis Herausgegeben von
Professor Dr. Dietmar Dorn und Professor Dr. Rainer Fischbach Bisher erse Behrens · Kirspel, Grundlagen der Volkswirtschaftslehre Bichler • Dörr, Personalwirtschaft Einführung mit Beispielen aus SAP® R/3® HR® Bontrup, Volkswirtschaftslehre Bradtke, Mathematische Grundlagen für Ökonomen Bradtke, Statistische Grundlagen für Ökonomen Busse, Betriebliche Finanzwirtschaft, 4. Auflage Clausius, Betriebswirtschaftslehre I Dorn · Fischbach, Volkswirtschaftslehre II, 3. Auflage Fank, Informationsmanagement Fank · Schildhauer · Klotz, Informationsmanagement: Umfeld - Fallbeispiele Fiedler, Einführung in das Controlling Fischbach, Volkswirtschaftslehre I, 10. Auflage Frodi, Dienstleistungslogistik Haas, Marketing mit Excel, 2. Auflage Hardt, Kostenmanagement Heine · Herr, Volkswirtschaftslehre Hofmann, Globale Informationswirtschaft Hoppen, Vertriebsmanagement Koch, Marketing Koch, Marktforschung, 2. Auflage Koch, Gesundheitsökonomie: Kosten- und Leistungsrechnung Krech, Grundriß der strategischen Unternehmensplanung Kreis, Betriebswirtschaftslehre, Band I, 5. Auflage Kreis, Betriebswirtschaftslehre, Band II, 5. Auflage
nene Werke: Kreis, Betriebswirtschaftslehre, Band III, 5. Auflage Lebefromm, Controlling - Einführung mit Beispielen aus SAP® R/3®, 2. Auflage Lebefromm, Produktionsmanagement Einführung mit Beispielen aus SAP® R/3®, 4. Auflage Martens, Statistische Datenanalyse mit SPSS für Windows Mensch, Kosten-Controlling Olivier, Windows-C - Betriebswirtschaftliche Programmierung für Windows Piontek, Controlling Piontek, Beschaffungscontrolling, 2. Auflage Piontek, Global Sourcing Posluschny, Kostenrechnung für die Gastronomie Posluschny · von Schorlemer, Erfolgreiche Existenzgründungen in der Praxis Reiter · Matthäus, Marketing-Management mit EXCEL Rudolph, Tourismus-Betriebswirtschaftslehre Schaal, Geldtheorie und Geldpolitik, 4. Auflage Scharnbacher Kiefer, Kundenzufriedenheit, 2. Auflage Schuster, Kommunale Kosten- und Leistungsrechnung Stahl, Internationaler Einsatz von Führungskräften Steger, Kosten- und Leistungsrechnung, 2. Auflage Weindl · Woyke, Europäische Union, 4. Auflage
Windows-C Betriebswirtschaftliche Programmierung für Windows
Von
Prof. Dr. Gottfried Olivier
R. Oldenbourg Verlag München Wien
Die Deutsche Bibliothek - CIP-Einheitsaufnahme Olivier, Gottfried: Windows-C : betriebswirtschaftliche Programmierung für Windows / von Gottfried Olivier. - München ; Wien : Oldenbourg, 2000 (Managementwissen für Studium und Praxis) ISBN 3-486-24719-0
© 2000 Oldenbourg Wissenschaftsverlag GmbH Rosenheimer Straße 145, D-81671 München Telefon: (089) 45051-0, Internet: http://www.oldenbourg.de Das Werk einschließlich aller Abbildungen ist urheberrechtlich geschützt. Jede Verwertung außerhalb der Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Bearbeitung in elektronischen Systemen. Gedruckt auf säure- und chlorfreiem Papier Gesamtherstellung: Druckhaus „Thomas Müntzer" GmbH, Bad Langensalza ISBN 3-486-24719-0
Übersicht
V
Übersicht Das vorliegende Buch stellt die Basisideen der Windows-Programmierung dar und vermittelt Ihnen die sprachlichen Mittel, um betriebswirtschaftliche Anwendungen in C zu entwickeln. Es bezieht sich identisch auf Windows 95, Windows 98 und Windows NT. Nach dem Durcharbeiten werden Sie über einen bewährten und schon recht weit reichenden Methodenvorrat für die betriebswirtschaftliche Anwendungsprogrammierung verfügen. Eine Anzahl von Anwendungen, mit denen dieser Text abgeglichen wurde, haben die Leistungsfähigkeit der einbezogenen Sprachbausteine gezeigt. Sie können sich dann aber auch, wenn Sie den Wunsch haben, selbständig Mittel erarbeiten, die über fortgeschrittene oder spezielle Texte zugänglich sind. Obgleich der Text für die Ausbildung von Betriebswirten entwickelt worden ist, wird er in gleicher Weise für fachbezogene Entwickler in anderen beruflichen Gebieten, bei Mathematikern, Biologen oder Medizinern brauchbar und im Wesentlichen ausreichend sein. Sie lernen die Programmierung mit Dialogfenstern und Dialogelementen, die Plattenprogrammierung, die Druckerausgabe und die graphische Bildschirmprogrammierung. C wurde als Sprache gewählt, weil die Systemfunktionen tatsächlich in dieser Sprache geschrieben sind. Auch wenn Sie auf längere Sicht mit einer Klassenbibliothek programmieren möchten, sollten Sie lieber nicht versuchen, die CProgrammierung für Windows zu überspringen. Dieser Weg führt nach aller Erfahrung zu mindestens einem halben Jahr hauptberuflicher Einarbeitungszeit. Von C ausgehend brauchen Sie einen Bruchteil. Das vorliegende Buch lässt sich in wenigen Tagen lesen und in ein paar Wochen für den sicheren eigenen Gebrauch vollständig durcharbeiten. Es wird angenommen, dass Sie in ANSI-C programmieren können. Sie müssen aber keineswegs Berufsprogrammierer sein. Zum Beispiel ein Hochschulkurs über 4 oder 6 Semesterwochenstunden für Betriebswirte reicht aus. Das Buch hat in Skriptenform als Grundlage von Kursen gedient, die ich seit dem Wintersemester 1994/95, zunächst für Windows 3.1, für Studenten des Schwerpunktfaches Wirtschaftsinformatik am Fachbereich Wirtschaft der Fachhochschule Bielefeld gehalten habe. Die Windows-Programmierung wird bei uns in einem Kurs erarbeitet, der über ein Semester mit 2 bis 4 Wochenstunden läuft. Ein großer Teil des Codes betriebswirtschaftlicher Anwendungen wird in SQL geschrieben. SQL braucht in einem Windows-Kurs aber nicht vorausgesetzt zu werden und wird in diesem Buch auch nicht vorausgesetzt. Gottfried Olivier
VII
Inhaltsverzeichnis
Inhaltsverzeichnis 1 Einleitung 1.1 Anwendungsprogrammieren in C/C + + 1.2 Windows 95, Windows 98 und Windows NT 1.3 Ausführbare Programme 1.4 Der Prozessbegriff 1.5 Programm-und Datenumfang 1.6 Die C-Sprache in der Form für Windows 1.7 Über den Inhalt
Seite 1 1 6 7 8 9 10 12
2
Fensterorientierte Programmierung 2.1 Fenster auf dem Schirm 2.2 Datentypen und Notation in Windows-Programmen 2.2.1 Programmobjekte im Systemspeicher 2.2.2 Windows-Datentypen 2.2.3 Das Bilden von Bezeichnern 2.2.4 Ungarische Notation
16 17 23 23 25 29 30
3
Die Struktur dialogorientierter Windows-Programme 3.1 Übersicht 3.2 Das Programm Kuchen 3.2.1 Die Quelltextgliederung 3.3 Zusammenfassung 3.4 Einige Hinweise zum Arbeitsstart 3.4.1 Elemente der Anwendungs- und der Systemprogrammierung 3.4.2 Das Microsoft Developer Studio 3.4.3 Die Ressourcedatei 3.7 WinMain 3.8 Nachrichten 3.9 Typenumwandlungen 3.10 Fensterprozeduren 3.11 Die Hauptnachrichtenschleife
33 33 41 50 57 64
4
Die Fensterzustände für die Nachrichtenannahme 4.1 Der hierarchische Status 4.2 Die Z-Ordnung 4.3 Aktivität und Fokus 4.4 Die Zuweisung von Aktivität und Fokus 4.4.1 Der Mausklick
64 67 70 74 83 86 90 94 104 104 108 115 122 123
Vili
Inhaltsverzeichnis
4.4.2 4.4.3 4.4.4 4.4.5 4.4.6
Das Erzeugen eines sichtbaren Fensters Sichtbarmachen eines Fensters Aktivieren und Fokus-Zuweisen mit SetFocus Das Programm Kuchen_Kind Auswirkungen der Deaktivierung eines aktiven Fensters 4.4.7 Änderungen der Z-Ordnung bei Kindgeschwistern 4.4.8 Das Programm Kind Z Ordnung 4.4.9 Das Programm Z Order 4.4.10 Das Programm Fokus 4.4.11 Das Programm SetFocus 4.5 Unsichtbare und minimierte Fenster 4.5.1 Das Programm Visib 4.6 Gesperrte Fenster 4.6.1 Modale Fenster 4.6.2 Das Programm Enable 4.7 Der hierarchische Status (Fortsetzung) 4.8 Eingabenachrichten des Anwenders 4.8.1 Maus-Signale 4.8.2 Tastaturnachrichten
126 128 130 131 136 136 136 139 145 149 155 159 164 168 170 174 177 177 179
5
Weitere Mittel zur Arbeit mit Fenstern 5.1 Das Adressieren von Nachrichten an Fenster 5.2 Der Fenstertext
182 182 188
6
Fensterklassen 6.1 Private und vordefinierte Fensterklassen 6.2 Das Erzeugen privater Fensterklassen 6.3 Die Definition eines Programmicons
194 205 198 202
7
Individuelle Fenster 7.1 Die Fenster-erzeugenden Funktionen 7.2 Das Platzangebot des Bildschirms 7.3 Create Window(Ex) 7.3.1 Der Fensterstil 7.4 DestroyWindow 7.5 WMCLOSE 7.6 Das Herunterfahren des Systems
206 206 210 214 221 227 227 228
8
Das Erzeugen und Zerstören offener Dialoge 8.1 DIALOG(EX) 8.2 Die Dialogprozedur
232 233 244
Inhaltsverzeichnis
8.3 9
CreateDialog(Param)
Abgeschlossene Dialoge 9.1 DialogBox(Param) 9.2 Temporäre Nachrichtenschleifen
IX
248 250 250 256
10 V3-Dialogelemente 10.1 Das Programm Uboot 10.2 Die Fokusverwaltung in Dialogfenstern 10.2.1 Die Weitergabe des Fokus mit der Tastatur 10.2.2 Tastenkürzel 10.2.3 Die Weitergabe über Programm 10.2.4 Die Anfangszuweisung des Fokus 10.3 Die Wahl eines Font für einzelne Dialogelemente 10.4 Statische Fenster 10.4.1 Statische Textfenster 10.4.2 Iconbilder 10.4.3 Bitmapbilder 10.4.4 Statische Rahmen und Rechtecke 10.4.5 Das Stilbit SS_NOTIFY 10.5 Editfelder 10.5.1 Einzeilige Editfenster 10.5.2 Korrektheitsprüfungen für Eingaben 10.6 Button-Elemente 10.6.1 Schaltknöpfe 10.6.1.1 Das Schließen des Dialogfensters 10.6.2 Radioknöpfe 10.6.3 Wechselschalter 10.6.4 Bitmap- und Iconschalter 10.6.4 Gruppenrahmen 10.7 Listenfenster 10.8 Kombifenster 10.9 Funktionen für Eingabenachrichten
259 268 277 277 281 283 284 286 291 291 293 297 298 299 300 302 304 305 305 310 313 315 316 318 319 330 333
11 Farben und Flächenmuster 11.1 Das Programm FarbDlg 11.2 Der Clientbereich eines Hintergrundfensters ohne Dialogelemente 11.3 Der Clientbereich eines Dialogfensters 11.4 Das Färben von Dialogelementen 11.5 Funktionen und Makros für das Färben
335 341 346 347 348 354
X
Inhaltsverzeichnis
12 Menüs 21.1 Menüpunkte für Zustandsgrößen
356 364
13 Statusleisten 13.1 Mehrere Textfelder
366 371
14 Timer 14.1 Das Programm Weltzeit
379 381
15 Messageboxen
386
16 Die Plattenprogrammierung 16.1 Speicherbasierte Dateien
389 394
17 Die Druckerausgabe 17.1 Übersicht 17.2 Die Druckaufbereitung 17.2.1 Die Textausgabe 17.2.2 Das Zeichnen graphischer Figuren 17.2.3 Die Funktion SetDCFont 17.3 Die Verwaltung des Druckauftrages 17.3.1 Der Abortdialog 17.3.2 Die Druckverwaltungsfunktionen
399 399 405 406 412 420 422 422 428
18 Die graphische Bildschirmprogrammierung 18.1 Das Programm Springende Kreise
433 438
Anhang. Die Dateien myEnv.h und myEnv.c Literaturverzeichnis Stichwortverzeichnis
442 447 449
I Einleitung
1
Einleitung
1.1
Anwendungsprogrammieren in C/C + +
1
Da man Windows-Anwendungen in unterschiedlichen Sprachen schreiben kann, wird die Wahl der Sprache hier etwas diskutiert. Die primäre Sprache für die Windows-Programmierung ist C. Die Sprachelemente stellen sich der Anwendungsentwicklerin1 als ein Komplex von C-Funktionen, Nachrichten, C-Makros und Strukturdefinitionen (C-Bibliothek mit Headerdateien) dar. Windows bietet materiell eine objektorientierte Betriebssystemoberfläche mit graphischen Objekten. Die wichtigste Oberklasse von Objekten sind die Fenster. Das System nutzt die Möglichkeiten von C zur objektorientierten Programmierung, soweit sie nun einmal reichen. Zur Zeit der Entwicklung von Windows hatte sich der objektorientierte Sprachtyp noch nicht durchgesetzt. Das Guide to Programming für Windows 3.1 empfahl 1992 C gegenüber Assembler und Pascal, ohne C + + auch nur zu erwähnen. Durch die heute verfügbaren C + + - Klassenbibliotheken, der Verbreitung nach hauptsächlich die MFC von Microsoft und die OWL von Borland (jetzt Firma Inprise), werden die graphischen Windows-Objekte, zum Beispiel die Fenster, über Elementfunktionen zur Verfügung gestellt. Die Klassen enthalten intern schon zur unmittelbaren Anwendung entwickelte C-Funktionen. Die Funktionalität stimmt überein, die Quelltextformulierung ist unterschiedlich. Einige Klassen, die erst später überlegt worden sind, stehen funktional unter C nicht zur Verfügung. Zu ihnen rechnen die eigentümlichen "Docking Windows". Bei kleinen bis mittelgoßen Anwendungen ist C-Code kompakter und übersichtlicher gegliedert. Bei großen und arbeitsteilig zu entwickelnden Anwendungen ist C + + wegen der natürlichen Aufgliederung des Codes auf viele Funktionen bekanntlich vorzuziehen. Der vorliegende Text berücksicht noch keine Klassenbibliothek. Wenn Sie mit einer Klassenbibliothek programmieren wollen - eine oft an hauptberufliche Entwicklerinnen, die in einem Team arbeiten, gestellte Anforderung - , gliedert sich das Gesamtgebiet der Windows-Programmierung insofern für Sie lerntechnisch in zwei Abschnitte: 1. Erwerb von C-Kenntnissen. Windows-Programmierung mit C. 2. Erwerb von Kenntnissen zur C + + - Klassenprogrammierung. Einarbeitung in die Klassen unter Voraussetzung der wesentlichen Kenntnisse über 1) Gattungsbegriffe für Personen werden jeweils über längere Textabschnitte durchgängig in der weiblichen oder der männlichen Form gebildet. Den Anfang macht die weibliche Form.
2
/ Einleitung
die materiellen Inhalte nach einem zugehörigen Text. Die Auswahl der Klassenbibliothek wird in Anpassung an die Programmierpraxis des Teams vorgenommen. Nun einige Bemerkungen zu anderen Methoden der Windows-Programmierung: Die Datenbank-Hersteller bieten alternativ sogenannte 4GL-Werkzeuge für die Entwicklung an. Bei Oracle heißt das Produkt Developer 2000. Dies sind Generatoren für das Prototyping und für häufig vorkommende Programmstrukturen. Die Anforderung an die Entwicklerin, in Programmstrukturen (Schleifen und Fallunterscheidungen) denken zu können, ist bei 4GLWerkzeugen geringer als bei einer prozeduralen Sprache wie C/C + + . Dem steht freilich ein hoher Leseaufwand für intellektuell einförmige Details, zum Beispiel für die vielen Varianten von Triggern, gegenüber. Nach Einarbeitung garantieren die 4GL-Produkte für die überwiegende Mehrzahl der Dialoganwendungen eine Arbeitseffizienz, die auf andere Weise nicht erreichbar ist. Sie sind allerdings keine ganz universellen Werkzeuge. Sie verwenden intern Bausteine, die nicht immer ausreichen. Wenn die Programmlogik einer Anwendung von der fest eingebauten Logik des Generators nicht mehr voll abgedeckt wird, entstehen Probleme. Entwicklerinnen, die eine universelle Technik wie die C/C + + - Programmierung nicht beherrschen, versuchen dann, die Aufgabe durch Umgehen der fixierten Standardlogik zu lösen. Mal gelingt es, mal nicht, und jedenfalls geht ein Teil der bei den typisierten Aufgaben erwirtschafteten Arbeitsersparnis dabei wieder verloren. Die 4GL-Werkzeuge sind nicht standardisiert. Jedes von ihnen ist nur für die Datenbank verwendbar, für die es entwickelt ist. Anwendungen sind nicht übertragbar. Es gibt keine allgemeine Theorie zu 4GL-Produkten. Eine Auswirkung ist, dass die Werkzeuge stets durch neue Detailideen verbessert werden können und deshalb auch tatsächlich insgesamt von Version zu Version erheblichen Änderungen unterworfen werden. Den 4GL-Werkzeugen sind - kurz gesagt - sehr hohe Effizienz bei Standardaufgaben, praktisch oft nicht lösbare Probleme bei fortgeschrittenen Anwendungen, viele Detailvorschriften und kurzer Lebenszyklus gemeinsam. Makrosprachen-Produkte wie Visual Basic bilden eine andere Klasse von Werkzeugen zur Windows-Programmierung. Die Dokumentation ist hier anwendungsfreundlicher als für C/C + + abgefasst. Der auf die Erzeugung der Fenster und ihrer äußeren Eigenschaften (Randauswahl, Clienthintergrund) bezogene Teil der Programmierung kann in Visual Basic etwas schneller als in C/C + + ausgeführt werden. Das Entwicklungswerkzeug bietet sehr bedienungsfreundliche Auswahltabellen für diese Arbeit. Die darin abschließend aufgezählten Wahlmöglichkeiten ergänzen beziehungsweise ersetzen sehr ansprechend
I Einleitung
3
die Dokumentation. In C/C+ + gibt man teilweise Code an, dessen Einsatz man erst einmal gelernt haben muss. Die Definition dieser äußeren Fenstereigenschaften macht aber nur einen sehr kleinen Teil der gesamten Arbeit aus. Bei betriebswirtschaftlichen Anwendungen viel wesentlicher ist alleine schon die Frage des Zugriffs auf eine Datenbank. Da Visual Basic keine standardisierte und relativ langfristig unveränderliche Sprache ist, entwickeln die Datenbankhersteller dafür keine Precompiler. Die Entwicklerin muss unter Verlusten an Programmierkomfort und Programm-Performance auf eine andere Schnittstelle wie ODBC ausweichen. Im Übrigen muss die Programmlogik wie in C/C + + prozedural programmiert werden. Wenn ich vergleichend auf Zeitaufwand und Ergebnisse bei den betriebswirtschaftlichen Entwicklungsarbeiten von Studentinnen und Absolventinnen unseres Fachbereiches blicke, sehe ich in der Summe eine Überlegenheit von Visual Basic (oder Access mit seiner Makrosprache) gegenüber C / C + + weder bei der Einarbeitung noch bei der Anwendungsentwicklung. Visual Basic, Access und ähnliche Makrosprachenprodukte haben ihre Bedeutung bei einfacheren Arbeiten. Die Systementwicklerinnen bei Microsoft wollten die Anwendungsprogrammiererinnen nicht allein auf C/C + + einschränken. Für weniger anspruchsvolle Arbeitsbereiche haben sie alternative Werkzeuge bereitgestellt. Das Lernen der Windows-Programmierung in C gilt als schwer. Die Schwierigkeit von C selbst ist damit nicht gemeint. Für ANSI-C gibt es erstklassige Lehrbücher und Sprachbeschreibungen, die der Entwicklerin die vollständige Menge der Ausdrucksmöglichkeiten zur Verfügung stellen. Sie kann sich aus ihnen aussuchen, was ihre Aufgaben gerade erfordern. Die Einarbeitung in die Windows-Entwicklung wird von den Dokumentationsschriftstellerinnen bei Microsoft aber nur unzureichend unterstützt. Man kann nur zur Kenntnis nehmen, dass die erfolgreiche Firma für die unterschiedlichen Arbeiten mit ihrem bedeutenden Produkt nicht generell Einführungstexte vorlegt, die normalen hochschulmäßigen Anforderungen in der Lehre entsprechen. Freiberufliche Autoren, darunter Buchheit und Newcomer1, haben ihre umfangreichen Kenntnisse des Gebietes in Büchern weitergegeben. Ihr äußerst ehrgeiziger Ansatz, möglichst das ganze System zu erfassen, schließt aber ein, dass diese Texte über große Strecken nur fortgeschrittenen Lesern verständlich sind. Als die graphischen Oberflächen noch nicht standardmäßiger Komfort von Programmen waren, konnte eine interessierte Studentin eines beliebigen Fach-
1) - Literaturverzeichnis.
4
I Einleitung
gebietes ansprechend wirkende Anwendungen für ihren Arbeitsbereich auf der Basis eines C-Kurses oder eines C-Buches schreiben. Programmieren war etwas, das man neben vielem anderen machte und programmieren konnte jede, die es in ihrem Fach brauchte. Eine Mathematikerin, die Untersuchungen zur Kryptographie machte, brauchte keinen Programmierprofi für ihre Programme. Sie wäre nicht auf die Idee gekommen, dass für sie selbst nur eine so eingeschränkte Sprache wie Basic, die sowieso nicht ausreicht, in vernünftiger Reichweite war. Eine Biologin, die Simulationen zur Populationsgenetik rechnen wollte, konnte sie auch programmieren. Zur Ausbildung einer Physikerin gehörte ganz selbstverständlich, dass sie ihre Differentialgleichungen selbst numerisch lösen konnte. Einen anderen Standpunkt wird man wohl grundsätzlich auch gegenwärtig nicht einnehmen. Wenn es hier heute dennoch Probleme gibt, hat das keinen in der Sache selbst liegenden Grund. Bisher fehlen passende Hochschultexte. Für eine fachbezogene Anwendungsentwicklerin besteht das Lernen der Windows-Programmierung hauptsächlich darin, sich mit reichlich 100 C-Funktionen grundsätzlich vertraut zu machen. Sie sind in Gruppen untereinander verwandt, so dass diese vielleicht erschreckend hohe Zahl nicht allzu viel besagt. Auch in anspruchsvollen betriebswirtschaftlichen Anwendungen werden höchstens 50 oder 70 mehr oder weniger regelmäßig benutzt. Man braucht sie nicht einzeln auswendig zu lernen, weil sie überwiegend in Textbausteinen und nach dem Vorbild von Quelltextvorlagen eingesetzt werden. Hinzu kommt noch ein Katalog von Nachrichten. Syntaktische Einzelheiten kann man in einer praktisch verzögerungsfrei verfügbaren Online-Hilfe nachschlagen. Umgekehrt gesagt: Windows programmieren zu lernen heißt für die fachbezogene Entwicklerin nicht, die 1000 Funktionen, die das System hat, und diese noch in C und in mehreren Klassenbibliotheken syntaktisch unterschiedlich präsentiert, und die Hunderte von Nachrichten und Strukturen zu lernen. Um eine Fachanwendung schreiben zu lernen, muss man nicht sein Arbeitsgebiet aufgeben. Wie lange es dauern wird, bis die gegenwärtige Programmiertechnik für die graphische Anwendungsoberfläche überarbeitet werden muss, ist natürlich ungewiss. Man darf aber eine plausible Vermutung äußern. Die Grundprinzipien der Windows-Programmierung sind unterdessen ausgereift. Die Umstellung auf die 32-Bit-Programmierung ist überstanden. Neuere Fassungen des Systems werden aller Voraussicht nach dieselbe Oberflächenprogrammierung wie Windows 95/98 und Windows NT verlangen. Zur Vorbereitung und Absicherung des Inhalts dieses Buches haben Studenten/Studentinnen und Mitarbeiter/Mitarbeiterinnen des Fachbereiches Wirtschaft der Fachhochschule Bielefeld beigetragen. Mein besonderer Dank gilt den Herren Dipl.-Bw. Olaf Langer und Dipl.-Bw. Frank Strunk. Herr Langer
1 Einleitung
5
hat wesentlich an der Erarbeitung der ursprünglichen Fassung der Lehrveranstaltung für Windows 3.1 mitgewirkt. Er hat unter anderem auch die erste Fassung einer Musteranwendung geschrieben, die den notwendigen Methodenumfang für eine vollständige Datenbankanwendung demonstrierte. Bei der Umstellung des Stoffes auf das 32-Bit-Windows hatte ich wertvolle Unterstützung durch Herrn Strunk. Er hat - neben vielen anderen Arbeiten - die virtuellen Speicherdateien praktisch erprobt. Mehrere Studenten/Studentinnen des Schwerpunktfaches Wirtschaftsinformatik (vor allem Herr Dipl-Bw. Carsten Schwegmann, Herr Dipl.-Bw. Ralf Hoffmann, Herr Strunk, Frau Dipl.Bw. Karin Menne) haben durch Anwendungsentwicklungen für Auftraggeber außerhalb der Fachhochschule Beiträge zu dem notwendigen Erfahrungsvorrat geleistet. Durch diese Arbeiten der Studenten des Schwerpunktfaches hat sich ein abgerundetes Bild über den Einarbeitungs- und Entwicklungsaufwand und über die notwendigen Programmelemente bei betriebswirtschaftlichen Anwendungen und speziell bei Datenbankanwendungen gebildet. Frau Dipl.-Inf. H. Schönenberg vom Rechenzentrum des Fachbereiches hat zu den Untersuchungen der "common controls" beigetragen. Herr Dipl.-Bw. Hans-Gerd Kowitzke, ebenfalls Rechenzentrum des Fachbereiches, hat jederzeit Installation und Anpassungen von Hardware, Betriebssystemen, Netzverbindungen und Datenbanken sichergestellt. Ihnen allen habe ich zu danken.
6
/ Einleitung
1.2
Windows 95, Windows 98 und Windows NT
Der vorliegende Text bezieht sich in gleicher Weise auf Windows 95, Windows 98 und Windows NT ab Version 4. (Windows NT Version 3.5 hatte noch die graphische Oberfläche von Windows 3.1.) Windows NT, Windows 95 und Windows 98 sind Geschwisterbetriebssysteme. Aus der Sicht der Anwenderin sind Windows 95 und 98 vereinfachte Fassungen von Windows NT mit reduziertem Leistungsumfang. Es gibt vor allem die folgenden Unterschiede: -
-
Windows 95 und 98 sind schon für Multitasking ausgelegt, kennen aber noch keinen Multiuserbetrieb. Die Sicherheitsvorkehrungen, mit denen die laufenden Anwendungen gegen einander und das System gegen die Anwendungen geschützt sind, sind reduziert. Unter Windows 95 und 98 kann eine Anwendung eher einmal abstürzen, weil eine andere fehlerhaft läuft. Windows NT verlangt etwas mehr Maschinenlaufzeit.
Intern sind Windows 95 und 98 aber nicht um einige Moduln gekürzte und um andere erweiterte Fassungen von Windows NT, sondern weitgehend im Detail anders aufgebaut. Die Autoren, die die Systeme beschreiben, betonen übereinstimmend die internen Unterschiede zwischen Windows 95/98 und Windows NT. Sie sehen in Windows 95 teilweise eher eine Weiterentwicklung von Windows 3.11. Windows 95 und Windows 98 enthalten, wie generell in Publikationen über die Systeme mitgeteilt oder angenommen wird, auch noch 16Bit-Bausteine. Als C-Entwicklerin brauchen Sie sich um diesen Punkt nicht weiter zu kümmern. Da interessieren Sie sich für die programmiertechnische Schnittstelle zu den Systemen. Wie die Systembibliotheken intern aufgebaut sind, spielt ebensowenig wie bei anderen Betriebssystemen eine Rolle. Aus der Sicht der Anwendungsprogrammierung stimmen die meisten Funktionen in Windows NT und Windows 95 beziehungsweise 98 vollständig überein. In der Dokumentation wird für jede Funktion mitgeteilt, in welchem System sie verfügbar ist. Die in diesem Buch verwendeten Funktionen, Nachrichten usw. zur betriebswirtschaftlichen Programmierung betreffen fast alle unterschiedslos Windows NT und Windows 95/98. Zwei oder drei Ausnahmen sind als solche bezeichnet. Für Windows 95/98 erzeugte Programme laufen insoweit auch unter Windows NT und umgekehrt. Bei Eigenschaften, die alle Windows-Systeme betreffen, sprechen wir übergreifend von Windows, Win32 oder 32-Bit-Windows.
1 Einleitung
1.3
7
Ausführbare Programme
Man kann für das 32-Bit-Windows 2 Arten von Programmen, nämlich -
zeichenorientierte Programme, fensterorientierte Programme
schreiben. Zeichenorientierte Programme haben keine graphische Oberfläche. Sie können aus dem Quellcode heraus keine Fenster eröffnen. Sie dürfen die ANSIC-Funktionen und zusätzliche Funktionen des Betriebssystems ansprechen, die sich nicht auf die graphische Oberfläche beziehen. Die Schirmausgabe wird wie bei ASCII-Terminals mit printf, puts usw., die Tastatureingabe mit gets, scanf usw. programmiert. Zeichenorientierte Programme werden in der Microsoft-Literatur oft als Konsole-orientiert bezeichnet. In fensterorientierten Programmen stehen dagegen die Windows-Funktionen für die graphische Oberfläche zur Verfügung. Auf der Arbeitsoberfläche des Schirms kann man graphische Fenster für die zeichenorientierte Kommunikation mit dem System eröffnen. Sie werden MSDOS-Fenster genannt. Ein MS-DOS-Fenster entspricht funktional in jeder Beziehung einem vollständigen ASCII-Schirm. Man spricht darin ähnlich wie auf der Systemebene im Unix den Kommando-Interpreter des Betriebssystems an. Die verfügbare Kommandosprache heißt MS-DOS. Man startet ein Konsoleorientiertes Programm darin durch Angabe des Dateinamens. In dem Fenster erscheinen die mit printf oder puts veranlassten Anzeigen beziehungsweise die Tastatureingaben. Ein MS-DOS-Fenster kann statt als normales graphisches Windows-Fenster auch schirmfüllend im ASCII-Modus eröffnet werden. Dann sieht man nur nicht mehr gleichzeitig die Fenster anderer Programme. Die Kommandosprache stimmt hinsichtlich der Bezeichnung der Instruktionen und im Ausdrucksumfang weitgehend mit dem DOS der 16-Bit-Programmierung überein und hat nur einige Erweiterungen. Zum Beispiel unterstützt sie auf FATPartitionen lange Dateinamen. In einigen Texten wird sie als DOS 7.0 referenziert. Unter einer Shell versteht man eine Schnittstelle, über die Anwender innen ein Betriebssystem ansprechen können. Insofern hat Windows 2 Shells: -
-
die graphischen Arbeitsmittel auf der Arbeitsoberfläche, in der die unterschiedlichsten Arbeiten in variantenreicher Weise graphikorientiert, zum Beispiel durch Mausklick, ausgeführt werden können. Hier können beispielsweise graphikorientiert Verknüpfungen erzeugt werden. den Kommando-Interpreter in den MS-DOS-Fenstern.
8
I Einleitung
Die beiden Schnittstellen arbeiten in enger Verflechtung. Sie sprechen im Multitasking-Betrieb gleichzeitig das Betriebssystem an. Man kann ein fensterorientiertes Programm durch eine Kommandozeile aus einem MS-DOS-Fenster starten. Die von dem Programm erzeugten Fenster überlagern dann das MSDOS-Fenster. Wenn das MS-DOS-Fenster schirmfiillend ist, verschwindet es und macht der graphischen Arbeitsfläche Platz. Umgekehrt kann man ein Konsole-orientiertes Programm aus der graphischen Oberfläche, etwa aus dem Explorer, starten. Das System eröffnet dann dafür ein zeichenorientiertes Fenster. Wenn man ein C-Programm mit der Funktion main erzeugt, wie es das ANSI-C vorsieht, ist es automatisch zeichenorientiert. Es kann dann keine Fenster bilden. Windows erwartet von fensterorientierten Programmen, dass sie die Startfunktion WinMain statt main haben. Ein Programm mit WinMain wird automatisch fensterorientiert erzeugt. Rein syntaktisch muss es keine Fenster bilden, kann das aber. Jedes ausführbare Programm hat eine Kennung, die das System beim Start abfragt und die angibt, von welchem Typ es ist. Die 32-Bit-Windows-Systeme können 16-Bit-Programme (DOS oder Windows 3.x) ausführen. 16-Bit-DOS-Programme und 16-Bit-WindowsProgramme werden bei ihrem Start als solche erkannt und mit passenden Bibliotheken ausgeführt. Wenn man in Windows 95 den Startknopf drückt und "Beenden" auswählt, bietet das System an, den Computer im MS-DOS-Modus zu starten. Hierbei handelt es sich nicht nur um das Löschen der graphischen Oberfläche. Bestätigt man die Option, wird das gesamte 32-Bit-System aus dem Speicher entfernt und ein 16-Bit-DOS geladen. Das 32-Bit-Windows ist nicht mehr verfügbar. Fensterorientierte und zeichenorientierte 32-Bit-Programme sind nicht mehr ausführbar. Wenn Ihnen also der Ausdruck MS-DOS in einer Beschreibung oder Bedienerführung begegnet, müssen Sie überlegen, ob nun von dem 32-Bit-System oder dem 16-Bit-System die Rede ist.
1.4
Der Prozessbegriff
Ein Programmlauf heißt in Windows ein Prozess. Ein Prozess hat dementsprechend unter anderem einen Stack und einen Heap und bekommt Rechenzeit zugeteilt. Eine Anwendung kann unter Windows grundsätzlich mehrmals gleichzeitig gestartet werden. (Sie kann allerdings durch passende Programmierung ausschließen, dass sie mehrfach gestartet wird.) Dann bildet die einzelne Instanz, der einzelne Programmlauf, immer einen eigenen Prozess und ist in jeder
/ Einleitung
9
Beziehung selbständig. Jeder Prozess einer fensterorientierten Anwendung hat dann zum Beispiel auch seine eigenen Fenster. Beziehungen zwischen mehreren gleichzeitig laufenden Prozessen einer Anwendung gibt es nicht, es sei denn, die besonderen Möglichkeiten der Inter-Prozess-Kommunikation in Windows würden genutzt. Zwei Prozesse einer Anwendung sind genauso voneinander getrennt wie zwei Prozesse unterschiedlicher Anwendungen.
1.5
Programm- und Datenumfang
Das Betriebssystem arbeitet mit virtuellem Speicher. Das heißt bei Windows: Es teilt den logischen Speicherplatz, den ein Prozess in Anspruch nimmt, in Stücke von 2 KB Größe, sogenannte Pages. Eine Tabellenverwaltung ordnet den Pages die physischen Adressen und damit die Aufenthaltsorte im Hauptspeicher zu. Sie ändern sich im Allgemeinen während der Programmlaufzeit. Wenn die gestarteten Prozesse mit ihren Daten die Hauptspeichergröße überfordern, werden Pages für gerade nicht benutzte Teile von Code und Daten in eine Auslagerungsdatei auf der Platte übertragen. Sobald sie in einem Prozess angesprochen werden, werden sie zurückgeholt. Die in einem C-Programm vorkommenden Hauptspeicheradressen, die man zum Beispiel mit dem Adress-Operator & anspricht oder die von ma 11 oc zurückgegeben werden, sind "logische" Adressen. In welchen Pages sich die logischen Adressen befinden, braucht einen nicht zu interessieren. Die logischen Adressen sind natürlich während der Laufzeit eines Programms unveränderlich, sonst würden sie einem nichts nützen. Eine logische Hauptspeicheradresse hat eine Darstellungsbreite von 4 Bytes. Die Anzahl möglicher Adressen entspricht der Anzahl möglicher Werte von unsigned int. Jeder damit darstellbare Wert, das heißt jede Zahl im Bereich von 0 bis 232 - 1, kann eine Adresse sein. In jeden Prozess ist das Betriebssystem eingebunden. Code und Daten des Betriebssystems, zum Beispiel auch sämtliche Systemfunktionen, die man aus einem Programm heraus aufrufen kann, haben während der Lebensdauer eines Prozesses darin logische Adressen. Mit dieser Technik wird erreicht, dass das Betriebssystem und der Prozess gleichzeitig aktiv sein können und dass dem Prozess die Funktionen des Systems zum Aufruf zur Verfügung stehen. Schutzmechanismen sorgen dafür, dass ein Programm Code und Daten des Betriebssystems nur in der richtigen Weise ansprechen und zum Beispiel nicht überschreiben kann. Das Betriebssystem belegt die Hälfte des Adressvorrates. Für die Anwendung, Code und Daten, steht ein Adressbereich von 231 Bytes (2 GigaBytes = ca. 2000 MBytes ξ Ca. 2.000.000.000 Bytes) zur Verfügung. Für den
10
/ Einleitung
Code allein gibt es somit keine praktisch wirksame Speicherbegrenzung. Mit sehr großem Datenumfang kann man aber auch diesen Adressvorrat überfordern und dann reicht als Rückwirkung der Platz auch nicht für die Gesamtheit von Code und Daten.
1.6
Die C-Sprache in der Form für Windows
Die Programmierung verwendet die Syntax von ANSI-C. Es gibt nur wenige Abweichungen: die Startfunktion einer fensterorientierten Anwendung heißt nicht main, sondern WinMain und hat einen etwas veränderten Prototypen. Außerdem gibt es einige wenige Microsoft-Erweiterungen um Schlüsselworte wie CALLBACK. Sie sind, soweit sie in diesem Buch verwendet werden, im jeweiligen Zusammenhang als Syntaxvorschriften eingeführt. Das Aussehen von Windows-Programmen wird freilich durch eine Anzahl von Datentyp-Definitionen wesentlich beeinflusst. Schon Kernighan und Ritchie weisen ja in ihrem Einfuhrungstext zu C daraufhin, dass man das Erscheinungsbild von Quelltext auf dem Wege über Textersatz stark abwandeln kann. Es gibt umfangreiche Windows-Headerdateien. In ihnen werden zusätzliche Bezeichner für Datentypen definiert, zum Beispiel UINT für unsigned int. Es ist üblich, diese Bezeichner weitgehend auch tatsächlich zu verwenden. Die Windows-Headerdateien und Windows-Systembibliotheken, deren Inhalte man im Quellcode ansprechen kann, bieten außerdem eine Vielzahl von Funktionen, Funktions-Makros und vordefinierten Strukturen, die es im ANSI-C nicht gibt. Diese Elemente erweitern nicht syntaktisch, aber im Effekt den Sprachumfang von C. Man kann sie in 2 Bereiche aufgliedern: -
-
Elemente der graphischen Oberfläche. Zu einem großen Teil betreffen die Erweiterungen die graphische Programmoberfläche. Sie dienen vor allem dem Umgang mit Fenstern und mit Objekten, die beim Zeichnen von Fenstern Bedeutung haben, zum Beispiel mit Füllgraphiken für den Fensterhintergrund. Elemente zu anderen Programmbereichen. Das System stellt Zusätze zum ANSI-C bereit, die von der verwendeten Shell unabhängig sind und auch in zeichenorientierten Anwendungen genutzt werden können. Hier sind unter anderem die folgenden Arbeitsgebiete anzusprechen: -
Windows hat eigene Funktionen zur Verwaltung von Plattendateien. Sie bieten Dienste, die das Multitasking-Verhalten von Windows berücksichtigen. Sie ermöglichen auch, Plattendateien in den Haupt-
/ Einleitung
-
-
11
Speicher abzubilden und virtuell wie Hauptspeicher-Arrays anzusprechen. Windows stellt zusätzliche Funktionen für die Arbeit im Hauptspeicher zur Verfügung. So kann eine Anwendung wahlweise mehrere Heaps - nicht nur einen wie im ANSI-C - unterhalten. Eine Anwendung kann mehrere zeitlich quasiparallel ablaufende Rechenvorgänge (Threads) bilden, denen private Bereiche aus dem Hauptspeicher des Prozesses gehören. Programme können mit Prioritätskennungen versehen werden, die bestimmen, wieviel Rechenzeit ihnen im Multitasking-Betrieb zugeteilt werden soll.
Die aufgezählten Möglichkeiten bieten Zusätze gegenüber dem ANSI-C. Sie schließen den Gebrauch von ANSI-C-Funktionen nicht aus. Zum Beispiel gibt es neben den Windows-Funktionen für die Arbeit mit Plattendateien unverändert die ANSI-C-Funktionen. Sie sind aber nur eingeschränkt verwendbar, weil sie das Multitasking nicht beachten. Eine Datei kann gleichzeitig von mehreren Anwendungen mit f o p e n eröffnet werden, ohne dass der simultane Zugriff automatisch überwacht wird. Wenn das nicht schadet, kann man mit den ANSIC-Funktionen arbeiten. Einige ANSI-C-Funktionen sind im Kontext einer graphischen Anwendungsoberfläche nicht brauchbar. Dabei handelt es sich um die Funktionen für die Bildschirmausgabe und die Tastatureingabe, zum Beispiel p r i n t f und s c a n f . Sie sind nur in MS-DOS-Fenstern, nicht in den Fenstern von fensterorientierten Programmen ausführbar. Sie bewirken dort nichts, so dass der Aufruf dieser Funktionen dort keinen Sinn ergibt. Im richtigen Zusammenhang dürfen aber stets die gewohnten ANSI-C-Funktionen aufgerufen werden. Die Gesamtheit der Spracherweiterungen gegenüber dem ANSI-C wird als das Application Programming Interface - API - bezeichnet. Wenn man so will, kann man die insgesamt resultierende Form der C-Sprache, in der man Windows-Anwendungen entwickelt, als Windows-C bezeichnen. Diese Sprachvariante ergibt sich dann nach der Formel: ANSI-C + API = Windows-C Wie gesagt ist Windows-C - abgesehen von den ganz wenigen Abweichungen wie dem Schlüsselwort CALLBACK - standardmäßiges C. Die vielen Ergänzungen durch Bezeichner und Funktionen nutzen nur die im ANSI-C vorgesehenen Mittel der Sprachgestaltung durch Headerdateien und Bibliotheken.
12
I Einleitung
Genau genommen ist C nicht die einzige im Quellcode einer Anwendung vorkommende Programmiersprache. Für bestimmte Arbeiten (unter anderem die geometrische Konstruktion der Fensterflächen mit ihren Elementen) schreibt man ein "Ressourcen-Skript" in einer eigens dafür entwickelten einfachen Syntax. Das Skript wird mit einem eigenen Compiler, dem Ressourcen-Compiler, übersetzt und zusammen mit den anderen Programm-Moduln vom Binder in die ausführbare Datei eingebunden. Programmiert man eine Anwendung für eine relationale Datenbank, kommt Embedded SQL als weitere Sprache hinzu. Das Einfügen von SQL-Anweisungen in ein Windows-Programm wird wie bei ANSI-C-Programmen ausgeführt und verursacht weder syntaktisch noch beim Precompilieren zusätzliche Probleme oder Überlegungen.
1.7
Über den Inhalt
Die Microsoft-Dokumentation gliedert sich in eine Anzahl von "Büchern". Sie stehen online in bedienerfreundlicher Form zur Verfügung. Durch Migration zwischen Querverweiszielen über Mausklick können Sie oft rasch prüfen, welche Stichworte in einem thematischen Bereich insgesamt bedeutungsvoll sind. In keinem gedruckten Text können Sie derartig schnell nachschlagen oder blättern. Sie können ganze Bücher oder Informationen zu einzelnen Stichworten auch ausdrucken. Soweit sich die Dokumentation auf das API bezieht, heißt ein großer Teil von ihr das Software Development Kit, kurz genannt SDK. Auch die Pluralform wird benutzt, so dass von einzelnen Büchern als SDKs die Rede ist. Das SDK betrifft überwiegend die Oberflächenprogrammierung und damit die fensterorientierten Programme. Einige Abschnitte behandeln aber auch Funktionen, die nicht mit der graphischen Oberfläche zusammenhängen und die auch in Konsoleorientierten Programmen zur Verfügung stehen. Im Literaturverzeichnis sind die wichtigsten Suchwege der SDK-Dokumentation angegeben. Das SDK dient dem Nachschlagen. Zur Einführung in die Windows-Programmierung ist es nicht geeignet. Das vorliegende Buch wird Sie soweit orientieren, dass Sie bei Hinzunahme des SDK im Allgemeinen vollständige Anwendungen schreiben können. Es ist programmiertechnisch orientiert. Es geht daneben auch auf stilistische Fragen des Schirmbilddesigns, auf Konventionen für den Einsatz der Tastaturanschläge
1 Einleitung
1 3
und die Deutung der Maustasten ein. Lesen Sie dazu bitte aber auch die nur auf das Design ausgerichteten Windows Interface Guidelines1. Das Buch besteht aus Abschnitten mit programmiertechnischen Mitteilungen, die Technoten genannt werden. In ihnen werden im passenden thematischen Zusammenhang übergreifende Erklärungen, Funktionen, Nachrichten usw. vorgestellt. Im Großen und Ganzen behandeln die Technoten anfangs eher allgemeine Themen und gehen dann zu den speziellen Fragen über. Der Text hat aber insgesamt nicht die Standardgliederung eines Lehrbuches, bei dem an Kenntnissen überall nur vorausgesetzt wird, was vorher schon mitgeteilt wurde. Die Eigenschaften von Fenstern bilden ein Begriffsgeflecht, das sich allenfalls mit ganz unvertretbarem Textvolumen und ermüdenden Wiederholungen streng aufbauend beschreiben ließe. Wenn man die Darstellung übersichtlich halten will, kommt man nicht ohne Vorverweise aus. An vielen Stellen zur Fensterprogrammierung werden erst später erklärte Begriffe verwendet. In diesen Bereichen können Sie den Text nicht ganz verstehen, ohne ihn zweimal zu lesen. Programmiersprachen werden gewöhnlich in einer Weise gelehrt, bei der die Anfängerinnen, beginnend mit kleinen Beispielen, sofort praktisch arbeiten können. Anfangs werden etwa Aufgaben mit einfachen Fallunterscheidungen behandelt. Das ist möglich, weil der rechnerische Ansatz dieser Beispiele einfach ist und mit ganz elementaren Hilfsmitteln programmiertechnisch realisiert werden kann. In der fensterorientierten Windows-Programmierung liegen die Voraussetzungen dafür nicht vor. Schon das einfachste Programm muss Fenster enthalten. Fenster sind komplexe Objekte, als Individuen und auch in ihrer Wechselwirkung mit anderen Fenstern. Sie bestimmen durch ihre Eigenart die Aufgliederung der Programme in die einzelnen C-Funktionen. Aus diesem Grund ist etwas Geduld erforderlich, bis Sie die ersten Aufgaben selbständig lösen können. Auslassungen werden als Gestaltungsmittel des Textes aufgefasst. Zum Beispiel hat man bei vielen Funktionen Möglichkeiten für die Wahl der Parameter, aus denen nur eine unvollständige Auswahl angegeben wird. Solche Auslassungen werden meistens nicht als solche gekennzeichnet. Die Darstellung stellt auf die Grundgedanken und auf die daraus resultierende Wichtigkeit der Optionen ab. Eine vollständige Auflistung der Optionen haben Sie online im SDK. Beispielsprogramme mit Quelltexten und Abbildungen ergänzen die Erklärungen. Im engeren Sinne betriebswirtschaftliche Programme werden dabei nicht vorgestellt. Die Beispiele sollen Funktionen oder Programmbausteine
1)
Literaturverzeichnis.
14
/ Einleitung
erklären. Ganze betriebswirtschaftliche Anwendungen sind umfangreiche Programme und zur Demonstration einzelner Sprachelemente nicht geeignet. Es gibt Beispielsprogramme und Programmabschnitte, die Sie am besten sofort lesen, wenn Sie an die zugehörigen Textstellen kommen. Ob das der Fall ist, geht aus dem Kontext hervor. Die genaue Lektüre anderer Quelltexte können Sie aber erst einmal zurückstellen und vielleicht zu einem Zeitpunkt nachholen, zu dem Sie eine einschlägige Aufgabe programmieren wollen. Es wird Ihre Einarbeitung erleichtern, wenn Sie die Beispiele im Rechenbetrieb anschauen. Fordern Sie bitte die Quelltexte und exe-Dateien über Email an.1 Nun ist noch eine Bemerkung zur definitorischen Genauigkeit von WindowsC notwendig: Wenn eine Programmiersprache in einem Lehrbuch dargestellt wird, wird eine nach den Maßstäben der praktischen Programmierarbeit vollständige Präzision angestrebt und gewöhnlich auch erreicht. Damit ist gemeint: Wenn ein Programm, zum Beispiel ein ANSI-C-Programm, einen Fehler enthält, hat die Entwicklerin etwas falsch überlegt. Nachdem sie den Fehler erkannt hat, kann sie sagen, an welchem Punkt sie etwas übersehen hatte. Wenn sie von Anfang an aufmerksam genug gewesen wäre, hätte sie den Fehler vermeiden können. Diese Eigenschaft der Präzisierbarkeit hat auch das API in weiten Bereichen. Es gibt aber ein paar Ausnahmen. Die mit einigen Funktionen und Nachrichten zur Oberflächenprogrammierung verbundenen Vorgänge werden nicht in diesem Buch und auch nirgendwo sonst so genau beschrieben, dass der Verlauf in jeder Einzelheit vorausgesagt werden kann. Manches muss man im Anwendungsfall experimentell ermitteln. Dazu gehören vor allem Fragen des Zeitpunktes bei einigen Vorgängen. Ein Beispiel ist: Unter allen "aktivierbaren" Fenstern auf dem Schirm ist zu jedem Zeitpunkt immer nur höchstens eins aktiv. Sie wissen: Wenn ein aktivierbares Fenster erzeugt und angezeigt wird, wird es vom System aktiviert. Wenn das Fenster während seiner Erzeugung aber ein zweites aktivierbares Fenster erzeugt (dies ist möglich), welches ist dann nach Abschluss des Gesamtvorgangs aktiviert? Das ist eine sehr spezielle Frage, und Sie wissen die Antwort vielleicht nicht im Voraus. Ein anderes Beispiel: Eine von Ihnen geschriebene Funktion für Arbeiten in einem Fenster sei gerade tätig. Sie möge durch einen zugehörigen Funktionsaufruf veranlassen, dass ein anderes, bisher unsichtbares Fenster sichtbar wird. Dann rechne sie noch einige Anweisungen weiter, bis ihre Arbeit beendet ist. Wird nun das bisher unsichtbare Fenster sofort mit dem genannten Funktionsaufruf sichtbar oder erst nach dem Arbeitsende der gerade tätigen Funktion? In diesen beiden Beispielen wird das System über den Code aufge-
1) Adresse: [email protected]
1 Einleitung
1 5
fordert, den Zustand eines Fensters zu ändern. Es führt derartige Anweisungen zwar natürlich zu einem objektiv definierten, aber aus Ihrem Kenntnisstand vielleicht nicht exakt vorhersagbaren Zeitpunkt aus. Es gibt auch andere Beispiele: Durch Anklicken des Icons links oben in der Titelleiste eines Fensters kann ein Systemmenü eröffnet werden. Das Fenster bekommt ein solches Icon nur, wenn es das "Stilbit" (die Eigenschaft) WS SYSMENU hat. Rechts oben in der Titelleiste kann unter anderem ein Minimierungsknopf dargestellt werden. Er wird gebildet, wenn das Fenster das Stilbit WS_MINIMIZEBOX hat. Dem Fenster in der Definition dieses letztere Stilbit mitzugeben, wirkt sich aber tatsächlich nur aus, wenn es zugleich das Stilbit WS_SYSMENU hat. Diese Koppelung ist logisch nicht notwendig. Sie ist im SDK nicht angegeben. Wer ws_MINIMIZEBOX ohne WS_SYSMENU programmiert, wundert sich - jedenfalls beim ersten Mal - über das Fehlen des Minimierungsknopfes und muss dann durch Probieren herausbekommen, wo der Fehler liegt. Diese eine Frage ist hier nun beantwortet und damit hier nicht mehr unbestimmt. Es wäre aber praktisch nicht möglich, eine Darstellung zu geben, die in allen Einzelheiten so exakt und vollständig wie die Darstellungen für das ANSI-C wäre. Die Auswirkungen des Unterschiedes an Genauigkeit zwischen dem API und dem ANSI-C sind in der praktischen Arbeit nicht wesentlich. Die Quelltextabschnitte zur Oberflächenprogrammierung sind ihrer Art nach grundsätzlich unkompliziert und durchsichtig. Sie sind leicht zu programmieren und enthalten keine Passagen erhöhter Fehleranfalligkeit, die im ANSI-C-Teil einer Anwendung, wo voluminöse und komplizierte Algorithmen realisiert werden, oft unvermeidbar sind. Fast alle Änderungen, die nachträglich notwendig werden, beruhen genau so wie im ANSI-C-Teil des Programms auf Mängeln der Aufmerksamkeit. Die Ausnahmefälle, in denen man für die Gestaltung der Programmoberfläche gelegentlich experimentieren muss, haben einen schnellen Testzyklus und verursachen für die Entwicklung einer Anwendung keinen nennenswerten Aufwandsanteil.
16
2 Fensterorientierte Programmierung
2
Fensterorientierte Programmierung
Die folgenden Technoten sind über einen längeren Bereich erst einmal der dialogorientierten Programmierung mit Fenstern gewidmet. Themen, die nicht mit Fenstern zusammenhängen, werden erst später angesprochen. Es besteht kein Bedarf an Technoten, die sich allein auf die Konsoleorientierte Programmierung beziehen. In den vorliegenden Text sind deshalb auch keine eingearbeitet. Man schreibt ein Konsole-orientiertes Programm einfach, indem man die Startfunktion m a i n wählt und auf graphische Elemente verzichtet. Die Darstellung zur Oberflächenprogrammierung einer Anwendung ordnet sich im Wesentlichen um zwei zentrale Grundbegriffe: das Fenster und die Nachricht. Man kann nicht vollständig sagen, was ein Fenster ist, wenn Sie nicht wissen, wie es mit den Nachrichten zugeht, und man kann die Nachrichten nicht erklären, ohne sich auf die Fenster zu beziehen. So werden wir das Thema bewältigen, indem wir abwechselnd über Fenster und Nachrichten reden. SystemMenüknopf
Titeltext
Titelleiste
1
/
i
Schließen Maximieren Minimieren
Knöpfe
\v
rara Fenster Z2 Fenster K1 IrfJil-WJJia Brenden! itllffnen ScfflifBen
Hauptfenster
Fenster Zi A
Menüleiste
Aufgeklapptes Pull-down-Menü Clientbereich
V
4— Rand
Horizontale Laufleiste
Vertikale Laufleiste
Bild 2.1.1 Die Fensterelemente
X: tfm
2 Fensterorientierte Programmierung
2.1
17
Fenster auf dem Schirm
Wir beginnen mit der Frage, aus welchen Elementen die Zeichnung eines Fensters auf dem Schirm besteht. Bei vernünftigem Entwurf hat ein Programm stets ein Hintergrundfenster. Seine Aufgabe ist, einen visuellen Gesamtrahmen für die Arbeit mit dem Programm bereitzustellen. Oft wird es zum Programmstart schirmfüllend dargestellt. Das Programm hat außerdem sogenannte "Dialogfenster", die so ähnlich wie ein Hintergrundfenster aussehen und die die Arbeit mit dem Programm nach Einzelaufgaben gliedern. Ein Hintergrundfenster kann auch selbst vom Typ der Dialogfenster sein. Drittens gibt es dann die sogenannten Dialogelemente, die ebenfalls Fenster sind. In dieser Technote wird erklärt, aus welchen Elementen die Zeichnung eines Hintergrundfensters oder Dialogfensters auf dem Schirm besteht. Die Darstellung dieser Fenster stimmt grundsätzlich überein, so dass sie gemeinsam beschrieben werden können ( - Bild 2.1.1). Die Dialogelemente haben demgegenüber ein vielfaltiges Erscheinungsbild und sehen teilweise wesentlich anders aus. Das Aussehen und der Einsatzzweck der Dialogelemente werden am Ende dieser Technote erstmalig kurz angesprochen. Fenster sind immer rechteckig. Sie werden wegen ihrer flächigen Erscheinung oft auch als Felder (englisch: box) bezeichnet. Man unterscheidet bei den Elementen, aus denen ein Hintergrund- oder Dialogfenster gezeichnet wird, zunächst einmal in: - den Nichtclientbereich, - den Clientbereich (engl.: client area). Der Clientbereich für sich allein ist auch immer rechteckig. Er hat eine Farbzeichnung, die aus der Fensterdefinition folgt. Sie kann zur Programmlaufzeit geändert werden. Im Bild 2.1.1 ist er weiß gezeichnet. Der Nichtclientbreich besteht aus den Elementen: - Rand (border, frame) -Titelleiste (caption bar), darin - Systemmenüknopf, dargestellt durch ein Icon - Titeltext oder Fenstertext (caption) - Minimierungsknopf - Maximierungsknopf - Knopf für Schließen (das heißt für das Zerstören des Fensterobjektes) - Menüleiste - Laufleisten (scrollbars)
18
2 Femterorientierte Programmierung
Der Nichtclientbereich eines Fensters kann weniger Elemente haben als hier angegeben sind. Sie geben in der Fensterdefinition an, welche er haben soll. Kein einziges der Elemente ist vorgeschrieben. Es muss insofern also im Effekt überhaupt keinen Nichtclientbereich geben. Ein Hintergrundfenster zeigt oft alle Elemente außer den Laufleisten. Der Titeltext gehört zu den Daten, die intern für das Fenster gespeichert werden. Er wird in der Titelleiste dargestellt. Wenn ein Fenster keine Titelleiste hat, wird er nicht angezeigt. Auch ein Fenster ohne Titelleiste kann aber einen nichtleeren Titeltext haben. Der Systemmenüknopf zeigt ein Rasterbild, ein sogenanntes Icon. Es wird von der Entwicklerin angegeben (und zwar für die "Klasse", der das Fenster angehört). Vor allem das Hintergrundfenster eines Programms wird im Allgemeinen einen Systemmenüknopf und ein Icon bekommen. Es soll nach Möglichkeit irgendwie den Zweck der Anwendung charakterisieren. Ein Fenster hat 4 unterschiedliche Zustände der Darstellung. Zu jedem Zeitpunkt befindet es sich in genau einem von ihnen: -
-
Arbeitsdarstellungen: - normal, - maximiert, Ruhedarstellungen: - minimiert, - unsichtbar.
Die maximierte und die normale Darstellung sind die Zustände für die Arbeit der Anwenderin mit dem Fenster. Nur in ihnen wird es vollständig mit seinen Elementen gezeichnet. Bei einem Fenster, das sich zeichnerisch gegen andere Fenster nicht abhebt, zum Beispiel weil es keinen Rand hat, kann die Anwenderin die Abmessungen des Fensters auf dem Schirm möglicherweise nicht genau erkennen. Im maximierten Zustand hängt die Größe eines Fensters von seinem Typ ab. Bei dem Hintergrundfenster einer Anwendung und bei einer anderen Gruppe von Fenstern, den sogenannten zugeordneten Fenstern, ist sie schirmfüllend. Fenster einer weiteren Art, die sogenannten Kindfenster, werden immer im Clientbereich eines anderen Fensters dargestellt. Bei ihnen füllt die maximale Größe genau diesen Clientbereich aus. Die Position eines maximierten Fensters ist nicht frei wählbar. Ein schirmfüllendes Fenster füllt nicht nur nach der Größe, sondern auch nach der Position den Schirm aus. Es ist nicht beweglich. Ein maximiertes Kindfenster liegt der Position nach genau im Clientbereich des Fensters, an das es der Größe nach angepasst ist. Bei "normaler" Darstellung hat das Fenster bestimmte Abmessungen und eine Schirmposition, die sich aus dem Programmcode oder aus Mausaktionen der
2 Femterorientierte Programmierung
19
Anwenderin ergeben haben. Abmessungen und Schirmposition sind grundsätzlich veränderlich. Normal als Bezeichnung des Darstellungszustandes hat mit Normalität nichts zu tun, sondern steht im Gegensatz zu "maximiert" und bedeutet meistens eher so etwas wie mittelgroß. Die Position kann solche Werte haben, dass sich das Fenster ganz oder teilweise außerhalb des Schirms befindet und deshalb im Effekt nicht oder nicht vollständig abgebildet wird. Bei den eben erwähnten Kindfenstern, die immer im Clientbereich eines anderen Fensters dargestellt werden, kann die normale Position Werte haben, bei denen sich das Fenster ganz oder teilweise außerhalb dieses Clientbereiches befindet, so dass auch ein solches Fenster nicht oder nur teilweise zu sehen ist. Die normale Darstellung des Fensters ist auch dann gespeichert, wenn es gerade nicht normal gezeichnet wird. Es gibt Funktionen, mit denen man die Darstellung zwischen den 4 Zuständen hin- und herschalten kann. Wenn man ein normal dargestelltes Fenster maximiert, minimiert oder unsichtbar macht und danach wieder in den normalen Zustand zurückschaltet, wird es wieder mit den alten Abmessungen und an der alten Stelle dargestellt. Ein Fenster hat auch dann Werte für die normale Darstellung, wenn es noch nie normal abgebildet worden ist. Ein Fenster kann unsichtbar sein. Es hat dann alle seine Elemente, wird aber nicht auf den Schirm gezeichnet. Wenn ein Fenster "minimiert" ist, wird nur eine verkürzte Titelleiste dargestellt. In ihr sieht man das Icon und den Anfang des Titeltextes, falls es diese Elemente gibt. Diese Abbildung findet sich bei dem Hintergrundfenster eines Programms in der Startleiste1, bei anderen Fenstern auf der Arbeitsfläche des Schirms. Für ein Hintergrundfenster befindet sie sich in der Startleiste immer, auch wenn das Fenster gerade nicht minimiert, sondern normal, maximiert oder unsichtbar ist. Sie ist zusätzlich und zeigt an, dass das Programm gestartet ist. Die unsichtbare und die minimierte Darstellung werden unter dem Begriff der Ruhedarstellung zusammengefasst. Die Darstellbarkeit eines Elementes kann davon abhängen, ob es ein anderes Element gibt. Zum Fehlen von Elementen wird aufgezählt: -
Wenn die Titelleiste fehlt, können die 4 in die Zeichnung eingetragenen Knöpfe nicht gebildet werden.
1) In der Literatur ist häufig der Ausdruck Taskleiste zu finden. Diese Bezeichnung ist inhaltlich unzutreffend. Unter einem Task wird bei Betriebssystemen ein Prozess oder ein Thread verstanden. In der Leiste werden der Startknopf und die Hintergrundfenster der Anwendungen angezeigt. Wenn ein Prozess keine Fenster hat, wie eine Anwendung, bei der wegen eines fehlerhaften Entwurfs alle Fenster zerstört sind, wird er in der Leiste nicht nachgewiesen. Die Tasks werden in der Taskliste gezeigt, die man zum Beispiel mit Strg - Alt - Entf aufrufen kann. Dort werden keine Fenster nachgewiesen.
20
2 Fensterorienüerle
-
-
-
-
Programmierung
Der Knopf rechts oben für Schließen (Zerstören) wird automatisch immer gebildet, wenn die Entwicklerin das Systemmenü vorgeschrieben hat. Es ist nicht möglich, den Knopf zu bilden, ohne ein Systemmenü vorzuschreiben. Die Knöpfe für das Minimieren und Maximieren des Fensters können nur gebildet werden, wenn auch der Systemmenüknopf vorgeschrieben wird. Sie können unabhängig voneinander vorgeschrieben werden. Wenn nur einer von ihnen vorgeschrieben wird, wird der andere zwar auch gebildet, ist aber grau gezeichnet und nicht ansprechbar. Wenn ein Fenster eine Titelleiste hat, wird automatisch auch ein Rand gebildet. Die Entwicklerin kann die Art des Randes aus einem vorgegebenen Typenangebot auswählen. Wenn sie keine Auswahl trifft, nimmt das System einen Standardtyp. Ein Fenster kann nur eine Menüleiste haben, wenn es auch eine Titelleiste hat. Ein Fenster kann nur eine Laufleiste haben, wenn es auch eine Titelleiste hat.
Es versteht sich von selbst, dass Sie eine derartige Aufzählung nicht gleich auswendig lernen werden. Es ist aber sinnvoll, sich zu merken, dass es überhaupt Abhängigkeiten gibt. Wenn Sie im Quellcode ein Fenster mit seinen Elementen definieren, rufen Sie eine Funktion oder je nach Sachlage mehrere Funktionen auf, um es zu erzeugen, seine Eigenschaften zu bestimmen und es auf dem Schirm anzuzeigen. Sie können über Programm zwischen den Darstellungszuständen hin- und herschalten. Ein normal dargestelltes Fenster können Sie über Programm in der Größe und der Position auf dem Schirm verändern. Für diese und ähnliche Aufgaben sind jeweils nur einfache Funktionsaufrufe notwendig. In den Einzelheiten übernehmen dann Systemfunktionen die Abbildung des Fensters auf dem Schirm. Auch die Arbeit der Anwenderin mit dem Fenster spricht Systemfunktionen an. Dies wird an den nachstehenden Beispielen verdeutlicht: -
-
Die Anwenderin kann ein normal dargestelltes Fenster durch Ziehen mit der Maus an der Titelleiste auf einen anderen Platz auf dem Schirm bewegen. Voraussetzung ist nur, dass Sie dem Fenster eine Titelleiste gegeben haben. Sie brauchen sonst keine Unterstützung dafür zu programmieren. Es gibt unterschiedliche Ränder. Bei einem von ihnen, dem Thickframe, kann die Anwenderin die Fenstergröße bei normaler Darstellung durch Ziehen mit der Maus am Rand verändern. Auch hier bedarf es keiner
2 Fensterorientierte Programmierung
-
-
-
21
Unterstützung durch die Entwicklerin. Ganz analog hat es hier ausgereicht, dass Sie dem Fenster einen Thickframe gegeben haben. Das Andrücken des Systemknopfes in der Titelleiste öffnet automatisch das Systemmenü, das der Anwenderin zum Beispiel anbietet, das Fenster zu maximieren. Sie brauchen nicht zu programmieren, dass sich das Systemmenü bei Anklicken öffnet. Sie brauchen den Inhalt des Systemmenüs nicht zu definieren. Er wird automatisch gebildet und richtet sich unter anderem danach, ob es die Knöpfe für Minimieren und Maximieren gibt. Wenn das der Fall ist, werden auch im Systemmenü Punkte "Minimieren" beziehungsweise "Maximieren" aufgeführt. Andernfalls fehlen sie. Wenn das Fenster auf dem Schirm normal abgebildet ist und die Anwenderin den Knopf für Maximieren andrückt, ändert er seine Darstellung. Er erscheint nun 3-dimensional eingedrückt. Wird er losgelassen, bilden Systemfunktionen das Fenster in maximaler Größe ab. Zugleich wird die in dem Maximierungsknopf dargestellte Zeichnung geändert. Der Maximierungsknopf ist ein Wechselschalter. Die Anwenderin kann mit ihm aus der maximierten in die normale Darstellung zurückschalten.
Die Verwaltung des Fensterbildes und speziell der Elemente des Nichtclientbereiches durch vordefinierte Systemfunktionen ist zentral für die Qualität des Objekttyps Fenster. Es bleiben nur wenige Programmierarbeiten übrig, die die Elemente des Nichtclientbereiches zur Zeit nach der Bildung des Fensters betreffen. Dazu gehören: -
-
-
Ein Fenster wird je nach Art automatisch zerstört (seine Lebensdauer wird beendet), wenn die Anwenderin den Knopf "Schließen" wählt. Es gibt aber Fenster, die in diesem Fall nicht automatisch zerstört werden. Dann schreiben Sie die Reaktion auf das Andrücken des Knopfes selbst. Sie geben in der Definition des Fensters vor, welchen Fenstertext es bei seiner Bildung haben soll. Wenn sich der Text während der Lebenszeit des Fensters ändern soll, muss das programmiert werden. Sie definieren das Erscheinungsbild der Menüleiste (wenn es eine geben soll) mit ihren Menüpunkten und Untermenüs als Teil der Fensterdefinition. Sobald das Fenster gebildet wird, sorgen Systemfunktionen dafür, dass sich beim Anklicken eines Menüpunktes in der Leiste das dazu gehörende Pull-down-Menü automatisch aufklappt. Die Reaktion auf das Anklicken einer Auswahloption ist aber eine individuelle Sache der einzelnen Anwendung und muss programmiert werden.
22
2 Fensterorientierte
Programmierung
Der Clientbereich dient der Aufnahme von Tastatureingaben und Mauseingaben und der Anzeige von Ergebnissen. Die Dateneingaben und Ergebnisanzeigen werden aber nicht unmittelbar auf den Hintergrund des Clientbereiches gezeichnet, sondern im Allgemeinen in die schon dem Namen nach erwähnten Dialogelemente, das heißt Fenster spezieller Typen, die auf dem Clientbereich angeordnet werden. Sehr vereinfacht ausgedrückt ersetzen die Dialogelemente bei fensterorientierter Programmierung die ANSI-Funktionen p r i n t f und s c a n f . Sie haben ein visuelles Erscheinungsbild, auf das die bisherigen Erklärungen nicht zutreffen. Update-Dialog Personalnummer
[7698
Name:
Gehalt:
¡ ! Bacher
Kommission:
eingestellt am:
11.5.97
Funktion:
| Controller
Abteilung:
|
Del | ¡30
Vorgesetzter
|
Del|
[7B39
5850
¡Entwicklung jKönlg
Bild 2.1.2 Dialogelemente in einem Fenster Bild 2.1.2 zeigt als Beispiel ein Fenster aus einem Datenbankprogramm, mit dem Personaldatensätze geändert werden können. Im Clientbereich unter der Titelleiste sind Dialogelemente angeordnet. Sie sehen darin -
-
die Anzeige von Erklärungstexten. Dazu rechnet der Schriftzug "Personalnummer: ". Felder für die Anzeige der Inhalte des Datensatzes, die in diesem Fall - bis auf einen - von der Anwenderin über Eingaben verändert werden können. Der Inhalt des Feldes für die Personalnummer kann nicht geändert werden. Um das anzuzeigen, ist das Feld hellgrau unterlegt. Schaltknöpfe, mit denen die Anwenderin eine Reaktion des Programms auslösen kann.
Rein programmiertechnisch kann auch der Clienthintergrund selbst Eingabeoder Ausgabemedium sein. Dialogelemente sind aber bequemer zu programmieren. Aufgabe des Clientbereiches ist es gewöhnlich, als Hintergrund für die Dialogelemente zu dienen.
2 Fenslerorienlierle Programmierung
2.2
23
Datentypen und Notation in Windows-Programmen
Ein fensterorientiertes C-Programm für Windows weicht im Aussehen des Quelltextes wesentlich von einem reinen ANSI-C-Programm für zeichenorientierte Bildschirmdarstellung ab. Es bindet in seine C-Quelldateien windows.h ein. windows.h lädt seinerseits viele andere Header. Sie deklarieren die vielen Windows-Funktionen, WindowsMakros und Windows-Strukturen. Sie führen Alternativbezeichnungen für die elementaren Standarddatentypen von C ein, die im Einzelnen je nach Kontext verwendet werden. Dadurch ergibt sich äußerlich, in der texttechnischen Präsentation, so etwas wie eine spezielle Syntax der Windows-C-Programme. Sie lernen sie in diesem Abschnitt kennen. Außer windows.h gibt es einige weitere Headerdateien, die nur für einzelne programmiertechnische Mittel notwendig sind. Soweit ein Programm ANSI-CFunktionen verwendet, werden auch die zugehörigen Header eingebunden. Auch in ein zeichenorientiertes Programm kann windows.h eingefügt werden. Dann stehen auch hier die Funktionen, Makros und Strukturen aus Windows zur Verfügung, soweit sie sich nicht auf die graphische Oberfläche beziehen.
2.2.1
Programmobjekte im Systemspeicher
Ein ANSI-C-Programm führt seine Daten bekanntlich in einem individuellen Speicherbereich, dessen Adressvorrat sich in drei Abschnitte gliedert: -
den Stack für die automatischen Variablen und die Formalparameter der Funktionen, den Heap für die mit m a l l o c ( c a l l o c usw.) reservierten Speicherplätze, den Bereich für die statischen Variablen.
Ein Windows-Programm führt die Daten für einige Objekttypen, vor allem für die Fenster, nicht in diesem individuellen Speicherbereich des Prozesses, sondern im zentralen Systembereich. Die Abweichung vom ANSI-C hat einen organisatorischen Grund: Fenster unterschiedlicher Anwendungen reagieren miteinander. Zum Beispiel überlagern sie sich auf dem Schirm. Sie sind Objekte, auf die das System je nach Situation auch selbständig zugreifen muss. Die Systementwicklerinnen haben dafür eine zentrale Datenhaltung gewählt. Eine Adressvariable verweist bekanntlich auf einen bestimmten Hauptspeicherplatz und identifiziert im Programm ein dort stehendes Objekt. Ein
24
2 Fensterorientierte Programmierung
Objekt im Heap kann in C überhaupt nur über seine Hauptspeicheradresse identifiziert werden. Sie wird in einer Adressvariablen geführt. Die Objekte der zentral gehaltenen Typen, vor allem also die Fenster, können wie Objekte auf dem Heap nur über eine Adresse (einen "Pointer", "Zeiger") angesprochen werden. Eine solche Adresse heißt ein "Handle", genauer eine "Handiekonstante". Sie ist vom Datentyp v o i d * (unspezifischer Zeiger). Sie wird während der Lebensdauer eines Objektes nicht geändert. Wie auch sonst bei Zeigern steht NULL = ( v o i d *) 0 für ein ungültiges Handle. Das System unterhält Adresstabellen für die betroffenen Objekte. Eine Handiekonstante gibt den Platz an, wo in einer Tabelle sich die Adressdaten eines Objektes befinden. Sie bietet auf diesem Wege den Zugang zu den Daten selbst. Die Adresstabellen und ihre Inhalte, die Adressen der Objektdaten, werden aber vom System gegen jeden direkten Zugriff der Anwendung geschützt. Eine Anwendung kann zum Beispiel die Adressdaten nicht lesen. Ein Versuch führt zu einem Systemfehler. Die Anwendung kann deshalb nicht auf dem Weg über die Adresstabelle direkt zu den Daten des Objektes gelangen. Stattdessen gibt es eine Anzahl von vordefinierten Windows-Funktionen, die die für die Objekte vorgesehenen Arbeiten ausführen können, und an die man Handle als Parameter übergibt. Mit Handiewerten adressiert man nur eine fest vorgegebene Reihe von im Windows-System vordefinierten Objekttypen. Zu ihnen rechnen wie gesagt auch die Fenster, von denen es im Detail unterschiedliche Typen gibt. Wenn von einem Handle die Rede ist, ohne dass ausdrücklich gesagt wird, man meine eine Handiekonstante, dann bezeichnet der Ausdruck oft eine Variable, die eine Handiekonstante aufnehmen kann oder enthält. Handle (das heißt Handlevariable) erfüllen somit in der Windows-Programmierung zum Beispiel für Fenster dieselben Aufgaben wie Adressvariable für Objekte im Heap. Wenn man einen Speicherplatz im Heap, zum Beispiel für ein d o u b l e , bilden möchte, sind zwei Schritte notwendig: double * x; erst definiert man die Adressvariable, χ = m a l l o c ( . . . ) ; dann bildet man den Speicherplatz, zum Beispiel mit m a l l o c , und weist seine Adresse der Adressvariablen zu. Man hat die entsprechenden beiden Schritte bei der Erzeugung eines Objektes im zentralen Systemspeicher, zum Beispiel: HWND hWindow;
Definition eines Handies für ein Fenster. HWND (Handle auf ein Window) ist eine in einem Windows-Header über t y p e d e f eingeführte alternative Bezeichnung für v o i d *.
2 Fensterorientierte Programmierung
2 5
hWindow = CreateWindow ( . . . ) ; Erzeugung des Fensters und Zuweisung der identifizierenden Handiekonstanten, dem Rückgabewert der Funktion CreateWindow, an das Handle. Das Handle (die in einer Variablen gespeicherte Handiekonstante) wird nun benutzt, wenn das Fensterobjekt identifiziert werden soll. Zum Beispiel gibt es eine Funktion, die ein Fenster unsichtbar machen kann (ShowWindow). Sie spricht das System an. Beim Aufruf wird das Handle übergeben. Beispiel: ShowWindow (hWindow, SW_HIDE) ; Wende auf das Fenster den Parameter SW HIDE an. Das Fenster wird vom System unsichtbar gemacht. Ein Handle, syntaktisch eine Adresse, ist in einem Windows-Programm in jeder praktischen Beziehung der Name für ein Objekt. Es gibt nur noch eine weitere, oft aber nicht immer anwendbare Möglichkeit, ein Fenster in der Anwendung zu identifizieren: die sogenannten Kindfenster haben ein Eiterfenster und in Bezug auf dieses eine Kindnummer, ein "Kindfenster-ID". Ein Kindfenster kann man (unter passenden Voraussetzungen, die später bei der genaueren Erklärung aufgeführt sind) auch durch das Elterhandle und die zusätzliche Angabe des Kindfenster-IDs bezeichnen. Auch hier ist zur vollständigen Identifizierung also ein Fensterhandle notwendig. Der Anwenderin vor dem Schirm ist das Fensterhandle nicht bekannt. Viele Fenster haben aber einen Titeltext. Für die Anwenderin liegt es nahe, in ihm den Namen des Fensters zu sehen. Dementsprechend wird hier je nach Kontext auch der Titeltext als Name eines Fensters angesprochen. Andere Objekte als Fenster, die in einer Anwendung ebenfalls durch Handle bezeichnet werden, weil die Daten im geschützten zentralen Systembereich liegen, sind Flächenmuster, Menüs, Icons usw. Zum Beispiel ein Flächenmuster ist ein kleines graphisches Rechteck, das vom System zum Füllen des Clientbereiches eines Fensters verwendet werden kann.
2.2.2
Windows-Datentypen
Die Windows-Entwicklerinnen verwenden ziemlich durchgängig alternative Bezeichner für die Datentypen von C. Weil sie in allen Quelltexten vorkommen, müssen wir sie hier gleich am Anfang einführen. Die Definitionen finden sich in den verschiedenen über windows.h geladenen Headerdateien, zum Beispiel in winnt.h: typedef char CHAR;
26
2 Fenslerorientierte
Programmierung
So entstehen Pseudotypen, von denen in Tabelle 2.2.1 eine nicht ganz vollständige Aufzählung gegeben wird. Je nach Kontext gebräuchliche Windows-Bezeichner
C-Datentyp
CHAR PCHAR, LPCH, LPTCH, PTCH, PCH, PSTR, PTSTR, NPSTR, LPSTR, LPTSTR, PSZ PCCH, LPCCH, PCSTR, LPCSTR UCHAR, BYTE, BOOLEAN PUCHAR, PBYTE, PTBYTE, LPBYTE, PBOOLEAN SHORT PSHORT USHORT, WORD, ATOM PUSHORT, PWORD, LPWORD INT, BOOL PINT, LPINT, PBOOL, LPBOOL UINT, WΡARAM PUINT, LPUINT LONG, LPARAM, LRESULT PLONG, LPLONG . ULONG, DWORD, COLORREF PULONG,PDWORD, LPDWORD, LPCOLORREF FLOAT ΡFLOAT PVOID, LPVOID LPCVOID HANDLE, HWND, HBRUSH, HINSTANCE, HGDIOBJ, HBITMAP HDC, HICON, HMENU, HFONT, HCURSOR PHANDLE, SPHANDLE, LPHANDLE
char char * noch: char * const char * unsigned char unsigned char J short short * unsigned short unsigned short 1 int int * unsigned int unsigned int * long long * unsigned long unsigned long * float float * void * const void * void * noch: void * noch: void * void **
Tabelle 2.2.1 Windows-Datentypen Die Bezeichner lassen sich in zwei Gruppen unterscheiden: 1. Ein Teil von ihnen hat die Aufgabe, einen C-Datentyp im Kontext des Programms näher nach seiner Verwendung zu klassifizieren: -
Eine besonders überzeugende Gruppe sind die Handle. Es mag in einem Programm unspezifische Zeiger (Typ v o i d *) für unterschiedliche Zwecke geben. Handle haben unter ihnen, was die Verwendung angeht, bestimmte gemeinsame Eigenschaften, mit denen sie sich von anderen Zeigern unterscheiden. Tatsächlich wird sogar die Bezeichnung HANDLE wenig verwendet. Stattdessen werden überwiegend spezielle Bezeich-
2 Fensterorientierte Programmierung
2.1
nungen gewählt. Ein Handietyp beginnt immer mit H ( - die letzten Zeilen der Tabelle): HWND HBRUSH
-
ist ein Handle auf ein Fenster, ein Handle auf ein Flächenmuster (engl.: brush) usw.
Eine Instanz ist ein Programmlauf, ein GDI-Objekt ein vom "GDI" (einem bestimmten Systemteil) verwaltetes graphisches Objekt, ein Bitmap eine Zeichnung in einem bestimmten graphischen Format usw. Diese Begriffe werden im Text erklärt, wo sie benötigt werden. Ein ATOM ist eine Nummer, die im Programm eine Zeichenkette identifizieren kann. In C hat ein Zeiger auf eine O-terminierte Zeichenkette den Datentyp char *. Aber nicht jede Größe mit Datentyp char * ist Zeiger auf eine Zeichenkette. In Windows-C wird deshalb die Zeichenfolge PSTR (Pointer auf String) empfohlen. Den Pseudotyp S TR allein (ohne den Zusatz Zeiger) gibt es nicht. Man kann also definieren: PSTR pszText; // steht für char pszText; Aber man gibt an: char szTextflOO];
*
Die Typenbezeichner W Ρ ARAM, LP ARAM und LRESULT werden sehr speziell eingesetzt. W Ρ AR AM und LP ARAM werden ausschließlich als Datentyp für je eine bestimmte Komponente einer "Nachrichtenstruktur" (Strukturtyp MSG = message) verwendet, nämlich für die dort definierten Komponenten wParam und lParam. LRESULT tritt nur als Rückgabewert der sogenannten Fensterprozeduren auf. - C O L O R R E F bezeichnet einen RGB-Wert, das heißt eine Farbe. - BOOL steht für einen booleschen Wert. Hier muss die Entwicklerin einer Fehlerquelle ausweichen: BOOL ist int. Es gibt aber außerdem BOOLEAN und das ist unsigned char. Die Darstellungsbreiten stimmen nicht überein. BOOL ist der Typ des Rückgabewertes der allgegenwärtigen "Dialogprozeduren". Man tut deshalb gut daran, den Typ BOOLEAN nicht zu benutzen. In diesem Zusammenhang wird angemerkt: TRUE und FALSE sind in Windows-C mit 1 und 0 vordefiniert. -
Der Compiler kann diese Alternativbezeichner nicht von den Original-CBezeichnern unterscheiden (Eine Ausnahme machen bei einer bestimmten Programmvoreinstellung die Handle.) Die Verwendung hat allein kommentierende Bedeutung. Sie ist aber sehr wohl geeignet, die Quelltexte lesbarer zu machen. Die Bezeichner ergänzen und unterstützen in ihrer Leistung die Verwendung der sogenannten "ungarischen Notation" ( - im Folgenden).
28
2 Fensterorientierte Programmierung
2. Die jetzt noch nicht genannten Pseudotypen der Tabelle bezeichnen nicht eine bestimmte Verwendung der Werte, sondern ersetzen einfach nur die Standard-C-Bezeichnungen durch andere. Statt char soll CHAR, statt int soll INT, statt long soll LONG benutzt werden. Einen Sinn hat das nicht. Die Bezeichner sind auch von Microsoft nicht zentral und koordiniert eingeführt worden, sondern offensichtlich nach dem Geschmack der einzelnen Programmiererinnen. Für char * gibt es vollständig gleichbedeutend die Typen PCHAR, PTCHAR und PCH, für unsigned short die Typen USHORT und WORD. Einige andere Bezeichner fehlen umgekehrt. Trotzdem haben sich die Windows-Entwicklerinnen angewöhnt, die Pseudotypen für ihre Anwendungen zu übernehmen. Damit sind Fakten geschaffen. Es ist sinnvoll, sich mit der Tabelle einleitend vertraut zu machen. Sie brauchen die Typenbezeichner der zweiten Gruppe nicht selbst zu verwenden. Aber sie werden Ihnen durchgängig im SDK und in publizierten Programmen begegnen. Nachzutragen sind noch die folgenden allgemeinen Regeln der Tabelle: - Ρ steht für Pointer, LP für long Pointer, NP und SP für near Pointer. Im 32-Bit-Windows gibt es keine besonderen long oder near Pointer mehr. Alle Zeiger sind long, das heißt umfassen 4 Bytes. Die Bezeichner LP, Ν Ρ und SP stammen aus dem 16-Bit-Windows und werden nicht mehr benötigt. Sie sind aus Kompatibilitätsgründen noch in den WindowsHeadern enthalten und auch noch in neueren Programmen zu finden. - C wie in PCSTR steht für constant, U wie in UINT für unsigned. Aufmerksamkeit verlangt dieser Punkt: -
Der Beginn des schon erwähnten Bezeichners W Ρ AR AM mit dem Buchstaben W deutet an sich auf ein WORD mit 2 Bytes Darstellungsbreite hin (-> S. 30 unter "ungarische Notation"). W Ρ ARAM hat aber 4 Bytes Darstellungsbreite. Im 16-Bit-Windows war w ΡARAM 2 Bytes breit. Von dort ist die Bezeichnung aus Kompatibilitätsgründen ohne Änderung in das 32Bit-Windows übernommen worden.
Da einige Pseudotypen für ein- und dieselbe Bedeutung stehen, werden Sie in Ihren eigenen Anwendungen einen von ihnen auswählen. So ergibt sich eine gekürzte Liste, die zum Beispiel wie die Tabelle 2.2.2 aussehen kann. Sie wird noch einmal kürzer, wenn Sie bei der zweiten Gruppe, also etwa bei char oder long, die C-Originalbezeichnungen verwenden, oder wenn Sie statt des Buchstabens Ρ das C-Zeichen * wählen, also zum Beispiel WORD * statt PWORD schreiben.
2 Fensterorientierte Programmierung
Windows-Bezeichner je nach Kontext
C-Datentyp
CHAR PCHAR, PSTR PCCH BYTE PBYTE SHORT PSHORT WORD, ATOM PWORD INT, BOOL PINT, PBOOL UINT, WΡARAM PUINT LONG, LPARAM, LRESULT PLONG DWORD, COLORREF ΡDWORD, LPCOLORREF FLOAT ΡFLOAT Ρ VOID LPCVOID HANDLE, HWND, HBRUSH, HINSTANCE, HGDIOBJ, HBITMAP, HDC, HICON, HMENU, HFONT, HCURSOR
char char * const char * unsigned char unsigned char * short short * unsigned short unsigned short int int * unsigned int unsigned int * long long * unsigned long unsigned long v float float * void * const void * void * void * void *
29
Tabelle 2.2.2 Windows-Datentypen (Auswahl)
2.2.3
Das Bilden von Bezeichnern
Es ist üblich, bei den Bezeichnern für Funktionen und Objekte systematisch Gebrauch von großen und kleinen Buchstaben zu machen. Die großen Buchstaben werden benutzt, um die Wörter, aus denen ein Bezeichner zusammengesetzt wird, voneinander zu trennen. Es ist nicht üblich, Unterstriche für diese Trennung einzusetzen. Beispiele: Eine Funktion zum Prüfen einer Zahl auf Ganzzahligkeit wird man vielleicht IstGanz nennen. Sie hat etwa den Prototyp BOOL IstGanz(double x); Die Windows-API-Funktionen heißen zum Beispiel: RegisterClass, CreateWindow, ShowWindow, SetFocus, EnableMenuItem.
30
2 Fensterorientierte
2.2.4
Programmierung
Ungarische Notation
In der Windows-Programmierung pflegt man Variable durch Präfixkürzel so zu bezeichnen, dass man ihren Datentyp am Namen erkennen kann. Diese Technik wird "ungarische Notation" genannt. Sie wird oft etwa in der Weise eingesetzt, die in Tabelle 2.2.3 (nächste Seite) zusammengestellt ist. Sie ist bei den einzelnen Autorinnen beziehungsweise in der Microsoft-Literatur im Detail unterschiedlich. In den meisten Tabellen werden Sie die Präfixe j, k, id und d nicht finden. Sie sind für die Arbeit aber notwendig oder nützlich. Wie schon bei den Windows-Datentypen angemerkt wurde, wird die ungarische Notation durch die Leistung der Windows-Datentypdefmitionen unterstützt. Dabei kommt der ungarischen Notation für die Lesbarkeit des Textes die größere Bedeutung zu. Die in die Bezeichnung einer Variablen, zum Beispiel szText, eingearbeitete Aussage über den Typ ist der Angabe PSTR bei der Variablendefinition insofern überlegen, als die Gedächtnisstütze zum Datentyp überall gegeben wird, wo die Variable im Programm erscheint, während PSTR nur an der Stelle der Datendefinition auftritt. Das Windows-API definiert eine Vielzahl von Strukturen, zum Beispiel POINT für die Koordinaten eines Punktes auf dem Schirm: typedef struct { LONG left; LONG top; } POINT; Obgleich einige dieser Strukturen sehr oft in den Anwendungen vorkommen und einen festen Bestandteil des API bilden, sind keine Präfixe für sie vorgegeben. Man definiert eine Punkt-Variable im Programm ohne ungarische Notation, zum Beispiel mit POINT point; Die ungarische Notation ist bei Strukturen auch deshalb notwendig unvollständig, weil man über typedef immer noch zusätzliche Strukturtypen einführen kann. Newcomer1 nimmt entschieden gegen die ungarische Notation Stellung. Er führt unter anderem an, dass der Datentyp einer Variablen oft während der Programmentwicklung oder im Laufe der späteren Programmpflege oder beim Portieren auf ein anderes System 1) - Literaturverzeichnis. S. 39 f.
2 Fensterorientierte Programmierung
31
Präfix
Datentyp
c s
CHAR (signed char) cBuchstabe PCHAR (char *) sZeichenfolge bezeichnet eine nicht O-terminierten Zeichenfolge PSTR (char *) szText bezeichnet eine O-terminierten Zeichenkette BYTE (unsigned char) byZahl SHORT (signed short) shZahl WORD (unsigned short) wZahl INT (signed int) iZaehler, nZaehler INT (signed int) idKind bezeichnet das "ID" eines Objektes BOOL (signed int) bPositiv UINT (unsigned int) uiZahl signed long lGrosseZahl DWORD (unsigned long) dwParameter FLOAT (float) flGleitkommaZahl double dDoubleZahl Zeiger, lp (long Pointer) ist sachlich zur Abgrenzung gegen ρ nicht mehr notwendig, wird aber manchmal noch verwendet pAnzahl Das Präfix wird auch zusammen mit anderen eingesetzt: pszText Funktion pfnlstGanz nicht näher spezifiziertes HANDLE hWindow Handle auf: ein Fenster hwndDialogFenster die Programminstanz, das heißt den hilnstanz Prozess ein Brush (Flächenmuster) hbrGruen einen Displaykontext usw. hdcDrucker COLORREF (unsigned long) Bezeichnung eines RGB-Farbwertes ebenfalls rgbGruen int, bezeichnen die Koordinaten eines Fensterpunktes: horizontal χ, vertikal y xBreite, yHoehe
by sh w i, j, k, η id b ui 1 dw fl d lp
fn h hwnd hi, hinst hbr hdc clrref rgb χ, Y
Beispiel
Tabelle 2.2.3 Ungarische Notation geändert werden muss. In einem solchen Fall muss man überall im Quelltext den Namen ändern, wenn die ungarische Notation zuverlässig sein soll. Ein in der Tat augenfälliges Beispiel ist die schon erwähnte Komponente wParam der Nachrichtenstruktur MSG mit dem Typ WPARAM, deren Bezeichnung beim Übergang zum 32-Bit-Windows nicht geändert wurde und jetzt falsch ist. Man
32
2 Fensterorientierte
Programmierung
muss die ungarische Notation aber jedenfalls lernen, weil sie in den publizierten Programmen und Texten überall angewendet wird. Von uns wird sie hier für oberflächennahe Programmierung mit wenigen Ausnahmen durchgängig eingesetzt.
3 Die Struktur dialogorientierter Windows-Programme
33
3
Die Struktur dialogorientierter Windows-Programme
3.1
Übersicht
Die graphische Windows-Shell ist eine Laufzeitumgebung für die Ausführung von dialogorientierten Programmen. Ein Programm wird als dialogorientiert bezeichnet, wenn es sich in seinem Arbeitsablauf im Wesentlichen durch die folgende einfache Schleife beschreiben lässt: — >
1. Entgegennahme einer Eingabe von der Tastatur oder der Maus 2. Reaktion auf die Eingabe, Arbeit für die Eingabe, Anzeige von Ergebnissen am Schirm
Die Reaktion auf die Eingabe können Rechnungen im Hauptspeicher sein. Auch der Drucker und Dateien können angesprochen werden. Wegen der ganz außerordentlichen Geschwindigkeit, mit der die Maschine die meisten Reaktionen auf Eingaben ausführen kann, und wegen der im Vergleich dazu unvorstellbaren Langsamkeit, mit der die Anwenderin Eingaben vornimmt, befindet sich ein Dialogprogramm im Allgemeinen den größten Teil seiner Laufzeit im Wartezustand. Über die meiste Zeit der Arbeit mit einer Anwendung ist es deshalb realistisch, die Schleife so anzugeben: —>
1. Entgegennahme einer Eingabe von der Tastatur oder der Maus 2. Reaktion auf die Eingabe, Arbeit für die Eingabe, Anzeige von Ergebnissen am Schirm 3. Wartezeit
Man kann Programme mit ganz uneingeschränkter Arbeitsweise zur Ausführung unter der graphischen Shell entwickeln. Jede Konsole-orientierte Anwendung kann man durch Austausch der Eingabe- und Ausgabe-Operationen für die graphische Oberfläche umarbeiten. Man kann auch eine Anwendung schreiben, die ohne jede Eingabe oder Schirmausgabe eine dateiorientierte Arbeit erledigt (falls man so etwas wirklich haben möchte). Praktisch interessiert man sich aber in erster Linie für Dialogprogramme. Wenn die ANSI-C-Funktionen ausreichen, kann man einfacher eine Konsole-orientierte Anwendung schreiben. In diesem Einführungsabschnitt wird die Struktur von dialogorientierten Programmen für die graphische Oberfläche dargestellt und diskutiert.
34
3 Die Struktur dialogorientierter
Windows-Programme
Der Startpunkt eines Windows-C-Programms ist die Funktion WinMain. Sie hat einen Prototyp, der auf die Bedürfnisse von Dialogprogrammen ausgerichtet ist. Sie kann, wie man dies in einem C-Programm erwartet, andere Funktionen aufrufen, diese wieder andere usw. Der Dialog entwickelt sich wie gesagt zeitlich in Eingaben (über Maus und Tastatur) und Bildschirmausgaben. Eine Dialoganwendung unterhält Fenster auf dem Schirm. Eine einzelne Dialogeingabe richtet sich immer an ein Fenster. Ein Mausklick betrifft das Fenster, auf das gerade der Mauszeiger weist. Zum Beispiel das Anklicken eines Menüpunktes gelangt an das Fenster, in dem das Menü angezeigt ist. Die Tastatureingabe eines Buchstabens betrifft auch zu jedem Zeitpunkt ein vorbestimmtes Fenster. Man sagt, dieses Fenster habe den "Fokus". In einer richtig programmierten Anwendung weiß die Anwenderin auf Grund von graphischen Hervorhebungen oder durch den Tastencursor (der in Windows "Caret" genannt wird) ohne lange Erklärungen, welches Fenster gerade den Fokus hat. (Es gibt Ausnahmen, nämlich Programmsituationen, in denen die Position des Fokus ohne Bedeutung ist und in denen die Anwenderin deshalb auch nicht zu wissen braucht, wo er ist. Dann muss die Position auch nicht erkennbar sein.) Was heißt es intern, dass eine Eingabe sich an ein Fenster richtet? Programmiertechnisch wendet sich eine Eingabe selbstverständlich immer an eine Funktion, die auf die Eingabe reagieren soll. Man kann sie als die Zielfunktion der Eingabe bezeichnen. Dabei kann es sich (wie auch analog im ANSI-C) um -
eine vom System beziehungsweise Compilerpaket bereitgestellte Bibliotheksfunktion handeln, die in der Anwendung benutzt wird. (Im ANSI-C ist bei Eingaben etwa s c a n f eine solche Funktion). Unter Windows wendet sich zum Beispiel eine Mausaktion, mit der die Größe eines Fensters verändert wird (Ziehen am Fensterrand), inhaltlich an eine Systemfunktion, denn die Anpassung der Fenstergröße an eine solche Operation ist in Windows standardisiert programmiert. Man braucht die hier speziell zuständige Funktion nicht einmal explizit in dem WindowsProgramm aufzurufen. Sie wird auf Grund eines sinnvollen Mechanismus auch ohnedies angesprochen.
-
Oder es handelt sich um eine individuell programmierte Funktion der Anwendung. Wenn zum Beispiel ein Fenster eine Menüleiste hat, gehören zu den einzelnen Menüpunkten individuell in der Anwendung vorgesehene Konsequenzen. Wenn also ein Menüpunkt gedrückt wird, muss eine Funktion tätig werden, in der die vorgesehenen Reaktionen programmiert sind.
Obgleich es also eine Funktion ist, die die Reaktion auf die Eingabe übernehmen muss, ist die Ausdrucksweise, nach der sich eine Eingabe an ein Fenster richtet,
3 Die Struktur dialogorientierter Windows-Programme
35
sinnvoll. Ein Fenster ist nicht nur eine Zeichnung auf dem Schirm.1 Es kann sogar überhaupt keine Zeichnung sein, weil es gerade unsichtbar ist. Es besteht vielmehr intern zum Ersten aus einer Datensammlung. Sie gibt zum Beispiel an, ob das Fenster sichtbar ist und wo auf dem Schirm es sich befindet. Die Daten eines Fensters werden zentral im Datenbereich des Systems gehalten, nicht im Datenbereich der Anwendung. Zu jedem Fenster gehört zweitens eine fest daran gebundene Funktion.2 Sie heißt die zugehörige Fensterprozedur und ist konstitutiv für die Fensterdefinition. Sie erhält alle Eingaben, die das Fenster betreffen. Die Darstellungen der (Zuspräche verwenden sonst den Begriff der Prozedur nicht. Eine Funktion, die die Eingaben für ein Fenster entgegennimmt, wird in der Windows-Literatur aber als Prozedur bezeichnet. Auf einen Teil der Eingaben, nämlich diejenigen, die das spezielle Verhalten des Fensters in der Anwendung festlegen, reagiert die Fensterprozedur selbst. Die anderen Eingaben, für die es Systemfunktionen mit standardisierten Reaktionen gibt (zum Beispiel das Verändern der Größe eines Fensters durch eine Mausaktion), gibt sie an diese weiter. Dazu ruft sie in Befolgung einer verbindlichen Vorschrift für Fensterprozeduren eine spezielle Funktion mit dem Namen DefWindowProc auf, der sie die Eingabe als Parameter übergibt. Diese Funktion führt die standardisierten Arbeiten selbst aus oder leitet die Eingaben ihrerseits weiter. Die individuelle Logik eines Programms liegt überwiegend in den Fensterprozeduren. Das standardisierte Verhalten der Fenster wird von DefWindowProc vermittelt. Außer den Eingaben gibt es auch andere Mitteilungen an ein Fenster beziehungsweise an die zugehörige Fensterprozedur. Der Grund liegt darin, dass Eingaben bei ihrer Verarbeitung oft neue Mitteilungen erzeugen, diese wieder neue usw. Die weitaus meisten Mitteilungen, die eine Fensterprozedur erhält, entstehen auf diese Weise erst sekundär als Folgenachrichten. Selbstverständlich muss der Prozess einmal zu Ende sein. Eingaben sind nur eine besonders wichtige Form und eine primäre Form von Mitteilungen. Allgemein werden alle Mitteilungen an ein Fenster datenorganisatorisch durch den Begriff der "Nachricht" (message) erfasst. Um ein Beispiel anzuführen: Jeder Tastendruck ist für das System eine eigene Nachricht. Sie drücken also zum Beispiel die a-Taste. Diese Nachricht geht an
1) Es ist ein Objekt im Sinne der objektorientierten Programmierung. Ein solches Objekt besteht aus einer Datensammlung und aus daran gebundenen Funktionen, die mit den Daten arbeiten können. Fenster sind freilich in C programmierte Gebilde, und in C gibt es formal den Begriff des Objektes nicht (beziehungsweise er ist dort in einem anderen Sinn belegt). In jeder inhaltlichen Hinsicht ist ein Fenster aber ein Objekt. 2) In C + + würde man sie eine Elementfunktion des Fensters nennen.
36
J Die Struktur dialogorientierter
Windows-Programme
die Fensterprozedur des Fensters, das den Fokus hat. Aus ihr geht noch nicht hervor, welcher Buchstabe eingegeben worden ist, denn dazu muss ausgewertet werden, ob ein großes oder kleines a gemeint ist. Das hängt davon ab, ob die shift-Taste gedrückt war. Wenn sie gedrückt wird, gibt es darüber ebenfalls eine Nachricht. Aus beiden Informationen zusammen wird die Nachricht "a" oder "A" als sekundäre Nachricht erzeugt und an das Fenster gegeben. (Der tatsächliche Arbeitsvorgang ist noch etwas umfangreicher, weil auch noch andere Sondertasten wie Strg oder alt zusätzlich gedrückt sein können.) Ein Fenster kann Nachrichten erhalten, für die es sich in keiner Weise interessiert. Zum Beispiel gibt es Eingabefenster, die für die Aufnahme von Zahlenwerten bestimmt sind. Wenn man die a-Taste drückt, während ein solches Fenster den Fokus hat, bekommt die Fensterprozedur die zugehörige Nachricht. Sie bekommt ferner wie eben erklärt die Nachricht über den damit erzeugten Buchstaben. Aus ihrer Sicht sind diese Nachrichten aber nicht sinnvoll, sie sind nichts anderes als Eingabefehler. Die Fensterprozedur reagiert technisch auf sinnlose oder uninteressante Nachrichten, indem sie sie an DefWindowProc weitergibt. Diese Funktion weiß, auf welche Nachrichten es Reaktionen des Systems geben muss. Bei Nachrichten, die sie nicht in ihrem Katalog hat, gibt sie die Regie einfach zurück an die Fensterprozedur. Das ist auch der Fall, wenn die Nachricht in einem "a" oder "A" besteht. Die Fensterprozedur betrachtet die Nachricht nun als verarbeitet und gibt auch ihrerseits die Regie zurück. Es gibt Nachrichten, die keine Eingabenachrichten und auch keine sekundären Nachrichten sind, die sich durch Rückverfolgung auf Eingaben zurückführen lassen. Ein Beispiel ist: Wenn ein Programm startet, wird es ein Fenster einrichten. Die zugehörige Fensterprozedur erhält eine Anfangsnachricht, wenn das Fenster datenorganisatorisch gebildet worden ist und ehe es auf dem Schirm angezeigt wird. Die Nachricht teilt der Fensterprozedur mit, dass das Fenster jetzt existiert. Sie kann als Auslöser benutzt werden, um Initialisierungsarbeiten für das Fenster zu veranlassen. Es gibt zwei Gruppen von Möglichkeiten, um auf ein Fenster einzuwirken: 1. Die Nachrichten. Alle Nachrichten für ein Fenster gelangen wie gesagt an die Fensterprozedur. 2. Systemfunktionen. Man kann ein Fenster nicht nur über Nachrichten beeinflussen. Neben der Fensterprozedur, die die Nachrichten für das Fenster verarbeitet oder weitergibt, gibt es noch andere, im System vordefinierte Funktionen, die auf ein Fenster einwirken können. Viele von ihnen können beliebige Fenster ansprechen, andere einen großen Teil, zum Beispiel alle sogenannten Kindfenster. Wenn man etwa ein Fenster zerstören (seine Lebensdauer beenden) oder wenn man es unsicht-
3 Die Struktur dialogorientierter Windows-Programme
37
bar machen will, ruft man jeweils eine dafür zuständige Bibliotheksfunktion auf. Man schickt keine Nachricht an das Fenster. Damit ist erst einmal etwas gesagt, auf welche Weise sich die Eingaben des Dialoges an ein Fenster richten. Sie sprechen die Fensterprozedur an. Dass auch die Schirmausgaben des Dialogbetriebes Fenster benutzen, ist inhaltlich eine ziemlich einfache Aussage. Sie bedeutet nur, dass es keine Ergebnisausgabe einer Anwendung unmittelbar auf den Schirmhintergrund gibt. Man bildet Text oder Graphik in Fenstern ab. Eine Anwendung kann über entsprechenden Programmcode veranlassen, dass ein Fenster in festen Abständen Zeittaktnachrichten von einem Impulsgeber bekommt. Diese Nachrichten werden zur Auslösung von Reaktionen, zum Beispiel für das Nachstellen des Zeigers einer Uhr, benutzt. Sie werden programmiertechnisch wie Maus- oder Tastatureingaben behandelt. Wie wird nun die auf S. 33 angegebene Dialogschleife programmiertechnisch realisiert? Wenn eine neue Eingabe für eine Anwendung eintrifft, nimmt das System sie zur Kenntnis und notiert die Details in einer Datenstruktur. Wenn zu dieser Zeit eine Anwendung rechnet, das heißt wenn die Maschine nicht gerade in einem Wartezustand ist, wird die arbeitende Funktion kurzzeitig unterbrochen. Vorübergehend wird ihr der Prozessor entzogen. Davon merkt sie nichts, und sie hat nichts damit zu tun. Sobald das System die Arbeiten an der Entgegennahme und Einspeicherung der Eingabe beendet hat, stellt es den alten Zustand wieder her. Es gibt die Regie an die unterbrochene Funktion zurück, die genau fortfährt, wo sie unterbrochen worden ist. Eine Anwenderin vor dem Schirm bemerkt die Unterbrechung nicht, weil die dafür verbrauchte Zeitspanne unterhalb der Wahrnehmungsgrenze liegt. Das Zeitzuteilungsverfahren des Systems gibt den gestarteten Anwendungen in einem Reihumverfahren "Zeitscheiben" Rechenzeit. Eine Anwendung rechnet während ihrer Zeitscheiben, als ob sie die Maschine alleine hätte. Die an andere Anwendungen verteilten Zeitscheiben existieren für sie nicht. Man braucht für das Verhalten einer Anwendung die Existenz der übrigen Anwendungen nur in besonderen Fällen zu beachten. (Zum Beispiel sind absichtlich programmierte Interaktionen zwischen Prozessen möglich.) Eine Anwendung, die an der Verarbeitung einer Nachricht arbeitet, rechnet in diesem Sinne allein und ungestört - mit ihren eigenen Funktionen beziehungsweise mit Bibliotheksfunktionen -, bis sie die Arbeiten erledigt hat, die als Reaktion auf die Nachricht auszuführen sind. Wir wollen sagen, eine Anwendung sei an einem Haltepunkt, wenn sie eine Nachricht vollständig verarbeitet hat. Um sich beschäftigen zu können, braucht sie die nächste Nachricht.
38
J Die Struktur dialogorientierter Windows-Programme
Das System führt für jede gestartete Anwendung eine zentrale Warteschlange, die die Eingaben für sie (das heißt für ihre Fenster) aufnimmt. Genau gesagt führt das System eine eigene Schlange für jedes Thread einer Anwendung. Die Rechnung auf Threads (parallele Rechenlinien) aufzugliedern bringt einen Zeitvorteil, wenn der Computer mehrere Prozessoren hat. Wir beschreiben in diesem Buch keine Programme mit mehreren Threads und sprechen gewöhnlich einfach von der Warteschlange der Anwendung. Wenn eine Anwendung nach dem Abschluss der Arbeiten an einer Nachricht einen Haltepunkt erreicht, sind alle in der Zwischenzeit eingetroffenen Nachrichten in die Schlange eingefügt. Die Anwendung entnimmt ihr die nächste Nachricht und verarbeitet sie. Ist die Schlange leer, gerät sie in einen Wartezustand. Die Eingaben sind in der Nachrichtenschlange - man kann sagen, selbstverständlich - in der zeitlichen Folge ihres Eintreffens angeordnet und werden auch in dieser Folge entnommen. Wenn ein Wort über die Tastatur eingegeben wird, werden nicht etwa die Buchstaben durcheinandergeworfen. Die Programmiererin kann sich zu jedem Zeitpunkt darauf verlassen, dass die zeitliche und damit die kausale und logische Folge der Eingaben bei der Verarbeitung gesichert ist.1 Das System leitet fast keine sekundären Nachrichten für die Anwendung über die Nachrichtenschlange. Fast alle werden bei der Entstehung rekursiv direkt an die zuständige Fensterprozedur gegeben, ohne den Weg über die Warteschlange zu nehmen. Sie sind bereits verarbeitet, wenn ein Haltepunkt erreicht wird. Eine Eingabe ist ganz - nämlich mit den als Folge vom System generierten Nachrichten - verarbeitet, ehe die Arbeit an der nächsten beginnt. Auch die anderen intern vom System erzeugten Nachrichten (zum Beispiel die erwähnte Anfangsnachricht an ein Fenster, wenn es erzeugt ist) werden der Fensterprozedur direkt übermittelt. Aus der geschilderten Regelung folgt: - eine Eingabe, die eine lange Rechenschleife auslöst, - oder die eine sehr lange Kette von sekundären Nachrichten generiert, macht die Anwendung für die Dauer der Verarbeitung unansprechbar. Es gibt mehrere programmiertechnische Möglichkeiten für die Entnahme der jeweils nächsten Nachricht aus der Schlange. Unter ihnen befindet sich eine Standardlösung, die fast in jedem Programm zu lesen ist. Dabei wird die Bibliotheksfunktion Getmessage in einer Schleife eingesetzt, die im Detail nach dem Einzelfall gestaltet wird. Diese Schleife, die sogenannte Hauptnachrichtenschleife der Anwendung, wird regelmäßig in der Funktion WinMain
1) Zeittaktnachrichten (WM_TIMER) haben geringere Priorität als Maus- und Tastatureingaben. Die logische Folge wird dadurch nicht beeinträchtigt.
3 Die Struktur dialogorientierter Windows-Programme
39
positioniert. Die Schleife heißt //awpinachrichtenschleife, weil es oft noch weitere Nachrichtenschleifen in einer Anwendung gibt. In der Positionierung der Hauptnachrichtenschleife in WinMain liegt eine aus der Aufgabenstruktur von Dialogprogrammen ziemlich zwangsläufig folgende Standardisierung. Die Quelltextfassungen von WinMain sind sich auch sonst in allen Dialogprogrammen sehr ähnlich. Gewöhnlich wird nach ein paar Einleitungsschritten zuerst ein Hauptfenster erzeugt und angezeigt. Es bildet den zeichnerischen Hintergrund der Bildschirmdarstellungen im Programm. Dann folgt manchmal schon die Hauptnachrichtenschleife, an die sich die return-Anweisung für das Ende der Anwendung anschließt. In der Programmierpraxis werden in einer Textvorlage für WinMain oft nur einige Optionen ausgetauscht, die teilweise durch ein einziges Wort ausgedrückt werden können. Eine vereinfachte, etwas unvollständige Gliederung hat man mit dem Quelltext: WinMain {
( ... )
M S G msg;
Bilde Bilde
das evtl.
// Messagestruktur
Hintergrundfenster noch ein Fenster
while (GetMessage (&msg, NULL, 0, 0)) //NachrichtenDispatchMessage (&msg); // schleife return msg.wParam;
} Pseudocode wird hier und an anderen Stellen des Buches kursiv gedruckt. In W i n M a i n wird eine Struktur msg vom Typ MSG (Nachricht) definiert. Eine Nachricht besteht aus mehreren Komponenten. GetMessage fragt beim System an, ob eine Nachricht für die Anwendung da ist. Die Funktion wartet, bis das der Fall ist, und kehrt erst dann zurück. Die Nachricht ist dann in msg eingetragen. -
-
GetMessage gibt einen Wert ungleich FALSE zurück, wenn die Anwendung weiter arbeiten soll. In diesem Fall wird die Schleife weiter durchlaufen und DispatchMessage aufgerufen. Dieses ist auch eine Windows-Bibliotheksfunktion, die die Nachricht an das zuständige Fenster (an dessen Fensterprozedur) gibt. Wenn die Fensterprozedur zu Ende gerechnet hat, kehrt DispatchMessage zurück. Ein neuer Haltepunkt ist erreicht. GetMessage wird von Neuem aufgerufen. Wenn die Anwendung - gewöhnlich auf Grund einer entsprechenden Eingabe - beendet werden soll, gibt GetMessage FALSE zurück. Die
40
3 Die Struktur dialogorientierter
Windows-Programme
Schleife wird verlassen. Es wird nur noch ein Element der Nachricht, nämlich msg. wParam, als Rückgabewert der Anwendung an das System zurückgegeben. Die Nachrichtenschleife ist hier noch unrealistisch reduziert. Im Allgemeinen umfasst sie einen Abschnitt von vielleicht 10 Zeilen Quellcode. Dies ist C-Code und von der Programmiererin vorzugeben. Meistens wird eine von wenigen (etwa 3 oder 4) unterschiedlichen Formulierungen verwendet, die man wie Bausteine sinngemäß fertig vorformuliert zur Verfügung hat. Eine von ihnen fügt man am Ende von WinMain ein. Die Anwenderin oder Entwicklerin, die die Leistungen des Windows-Systems praktisch beobachtet, könnte den Eindruck gewinnen, dass eine Art gleichberechtigter Partnerschaft mit einem Ping-Pong-Verhalten zwischen der Anwendung und dem System besteht. Mal rechnet die Anwendung, dann das System usw. Solche Bilder haben immer ihre Schwächen und andererseits sind sie manchmal nicht vollständig falsch. Sie können aber leicht irrig gedeutet werden. Im vorliegenden Fall mag eine korrigierende Bemerkung angebracht sein, auch wenn sie nur eine Selbstverständlichkeit zum Ausdruck bringt: Ihre Anwendung spielt die allein dominierende Rolle. Sie wird sich genau so verhalten, wie das Ihr Quellcode vorgegeben hat. Das System entzieht ihr zwar zwischenzeitlich einmal die Regie, um Eingaben anzunehmen oder um im Multitasking-Betrieb andere Anwendungen zu versorgen. Das hat aber nur Konsequenzen für die Arbeitsgeschwindigkeit. Die Systemfunktionen, die aufgerufen werden, nachdem eine Fensterprozedur eine Nachricht an DefWindowProc weitergegeben hat, realisieren fest definierte Vorgaben. Die dabei erzeugten sekundären Nachrichten und ihre Verarbeitung sind ein Teil dieses vorgegebenen Verhaltens. Es ist im Prinzip nicht anders, als wenn ein ANSI-C-Programm printf aufruft. Der von Ihnen geschriebene Programmcode legt die Eigenschaften der Anwendung genau so exakt fest wie das im ANSI-C in einer Konsole-orientierten Umgebung der Fall ist.
S Die Struktur dialogorientierter
3.2
Windows-Programme
41
Das Programm Kuchen
Zur Illustration wird auf den folgenden Seiten, die als"Quelltext Kuchen" gekennzeichnet sind, ein einfaches Programm mit einer typischen Funktion WinMain und der darin enthaltenen Nachrichtenschleife im Quelltext abgedruckt und in einer vorläufigen Weise erklärt. Der Quelltext Kuchen führt zwanglos die Begriffe der Nachricht, der Fensterprozedur, der Dialogprozedur, der Fensterklasse und der Hauptnachrichtenschleife ein. Nachdem das Beispiel erörtert ist, sind Ihnen somit später eine Anzahl grundlegender Begriffe schon anschaulich bekannt und die Probleme der Stoffgliederung, auf die einleitend hingewiesen wurde, sind dann demnach etwas entschärft.
um
jfè Kinderparty iKufchenvertelking
Kinder
1
Kuchenstücke
3
1»
G
1
Stücke je Kind
OK
1
Stücke übrig
Bild 3.2.1 Stücke Kuchen je Kind auf einer Party Im Hintergrund wird ein Fenster mit der Titelleiste "Kinderparty" und einem Icon erzeugt. Davor wird ein weiteres Fenster gebildet. Es hat die Titelleiste "Kuchenverteilung". Dieses ist ein sogenanntes Dialogfenster. So werden Fenster dann genannt, wenn sie Dialogelemente zur Datenanzeige und Dialogeingabe enthalten. Das Dialogfenster zeigt in der oberen Reihe zwei Eingabefelder. In eines ist die Anzahl Kinder, in das andere die Anzahl verfügbarer Kuchenstücke einzugeben. Wenn man mit der Maus den rechts eingezeichneten OK-Knopf drückt Textfortsetzung auf S. 45
42
3 Die Struktur dialogorientierter
Textliste 3.2.1
Windows-Programme
Quelltext Kuchen Quelldatei prog.c:
#include #include "prog.h" LRESULT CALLBACK HProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam); BOOL CALLBACK DlgProc(HWND hDIg, UINT message, WPARAM wParam, LPARAM IParam); int CALLBACK WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR pszCmdLine, int nCmdShow)
{ HWND hwndH, hDIg = NULL; MSG msg; WNDCLASS wndclass;
// Fensterhandle // Messagestruktur // Fensterklassenstruktur
hwndH = FindWindow(NULL, "Kinderparty"); if(hwndH != NULL) { ShowWindow(hwndH, SW_RESTORE); SetForegroundWindow(hwndH); return 0;
wndclass.style 0; wndclass.lpfnWndProc HProc; wndclass.cbCIsExtra 0; wndclass. cbWnd Extra 0; wndclass. hl nstance hlnstance; wndclass.hlcon Loadlcon (hlnstance, "Proglcon"); wndclass.hCursor LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush (RGB(255, 255, 255)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "HCIass"; RegisterClass (Swndclass); hwndH = CreateWindow ( "Hclass", // Klassenname "Kinderparty", // Titeltext WS_OVERLAPPEDWINDOW, // Fensterstil CW_USEDEFAULT, CW_USEDEFAULT, // FensterCWUSEDEFAULT, CW_USEDEFAULT, // Koordinaten NULL, // Elter NULL, II individuelles Menu hlnstance, II Programminstanz NULL); II betrifft MDI-Fenster ShowWindow (hwndH, SW_SHOWMAXIMIZED);
i Die Struktur dialogorientierter Windows-Programme
Fortsetzung:
Quelltext Kuchen, Quelldatei prog.c
hDIg = CreateDialog(hlnstance, "#100", hwndH, DlgProc); while (GetMessage (&msg, NULL, 0, 0)) { if(hDlg == NULL || NsDialogMessage(hDlg, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg);
}
}
return msg.wParam;
LRESULT CALLBACK HProc(HWND hWnd, UINT message, W P A R A M wParam, LPARAM IParam)
{
switch (message) {
}
case WM_CLOSE: PostQuitMessage(O); return 0;
return DefWindowProc (hWnd, message, wParam, IParam);
BOOL CALLBACK DlgProc(HWND hWnd, UINT message, W P A R A M wParam, LPARAM IParam)
{
BOOL bZahl; int ¡Kinder, ¡Kuchenstuecke; static HBRUSH hbrWeiss, hbrBlassgruen; switch (message) { case WMJNITDIALOG: hbrBlassgruen = CreateSolidBrush( RGB(192, 220, 192)); hbrWeiss = CreateSolidBrush( RGB(255, 255, 255)); return 1; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: ¡Kinder = GetDigltemlnt (hWnd, 101, SbZahl, TRUE);
43
44
3 Die Struktur
Fortsetzung:
dialogorientierter
Windows-Programme
Quelltext Kuchen, Quelldatei prog.c if (bZahl == FALSE || ¡Kinder == 0) { SendMessage (hWnd, WM_NEXTDLGCTL, (WPARAM)GetDlgltem(hWnd, 101), 1); return 1;
}
iKuchenstuecke = GetDIgltemlnt (hWnd, 102, ÄbZahl, TRUE); if (bZahl == FALSE) { SendMessage (hWnd, WMNEXTDLGCTL, (WPARAM)GetDlgltem(hWnd, 102), 1); return 1 ;
}
}
SetDlgltemlnt(hWnd, 201, iKuchenstuecke / ¡Kinder, TRUE); SetDlgltemlnt(hWnd, 202, iKuchenstuecke % ¡Kinder, TRUE); return 1 ;
return 0; case WMCTLCOLORSTATIC: { int id; id = GetDlgCtrllD((HWND)IParam); switch (id) { case 201: case 202: SetBkColor((HDC)wParam, RGB(255, 255, 255)); return (BOOL) hbrWeiss; default: SetBkColor((HDC)wParam, RGB(192,220,192)); return (BOOL) hbrBlassgruen;
}
}
}
case WM_CTLCOLORDLG: return (BOOL) hbrBlassgruen; } // switch (message) return 0;
3 Die Struktur dialogorientierter Windows-Programme
Fortsetzung:
45
Quelltext Kuchen Ressourcedatei progres.rc:
#include #include "prog.h" Proglcon ICON
standard.ico
100 DIALOG 10, 20, 180, 100 STYLE DS_MODALFRAME | WS_POPUP CAPTION "Kuchenverteilung" FONT 10, "System" BEGIN -1, CTEXT "Kinder", CTEXT "Kuchenstücke", -1, EDITTEXT 101, 2 4 , 3 2 , 2 5 , 1 2 ,
| WS_VISIBLE | WS_CAPTION
24, 20, 25, 12 59, 20, 50, 12 ES_NUMBER | ES_MULTILINE | ESRIGHT I WSGROUP ES_NUMBER | ES_MULTILINE | EDITTEXT 102, 7 2 , 3 2 , 2 5 , 1 2 , ES_RIGHT I W S GROUP DEFPUSHBUTTON "OK" IDOK, 120, 44, 30, 10, WS_GROUP 201, 2 4 , 5 4 , 2 5 , 1 2 , WS_BORDER RTEXT "", RTEXT "", 202, 72, 54, 25, 12, W S BORDER 24, 70, 25, 20 CTEXT "Stücke\nje Kind", -1, 72, 70, 25, 20 CTEXT "Stücke\nübrig", -1, END
oder wenn man die Entertaste drückt, wird in dem einen der beiden unteren Felder die Anzahl ganzer Kuchenstücke je Kind ausgegeben. Das andere Feld sagt, wieviel Stücke dann noch übrig sind. In die Eingabefelder können nur ganze, vorzeichenfreie Zahlen eingetragen werden. Um sie anzusteuern, kann man sie mit der Maus anklicken oder mit der tab-Taste aufsuchen. Die im Dialogfenster "Kuchenverteilung" angezeigten Ein- und Ausgabefelder sind alles Dialogelemente. Sie gehören speziellen Fenstertypen an, die vom Windows-System fertig definiert zur Verfügung gestellt werden. Zu ihrer vorgegebenen Definition gehören auch die Fensterprozeduren, die im System und in der Anwendung gegenwärtig sind, ohne dass man sie in den eigenen Quellcode aufnehmen muss. Fast die gesamte Eingabe in eine Windows-Anwendung und fast die gesamte Schirmausgabe wickeln sich über Dialogelemente ab. Zusätzlich gibt es vor allem nur noch die Eingabe über Menüs und die Ausgabe von Graphik auf einen Fensterhintergrund. Eine Menüleiste mit ihren aufklappbaren Pull-down-Menüs ist funktional in jeder Beziehung dasselbe wie ein Dialogelement, aber syntaktisch etwas anderes. Sie ist kein eigenes Fenster, sondern ein Teil (ein Element) des Fensters, in dem sie dargestellt wird. Auch ein
46
3 Die Struktur dialogorientierter Windows-Programme
Systemmenü, das über den Systemmenüknopf in der Titelleiste eines Fensters aufgeklappt werden kann, ist kein Dialogelement. Einige Dialogelemente können nach Wahl der Entwicklerin mit oder ohne Rand gezeichnet werden. Die Eingabe- und Ausgabefelder für die Zahlen haben einen Rand. Die erklärenden Beschriftungen "Kinder", "Kuchenstücke" usw. sind Dialogelemente ohne Rand. Die Beschriftung "OK" des Schaltknopfes ist kein eigenes Dialogelement. Sie gehört als Text zu dem Schaltknopf. Es gibt eine ganze Vielzahl von Typen von Dialogelementen. Wenn man so will, kann man drei von ihnen als programmiertechnische Grundausstattung in der Windows-Entwicklung ansehen. Sie kommen in fast jedem Dialogfenster vor. Nicht alle Aufgaben von Dialogfenstern kann man mit ihnen programmieren, aber doch schon einen ansehnlichen Teil. Die Typen kommen auch in dem Dialogfenster "Kuchenverteilung" und in dem Dialogfenster auf S. 22 vor. Es sind: -
-
-
die "statischen Textfenster". Sie dienen der Anzeige von Text einschließlich Zahlen. Das Programm kann den Text ändern. Die Anwenderin kann ihn nicht ändern. die Editfelder. Sie dienen in erster Linie als Eingabe-Elemente für Text. Sie können auch allein für die Textanzeige verwendet werden, doch ist das kein typischer Einsatz. Die Schaltknöpfe. Sie anzudrücken löst eine in der Anwendung programmierte Reaktion aus.
Für den Hintergrund einer Anwendung verwendet man oft ein Fenster ohne Dialogelemente, das schirmfüllend gezeichnet wird oder werden kann. (Den Wechsel zwischen schirmfüllender und normaler Größe veranlasst die Anwenderin durch das Andrücken des Maximierungsknopfes in der Titelleiste.) Es erhält ein Programmicon und einen Titeltext. Oft hat es eine Menüleiste. Die Dialogfenster werden im Vordergrund, das heißt näher an der Anwenderin dargestellt. Dieser Fall wird auch in Kuchen gezeigt. Das Hintergrundfenster "Kinderparty" hat Icon und Titeltext. Eine Menüleiste wird in diesem Fall nicht gebraucht. Das im Vordergrund darauf abgebildete Dialogfenster "Kuchenverteilung" enthält die Dialogelemente. Diese Anwendung käme aus, wenn sie ein einziges Fenster als Hintergrund für die Dialogelemente hätte, das die verteilten Aufgaben zusammenfassen würde. Es hätte dann also zugleich -
die Knöpfe aus der Titelleiste des Fensters "Kinderparty" und die Dialogelemente des Dialogfensters.
Aufgaben mit während der Arbeit wechselnden Dialogfenstern brauchen aber ein gleichbleibendes, gemeinsames Hintergrundfenster. Auch wenn es nur ein
3 Die Struktur dialogorientierter Windows-Programme
47
Dialogfenster gibt, kann es programmiertechnische Gründe geben, Hintergrund und Dialog zu trennen. Für die Fensterplanung nach Aufgaben hat man also die Standardgliederung : -
Hintergrundfenster, Dialogfenster, Dialogelemente.
Die zugehörige Programmiertechnik wird hier demonstriert. (Sie finden später noch Aufgaben mit einem kombinierten Hintergrund-Dialogfenster programmiert.) Ein Fenster gehört stets einer Klasse an. Die Klasse ist der Objekttyp des Fensters und bestimmt die Fensterprozedur und einige Datenparameter. Das Icon, das links oben in der Titelleiste eines Fensters angezeigt wird, wird durch die Klasse vorgegeben. Die Entwicklerin kann eigene Klassen für ihre Anwendung definieren. Im Übrigen enthält das Windows-System vordefinierte Klassen. Die Klassen für die Dialogelemente wurden eben (S. 45) und auch schon früher erwähnt. Dabei wurde die Formulierung gewählt, dass die Dialogelemente "Fenstertypen" angehören. Der im Zusammenhang mit der Sprachsyntax übliche Ausdruck ist Klasse. Eine von der Entwicklerin definierte Klasse wird "privat" genannt. Die Bezeichnung privat wird allgemein in der Windows-Programmierung benutzt, um Definitionen in der Anwendung gegen Definitionen, die das System bereits bereithält, abzugrenzen. Das Hintergrundfenster "Kinderparty" ist aus einer in der Anwendung definierten, privaten Klasse. Es gibt eine im System für die Verwendung als Dialogfenster vordefinierte Klasse mit einer zugehörigen Fensterprozedur. Die Klasse heißt #32770, ihre Fensterprozedur DefDlgProc. Ebenso wie bei den im System definierten Dialogelementen braucht man beide nicht im Quellcode aufzuführen. Sie sind auch ohnedies im System anwesend. Das Dialogfenster "Kuchenverteilung" gehört der Klasse #32770 an. Mit ihr verhält es sich so: Eine Nachricht besteht aus mehreren Komponenten. Darunter befindet sich immer die Angabe eines Fensters. Es wird durch sein Handle (Typ HWND) gekennzeichnet. Bei einer Tastatureingabe findet sich in diesem Nachrichtenelement die Mitteilung, welches Fenster den Fokus hat. Bei einer Mauseingabe ist das Fenster eingetragen, das mit der Maus angeklickt worden ist. Die Funktionen der Hauptnachrichtenschleife entscheiden, an welches Fenster eine Nachricht gegeben wird. Standardmäßig ist das einfach das Fenster, das in der Nachricht angegeben ist. Bei Dialogfenstern der Klasse #32770 gibt es aber Ausnahmen. Die spezielle Eignung von #32770 und ihrer Fensterprozedur DefDlgProc für Dialogfenster wird durch eine besondere Lenkung des Nachrichtenflusses ermöglicht. Ein
48
3 Die Struktur dialogorientierter
Windows-Programme
Dialogfenster mit der Fensterprozedur Def DlgProc und die darin enthaltenen Dialogelemente werden von der Nachrichtenschleife als eine organisatorische Einheit angesehen. Sie sortiert die Nachrichten und gibt sie je nach sachlicher Zuständigkeit an ein Dialogelement oder an die Fensterprozedur Def DlgProc des Dialogfensters. Ein Dialogfenster kann eine Menüleiste und/oder ein Systemmenü haben. Das Fenster "Kuchenverteilung" hat kein Menü. In die gegenwärtige Übersicht wird die Nachrichtenlenkung für Menüs in Dialogfenstern nicht einbezogen. Manche Dialogfenster haben nur Dialogelemente, die allein der Anzeige dienen und nie eine Eingabe zur Verarbeitung erwarten. Wenn die Anwenderin hier den Fokus über Mausklick oder auf andere Weise an das Dialogfenster beziehungsweise an ein zugehöriges Dialogelement gibt, ist es egal, wo er dort genau verbleibt. Kein Dialogelement erwartet eine Tastatureingabe und wenn es eine bekommt, wird es sich nicht darum kümmern. Auch das Dialogfenster selbst erledigt ("verarbeitet") eine Tastatureingabe einfach, indem es sie übergeht. Um also diese Konstruktion eines reinen Anzeigedialoges zu erzielen, braucht man keine besondere Dialogfensterklasse, kann #32770 aber verwenden. Anders ist es bei den Dialogfenstern, deren Dialogelemente Tastatureingaben verarbeiten können. Diese Dialogfenster, die in der einen oder anderen Form Eingaben über ihre Dialogelemente erwarten, stellen zahlenmäßig den Regelfall dar. Ein solches Fenster ist das Fenster "Kuchenverteilung". Aus der Sicht der Anwenderin bildet das Dialogfenster hier eine Einheit mit den Dialogelementen. Sie arbeitet zu irgendeinem Zeitpunkt in diesem Dialog als Ganzem. Hier muss sichergestellt werden, dass es nach außen als eine Einheit erscheint. Dazu müssen alle Nachrichten an das jeweils sachlich zuständige Einzelfenster gehen. Dies leistet eine passend formulierte Hauptnachrichtenschleife zusammen mit der Fensterklasse #32770 und seiner Fensterprozedur Def DlgProc. Wird so programmiert, hat bei fachgerechter Konstruktion immer ein Dialogelement den Fokus. Wenn das Dialogfenster durch eine Aktion der Anwenderin den Fokus bekommt, gibt es ihn sofort an ein Dialogelement ab. Es benötigt ihn auch nicht selbst. Die Eingaben von Zeichenketten und Zahlen richten sich an die Dialogelemente, nicht direkt an das Dialogfenster. Die Hauptnachrichtenschleife gibt die Tastaturnachrichten an das Element, das gerade den Fokus hat. So kann man in Kuchen Zahlen in die Felder "Kinder" und "Kuchenstücke" eingeben. Wenn eines dieser Elemente den Fokus hat und man eine Buchstabentaste drückt, geht auch diese Nachricht an die Fensterprozedur des Elementes. Nur wird sie dort einfach übergangen. Dies ist die standardmäßige Weitergabe der Nachrichten über die Nachrichtenschleife. Es gibt aber besondere Regeln:
3 Die Struktur dialogorientierter Windows-Programme
49
Def DlgProc übernimmt übergreifende Standardarbeiten, die nicht nur ein einzelnes Dialogelement betreffen. Dazu gehören: -
-
Wenn die Anwenderin die tab-Taste drückt, gelangt diese Nachricht an Def DlgProc, egal wo gerade der Fokus ist. Sie wertet sie aus, indem sie den Fokus einem anderen Dialogelement zuteilt. Mit der tab-Taste kann man in einem solchen Dialogfenster also den Fokus von Dialogelement zu Dialogelement weitergeben. In der Anwendung Kuchen kann er auf diese Weise zyklisch in gleichbleibender Reihenfolge über die beiden Eingabefelder und den Schaltknopf wandern. Diese 3 Dialogelemente bilden den "tab-Zyklus" des Dialogfensters. Die anderen Dialogelemente, die niemals eine Eingabe erwarten, bleiben ausgespart. Das Andrücken der Entertaste wird im Fenster "Kuchenverteilung" immer wie ein Anklicken des OK-Knopfes interpretiert, egal bei welchem Dialogelement der Fokus sich gerade befindet. Im Einzelnen spielt sich ab: Wenn die Anwender in im Fenster "Kuchenverteilung" die Entertaste drückt, wird über eine besondere Nachrichtenverarbeitung Def DlgProc als sekundär erzeugte Nachricht mitgeteilt, der Schaltknopf sei gedrückt worden. Wenn die Anwenderin den Schaltknopf tatsächlich mit der Maus andrückt, erhält er diese Nachricht zunächst selbst, benachrichtigt dann aber auch Def DlgProc, dass er gedrückt worden ist, so dass beides auf dasselbe hinausläuft.
Die besondere Nachrichtenlenkung und Def DlgProc vermitteln zusammen eine integrierte Dialogfensterverwaltung. Die Zusammenfassung des Dialogfensters mit den Dialogelementen zu einer organisatorischen Einheit kann sich nur auf Standardaufgaben beziehen und die individuelle Programmierung, hier die Berechnung und Anzeige der Kuchenverteilung, nicht einbeziehen. Deshalb ruft Def DlgProc standardmäßig eine von der Entwicklerin für das individuelle Fenster entworfene und beigesteuerte Funktion auf, die sogenannte Dialogprozedur. Materiell gesehen ist die Dialogprozedur bei dem einzelnen Dialogfenster somit ein ausgelagerter Teil der Fensterprozedur. Die Fensterprozedur DefDlgProc und die von ihr aufgerufene Dialogprozedur machen zusammen der Sache nach das aus, was bei Fenstern anderer Klassen oft syntaktisch allein in der Fensterprozedur programmiert wird. Andere Fensterprozeduren können aber auch Teile des Codes in Funktionen auslagern, die sie dann aufrufen. Das ist ja eine allgemeine Möglichkeit in C. Wegen der häufigen Verwendung der vordefinierten Dialogfensterklasse #32770 und ihrer Fensterprozedur DefDlgProc findet sich der größte Teil der Verhaltenslogik betriebswirtschaftlicher Programme in Dialogprozeduren.
50
3 Die Struktur dialogorientierter Windows-Programme
Aus der weiter vorne angegebenen standardmäßigen Fensterplanung einer Anwendung nach Aufgaben ergibt sich die standardmäßige Gliederung nach Klassen: -
3.2.1
Es gibt ein Fenster aus einer privaten Klasse mit einem individuellen Icon für den Hintergrund, ein oder mehrere Dialogfenster aus der vordefinierten Klasse #32770, Dialogelemente aus den ebenfalls im System vordefinierten Klassen.
Die Quelltextgliederung
Das Programm Kuchen besteht aus den Dateien: bezeichnet als einer C-Quelldatei, einer Ressourcedatei, einem Header, einem Icon
prog.c progres.rc prog.h Standard, ico
Die Headerdatei prog.h ist tatsächlich leer und dürfte somit fehlen. Zuerst sehen Sie in der Quelltextliste 3.2.1 den Text der C-Quelldatei prog.c. Dann folgt der Inhalt der Ressourcedatei progres.rc. Das Header prog.h ist in dem Abdruck nicht berücksichtigt. prog.c gliedert sich in -
die Funktion WinMain, die Fensterprozedur HProc des Hintergrundfensters, die Dialogprozedur DlgProc des Dialogfensters.
Zuerst wird wie bei jedem C-Quelltext für ein fensterorientiertes WindowsProgramm windows.h in prog.c eingebunden. Mit der Einbindung von prog.h schafft man eine Brücke für übergreifende Deklarationen in den möglicherweise mehreren Quelldateien des C-Textes und im Ressource-Text. In dieser Anwendung gibt es aber keine übergreifenden Deklarationen. Die Prototypen der Fensterprozedur HProc und der Dialogprozedur DlgProc schließen sich an. Es wäre in jedem Fall normale Programmiertechnik, die Prototypen von Funktionen, die in der Datei definiert werden, an den Anfang zu stellen. Sie werden hier aber auch tatsächlich formal benötigt, weil beide in WinMain angesprochen werden. Der Prototyp einer Fensterprozedur ist verbindlich vorgegeben. Nur der Name kann frei gewählt werden. Die Funktion nimmt Nachrichten entgegen und
3 Die Struktur dialogorientierter Windows-Programme
51
reagiert darauf. An diese Aufgabe ist ihr Prototyp angepasst. Die Formalparameter sind Komponenten der Nachricht: -
das von der Nachricht adressierte Fenster, gekennzeichnet durch sein Handle vom Typ HWND, die Nachrichteninhalte m e s s a g e , w P a r a m und l P a r a m in ihren
zugehörigen Datentypen. Der Typ LRESULT des Rückgabewertes steht für long. CALLBACK ist eine Microsoft-Erweiterung der C-Sprache. Fensterprozeduren, Dialogprozeduren und WinMain müssen CALLBACK definiert werden. Dazu werden später in passendem Zusammenhang noch ein paar Sätze gesagt. Auch der Prototyp einer Dialogprozedur ist verbindlich vorgegeben. Andernfalls könnte Def DlgProc sie nicht standardisiert aufrufen. Er unterscheidet sich vom Prototyp einer Fensterprozedur nur dadurch, dass der Rückgabeweit BOOL ( = int ) ist. Der Name der Dialogprozedur ist frei wählbar. WinMain hat ebenso wie main in Standard-C einen vorgeschriebenen Prototypen. Von den Parametern, die das System beim Starten des Programms an WinMain übergibt, braucht man in vielen Fällen nur hlnstance, ein Handle auf den gegenwärtigen Prozess (die Programminstanz). Es wird zum Beispiel angesprochen, wenn man für den Prozess eine Fensterklasse definieren will. WinMain ist so programmiert: -
-
Die ersten 3 Textzeilen enthalten Variablendefinitionen, die in der Funktion benötigt werden. Der anschließende Textblock mit dem Aufruf von Findwindow schließt aus, dass man die Anwendung mehrfach starten kann. Dann wird das Hintergrundfenster eingerichtet. Erst wird die oben definierte Fensterklassenstruktur wndclass mit Werten gefüllt. Dabei wird unter anderem die Fensterprozedur HProc an die Klasse gebunden. Die Klasse bekommt das Icon Proglcon. Der Aufruf von RegisterC l a s s spricht das System an, übergibt die definierten Werte und schafft die benötigte Fensterklasse. CreateWindow erzeugt ein, in diesem Fall noch unsichtbares, Fenster zu dieser Klasse. Sie gibt ein Handle auf das Fenster zurück, das in hwndH abgelegt wird. ShowWindow zeigt das Fenster schirmfüllend an. Mit CreateDialog wird das Dialogfenster eingerichtet. Eine Fensterklasse braucht nicht geschaffen zu werden. Das Fenster hat die Klasse #32770. Detailangaben zu dem Fenster und Angaben, welche Dialogelemente es enthalten soll, befinden sich in der Ressourcedatei. Der zugehörige Block hat dort die Nummer, den sogenannten RessourceBezeichner 100, der beim Aufruf von CreateDialog mit angegeben
52
3 Die Struktur dialogorientierter
-
Windows-Programme
wird. Dabei wird das Zeichen # hinzugefügt. Es wird auch angegeben, dass DlgProc die Dialogprozedur des Fensters sein soll. CreateDi al o g gibt ein Handle auf das Dialogfenster zurück, das in hDlg sichergestellt wird. Dann schließt sich die Hauptnachrichtenschleife in Form einer whileAnweisung an (diesmal nicht mehr in einer reduzierten Form). Mit GetMessage nimmt sie die Nachrichten vom System entgegen. Dann bringt sie sie auf den Weg. Dafür hat sie DispatchMessage und IsDialogMessage. DispatchMessage gibt die an das Hintergrundfenster gerichteten Nachrichten an HProc, IsDialogMessage die an das Dialogfenster gerichteten Nachrichten an Def DlgProc und die Nachrichten für die Dialogelemente an deren Fensterprozeduren. Die Ausdrucks weise, dass eine Funktion (DispatchMessage oder IsDialogMessage) eine Nachricht an eine andere Funktion gibt, heißt syntaktisch, dass sie diese Funktion mit den Komponenten der Nachricht als Parametern aufruft. Bevor eine Nachricht mit DispatchMessage an HProc gegeben wird, wird noch TranslateMessage damit aufgerufen. Wenn die Nachricht einen Tastenanschlag mitteilt, erzeugt TranslateMessage eine Nachricht für das zugehörige Zeichen und bringt sie auf den Weg nach HProc. Sonst tut sie nichts. Allerdings verarbeitet HProc gar keine Zeichen, TranslateMessage ist in dieser Anwendung überflüssig. Der Funktionsaufruf ist in dem verwendeten Baustein für die Hauptnachrichtenschleife enthalten. Weil er nicht stört, wurde er nicht gestrichen. Die Hauptnachrichtenschleife wird zum Programmende verlassen. Es folgt nur noch eine return-Anweisung.
WinMain folgt dem auf S. 39 knapp angegebenen Schema. Sie startet das Programm, führt Anfangsarbeiten aus und gelangt dann zur Hauptnachrichtenschleife. Wenn der Dialog erst begonnen hat, wird nur noch ständig die Hauptnachrichtenschleife durchlaufen. Zum Schluss wird noch die return-Anweisung aufgerufen. Nachdem die Hauptnachrichtenschleife einmal erreicht ist, werden andere Code-Abschnitte aus WinMain nicht mehr angesprochen. Von den Nachrichtenelementen message, wParam und lParam gibt message die Bezeichnung einer Nachricht an. Die Nachrichtenbezeichnungen sind im System vordefiniert. Eine Bezeichnung message kann unterschiedliche Ereignisse betreffen, die dann durch Zusatzinformationen in den Komponenten der Nachrichtenstruktur voneinander getrennt werden. Diesem Zweck dienen wParam und lParam. Die Prozedur HProc reagiert auf die Nachricht WM_CLOSE. Sie teilt dem Hintergrundfenster mit, dass es zerstört werden soll. Sie wird zum Beispiel
3 Die Struktur dialogorientierter Windows-Programme
53
ausgelöst, wenn die Anwenderin im geöffneten Systemmenü des Hintergrundfensters die Option Schließen wählt. HProc kann das Hintergrundfenster mit DestroyWindow (hWnd) zerstören. Es hat aber keinen Sinn, das Hintergrundfenster zu zerstören und die Anwendung noch weiter laufen zu lassen. HProc beendet deshalb mit PostQuitMessage gleich die ganze Anwendung. Dann gibt sie 0 zurück. Alle übrigen Nachrichten gibt HProc an DefWindowProc weiter. Bevor Def DlgProc eine Nachricht verarbeitet, das heißt gleich am Anfang ihres Codes, ruft sie die Dialogprozedur DlgProc auf. Diese reagiert auf die Nachrichten WM_INITDIALOG, WM_COMMAND, WM_CTLCOLORS TAT IC und WM_CTLCOLORDLG. - WM_INITDIALOG erreicht das Fenster am Anfang, unmittelbar nachdem es gebildet worden ist und noch bevor es sichtbar wird. DlgProc führt daraufhin Vorbereitungsarbeiten für das Einfärben des Dialogfensters und der Dialogelemente aus. Das Dialogfenster soll einen blassgrünen Clientbereich, die 4 Elemente für Zahlen sollen einen weißen Hintergrund bekommen. - Auf WM_COMMAND reagiert DlgProc nur dann, wenn es sich um den Spezialfall LOWORD (wParam) = IDOK handelt. Das bedeutet hier, dass der Schaltknopf gedrückt worden ist. In diesem Fall werden die Inhalte der Eingabefelder über GetDlgltemlnt in die Zahlenvariablen iKinder und iKuchenstuecke gelesen. Mit SetDlgltemlnt werden die Rechenergebnisse an die Ergebnisfelder gegeben. Es gibt in dieser Anwendung zwei Möglichkeiten für fehlerhafte Eingaben. Das heißt wenn der OK-Knopf gedrückt wird, können die Eingabefelder 2 Arten von Fehlern zeigen: -
eines der Felder oder beide können leer sein, in dem Feld für Kinder kann eine 0 eingetragen sein.
Beide Fehler werden in der Dialogprozedur abgefangen. Beim Lesen der Anzahl Kinder mit GetDlgltemlnt wird über die boolesche Variable bZahl geprüft, ob eine Zahl übertragen worden ist. Ist das nicht der Fall oder ist die gefundene Anzahl Kinder 0, wird der Fokus über SendMessage auf dieses Element gesetzt und die Verarbeitung der Nachricht beendet. Entsprechend verfährt die Funktion, wenn es keine Eintragung für die Anzahl Kuchenstücke gibt. In diesem Fall wird der Fokus auf dieses Element gesetzt und die Arbeit an der Nachricht dann auch beendet. Diese Art, der Anwenderin zu zeigen, dass sie einen Fehler gemacht hat, folgt den Windows Interface Guidelines von Microsoft. Darin wird generell empfohlen, der Anwenderin niemals die
54
J Die Struktur dialogorientierter
-
Windows-Programme
Rückmeldung "Fehler" zu geben, sondern nur ihre Aufmerksamkeit auf die fehlerhafte Stelle zu lenken. Die Nachrichten WM_CTLCOLORSTATIC und WM_CTLCOLORDLG werden ausgewertet, um das Dialogfenster und die Dialogelemente einzufärben. Dies wird hier nicht näher erklärt.
Wenn DlgProc eine Nachricht übergangen hat, gibt sie 0 an Def DlgProc zurück. Dann soll sich Def DlgProc um die Nachricht kümmern. Def DlgProc wird, ihrer Eigenschaft als Fensterprozedur entsprechend, die Nachricht entweder verarbeiten oder an DefWindowProc weitergeben. Wenn eine Dialogprozedur eine Nachricht verarbeitet (darauf reagiert) hat, gibt sie oft 1 zurück. In solchen Fällen kann man den Rückgabe wert als Antwort auf die Frage "Ist die Nachricht verarbeitet?" auffassen. Dies ist der Grund, warum Dialogprozeduren nach ihrem Prototyp den Datentyp BOOL zurückgeben. Man kann dann statt 0 und 1 auch FALSE beziehungsweise TRUE schreiben. Insgesamt ist die Bezeichnung des Rückgabewertes mit dem Typen BOOL aber zu speziell. Der Wert ist nicht immer sinngemäß "wahr" oder "falsch". Eine Dialogprozedur gibt ziemlich regelmäßig auch Handiewerte zurück, zum Beispiel DlgProc in Kuchen die Werte hbrWeiss und hbrBlassgruen. Man kann sie auch dann, wenn man an die Typenfreiheiten von C gewöhnt ist, nicht gut als logische Werte auffassen. Es gibt ferner spezielle Fälle, in denen eine Dialogprozedur 0 zurückgibt, nachdem sie auf eine Nachricht reagiert hat. Dann soll Def DlgProc noch zusätzlich tätig werden. Sie werden sich damit später noch vertraut machen. Zutreffend ausgedrückt ist die Regelung, dass Def DlgProc weiter durchlaufen wird, wenn der Rückgabewert 0 ist, und dass sie die Regie sofort zurückgibt, wenn er einen anderen Zahlenwert hat. Auf den Typ oder Sinn, den die Rückgabe inhaltlich hat, kommt es nicht an. (-> auch S. 86.) Was Sie dem C-Quellcode nicht direkt ansehen können, ist, dass er fast nur in ANSI-C verfasst ist. Die Möglichkeiten für Textersatz, die der Preprozessor bietet, machen es unmöglich, C-fremde Elemente auf den ersten Blick von Makros zu unterscheiden. Tatsächlich weicht der Text aber nur in zwei Punkten vom ANSI-C ab: -
das Schlüsselwort CALLBACK ist eine Microsoft-Erweiterung von C, die Startfunktion heißt WinMain.
Die Datentypen BOOL, HWND, MSG usw. und die Windows-Funktionen, die in den Windows-Headern deklariert sind, erweitern formal die Syntax des ANSIC nicht.
3 Die Struktur dialogorientierter Windows-Programme
5 5
Nun zu der Ressourcedatei progres.re auf Seite 45: Die für eine Ressourcedatei vorgesehene Sprache hat keine Ähnlichkeit mit C. Eine Ressourcedatei enthält deklarative Anweisungen, die sich in C gar nicht oder nicht so übersichtlich ausdrücken lassen. Gibt es so etwas überhaupt? Durchaus. Wenn es um graphische Programmoberflächen geht, fehlen dem ANSI-C zunächst eine Anzahl von Konstruktionen, die nun ergänzt werden müssen. Die WindowsSystementwickler haben in großem Umfang zusätzliche C-Funktionen und Strukturen definiert. Zur Formulierung einiger Programmelemente haben sie die C-Sprache ganz verlassen und eine eigene einfache Syntax gebildet. Die gewünschten Elemente sind damit leicht beschreibbar. Die Anweisungen werden in einer gesonderten Quellcodedatei zusammengestellt. C enthält zum Ersten keine Sprachelemente, um eine Bilddatei direkt in die exe-Datei aufzunehmen. Wenn der Quellcode eine Bilddatei ansprechen soll, zum Beispiel um sie auf dem Bildschirm darzustellen, müsste sie bei Beschränkung auf C mit der exe-Datei transportiert und zur Laufzeit geladen werden. Das wäre vielleicht auch keine besondere Komplikation. Man wird es aber doch als einen Vorteil ansehen, dass es Ressource-Anweisungen zur Aufnahme von Bilddateien in die exe-Datei gibt. Zuerst fügt der Ressourcecompiler eine Bilddatei beim Übersetzen in den Objektcode der Ressourcedatei ein. Dieser wird dann zu einer Einheit mit den übersetzten C-Dateien gebunden. Die Ressourcedatei enthält unter anderem auch die Deklarationen von Dialogfenstern und Menüs, die man in C nur vergleichsweise unübersichtlich formulieren kann. In einer Ressourcedatei können die C-Preprozessor-Anweisungen #include, #if, #define usw. verwendet werden. Man kann also unter anderem Makros bilden und Header einbinden. Die Datei lädt das Header afxres.h. Es macht die in Ressourcedateien vorkommenden Windows-Deklarationen verfügbar, indem es eine Teilmenge der Deklarationen aus windows.h vermittelt. Man kann auch windows.h einbinden, hat dann aber längere Übersetzungszeiten. Die außerdem eingebundene Datei prog.h ist hier wie gesagt leer. progres.re enthält 2 Deklarationen: 1. Die Zeile mit dem Wort ICON bewirkt, dass die Bilddatei standard, ico im Programm verfügbar ist und mit dem Namen Proglcon angesprochen werden kann. Der Name, der sogenannte Ressourcebezeichner der Datei, wird in WinMain benutzt, indem Proglcon mittels der Funktion Loadlcon der dort gebildeten Fensterklasse zugeordnet wird. Das Icon wird in der Titelleiste des Hintergrundfensters und in der Startleiste angezeigt. Loadlcon vermag nicht, die Zuordnung des Icons zu der Fensterklasse unmittelbar über den Dateinamen vorzunehmen. 2. Der Rest der Ressourcedatei ist ein zusammenhängender Textabschnitt, in dem das Dialogfenster "Kuchenverteilung" mit seinen Dialogelementen deklariert
56
3 Die Struktur dialogorientierter
Windows-Programme
wird. Die Zeile mit DIALOG besagt erstens, dass im C-Teil des Programms auf die Deklaration mit der Nummer (dem Ressourcebezeichner) 100 verwiesen werden soll. Die Nummer wird dort bei der Definition des Fensters mit CreateDialog benutzt. Die 4 Zahlen am Ende der Zeile geben die Fensterkoordinaten auf dem Schirm an. Die Lage und Größe eines Fensters wird in Windows-Programmen durch die Position der linken oberen Ecke und durch seine Breite und Höhe bezeichnet. 10 und 20 bestimmen hier die linke obere Ecke des Fensters. Sie ist wagerecht (in Windows-Texten stets als x-Richtung bezeichnet) 10 und senkrecht (in yRichtung) 20 Einheiten von der linken oberen Ecke des Schirms entfernt. Die Breite des Fensters (genannt dx) beträgt 180, die Höhe (genannt dy) 100 Einheiten. Die Frage, wie groß eine Einheit ist, stellen wir hier zurück. Die nächste Zeile sagt: Das Fenster hat den Rand Modalframe, es ist vom "Beziehungstyp" Popup, ist bei der Erzeugung sofort sichtbar und es hat eine Titelleiste. Diese Angaben, das heißt die Angaben in der Zeile STYLE, heißen Stilbits. Allgemein werden viele Eigenschaften von Fenstern über Angaben deklariert, die Stilbits genannt werden. Der Ausdruck erklärt sich damit, dass die Eigenschaften in den Objektdaten des Fensters in einem jeweils zugehörigen 4Byte-Speicher über den Wert einer einzigen binären Stelle geführt werden. Der Speicher kann deshalb viele Stilbits aufnehmen. Zum Beispiel hat DS_MODALFRAME den Zahlenwert 0x80. Er belegt nur eine binäre Stelle. Wenn sie 0 ist, hat das Fenster den Rand DS_MODALFRAME nicht. Weiter bedeutet der Deklarationstext: Die Titeltext ist "Kuchenverteilung", die Schrift der Dialogelemente ist System 10 Punkt. (Die Schrift in der Titelleiste ist von der Angabe nicht berührt. Die Schrift in Titelleisten von Fenstern wird für die Maschine einheitlich im System-Setup eingestellt.) Zwischen den Begrenzern BEGIN und END sind die Dialogelemente deklariert. Zum Beispiel bezeichnet CTEXT ein statisches Textfeld mit zentriertem (centered) Text. Das Wort umfasst die Angabe der Fensterklasse und auch schon Stilbits. In das zuerst genannte CTEXT-Feld soll der Text "Kinder" eingetragen werden. Das Dialogelement hat in Bezug auf das Dialogfenster die Kindfenster-Nummer, das sogenannte Kindfenster-ID, -1. Dies ist eine ungültige Nummer, mit der Wirkung, dass das Dialogelement im Effekt kein brauchbares Kind-ID hat. Die Ressourcesyntax schreibt vor, dass ein Kind-ID angegeben wird, aber hier wird keines benötigt. Die Zahlen 24, 20, 25 und 12 geben die Fensterkoordinaten (x, y, dx, dy) relativ zur linken oberen Ecke des Clientbereiches des Dialogfensters an. Die Deklaration des ersten Eingabefensters in der Aufzählung ist so zu lesen: EDITTEXT gibt die Fensterklasse an und enthält auch schon Stilbits. Das Kind-ID ist 101. Mit dieser Nummer wird das Eingabefenster im Quelltext bei der Verarbeitung von IDOK angesprochen. Man
3 Die Struktur dialogorientierter
Windows-Programme
57
kann in das Fenster nur nichtnegative ganze Zahlen eingeben (Stilbit ES_NUMBER), der Text wird rechtsbündig dargestellt (Stilbit ES_RIGHT). Der Sinn von ES_MULTILINE und WS_GROUP wird hier übergangen. Für die anderen Dialogelemente kann man entsprechende Erklärungen geben. Aber dies mag zunächst ausreichen, um die Art und Weise zu demonstrieren, wie ein Dialogfenster in der Ressourcedatei deklariert wird.
3.3
Zusammenfassung
Der Quelltext zu Kuchen hat einen Eindruck vermittelt. Mit diesem ersten Beispiel ist etwas Licht auf die Struktur eines dialogorientierten Windows-CProgramms -
mit seiner Funktion WinMain und der Hauptnachrichtenschleife, mit den an die Fenster beziehungsweise ihre Prozeduren gerichteten Nachrichten und die Aufgabe und Art der Ressourcedatei
gefallen. Was Sie für die Programmierung von Anwendungen an Einzelheiten tatsächlich wissen müssen, wird natürlich in den zuständigen Technoten genauer erklärt. Der Text mag der ANSI-Programmiererin für eine so kleine Aufgabe erstaunlich lang vorkommen. Aber in der Windows-Programmierung sind Quelltextumfang und Arbeitsaufwand keine Synonyme. Ihre integrierte Arbeitsplattform legt für jedes Programm ein eigenes Verzeichnis an. Der Name eines Programms geht nicht aus den Namen der Quelldateien, sondern aus dem Namen des Verzeichnisses hervor. Aus einem Verzeichnis für ein schon früher geschriebenes ähnliches Programm kopieren Sie die Quelltexte als Arbeitsstartdateien, in denen Sie dann während Ihrer Arbeit ändern beziehungsweise zu denen Sie hinzufügen, was die spezielle Aufgabe verlangt. (Nur wenn Sie Ihre erste Übungsaufgabe schreiben, müssen Sie den Quelltext vollständig neu eingeben.) Sie werden vielleicht auch ein individuelles Icon für Ihre Anwendung zeichnen. Die Bilder zu den Übungsaufgaben in diesem Text sind mit einem einheitlichen persönlichen Standardicon dargestellt. Sie können somit annehmen, dass Sie die Arbeitsstartdateien von Kuchen aus dem Verzeichnis einer anderen Anwendung kopiert haben. Dann ist es ganz realistisch, zu unterstellen: -
Auf der ersten Textseite ist möglicherweise nur an 2 Stellen der Überschriftentext "Kinderparty" des Hintergrundfensters für das Programm
58
3 Die Struktur dialogorientierter
-
-
Windows-Programme
eingesetzt worden. Sonst ist die Seite vielleicht exakt der Code der in das Arbeitsverzeichnis kopierten Arbeitsstartdatei. Die Fensterprozedur HProc ist gegenüber der verwendeten Startvorlage vielleicht vollständig unverändert. Für die Dialogprozedur liefert eine Arbeitsstartdatei den Funktionsrahmen und einige Textzeilen, die das Einfärben betreffen. Im Übrigen ist der Funktionstext individuell zu programmieren. Er macht den logischen Kern der Anwendung aus. Die hier positionierte Auswertung der Eingaben und das Abfangen von fehlerhaften Werten müsste man auch in einem ANSI-C-Programm individuell formulieren. Der Block für die Deklaration des Dialogfensters in der Ressourcedatei ist individuell programmiert. Er wurde mit dem graphischen Dialogeditor erzeugt und nur nachträglich noch redigiert.
Man kann das auch umgekehrt ausdrücken: Sie können die Dateien von Kuchen für viele Beispiele in diesem Buch als Arbeitsstartdateien verwenden und werden dann nur Anpassungen vornehmen müssen, die ungefähr in dem skizzierten Umfang liegen. Man greift in der Windows-Programmierung viel auf Textbausteine zurück und erzeugt deshalb pro Zeiteinheit mehr Quelltext als bei einem ANSI-CProgramm. Andererseits verlangt das Programm Kuchen natürlich tatsächlich mehr Arbeit als ein entsprechendes ANSI-C-Programm. Der Mehraufwand ist der Preis für den Dialogkomfort eines Windows-Programms. Wir wiederholen, ergänzen zugleich ein bisschen und ziehen ein Resumé: Wenn die Anwendung gestartet wird, übergibt das System die Regie an WinMain. Die darin vorgesehenen Initialisierungsarbeiten, speziell das Einrichten eines oder mehrerer Fenster, werden durchgeführt. Danach läuft in WinMain nur noch die Hauptnachrichtenschleife. Mit der Funktion PostQuitMessage wird die Nachricht WM_QUIT in die Nachrichtenschlange der Anwendung gegeben. Wenn GetMessage die Nachricht WM_QUIT aus der Nachrichtenschlange entnommen hat, gibt sie FALSE zurück. Dies ist die Abbruchbedingung für die Hauptnachrichtenschleife. Die Anwendung endet. Die Fensterprozeduren führen eine auf die ANSI-C-Programmiererin zunächst fremdartig wirkende, anscheinend von der Hauptfunktion unabhängige Existenz. Sie werden von dort aus in einer nicht aus dem Quelltext von WinMain ablesbaren Reihenfolge oder Logik aufgerufen. WinMain ist so stark schematisiert, dass sich aus ihrem Quellcode nicht viel über die Arbeit der Fensterprozeduren erkennen lässt. Tatsächlich ist WinMain auch gar nicht im selben Sinne eine Hauptfunktion wie in der ANSI-C-Programmierung. Denken Sie an die Übungsaufgaben in
3 Die Struktur dialogorientierter
Windows-Programme
59
einem einführenden C-Kurs. Ihr Programm soll zum Beispiel in einer Schleife immer eine ganze Zahl von der Tastatur entgegennehmen und dann alle Primzahlen bis zu dieser Zahl auflisten. Oder es soll eine Serie von Zahlen annehmen und dann die größte von ihnen mitteilen. Im ANSI-C programmieren Sie das, wenn nichts anderes gefordert ist, in main. In einem WindowsProgramm programmieren Sie die zugehörige Rechenlogik ebenso selbstverständlich in einer Fenster- oder Dialogprozedur. WinMain schafft nur Basisoder Rahmenbedingungen für Ihre Fenster. Wenn das Hintergrundfenster Ihrer Anwendung ein privates Icon haben soll, erhält es eine private Fensterklasse. Die Anwendung braucht eine Nachrichtenschleife. Dafür ist WinMain zuständig. Insofern hat sie eine ähnliche Aufgabe wie die Ressourcedatei, wo unter anderem Dialogfenster deklariert, das heißt wo auch nur einleitende Voraussetzungen für den Programmlauf und seine Logik geschaffen werden. Eine Anwendung kann mehrere ganz unabhängige Dialogfenster haben, die gleichzeitig auf dem Schirm dargestellt werden. Jedes von ihnen kann unter Ausnutzung der von WinMain gebotenen Bedingungen eine selbständige Einheit bilden. Ihr Programm kann zum Beispiel, um wieder dieselben Übungsaufgaben zu nennen, in einem ersten Dialogfenster die Bildung der Primzahlen und parallel in einem zweiten das Suchen der größten Zahl anbieten. In einer echten Anwendung werden parallel gebildete und gleichzeitig dargestellte Dialogfenster nicht derartig zusammenhanglose Aufgaben wahrnehmen. Sie werden zusammenarbeiten. Das können sie unter anderem, indem sie sich gegenseitig Nachrichten zukommen lassen. Das Beispiel zeigt aber, in wie weitgehendem Maße Fenster- und Dialogprozeduren die Inhalte aufnehmen, die man in einem einfachen ANSI-C-Programm in main finden würde. In einem ANSI-C-Programm wird der Anwenderin bei einem Dialog vorgegeben, welche Zahl sie als nächste mit der Tastatur eingeben soll. In einem Windows-Programm kann sie sich im Allgemeinen aussuchen, in welches Dialogelement und überhaupt in welches Fenster sie als Nächstes etwas eingeben will. Die Fenster warten ab und werden tätig, wenn eine Eingabe sie anspricht. Eine Nachricht gelangt immer an die zuständige Fensterprozedur. Wenn sie die Nachricht verarbeitet hat, gibt sie oft 0 zurück. Nachrichten, auf die sie nicht reagieren will, werden an DefWindowProc und von dort möglicherweise an andere Bibliotheksfunktionen weitergegeben. Irgendwann oder irgendwo, das heißt in irgendeiner Funktion, gibt es eine Reaktion auf die Nachricht. Sie darf im einfachsten Fall - gewissermaßen als leere Reaktion - darin bestehen, dass die Nachricht als nicht interessant eingestuft und deshalb nicht mehr weitergereicht wird. Dann gibt die betreffende Funktion die Regie zurück an die Funktion, von der sie aufgerufen wurde. Diese kehrt ihrerseits zurück usw. Ist also zum Beispiel die Zielfunktion einer Nachricht eine Systemfunktion, die von
60
3 Die Struktur dialogorientierter
Windows-Programme
DefWindowProc aus aufgerufen wurde, geht die Regie über diese Systemfunktion, Def WindowProc und die Fensterprozedur zurück an die Hauptnachrichtenschleife. Diese ist einmal durchlaufen und beginnt von vorn. Die Regie geht an den Schleifenanfang zu GetMessage. Die Fensterprozedur kann Arbeiten in Hilfsfunktionen ausgelagert haben. Dafür steht der normale Vorgang des C-Funktionsaufrufs zur Verfügung. Die Fensterprozedur DefDlgProc eines standardisierten Dialogfensters ruft in jedem normalen Fall eine Dialogprozedur auf. Diese gibt 0 zurück, wenn sie eine Nachricht nicht verarbeitet hat. Wir gehen jetzt mit einigen Bildern auf typische Nachrichtenwege im Windows-System ein.
Nachrichtenschleife
S
Bild 3.3.1 Nachrichtenwege
Bild 3.3.1 zeigt den Weg einer Eingabenachricht für ein Fenster einer Klasse mit privater Fensterprozedur. Die Nachricht wird in Ausführung des allgemein im System vordefinierten Verhaltens von Fenstern zuständigkeitshalber von einer Systemfunktion verarbeitet. Ein Beispiel ist: Die Anwenderin ändert die Größe eines Fensters durch Ziehen mit der Maus am Fensterrand. Das Fenster erhält unterschiedliche Nachrichten. Für ihre Verarbeitung sind teilweise system eigene Funktio-
J Die Struktur dialogorientierter Windows-Programme
61
nen anzusprechen. Sie werden auf dem Weg über D e f W i n d o w P r o c aufgerufen. Wenn die Arbeit beendet ist, geht die Regie jeweils über die Fensterprozedur zurück an die Hauptnachrichtenschleife. In das Bild ist auch die Verarbeitung sekundärer Nachrichten eingezeichnet, die von den aufgerufenen Systemfunktionen erzeugt und direkt rekursiv an die Fensterprozedur gegeben werden. Die meisten Nachrichten, die in einer privaten Fensterprozedur selbst verarbeitet und nicht nur weitergereicht werden, sind sekundär von Systemfunktionen erzeugt und werden dann rekursiv, nicht auf dem Weg über die Nachrichtenschlange, an das Fenster gegeben. So verarbeitet die private Fensterprozedur des Programms Kuchen die Nachricht WM_CLOSE, die aus einer anderen Nachricht sekundär erzeugt wird, wenn die Anwenderin den Punkt Schliessen im Systemmenü des Fensters andrückt ( - S. 52). Auch die Fensterprozedur kann bei entsprechender Programmierung sekundäre Nachrichten erzeugen und sich damit selbst aufrufen oder sie in die Nachrichtenschlange geben. Dieser Fall ist nicht im Bild nachgewiesen.
System
£
Startbereich
Dialogprozedur
DefDIgProc Verarbeitungsbereich
Weg der Nachricht Weg des Rückgabewertes
Bild 3.3.2 zeigt den Weg einer Nachricht, die vom System an ein Standard-Dialogfenster gegeben und von der Dialogprozedur verarbeitet wird. Ein Beispiel ist die Nachricht mit der Mitteilung, dass der Clientbereich des Dialogfensters jetzt eingefärbt werden wird (WM_CTLCOLORDLG). Die Nachricht wird unter anderem vor
Bild 3.3.2 Ein Nachrichtenweg dem erstmaligen Zeichnen des Fenster gegeben. Sie gibt der Dialogprozedur die Option, ein Farbmuster für den Clientbereich an das System zurückzugeben. In diesem Bild wird angenommen, dass die Dialogprozedur die Möglichkeit nutzt. In der Anwendung Kuchen ist das so programmiert.
62
J Die Struktur dialogorientierter
Windows-Programme
Der Anfang von Def DlgProc wird in dem Bild als Startbereich bezeichnet. Eine Nachricht, die an Def DlgProc gelangt, wird zuerst von dort an die Dialogprozedur weitergegeben. In ihr kann eine Reaktion auf die Nachricht codiert sein. Nachdem die Nachricht verarbeitet ist, geht die Regie zurück an Def DlgProc. Diese Funktion kehrt dann sofort zurück. In Bild 3.3.3 sehen Sie den Weg einer Nachricht aus der Nachrichtenschlange, die an ein Standarddialogfenster gelangt und zuständigkeitshalber von DefDlgProc verarbeitet wird.
Weg der Nachricht @φ> Weg des Rückgabewertes
Bild 3.3.3 Ein Nachrichtenweg
Ein Beispiel: Die Anwenderin drückt die tab-Taste zur Weitergabe des Fokus an ein anderes Dialogelement. Die Nachricht wird an die Dialogprozedur gegeben, dort aber nicht verarbeitet. Sie wird von Def-
DlgProc verarbeitet, nachdem die Dialogprozedur zurückgekehrt ist. Der Weg einer Nachricht aus der Nachrichtenschlange an ein Standarddialogfenster, die zuständigkeitshalber auf dem Weg über DefWindowProc von einer Systemfunktion verarbeitet wird, ist in Bild 3.3.4, linke Seite, dargestellt. Es entspricht ungefähr dem Bild 3.3.1 auf S. 60. Nur wandert die Nachricht hier nicht über eine private Fensterprozedur, sondern über Def DlgProc und eine Dialogprozedur. Rekursiv erzeugte Nachrichten sind nicht eingezeichnet. Einen kurzen Weg nehmen die Tastatur- und Maus-Nachrichten, die aus der Nachrichtenschlange an ein Dialogelement gelangen und dort verarbeitet werden
63
3 Die Struktur dialogorientierter Windows-Programme
(Bild 3.3.4, rechte Seite). Wenn zum Beispiel im Programm Kuchen ein Editfeld den Fokus hat, gehen fast alle Tastatureingaben von IsDialogMessage an die vordefinierte Fensterprozedur des Dialogelementes. Dort werden sie ausgewertet.
Weg der Nachricht Εφ
Weg des Rückgabewertes
Dialogprozedur
t=4 DefwindowProc
Systemeigene Verarbeitungsfunktion Suütpmpiapnp
Bild 3.3.4 Nachrichtenwege
—
Nachrichtenschleife
Λ
Fensterprozedur des Dialogelementes
Β
64
3 Die Struktur dialogorientierter
Windows-Programme
3.4
Einige Hinweise zum Arbeitsstart
3.4.1
Elemente der Anwendungs- und der Systemprogrammierung
Wir beginnen nun damit, die syntaktischen Elemente der Windows-Programmierung im Einzelnen vorzustellen. Dazu ist noch voranzuschicken: Es gibt im Windows-System keine formale und strenge Trennung zwischen den programmiertechnischen Elementen, die für den Einsatz in fachbezogenen Anwendungen vorgesehen sind, und denjenigen, die eher interne Systembestandteile sind. Zum Beispiel kann man in den Anwendungen gut mit den vordefinierten Dialogelementen auskommen. Sie sind mit bestimmten Mitteln programmiert worden. Man kann aber auch auf die Idee kommen, eigene Dialogelemente für Spezialzwecke zu programmieren. Dann wird man selbst im Wesentlichen dieselben Mittel einsetzen müssen wie die Systemprogrammiererinnen, die die vordefinierten Dialogelemente geschrieben haben. Eine betriebswirtschaftliche Entwicklerin verwendet höchstens 50 oder 70 der 1000 API-Funktionen ziemlich routinemäßig. Das System unterscheidet ungefähr 300 Nachrichten. Die Entwicklerin verwendet allenfalls 50 mehr oder weniger oft. Die Anzahl ist auch davon abhängig, was man jeweils als eine eigene Nachricht auffassen will. Sie brauchen sich mit anderen Worten nur in einem Bruchteil der programmiertechnischen Mittel des Systems auszukeimen. Grundsätzlich liegen die Dinge hier nichts anders, als wenn Sie eine Standard-Programmiersprache wie ANSI-C lernen. Die Systementwicklerinnen, die Ihren Compiler geschrieben haben, haben auch dabei intern, in den mitgelieferten Bibliotheken, ein Fülle von Funktionen verwendet, die sie sich nach eigenem Ermessen als "tiefliegende" Basis- und Hilfsfunktionen für die Standardfunktionen der Sprache überlegt haben. Nur haben Sie persönlich nichts damit zu tun. Als Anwenderin des Systems beziehen Sie sich ganz selbstverständlich auf die in den Lehrbüchern und Sprachbeschreibungen vorgestellten Funktionen. In der SDK-Dokumentation für Windows wird dagegen zwischen den anwendungsbezogenen und den systembezogenen Elementen kein Unterschied gemacht. Sie finden ein Informationsangebot vor, das zu einem großen Teil dazu dienen kann, systemnahe Komponenten zu schreiben und so in einem Sinn das Betriebssystem selbst weiter zu entwickeln. Die SDK-Dokumentation ist für Entwicklungen zum Betriebssystem nicht vollständig. Aber ein großer Teil der Dokumentation spricht doch allein Systementwicklerinnen an und unterstellt oft auch die entsprechenden Kenntnisse. Sie müssen sich in einem Informationsangebot bewegen, das aus Ihrer Sicht überwiegend aus irrelevanten Angaben
J Die Struktur dialogorientierter
Windows-Programme
65
besteht und in dem Sie lernen müssen, wichtige von für Sie unbrauchbaren Textabschnitten zu trennen. Darin liegt eine Aufgabe. Stellen Sie sich von Anfang an auf den Standpunkt, dass alles, was Sie nicht offensichtlich benötigen, überflüssig und uninteressant ist. Die Anwendung und das System sind kooperativ arbeitende Komplexe. Teile der Anwendung, Funktionen und Daten, liegen beim System. Diese Teile fallen nicht in Ihre Verantwortung. Glauben Sie nicht, Sie müssten den automatisch verwalteten Bereich des Systems programmiertechnisch beherrschen, nur weil er Funktionen benutzt, die man im Prinzip aus der Anwendung heraus aufrufen kann. Sie haben zum Beispiel nichts davon zu lernen, welche Bedeutung der Nachricht WM_NCCREATE zukommt. Sie ist dokumentiert, betrifft aber die Erzeugung des Nichtclientbereiches, der vollständig vom System verwaltet wird. Eine Programmiererin, die aus Windows ein Forschungsobjekt macht, wird merken, dass sie nicht fertig wird und nichts mehr produziert. Trotz der Informationsfülle lassen sich auf der anderen Seite nicht alle Fragen, die Ihnen einfallen können, aus dem SDK beantworten. Zum Beispiel sind die Dialogelemente zwar in ihrem Verhalten beschrieben. Die Fensterprozeduren der Dialogelemente sind aber nicht dokumentiert. Wenn Sie eine Frage zu den Eigenschaften eines Dialogelementes haben, die in den Hilfetexten nicht behandelt ist, werden Sie die Antwort nur durch Probieren finden. Ehe Sie sich auf eine solche Arbeit einlassen, sollten Sie gut überlegen, ob Sie das Resultat überhaupt brauchen. Mit der Fensterprozedur Def DlgProc der standardisierten Dialogfenster ist es ähnlich wie mit den Dialogelementen. Sie war in ihrer Fassung für Windows 3.1 im Quelltext nachgewiesen, rief aber unbekannte andere Funktionen auf. Im 32-Bit-Windows wird der Text gar nicht mehr mitgeliefert. Des Weiteren ist die interne Datenrepräsentation der Fenster im Systemspeicher-Bereich nicht dokumentiert. Wie gesagt: ehe Sie versuchen, einen nicht dokumentierten Punkt zu erhellen, sollten Sie sich immer fragen, ob sich Lücken in den Kenntnissen überhaupt auf Ihre Arbeit auswirken. Es ist zweckmäßig, außer dem SDK auch die Headerdateien zur Dokumentation zu rechnen und zu Nachschlagezwecken heranzuziehen. Sie enthalten im Wesentlichen -
Makrodefinitionen, eingeführt über # de f i η e, Typendefinitionen, eingeführt über typedef, Funktionsprototypen.
Die Typendefinitionen erhält man gesammelt in einfacher Weise, indem man für eine C-Quelldatei die Ausgabe des Preprozessors erzeugt. Beim Visual-C + + von Microsoft gibt man dazu für eine C-Datei prog.c die Kommandozeile
66
3 Die Struktur dialogorientierter
Windows-Programme
c l / Ρ /EP / c p r o g . c 1 Es entsteht eine umfangreiche Ergebnisdatei (mit vielen Leerzeilen), die man in ein Textverarbeitungsprogramm laden kann. So findet man über Suchanweisungen leicht die Typendefinitionen. In dieser Datei sind die Makros bereits "expandiert". Es gibt in C bekanntlich zwei Arten von Makros, objektartige und funktionsartige. Ein objektartiges Makro führt eine Bezeichnung für eine andere Bezeichnung oder Zahl ein. Zum Beispiel ist WM_CLOSE, das in Kuchen vorkommt, ein Textersatz für die Zahl 0 x 0 0 1 0 . Ein funktionsartiges Makro verhält sich ähnlich wie eine Funktion. Ein bekanntes Beispiel ist die Definition #define max(a,b)
(((a)
> (b))
? (a)
:
(b))
aus stdlib.h, die ein Makro zu Bestimmung der größten von zwei Zahlen bereitstellt. Bei der Ausgabe des Preprozessors sind also alle Makros bereits durch den endgültigen Text ersetzt. Zum Beispiel statt der Zeile c a s e WM_CLOSE: findet man case
0x0010:
Die Datei enthält die Prototypen der Windows-Funktionen, aber nur derjenigen, die tatsächlich in den Windows-Bibliotheken vorkommen. Eine ganze Anzahl scheinbarer Windows-Funktionen sind tatsächlich Makros. Zum Beispiel C r e a t e W i n d o w , die in fast jedem Programm vorkommt, ist in Wirklichkeit ein Makro. Die zur Übersetzung an den Compiler gegebene Funktion heißt CreateWindowExA. Ein Prototyp von C r e a t e W i n d o w kommt deshalb in der Ausgabe des Preprozessors nicht vor. An die Makrodefinitionen und an sämtliche Funktionsprototypen zu kommen, ist etwas mühseliger, weil sie sich auf viele Headerdateien verteilen, und weil es nicht wie bei den Typendefinitionen eine Compilier-Anweisung gibt, die sie alle zusammenfasst. Ein Arbeitsaufwand, der sich auf die Dauer auszahlen wird, ist, selbst eine Suchdatei aus den wichtigsten Headern zu bilden, indem man sie in einer großen Textdatei zusammenstellt. Sie sollte jedenfalls mindestens winuser.h, windef.h, winbase.h, winnt.h, wingdi.h und commdlg.h enthalten.
1) o d e r e i
/D"STRICT"
/P
/EP
/ C p r o g . c . Für den Unterschied - S. 69.
3 Die Struktur dialogorientierter Windows-Programme
3.4.2
67
Das Microsoft Developer Studio
Die hochwertigen integrierten Arbeitsplattformen, die von den Herstellerfirmen für die Programmentwicklung angeboten werden, werden hier nicht erklärt. Sie bieten ausführliche Online-Hilfen und sind auch über einige Bereiche selbsterklärend. Nach ein paar Stunden Erprobung werden Sie sich möglicherweise schon so sicher damit fühlen, dass Sie Ihre Aufmerksamkeit auch schon einmal dem eigenen Quellcode widmen können. Einige wenige Bemerkungen können Ihnen den Einstieg aber vielleicht doch noch zusätzlich erleichtern. Ich beziehe mich dabei auf das Visual C + + Developer Studio von Microsoft, das ich selbst benutze. Allerdings gibt es auch hier mehrere Versionen und jederzeit kann die nächste angeboten werden. Dann findet sich das Insert Files vielleicht in einem anderen Menü und heißt auch ein wenig anders. Auch die Texte der Online-Dokumentation werden immer wieder unterschiedlich angeordnet. Ich empfehle Ihnen, die folgenden Erklärungen zu lesen und dann sinngemäß auf die von Ihnen benutzte Plattform zu übertragen. Bei der integrierten Arbeit stehen Editor, Compiler, Linker, Debugger, die Online-Dokumentation und andere Produkte zur Verfügung, ohne dass Sie das Developer Studio verlassen. Zuerst legen Sie über File - New - Project Workspace Application - Eingabe Name - Create ein "Project Workspace" beziehungsweise ein "Projekt" an. Sie werden normalerweise in einem Workspace nur ein Projekt unterhalten. Dann gibt es zwischen dem Workspace und dem Projekt keinen Unterschied. Ein Projekt steht für Ihr Programmiervorhaben und ist die Gesamtheit aus einem Basisverzeichnis mit Unterverzeichnissen, Projektverwaltungsdateien und Quelldateien. Das Developer Studio legt das Basisverzeichnis eines Projektes als Unterverzeichnis zu Msdev\Projects an. Es hat den von Ihnen für das Projekt gewählten Namen. Im Normalfall, das heißt wenn Sie nichts anderes vorschreiben, befinden sich in diesem Verzeichnis die Projektverwaltungsdateien und Ihre Quelldateien. Es erscheint ein Fenster mit der Bezeichnung Project Workspace, in dem Sie über Register die Auswahl zwischen drei Anzeigen ClassView, FileView und InfoView haben. Solange wir nicht mit Klassen arbeiten, interessieren wir uns für die Anzeige ClassView nicht. Die Anzeige FileView betrifft die Quelldateien des Projektes. Sie zeigt zunächst noch keine Dateien, weil das Projekt noch keine Quelldateien enthält. InfoView zeigt das Inhaltsverzeichnis der Online-Dokumentation. Später kommt auch noch ein Register für eine Anzeige ResourceView hinzu. Es wird erst gebildet, wenn es eine Ressourcedatei für das Projekt gibt.
68
3 Die Struktur dialogorientierter
Windows-Programme
Die Projektverwaltungsdateien werden automatisch vom Developer Studio eingerichtet und auf dem neuesten Stand gehalten. Zu ihnen gehört auch die Make-Datei, die bei Erzeugen des Projektes angelegt und später immer angepasst wird. Sie kann nicht über einen Texteditor redigiert werden. Alle gewünschten Änderungen müssen mit den Auswahlangeboten des Developer Studios vorgenommen werden. Für die Compiler-Einstellungen wählen Sie Build - Settings C/C++ an. Die von der Plattform standardmäßig gewählten Voreinstellungen sind grundsätzlich ausreichend. Es gibt aber 2 mögliche Änderungen, die man im Allgemeinen als Verbesserungen auffassen wird: 1. Der Compiler ist auf Warnstufe 3 voreingestellt. Dies ist einerseits auch berechtigt. Die Warnstufe 4 gibt eine Fülle von Warnungen, die bei WindowsProgrammen unbeachtlich sind. Andererseits gehen bei Warnstufe 3 wichtige Warnungen, wie zum Beispiel "unreachable code", verloren. Diese Warnung verweist in den meisten Fällen auf einen Programmierfehler. Es ist also wünschenswert, die Warnstufe 4 einstellen zu können, ohne die überflüssigen Warnungen auszulösen. Das Problem lässt sich leicht lösen: die Preprozessor-Direktive #pragma der C-Sprache hat Auswirkungen, die im einzelnen Compiler individuell definiert werden. Bei dem Microsoft-Compiler kann man damit unter anderem Warnungen ausgewählter Typen unterdrücken. Die überflüssigen Warnungen der Stufe 4 in Windows-Programmen gehören zu ganz wenigen Typen, die man leicht aufzählen kann. Das Programm Kuchen erzeugt insgesamt über 100 mal die Warnungen 4115 named type definition in parentheses 4201 nonstandard extension used: nameless struct/union 4214 nonstandard extension used: bit field types other than int 4100 unreferenced formal parameter 4514 unreferenced inline function has been removed Davon entfallen die Warnungen 4 1 1 5 , 4 2 0 1 , 4214 allein auf das System-Header windows.h und seine Unter-Header. Sie werden unterdrückt, wenn man die Direktive fpragma warning(disable: 4115 4201 4214) vor Einbindung der Headerdateien in prog.c aufnimmt. Der Quelltext aus prog.c löst die Warnungen 4 1 0 0 und 4514 insgesamt 6 mal aus. Die zusätzliche Direktive ipragma warning(disable: 4100 4514) besorgt, dass das Programm dann ohne Warnungen übersetzt wird.
3 Die Struktur dialogorientierter Windows-Programme
69
2. Sie können das Schlüsselwort STRICT zu den Optionen für den Preprozessor hinzufügen. Die Auswirkung ist, dass das Makro STRICT während der Arbeit des Preprozessors definiert ist. Sie können es alternativ auch über #def ine STRICT selbst in den C-Quelldateien definieren. Der Compiler kann dann zwischen Handle auf unterschiedliche Typen unterscheiden. Implizite Umwandlungen zwischen den Typen führen zu Warnungen. Wenn Sie zum Beispiel einen Wert für ein HWND (Handle auf ein Fenster) einer Variablen vom Typ HINSTANCE (Handle auf eine Programminstanz) ohne explizite Typenumwandlung zuweisen, gibt er eine Warnung. Um das im Rahmen der Regeln von C und bei der vorliegenden WarnungsOrganisation des Compilers zu ermöglichen, enthalten die Windows-Header alternative Handiedefinitionen für den Fall, dass STRICT definiert ist. Handle sind dann nicht, wie auf S. 23 ff. erklärt wurde, alle vom Typ void *, sondern sind Zeiger auf unterschiedliche Strukturen. Zum Beispiel für HWND finden sich im zugehörigen Header die Anweisungen: struct HWND
{ int unused;
}; typedef struct HWND
* HWND;
Hier wird ein Strukturtyp HWND eingeführt, der allein dem Ziel dient, einen Zeiger darauf definieren zu können. Der Compiler warnt regelmäßig bei impliziten Umwandlungen zwischen Zeigern auf unterschiedliche Strukturtypen, er warnt nun also auch bei impliziten Umwandlungen zwischen Handietypen. Die Systemfunktionen von Windows, die mit den so definierten Handiewerten aufgerufen werden, müssen intern eine Umwandlung in den von ihnen tatsächlich benötigten Typ vornehmen. Damit haben Sie als Anwendungsentwicklerin aber nichts zu tun. Mit der Wahl von STRICT beseitigen Sie eine vielleicht nicht sehr bedeutende, aber jedenfalls überflüssige Fehlerquelle. Ein neu angelegtes Projekt hat zunächst keine Quelldateien. -
-
Sie können eine C-Quelldatei über File - New - Textfile - OK erzeugen. Das Developer Studio legt sie im Projektverzeichnis ab. Dadurch wird sie aber noch nicht zu einem Teil des Projektes. Sie muss noch in das Projekt importiert werden. Dazu wählen Sie Insert Files into Project. Sie erhalten ein Auswahlfenster mit den Quelldateien im Projektverzeichnis, über das Sie Dateien in das Projekt einfügen können. Im Allgemeinen werden Sie Startdateien haben. Nachdem Sie ein noch leeres Projekt angelegt haben, kopieren Sie die Startdateien in das
70
3 Die Struktur diaìogorientierter
Windows-Programme
Projektverzeichnis und fügen sie dem Projekt über Insert - Files into Project hinzu. Voraussetzung dafür, mit einem Projekt zu arbeiten und ihm zum Beispiel Dateien hinzuzufügen, ist, dass es eröffnet ist. Ein neu definiertes Projekt ist sofort eröffnet. Wenn Sie das Developer Studio verlassen haben und später weiterarbeiten wollen, eröffnen Sie ein Projekt (beziehungsweise sein Workspace) über File - Open Workspace - Projektverzeichnis - Anklicken der mdp-Datei. Die mdp-Datei ist die Hauptverwaltungsdatei eines Projektes (Der Name wechselt aber zwischen den Versionen der Programmierplattform.) Das Developer Studio bietet die Projekte, mit denen man zuletzt gearbeitet hat, auch direkt zum Eröffnen im Auswahlfenster zu File an. Nur ein Projekt auf einmal kann eröffnet sein. Man schließt ein Projekt über Close Workspace oder durch Verlassen des Developer Studios. Sie compilieren und binden über den Menüpunkt Build. Wenn Sie das erste Mal für ein Projekt compilieren und binden, legt die Arbeitsumgebung ein Unterverzeichnis Debug zum Projekt-Basisverzeichnis an. Dort wird die exeDatei zusammen mit Zwischenergebnissen der Programmerzeugung, zum Beispiel den obj.-Dateien, abgelegt. Die Dateien enthalten Debug-Informationen. Wenn Sie keine derartigen Programmbestandteile mehr brauchen, wählen Sie Build - Set Default Configuration - Release. Dann legt die Arbeitsumgebung ein Unterverzeichnis Release zum Projekt-Basisverzeichnis an. Nun wird eine neu erzeugte exe-Datei dort abgelegt. Den Umgang mit Projekten finden Sie in der Online-Dokumentation unter Visual C++ Books - User's Guides - Visual C++ User's Guide - Working with Projects erklärt. Sie löschen ein Projekt, indem Sie das Verzeichnis mit seinen Unterverzeichnissen außerhalb der Arbeitsplattform löschen.
3.4.3
Die Ressourcedatei
Der Zweck der Ressourcedatei ist wie gesagt ( - S. 55) generell, die Programmierung für einige ausgewählte Aufgaben gegenüber der Verwendung von C zu vereinfachen oder überhaupt erst zu ermöglichen. Die Datei stellt in allen Fällen Deklarationen von Objekten zur Verfügung. Ein in der Datei deklariertes Objekt heißt eine Ressource. Die Deklaration ordnet dem Objekt einen Ressourcebezeichner zu, mit dem es im C-Teil des Programms angesprochen werden kann. Ein Programm hat nur eine, genau genommen höchstens eine Ressourcedatei. Auch für sie werden Sie im Allgemeinen eine Startdatei haben, die Sie in das
3 Die Struktur dialogorientierter Windows-Programme
71
Projekt importieren. Man kann eine Ressourcedatei mit dem graphischen Editor oder dem Texteditor bearbeiten. Das Layout eines Dialogfensters wird man regelmäßig mit dem graphischen Editor entwerfen. Für ein neues Dialogfenster wählen Sie Insert Resource - Dialog - OK. Wenn Sie es abspeichern, wird seine Deklaration der vorhandenen Ressourcedatei hinzugefügt. Wenn der graphische Editor eine Ressourcedatei speichert, fügt er ihr Informationen hinzu, die nicht zum Quelltext rechnen. Zum Beispiel die Ressourcedatei für Kuchen von S. 45 ist nach dem Abspeichern in einer Weise erweitert, die auf S. 73 dargestellt ist. Die Zusätze einschließlich der Datei resource.h, die vom graphischen Editor zusätzlich angelegt worden ist, betreffen vor allem die Projektverwaltung durch das Developer Studio. Auch Zwischenüberschriften sind eingefügt. Die Option DISCARDABLE bedeutet, dass das System die so deklarierte Ressource nur im Hauptspeicher zu halten braucht, wenn sie gerade benötigt wird. Sie ist eine Erinnerung an das 16-Bit-Windows, das noch keinen virtuellen Speicher hatte. Was den Inhalt des Quelltextes angeht, stören die Zusätze nicht, sie dürfen aber auch fehlen. Wenn Sie sie vor dem Compilieren über den Texteditor löschen, wird die erzeugte exe-Datei dadurch nicht berührt. Das Projekt wird auch nicht zerstört. Der graphische Editor hilft Ihnen auch bei der Erzeugung anderer Ressourcen als Dialogfenstern. Sie finden das Angebot unter Insert - Resource. Zur Syntax der Ressourcedatei wird aufgezählt: - Es sind die C-Preprozessor-Anweisungen und die KommentarKennzeichen / * , * / und / / erlaubt. - Statt der Schlüsselworte BEGIN beziehungsweise END dürfen auch wie in C geschweifte Klammern eingesetzt werden. Wenn man eine Ressource aber in den graphischen Editor lädt, ersetzt er die Klammern wieder durch BEGIN und END. - Anweisungen dürfen auf mehrere Zeilen verteilt werden (wie die beiden Anweisungen EDITTEXT auf S. 45). - Bei Ressourcebezeichnern wird nicht zwischen großen und kleinen Buchstaben unterschieden. Die Wahl der großen oder kleinen Buchstaben darf an verschiedenen Stellen im C-Teil des Programms voneinander abweichen. - Bei den Schlüsselworten der Ressourcesprache (ICON, DIALOG, EDITTEXT usw.) wird nicht zwischen großen und kleinen Buchstaben unterschieden. - Nur in Zeichenketten, zum Beispiel bei der Angabe eines Fenstertextes wie "Kuchenstücke", und bei Makros wird zwischen großen und kleinen Buchstaben unterschieden.
72
J Die Struktur dialogorientierter
Windows-Programme
Die Ressourcedatei kann unter anderem für die folgenden Aufgaben herangezogen werden: -
Die Deklaration eines Iconbildes zur Verwendung als Programmicon oder als Bildsymbol in einem Fenster. Die Deklaration eines Dialogfensters mit den Dialogelementen. Die Deklaration eines Menüs zur Einbindung in ein Fenster. Die Deklaration eines Bitmapbildes zur Verwendung als visuelle Oberfläche für ein Schaltelement oder als Graphik im Clientbereich eines Fensters.
Die folgenden Verwendungen werden in diesem Buch nicht behandelt: -
-
-
Die Deklaration eines Mauszeigerbildes. Das System stellt standardmäßig Mauszeiger in unterschiedlichen Varianten bereit. Man kann in der Ressourcedatei zusätzliche Mauszeigerbilder deklarieren, die dann neben den standardmäßig verfügbaren Mauszeigern benutzt werden können. Die Deklaration von Akzeleratoren. Ein Akzelerator ist - ausführlicher bezeichnet - eine Akzeleratortaste. Dies ist eine Taste, der eine Nachricht für ein Fenster zugeordnet ist. Das Andrücken der Taste löst die Nachricht aus und sie kann in der Fensterprozedur irgendwie verarbeitet werden. Für die Programmierung wird unter anderem die Hauptnachrichtenschleife mit TranslateAccelerator modifiziert. - Einem Menüpunkt ordnen Sie routinemäßig und in einfacher Weise ein sogenanntes Tastenkürzel zu. Auch hier geht es um die Angabe einer Taste. Das Andrücken der Taste wirkt sich dann aus, als ob der Menüpunkt mit der Maus angeklickt worden wäre. Es gibt einige Übereinstimmungen zwischen Akzeleratoren und Tastenkürzeln. Sie sind aber begrifflich und programmiertechnisch nicht dasselbe. Die Deklaration von Stringtabellen. Sie können in der Ressourcedatei eine Zeichenkette aufführen und ihr eine identifizierende Nummer zuordnen. Eine Zusammenstellung derartiger Zuordnungen in der Ressourcedatei heißt eine Stringtabelle. Sie können eine Zeichenkette der Tabelle im CTeil des Programms in ein char-Array laden und dann weiterverwenden, zum Beispiel als Titeltext eines Fensters. Die Programmierung ist insgesamt aufwändiger, als wenn die Zeichenkette gleich als Konstante im Programm geführt wird. Ein Anwendungsgebiet sind Anwendungen, die für mehrere Sprachen entwickelt werden. Man nutzt dabei aus, dass die Zeichenketten im C-Teil nur mit ihren Ressourcebezeichnern auftreten. Um eine Anwendung auf eine andere Sprache umzustellen, braucht man sie nur mit einer anderen Stringtabelle (das heißt mit einer anderen Ressourcedatei) zu binden. Freilich lässt sich das im Effekt auch ohne
3 Die Struktur dialogorientierter
Windows-Programme
73
weiteres allein mit einer C-Datei realisieren. Der Quelltext vereinfacht sich dann sogar noch. Die Idee war aber von Bedeutung, als die Windows-Betriebssysteme noch keine virtuelle Speicherverwaltung hatten und nur eine als discardable deklarierte Ressource vorübergehend aus dem Hauptspeicher entfernt werden konnte.
Demonstration der Projektverwaltungs-Informationen in einer Ressourcedatei //Microsoft Developer Studio generated resource script. // #¡nclude "resource.h" #def¡ne APSTUDIO_READONLY_SYMBOLS lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II Il Generated from the TEXTINCLUDE 2 resource. II #include "afxres.h" #include "prog.h" lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll #undef APSTUDIO_READONLY_SYMBOLS lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II German (Germany) resources #if !defined(AFX_RESOURCE_DLL) II defined(AFX_TARG_DEU) #ifdef_WIN32 LANGUAGE LANG_GERMAN, SUBLANG_GERMAN #pragma code_page(1252) #endif//_WIN32 lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II II Icon II
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II II Dialog II 100 DIALOG DISCARDABLE 10, 20, 180, 100 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Kuchenverteilung" FONT 10, "System" BEGIN CTEXT "Kinder,-1,24,20,25,12 CTEXT CTEXT END
"Stücke\nje Kind",-1,24,70,25,20 "Stücke\nübrig",-1,72,70,25,20
#ifdef APSTUDIOJNVOKED mimmimmiimiiiiiimimiiimiiiimimimimiiimmiiiiiiii II //TEXTINCLUDE II 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE II Icon with lowest ID value placed first to ensure application BEGIN icon "#include ""afxres.h""\r\n" '»include ""prog.h'"V\n" // remains consistent on all systems. "\0" PROGICON ICON DISCARDABLE "standard.ico"
..................... usw
Textliste 3.4.1
74
3 Die Struktur dialogorientierter Windows-Programme
3.7
WinMain
Die Funktion WinMain ist die Startfunktion und der Endpunkt einer Windows-Anwendung. Sie entspricht formal etwa der Funktion main eines ANSI-C-Programms. Sie ist die einzige Funktion der Anwendung, deren Namensbereich (für Formalparameter und automatische Variable) während der gesamten Laufzeit des Programms auf dem Stack ist. Dass sie nur in einem eingeschränkten Sinn als eine Hauptfunktion der Anwendung angesehen werden kann, wurde schon auf S. 58 f. erörtert. In dieser Technote lernen Sie ihren Prototyp und ihre Verwendung kennen. Prototyp: int CALLBACK WinMain ( HINSTANCE hlnstance, HINSTANCE hPrevInstance, PSTR pszCmdLine, int nCmdShow); Parameter: hlnstance Ein Handle auf die gegenwärtige Programminstanz. hPrevInstance Dieser Parameter wird aus Gründen der Kompatibilität mit Windows 3.1 noch formal geführt. Der vom System übergebene Wert ist immer NULL. In einem materiellen Sinne gibt es den Parameter also nicht mehr, ps zCmdLine Parameter aus der Kommandozeile. nCmdShow Sichtbarkeitsflag. Es kann zur Anzeige für das Hintergrundfenster der Anwendung herangezogen werden. Zum ersten Formalparameter hlnstance ist zu sagen: Ein WindowsProgramm kann mehrfach gleichzeitig gestartet werden. Jeder einzelne Programmlauf heißt eine Programminstanz und wird durch ein darauf verweisendes Handle identifiziert. Eine Programminstanz bildet einen Prozess. Das Handle wird im Allgemeinen an mehreren Stellen im Programm benötigt. Der Parameter pszCmdLine hat zwei unterschiedliche Anwendungen. Eine ist im Grundsatz aus der ANSI-C-Programmierung (Funktion main) bekannt. Man kann beim Programmstart aus einem MS-DOS-Fenster eine Zeichenkette als Parameter an WinMain übergeben. Im Gegensatz zu main ist es immer eine einzige Zeichenkette; die Größen arge und argv, über die eine Mehrheit von Textparametern an main übergeben werden kann, treten deshalb hier nicht auf. Der Unterschied ist aber nur formaler Art. Auch an WinMain können inhaltlich mehrere Textparameter übergeben werden. Das System fasst sie zu einer einzigen Zeichenkette zusammen und übergibt die Adresse in pszCmdLine. Es
3 Die Struktur dialogorientierter Windows-Programme
75
ist dann Aufgabe von WinMain, die Zeichenkette in ihre Bestandteile zu zerlegen. Ein Beispiel wäre das Starten einer Datenbankanwendung EMP unter Angabe von Username und Passwort aus einem MS-DOS-Fenster, also etwa mit EMP WALD LION. Dieser Aufruf startet das Programm EMP und übergibt ihm in ρ s ζ CmdL i η e einen Zeiger auf " WAL D LION". Man kann ein Programm auch aus einem anderen Programm heraus mit der ANSI-C-Funktion s y s t e m starten. Dann trifft eine entsprechende Erklärung zu. Man übergibt s y s t e m genau den Text, den man als Kommandozeile in ein MSDOS-Fenster schreiben würde. Um etwa den eben angegebenen Funktionsaufruf auszuführen, programmiert man system
("EMP WALD L I O N " ) ;
Eine andere und weiterreichende Bedeutung hat p s z C m d L i n e beim Starten einer Anwendung durch Anklicken in der graphischen Oberfläche. Der Name des Parameters, der an command line erinnern soll, trifft dann nicht wörtlich zu. In der graphischen Oberfläche kann man eine Datei mit dem Mauszeiger zu einer Anwendung führen und diese dann starten. In diesem Fall wird der Name der Datei mit voller Pfadangabe als Parameter p s z C m d L i n e an die Anwendung übergeben. Es ist dann ihr überlassen, was sie mit dem Dateinamen tun wird. Um im Kontext des eben angeführten Beispiels zu bleiben: Eine Datenbankanwendung EMP, die die Angabe von User-Name und Passwort erwartet, kann zum Beispiel über die Maus mit einer Textdatei aufruf.txt eröffnet werden, die die Worte WALD LION enthält. Die prozedurale Wirkung ist hier gegenüber dem Start aus einem MS-DOS-Fenster verändert. An die Anwendung EMP werden nicht Username und Passwort übergeben, sondern der Name der Textdatei mit Pfad, zum Beispiel c:\aaa\aufruf.txt. EMP muss prüfen, ob sich zulässige Angaben in der Datei befinden. Dieses Beispiel soll nur die gegenüber einem Aufruf aus der Kommandozeile veränderten Formalien herausstellen und ist natürlich praktisch nicht überzeugend. Ein vertrauter wirkendes Beispiel hat man, wenn man an eine Textdatei und einen Texteditor denkt. Wenn man die Textdatei mit der Maus an den Editor heranführt, übergibt man ihm beim Eröffnen das Verzeichnis und den Namen der Datei als p s z C m d L i n e . Der Editor muss so programmiert sein, dass er die Datei lädt und mit ihr arbeitet. Der letzte Parameter nCmdShow kann dazu verwendet werden, beim Programmstart die Darstellungsgröße des Hintergrundfensters der Anwendung zu steuern. nCmdShow enthält einen zur Übergabe an die Funktion ShowWindow ( - S. 158 f.) geeigneten Wert. Diese Funktion kann ein unsichtbares Fenster sichtbar machen, und zwar in einer Größe, die von einem mitgegebenen Parameter abhängt:
76
3 Die Struktur dialogorientierter Windows-Programme
Datenwert
Größe
S W_S HOWNORMAL
1
normal
SW_SHOWMAXIMIZED
3
maximiert
(Es gibt noch mehr mögliche Parameterwerte für ShowWindow, die im gegenwärtigen Zusammenhang aber uninteressant sind.) Zum Beispiel wird im Programm Kuchen das schon erzeugte, aber noch unsichtbare Hintergrundfenster mit der Anweisung ShowWindow
(hwndH,
SW_SHOWMAXIMIZED);
in maximaler Größe sichtbar gemacht. Statt dessen könnte man es mit ShowWindow
(hwndH, nCmdShow);
sichtbar machen. In diesem Fall würde der an WinMain übergebene Größenwert verwendet. nCmdShow wird in der Programmierung nur selten eingesetzt. Man kann nämlich erstens den Parameterwert nicht aus einer Kommandozeile an WinMain übergeben, weil wie gesagt alle Parameter der Kommandozeile vom System zu pszCmdLine zusammengefasst werden. Auch beim Programmstart durch Anklicken in der graphischen Oberfläche gibt es keine Möglichkeit, den Parameter mitzugeben. Das System verwendet deshalb eine Standardregel und übergibt regelmäßig SW_SHOWNORMAL. In Kuchen würde die Anzeige des Hintergrundfensters mit nCmdShow zur normalen Anfangsdarstellung "überlappter" Fenster (Fenster mit dem Stilbit WS_0 VERLAP PE DWIΝ DOW) führen. Die Abmessungen sind dann wesentlich kleiner als maximiert. Wie die Regelung für die normale Anfangsdarstellung überlappter Fenster im Einzelnen lautet, wird später noch gesagt. In dieser Anwendung wurde aber ein schirmfüllendes Hintergrundfenster gewünscht. Wenn Sie in Kuchen eine normale Darstellung des Hintergrundfensters wollen, können Sie auch gleich programmieren: ShowWindow
(hwndH,
SW_SHOWNORMAL);
Nur in besonderen Ausnahmefällen hat nCmdShow Anwendung: -
-
Einen anderen Wert als SW SHOWNORMAL erhält WinMain zum Beispiel bei einer entsprechenden Eintragung für die Anwendung in der Datei win.ini. Man kann einen individuellen Wert für nCmdShow übergeben, wenn man eine Anwendung aus einem anderen Programm heraus startet. Die ANSIC-Funktion system ist allerdings nicht geeignet. Wie in dem Beispiel für die Übergabe des Parameters pszCmdLine schon dargestellt worden ist, liegen die Dinge da wie bei einem Start aus der Kommandozeile.
3 Die Struktur dialogorientierter Windows-Programme
77
system nimmt nur einen einzigen Parameter entgegen, der vom System als Aufruf der Anwendung, evtl. mit einem zusätzlichen Textparameter, interpretiert wird. Es gibt aber zusätzlich die Windows-Funktion WinExec ( - SDK). Sie unterscheidet sich von system dadurch, dass sie um den Parameter nCmdShow erweitert ist. Soviel zu den Formalparametern von WinMain. Die im Prototyp angegebene Spezifikation CALLBACK darf bei der Abfassung von WinMain nicht vergessen werden. Sie würden diesen Fehler aber auch bemerken, weil der Compiliervorgang wegen der Abweichung vom Prototyp mit einer Fehlermeldung abgebrochen würde. Auch Fensterprozeduren, Dialogprozeduren und die bisher nicht erwähnten Timerprozeduren müssen als CALLBACK definiert werden. Auch bei ihnen gibt der Compiler eine Fehlermeldung, wenn das Schlüsselwort vergessen worden ist. Derartig definierte Funktionen werden Callbackfunktionen genannt. Statt CALLBACK darf man auch eine der Bezeichnungen
WINAPI, APIENTRY, API PR IVATE oder einige andere wählen. Sie alle sind in den Windows-Headern eingeführte Ersatzbezeichnungen für stdcall. Auf dieses Schlüsselwort spricht der Compiler an. Warum CALLBACK? Nehmen Sie die Vorschrift als formal zwingend, aber inhaltlich vollkommen uninteressant hin. Die Erklärung ist nur für Systemprogrammiererinnen mit einer Assembler-Ausbildung wirklich verständlich. Um einen Eindruck von dem thematischen Kontext zu geben, wird hier aber wiedergegeben: Wenn eine Funktion zurückkehrt, muss der Stack um den Platzbedarf für ihre Variablen reduziert werden. Diese Arbeit kann von der aufrufenden oder der aufgerufenen Funktion ausgeführt werden. Standard-C sieht vor, dass die aufrufende Funktion zuständig ist. Dann muss sich der zugehörige Code an jeder Stelle mit einem Funktionsaufruf befinden. Andererseits hat das den Vorteil, dass aufgerufene Funktionen variable Parameterzahl haben dürfen, wie es zum Beispiel bei printf der Fall ist. Als CALLBACK definierte Funktionen rechnen den Stack, ehe sie zurückkehren, selbst zurück. Sie können keine variable Anzahl Parameter haben. Die C-Compiler für Windows erlauben mit ihrem Schlüsselwort stdcall, ausgewählte Funktionen in diesem Punkt abweichend vom Standard zu konstruieren. Bei dem Microsoft-Compiler kann man auch die Option /Gz einstellen. Dann werden alle Funktionen automatisch stdcall. Nur Funktionen mit variabler Parameterzahl werden nach dem C-Standard compiliert. Man braucht dann kein CALLBACK zu geben; man erhält auch so korrekte Windows-Programme. Nach dem C-Standard und mit stdcall compilierte Funktionen können in einer Anwendung nebeneinander arbeiten. Das Windows-System verwendet intern weitgehend CALLBACK-Funktionen und erreicht damit etwas Platzersparnis.
78
J Die Struktur dialogorientierter Windows-Programme
Bevor über den Rückgabewert von winMain gesprochen wird, ergänzen wir die früheren Abschnitte zum Quelltext in dieser Funktion. Ziehen Sie bitte wieder WinMain aus dem Quelltext Kuchen ( - S. 42) und vielleicht auch die Erklärungen dazu (speziell S. 51 f.) heran. Auf die hochgradige Standardisierung beim Abfassen von WinMain wurde schon hingewiesen (S. 39). Die Funktion ist in allen Programmen sehr ähnlich. Auch bei allgemeiner Programmierung werden in einer Textvorlage (Arbeitsstartdatei) für WinMain oft nur einige Optionen ausgetauscht, die teilweise durch ein einziges Wort ausgedrückt werden können. In anderen Fällen kopieren Sie nur zusätzlich ein paar vorgefertigte Textabschnitte nach WinMain und ändern sie dort geringfügig ab. Sie benötigen in WinMain - zum Erzeugen einer privaten Fensterklasse (oder mehrerer Fensterklassen) eine Struktur vom Typ WNDCLASS ( - S. 198), zum Beispiel mit der Bezeichnung wndclass, - für die Hauptnachrichtenschleife eine Nachrichtenstruktur vom Typ MSG ( - S. 83), zum Beispiel msg. Diese Variablen werden definiert. Sie können prüfen, ob schon eine Programminstanz läuft. Bei Anwendungen, die auf vorgegebene Dateien oder eine Datenbank zugreifen, muss man schon einen speziellen Grund haben, die parallele Ausführung mehrerer Instanzen vorzusehen. Im Regelfall werden solche Programme in der Funktion WinMain so angelegt, dass sie mehrere Instanzen nicht zulassen. Um zu verhindern, dass eine Anwendung mehrfach gestartet werden kann, verwenden Sie in WinMain die Funktion FindWindow. Der Prototyp ist: HWND FindWindow( PSTR pszClassName, PSTR pszWindowName
// Klassenname // Fenstertext
); Mit ihr können Sie prüfen, ob ein nach Klasse und/oder Titeltext bestimmtes Hintergrundfenster schon erzeugt ist. Sie hat eine prozessübergreifende Reichweite. Sie schaut in alle Prozesse und findet gegebenenfalls ein Hintergrundfenster mit einem gesuchten Klassennamen und einem gesuchten Titeltext. Sie gibt das Handle zurück. Es ist systemweit eindeutig. Sie berücksichtigt nur Hintergrundfenster. Geben Sie NULL ein für den -
Klassennamen, stellt der Suchprozess allein auf den Fenstertext ab, Fenstertext, stellt er allein auf die Fensterklasse ab.
Wenn kein Fenster, das dem Suchkriterium genügt, gefunden worden ist, gibt FindWindow NULL zurück.
3 Die Struktur dialogorientierter Windows-Programme
79
Wenn Sie die Klasse des Hintergrundfensters nicht in jeder Anwendung eindeutig benennen wollen, können Sie auf den Fenstertext abstellen. Er wird eine Anwendung im Allgemeinen eindeutig kennzeichnen. Wenn der Rückgabewert von FindWindow ungleich NULL ist, aktiviert man die vorher bereits gestartete Instanz, bringt sie damit zugleich in den Bildschirmvordergrund und bricht die neue Instanz ab. (Für den Begriff der Aktivierung S. 115 ff.) Um also das mehrfache Starten der Anwendung auszuschliessen, programmieren Sie insgesamt in WinMain nach dem Vorbild des folgenden Textbausteins: H W N D hwndH; hwndH = FindWindow(NULL, if (hwndH != NULL)
"Kinderparty");
{ S h o w W i n d o w ( h w n d H , SW_RESTORE); SetForegroundWindow(hwndH); return 0 ;
} ShowWindow und SetForegroundWindow teilen sich die Aufgabe in dieser Weise: Sofern das gefundene Hintergrundfenster gerade minimiert, das heißt nur in der Startleiste mit seinem Icon und dem Fenstertext nachgewiesen ist, vergrößert ShowWindow ( - S. 158 ff.) es auf seine letzte Einstellung und aktiviertes. SetForegroundWindow ( - S. 130) wird zwar aufgerufen, wirkt sich aber nicht mehr aus. Ist das Fenster nicht minimiert, sondern nur von anderen Anwendungen auf dem Schirm überdeckt, hat umgekehrt ShowWindow keine Wirkung. In diesem Fall aktiviert SetForegroundWindow das Fenster und bringt es also auch in den Vordergrund. ShowWindow und SetForegroundWindow haben ebenso wie FindWindow prozessübergreifende Reichweite. Einige Windows-Funktionen, die in vielen Programmen aufgerufen werden, verlangen die Angabe der Programminstanz. Schlagwortartig ausgedrückt muss die Programminstanz in einer größeren Anwendung überall bekannt sein beziehungsweise andernfalls ermittelt werden. Es gibt die folgenden Lösungen: 1. Sie können das Handle auf die Programminstanz in einer globalen Variablen führen. In einigen Anwendungsbeispielen dieses Buches wird es in WinMain einer globalen Variablen hlnst zugewiesen: HINSTANCE hlnst; W i n M a i n ( ... )
{ hlnst =
hlnstance;
80
J Die Struktur dialogorientierter
Windows-Programme
Wenn man sie in mehreren Quelldateien verfügbar haben will, muss man sie in einem Header deklarieren. 2. Die Funktion GetWindowLong ( - SDK) liefert überall im Programm die Instanz, wenn Sie das Handle eines Fensters der Anwendung übergeben. GetWindowLong ist eine unter mehreren Funktionen, die Informationen aus den zentral im System für ein Fenster geführten Daten verfügbar machen. Der Code lautet dann zum Beispiel in einer Dialogprozedur: BOOL CALLBACK DlgProc (HWND hWnd, ... )
{
HINSTANCE hlnst; hlnst =
(HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
In einfachen Programmen wie Kuchen kommt es vor, dass man die Instanz außerhalb von WinMain nicht braucht, und sie ist darin auch nicht global notiert. In WinMain wird mittels der Struktur wndclass (vom Typ WNDCLASS) und mit RegisterClass eine private Fensterklasse für ein Hintergrundfenster erzeugt. Wie Sie das ausführen können, ist auf S. 198 ff. im Einzelnen beschrieben. Der Prototyp der Fensterprozedur (zum Beispiel HProc) muss WinMain vorangestellt werden, weil ihr Name in wndclass einzutragen ist und deshalb vorher deklariert sein muss. Das Hintergrundfenster wird meistens mit CreateWindow ( - S. 214 ff.) und evtl. mit ShowWindow erzeugt und angezeigt. Es deckt oft den gesamten Schirm. Die Funktion CreateWindow gibt das Handle des erzeugten Fensters zurück. Oft soll es, ähnlich wie das Instanzhandle, überall im Programm verfügbar sein. Sie haben dann hier entsprechend die Alternativen: 1. Sie können das Handle in einer globalen Variablen fuhren. 2. Oder Sie bestimmen es, wo es gebraucht wird, mit der schon erklärten Funktion FindWindow. - Das SDK gibt keine Auskunft, ob damit sicher das Hintergrundfenster der eigenen Instanz gefunden wird, wenn eine Anwendung simultan mehrfach laufen kann. In solchen Fällen können Sie FindWindow nicht wählen. Die Hauptnachrichtenschleife verwendet die Nachrichtenstruktur msg, die zu Beginn angelegt worden ist. Wenn die Anwendung enden soll, wird PostQuitMessage aufgerufen. Sie hat den Prototypen: void PostQuitMessage (int);
3 Die Struktur dialogorientierter Windows-Programme
81
Sie gibt die Nachricht WM QUIT in die Nachrichtenschlange. Die Funktion GetMessage gibt dann FALSE zurück, die while-Schleife wird dementsprechend nicht noch einmal durchlaufen. PostQuitMessage wird typischerweise mit dem Wert 0 aufgerufen: PostQuitMessage(0) ; Der Zahlenwert wird als die Komponente wParam von WM QUIT in die Nachrichtenschlange weitergegeben. WM_QUIT setzt sich somit zusammen: message
wParam
lParam
WM_QUIT
Parameter uninteressant von PostQuitMessage
Es ist üblich, den übergebenen Parameter als Rückgabewert von WinMain zu verwenden: return msg.wParam; Der Wert ist eigentlich vom Typ WPARAM (= unsigned int). Nach der C-Syntax ist aber keine Umwandlung der internen Datendarstellung notwendig, um ihn als int zurückzugeben, wie es der Prototyp von WinMain vorsieht. Bei einem Konsole-orientierten Programm erfüllt der Rückgabewert der Funktion main einen Zweck. In einer Batchdatei kann man ihn über die Größe errorlevel für den weiteren Verlauf auswerten. Der Rückgabewert von WinMain hat dagegen keine Bedeutung. Er steht weder in einer Batchdatei noch in einer exe-Datei, die die Anwendung über system oder WinExec aufgerufen hat, zur Verfügung. Die Praxis, msg.wParam zurückzugeben, ist ganz ohne Auswirkung und deshalb ganz unverbindlich. Das Auslösen des Programmendes, die Erzeugung der Nachricht WM_QUIT und ihre Übertragung in die Nachrichtenschleife mit PostQuitMessage, wird nicht in WinMain programmiert, sondern dort, wo die Mitteilung der Anwenderin eintrifft, dass sie die Anwendung beenden möchte. Das ist gewöhnlich die Fensterprozedur des Hintergrundfensters der Anwendung. Wenn die Anwenderin die Anwendung beenden möchte, wählt sie oft im Systemknopf des Hintergrundfensters den Menüpunkt Schließen. Das erzeugt dort die Nachricht WM_CLOSE. Man programmiert also z.B. nach der Textliste 3.9.1 (nächste Seite). Der Programmabschluss wird nicht schon durch Anklicken des Punktes "Schließen" im Systemmenü eines Hintergrundfensters ausgelöst. Dieser Vorgang erzeugt wie gesagt zunächst einmal die Nachricht WM CLOSE, die vom Programm - jedenfalls grundsätzlich - beliebig verarbeitet werden kann. Wenn
82
3 Die Struktur dialogorientierter Windows-Programme
die Fensterprozedur nichts anderes vorsieht, veranlasst WM_CLOSE DefWindowProc, das Fenster zu zerstören. case WM_CLOSE: PostQuitMessage(0); return 0; } return DefWindowProc (hWnd, message, wParam, lParam); } Textliste 3.7.1 Abschluss der Fensterprozedur eines Hintergrundfensters Der Programmabschluss muss ausdrücklich programmiert werden. Er wird nicht durch die Zerstörung sämtlicher Fenster einer Anwendung erreicht. Ein Prozess kann ohne Fenster existieren. Er existiert vom Start von winMain an bis diese Funktion zurückkehrt. Dabei ist egal, ob überhaupt Fenster erzeugt werden beziehungsweise ob zu irgendeinem Zeitpunkt noch welche bestehen. Bei Beenden eines Programms werden alle seine Fenster automatisch zerstört. Auch alle anderen vom Programm erzeugten Objekte werden automatisch zerstört. Bei dem oben angegebenen Code wird WM_CLOSE nicht an DefWindowProc weitergegeben und deshalb auch nicht unmittelbar zur Zerstörung des Hintergrundfensters verwendet. Das wird erst als Folge des Programmendes zerstört. Die Nachrichtenschleife erhält nicht die Nachricht WM_QUIT, wenn die Anwenderin das Ende der Windows-Sitzung über "Beenden" in der Startleiste veranlasst. Dann wird das Hintergrundfenster direkt vom System benachrichtigt, um dem Sitzungsende widersprechen oder Abschlussarbeiten ausführen zu können (Nachrichten WM_QUERYENDSESSION, WM_ENDSESSION, - S. 228). Aus der Nachrichtenschlange werden keine Nachrichten mehr entnommen.
3 Die Struktur dialogorientierter Windows-Programme
3.8
83
Nachrichten
Unter einer Nachricht kann man in Windows zwei Dinge verstehen, die sich geringfügig unterscheiden. Zum Ersten gibt es eine in winuser.h, einem UnterHeader zu windows.h, deklarierte Struktur MSG (message) der folgenden Form: typedef
struct
{ HWND
hwnd;
UINT
message;
WΡARAM
wParam;
Adressatenfenster Nachricht in Form eines unsigned int, das heißt in einer Zahl ausgedrückt Zusätzliche Information über die Nachricht. Die genaue Bedeutung hängt von dem Wert von m e s s a g e ab.
LPARAM DWORD POINT } MSG;
lParam; time; Pt;
Erklärung wie bei wParam Zeitpunkt der Nachricht Ort der Nachricht auf dem Schirm
Die Elemente haben die Bedeutung: Das erste Element ist ein Handle auf das Fenster, das die Nachricht erhalten soll. Diese Information muss die Nachricht, während sie von den Funktionen des Systems weitergereicht wird, begleiten wie eine Briefanschrift einen Brief bei dem Weg durch die Postorganisation. (In Dialogfenstern mit integrierter Fensterverwaltung gehen einige Nachrichten wegen ihres besonderen Inhalts an ein anderes als das in der Nachricht angegebene Adressatenfenster.) Der materielle Nachrichteninhalt ist zunächst message als Hauptbegriff der Nachricht. Obgleich message den Datentyp eines unsigned int hat, braucht man nur Wörter zu lernen beziehungsweise in den Code einzusetzen. Die im System und in der Programmierung vorkommenden message-Werte sind als Makros in Headern definiert, wie etwa WM_MOVE (Datenwert 0x0003) = WM_SIZE (Datenwert 0x0005) =
ein Fenster ist auf dem Schirm bewegt worden, oder die Größe eines Fensters ist verändert worden.
Die Buchstaben WM stehen für window message. Bei einem me s sage-Wert kann es sich um eine Sammelbezeichnung oder Übersichtsbezeichnung handeln. In diesem Fall wird der Nachrichteninhalt durch w P a r a m und/oder lParam präzisiert. Zum Beispiel verursacht das Anklicken eines Menüpunktes in der Menüleiste oder einem von ihr abhängigen Pull-downMenü eine Nachricht mit der Sammelbezeichnung WM_COMMAND im Feld
84
3 Die Struktur dialogorientierter
Windows-Programme
message. In wParam steht ein Code, der angibt, welcher Menüpunkt aktiviert worden ist. Allgemein dienen also wParam und lParam zur näheren Bestimmung von Sammelangaben in message. Dabei handelt es sich dem Gebrauch nach nicht nur um zwei zusätzliche Größen. Ein Programm kann 4-Byte-Werte mittels Makros in 2 2-Byte-Werte für den niederwertigen beziehungsweise höherwertigen Anteil aufspalten, in der Bezeichnung durch die Makros in das LOWORD und das HIWORD. So gibt es bei Bedarf insgesamt vier 2-Byte-Größen, die message näher beschreiben können. Der Nachrichteninhalt ist dann insoweit: wParam lParam message
LOWORD HIWORD
LOWORD HIWORD
Den Zerlegungsmakros stehen Synthesemakros gegenüber. Die Programmierung wird in der nächsten Technote angegeben. Der Strukturtyp MSG enthält nun noch die Elemente time und pt. Auf sie bezieht sich die eingangs gemachte Bemerkung, dass man unter einer Nachricht zwei verschiedene Dinge verstehen kann. Wenn das System eine Nachricht erhält, trägt sie ihren Inhalt in eine MSG-Struktur ein und füllt auch die Elemente time und pt aus. time ist eine 4-Byte-Größe, pt ist eine Struktur nach der Definition: typedef struct
{ LONG x; LONG y; } POINT; Sie kann die horizontale und die vertikale Koordinate eines Bildschirmpunktes aufnehmen. Die Koordinaten werden in Pixeln (physischen Bildschirmpunkten) gemessen, time und pt geben Zeitpunkt und Ort der Nachricht an. Man kann nicht allen Nachrichten sinnvoll einen Ort zuordnen; für manche Nachrichten gibt es hier nur "leere" Eintragungen. An die Fensterprozedur als Adressat der Nachricht werden diese beiden Elemente nicht weitergegeben. Der Grund ist, dass sich die Fensterprozedur bei vielen Nachrichten für diese Parameter - auch soweit sie einen Sinn haben - nicht interessiert. Wenn zum Beispiel ein Schaltknopf gedrückt worden ist, geht eine entsprechende Nachricht an das Dialogfenster, in dessen Clientbereich der Schaltknopf liegt. Die Koordinaten haben hier keinen Sinn und für den Zeitpunkt interessiert sich das Dialogfenster nicht. Es gibt auf der anderen Seite Nachrichten, bei denen einer dieser Parameter manchmal wichtig ist. Ein Beispiel ist die Meldung an ein Fenster, dass es einen linken Mausklick in den Clientbereich gegeben hat. In diesem Fall ist message = WM_LBUTTONDOWN. Je nach
3 Die Struktur dialogorientierter
Windows-Programme
85
Sachproblematik möchte die Fensterprozedur vielleicht auswerten, wo im Clientbereich die Maus gedrückt worden ist. Deshalb werden die Punktkoordinaten bei dieser Nachricht in 1 P a r am übertragen. Die x-Koordinate steht in LOWORD (1 P a r am), die y-Koordinate in HI WORD (1 P a r am). Der Zusatzparameter 1 P a r am kann also bei den Nachrichten, wo dies in Frage kommt, die Punktkoordinaten aufnehmen. So muss p t formal nicht an die Fensterprozedur gegeben werden. Die Funktionen D i s p a t c h M e s s a g e beziehungsweise I s D i a l o g M e s s a g e in der Hauptnachrichtenschleife nehmen die Nachricht in der Form einer MSG-Struktur entgegen und wandeln sie um. In der Fensterprozedur besteht eine eintreffende Nachricht dann immer nur aus den Elementen Typ
Bezeichnung
Bedeutung
HWND LJINT W Ρ AR AM LP AR AM
hwnd message wParam lParam
Adressatenfenster Hauptbezeichnung der Nachricht Zusätzliche Information Zusätzliche Information
Die vom System unterschiedenen Nachrichten sind zusammenhängend alfabetisch im SDK nachgewiesen.
86
3 Die Struktur dialogorientierter
3.9
Windows-Programme
Typenumwandlungen
Die Nachrichten, die an Fensterprozeduren oder auf dem Weg über Def DlgProc an Dialogprozeduren gelangen, haben also eine einheitliche Form nach ihren Elementen und deren Datentypen. Deshalb sind sehr oft Typenumwandlungen mit der Übergabe einer Nachricht an ein Fenster verbunden. Dabei braucht uns der 4-Byte-Parameter message mit dem Typ UINT ( ξ unsigned int) nicht zu beschäftigen. Die Zahlenwerte für message sind in den Windows-Headern passend definiert. Die beiden 4-Byte-Parameter wParam und lParam nehmen aber Details der Nachrichten auf, die mal von dieser, mal von jener Art sind. wParam ist WPARAM (unsigned int), lParam ist LPARAM (long). Wenn die tatsächlich zu übergebenden Inhalte von anderem Typ sind, müssen sie umgewandelt werden. Bei manchen Nachrichten wird ein Handle, ein Zeiger, an wParam oder lParam übergeben. Dann trifft der Wert umgewandelt als unsigned int oder signed long bei der Fenster- oder Dialogprozedur ein. Dort muss er für die Verarbeitung wieder in seine eigentliche Bedeutung, also einen Zeiger, rückverwandelt werden. Ein Beispiel können Sie dem Programm Kuchen entnehmen. Auf S. 44 findet sich in der Dialogprozedur die Anweisung SendMessage (hWnd, WM_NEXTDLGCTL, (WPARAM) GetDlgltem (hWnd, 101),1); Die etwas komplizierte Erklärung ist: Die Funktion SendMessage nimmt eine Nachricht als Parameter entgegen und ruft die zu dem adressierten Fenster gehörende Fensterprozedur auf. Wenn die Nachricht dort verarbeitet ist, kehrt SendMessage zurück. Die Nachrichtenschlange und die Nachrichtenschleife sind an dem Vorgang nicht beteiligt. Die Nachricht geht in dem Beispiel an hWnd, das heißt an das Dialogfenster selbst, message ist WM_NEXTDLGCTL. Damit kann man Def DlgProc veranlassen, den Fokus auf das Dialogelement zu setzen, dessen Handle in wParam angegeben wird. In lParam ist eine 1 anzugeben. Die Funktion GetDlgltem liefert das benötigte Handle, wenn ihr das Handle des Dialogfensters (das heißt hWnd) und das Kind-ID des Dialogelementes (hier 101) übergeben wird. Das Handle des Dialogelementes wird hier explizit mittels (WPARAM) in unsigned int umgewandelt. Auf S. 54 wurde ferner schon erörtert, dass Dialogprozeduren zwar formal einen Wert vom Typ BOOl zurückgeben, dass die inhaltliche Bedeutung der Rückgabewerte aber ganz unterschiedlich ist. Entsprechend gilt für Fenster-
J Die Struktur dialogorientierter Windows-Programme
87
Prozeduren, dass sie inhaltlich keineswegs immer LRESULT-Werte (long) zurückgeben. c h a r : 1 Byte, 8 Bits 256 -128
127 0x0 = 0 Oxff = 255 s h o r t : 2 Bytes, 16 Bits 65.536 -32.768 32.767 0x0 = 0 Oxffff = 65.535
Anzahl darstellbare Zahlen kleinster Wert s i g n e d größter Wert s i g n e d kleinster Wert u n s i g n e d größter Wert u n s i g n e d Anzahl darstellbare Zahlen kleinster Wert s i g n e d größter Wert s i g n e d kleinster Wert u n s i g n e d größter Wert u n s i g n e d
l o n g = i n t : 4 Bytes, 32 Bits 4.294.967.296 Anzahl darstellbare Zahlen -2.147.483.648 kleinster Wert s i g n e d 2.147.483.647 größter Wert s i g n e d 0x0 = 0 kleinster Wert u n s i g n e d Oxffffffff η 4.294.967.295 größter Wert u n s i g n e d Tabelle 3.9.1 Darstellungsbreite von Integern
Die Elemente wParam und 1 Par am der Nachrichten und die Rückgabewerte der Fensterprozeduren und Dialogprozeduren sind immer Integer (ganzzahlige Daten). Wesentlich ist, dass sie 4 Bytes aufnehmen können und damit die größtmögliche Darstellungsbreite von Variablen im WindowsSystem haben. Im Übrigen werden sie so variantenreich eingesetzt, dass sich ihr Typ nicht weiter einschränken lässt. Wenn es in C
einen allgemeinen unspezifischen Datentyp "4-Byte-Integer" gäbe, hätte man ihn für wParam, lParam und die Rückgabewerte der Fenster- und Dialogprozeduren herangezogen. Da das nicht der Fall ist, mussten die Typen dieser Größen (und auch noch die einiger anderer Größen in der Windows-Programmierung) unvermeidlich zu eng festgelegt werden. Die C-Sprache ist in ihrer Syntax gut an den freien Umgang mit Datentypen angepasst. Die Kommission zur Normierung der Sprache hat dem Prinzip der Typentreue auch ausdrücklich das Motto "Trust the Programmer" entgegengehalten ( - Mark Williams, S. 410). Die Regeln zur Typumwandlung, die für wParam und lParam in Fenster- und Dialogprozeduren und für ihre Rückgabewerte Bedeutung haben, werden hier angegeben. Zuvor werfen wir noch einen Blick auf die interne Zahlendarstellung. Es gibt im Windows-Betriebssystem Integer in 3 Darstellungsbreiten, und jeden Typ als signed und unsigned (Tabelle 3.9.1). Bei der internen Darstellung kann man jedes Byte als eine Ziffer auffassen. Die Zahlen werden dann in einem System zur Basis 256 gehalten. Der Wert in einem Byte kann durch zwei hexadezimale Ziffern ausgedrückt werden.
88
J Die Struktur dialogorientierter
Windows-Programme
Die Bytes der Typen short und long sind umgekehrt zu unserer gewohnten Schreibweise nach aufsteigender Wertigkeit geordnet. Beispiele für die Speicherung von unsigned long-Werten sind: Wert dezimal
hexadezimal
Speicherung
1 1.123.123.123
0x00000001 0x42fl7fb3
01 00 00 00 b3 7f fl 42
Der Typ unsigned eines Integers ist für Fragen der Umwandlungen als "nichtnegativ" zu interpretieren. Der Deutung nach haben die Zahlen durchaus ein Vorzeichen. Darstellbare Werte sind 0 oder positiv. Wenn man eine unsigned Zahl mit einer negativen signed Zahl multipliziert, erhält man ein negatives Ergebnis. Die Bezeichnung unsigned ist speichertechnisch zu verstehen. Der Speicherplatz braucht kein Vorzeichen mitzuführen, weil es für alle Werte identisch positiv beziehungsweise bei 0 irrelevant ist. Zeiger sind nichtnegative 4-Byte-Größen. Sie unterscheiden sich in der Zahlendarstellung nicht von unsigned int. Umwandlungen, die von Zeigern als Quellgrößen ausgehen oder zu Zeigern als Zielgrößen führen, werden ausgeführt wie Umwandlungen aus oder in unsigned int. Der Unterschied zwischen Zeigern und unsigned int liegt in der materiellen Deutung und äußert sich bei Zeigern unter anderem in ihrer besonderen Arithmetik (zum Beispiel (long *)1000 + 1 ergibt 1004). Wenn Parameterwerte und Rückgabewerte beim Umgang mit Fenster- und Dialogprozeduren umzuwandeln sind, geht es immer um die Umwandlung in 4Byte-Größen und die mögliche Rückumwandlung in den ursprünglichen Typ. Die Regeln dazu sind gutartig und lauten: -
-
Umwandlungen mit gleichbleibender Darstellungsbreite zwischen signed und unsigned. Die Bitfolge und damit auch die Bytefolge bleibt unverändert erhalten. Der Wert bleibt unverändert, wenn die Zahl im Zieltyp darstellbar ist. Die Rückumwandlung ergibt stets wieder den ursprünglichen Wert. Umwandlungen aus einem Typ mit geringerer Darstellungsbreite. Die Umwandlung erfolgt, indem höherwertige Bytes angefügt werden. Der Wert bleibt unverändert, wenn die Zahl im Zieltyp darstellbar ist. a. Bei Umwandlung in signed bleibt der Wert unverändert, egal ob der Ausgangstyp signed oder unsigned ist. Die Zahl ist auf jeden Fall als signed darstellbar.
3 Die Struktur dialogorientierter Windows-Programme
89
b. Bei Umwandlung in unsigned bleibt der Wert erhalten, wenn er nichtnegativ ist. Wenn er negativ ist, wird die Bitfolge so ergänzt, dass eine zweite Umwandlung in signed den korrekten Wert ergibt. c. Die Rückumwandlung in den ursprünglichen kürzeren Typ ergibt stets wieder den ursprünglichen Wert. Die angefügten Bytes werden wieder abgeschnitten. Nach der C-Syntax wird bei der Parameterübergabe an eine Funktion und bei der Übergabe eines Rückgabewertes implizit umgewandelt, wenn ein Wert noch nicht den richtigen Datentyp hat. Der Compiler warnt bei impliziten Umwandlungen: -
aus einem Zeiger in einen Zahlenwert (int, long usw.) oder umgekehrt, zwischen Zeigern auf verschiedene Typen, in einen Typ von geringerer Darstellungsbreite. Diese Umwandlung kommt bei der Übergabe von Parametern an Fenster- und Dialogprozeduren und bei der Bildung ihrer Rückgabewerte nicht vor.
Sie werden im Allgemeinen eine explizite Umwandlung vornehmen, um die Warnung zu vermeiden. Sie lernen nun die schon erwähnten Synthese- und Zerlegungsmakros kennen. Zur Synthese eines 4-Byte-Integers aus zwei 2-Byte-Werten gibt es die Makros: Rückgabewert
Makro
WΡARAM LPARAM LRESULT LONG
MAKEWPARAM (LoZweiBytes, HiZweiBytes) MAKELPARAM (LoZweiBytes, HiZweiBytes) MAKELRESULT (LoZweiBytes, HiZweiBytes) MAKELONG (LoZweiBytes, HiZweiBytes)
Sie verketten die beiden Bytepaare zu einer einzigen Größe und geben das Resultat zurück. Sie können auf signed und unsigned Werte angewendet werden. Berücksichtigt wird nur die Bytefolge. MAKELPARAM, MAKELRESULT und MAKELONG, die signed Werte erzeugen, unterscheiden sich nur in ihren Namen, nicht in ihrer Wirkung. Wenn eine Funktion also an ein Fenster eine Nachricht übermitteln möchte, bei der sich 1 Par am aus zwei verschiedenen Parametern für das niederwertige und das höherwertige Bytepaar zusammensetzen soll, verwendet sie das Makro MAKELPARAM. Ein solches Makro muss unter anderem manchmal angewendet werden, wenn ein Dialogfenster eine Nachricht an ein Dialogelement übermittelt. Beispiel: lParam soll durch Verkettung aus den unsigned shortWerten 32691 und 17137 zusammengesetzt werden. Man übergibt MAKELPARAM(32 691, 17137)
90
3 Die Struktur dialogorientierter
Windows-Programme
32691 ist hexadezimal 0x7fb3 und wird intern als b3 7f dargestellt. 17137 ist hexadezimal 0x42fl und wird intern als f 1 42 dargestellt. An die Fensterprozedur wird 1.123.123.123, intern dargestellt als b3 7f fl 42, übergeben. Die Fensterprozedur zerlegt l P a r a m wieder mit den Makros: Rückgabewert
Makro
WORD WORD
LOWORD (VierBytes) HIWORD (VierBytes)
Im Beispiel: LOWORD (1123123123) ergibt32691, HIWORD (1123123123) ergibt 17137. Diese Werte sind unsigned short und müssen, wenn signed gefordert ist, noch umgewandelt werden.
3.10
Fensterprozeduren
Eine Fensterprozedur hat einen vorgeschriebenen einheitlichen Prototypen, in dem nur der Funktionsname frei wählbar ist: LRESULT CALLBACK Funktionsname( HWND hWnd, UINT message, WΡARAM wParam, LPARAM lParam); Die Funktion DispatchMessage der Hauptnachrichtenschleife oder je nach Programmierkontext auch eine andere Funktion, zum Beispiel SendMessage ( - S. 86, S. 183), übergibt die darin angegebenen Nachrichtenelemente als Parameter an die Fensterprozedur. Das Wort CALLBACK darf nicht fehlen. Dieser Punkt wurde schon erörtert. Eine Fensterprozedur erhält alle an das Fenster gerichteten Nachrichten. (Dass bei Dialogfenstern mit integrierter Fensterverwaltung manche Nachrichten nicht an das Fenster gelangen, das als Adressatenfenster eingetragen ist, sondern an ein anderes, sachlich zuständiges Fenster, wurde schon erklärt.) Man kann die Nachrichten in drei Gruppen einteilen: 1. Nachrichten, die immer oder in aller Regel vom Windows-System zu verarbeiten sind, das heißt von Funktionen, die vom Hersteller mit dem System geliefert werden. Dazu gehört, um ein Beispiel zu nennen, die Nachricht WM_NCPAINT. Sie wird bei Bedarf vom System erzeugt und soll eine zuständige Systemfunktion veranlassen, den Nichtclientbereich des Fensters zu zeichnen. Es ist niemals Ihre Aufgabe als Entwicklerin, sich um diese Arbeit zu kümmern. Das System erledigt sie selbsttätig.
3 Die Struktur dialogorientierter Windows-Programme
91
2. Nachrichten, für die das System eine Standardreaktion vorsieht, auf die Sie aber auch individuell reagieren können. Ein Beispiel ist WM_CLOSE. Die Nachricht wird unter anderem erzeugt, wenn die Anwenderin im Systemmenü eines Fensters den Knopf "Schließen" andrückt. Je nach Fensterklasse ist die Standardreaktion des Systems, dass das Fenster dann tatsächlich zerstört wird. Sie können aber anders vorgehen und erst einmal im Programm prüfen, ob für ein ordnungsmäßiges Verhalten der Anwendung noch Abschlussarbeiten, zum Beispiel das Abschließen einer Datenbank-Transaktion mit COMMIT oder ROLLBACK, notwendig sind. Sie werden dann vielleicht erst noch eine Entscheidung der Anwenderin zu dieser Frage anfordern. 3. Nachrichten, für die sich das System nicht interessiert und für die es auch keine Standardreaktion vorsieht. Es kann dann eine Reaktion der Anwendung auf die Nachricht geben oder eben auch nicht. Zum Beispiel erhält ein Fenster, das den Fokus zugeteilt bekommt, darüber die Mitteilung WM_SETFOCUS. Als Entwicklerin können Sie vorsehen, dass die Nachricht irgendeine Reaktion auslöst. Sie können sie auch übergehen. Nachrichten dieser drei Gruppen laufen also bei der Fensterprozedur ein. Sie gestalten eine Fensterprozedur in der folgenden Weise: Sie wählen unter den Nachrichten der Ziffern 2 und 3 diejenigen aus, auf die programmindividuell zu reagieren ist, und programmieren die Reaktion. Rein quantitativ handelt es sich dabei nur um einen kleinen Teil der eintreffenden Nachrichten, denn die Systemnachrichten (Ziffer 1) werden gänzlich übergangen, und von den anderen auch sehr viele. Alle Nachrichten, für die keine individuelle Reaktion vorgesehen wird, werden an eine Bibliotheksfunktion mit Namen DefWindowProc weitergeleitet. Es gibt nicht viele Nachrichten der Gruppe 2. Der Quelltext von DefWindowProc für Windows 3.1 war im Paket des Visual-C + + enthalten. Von den über 40 Nachrichten, für die DefWindowProc eine Reaktion vorsah, konnte man nur wenige, kaum mehr als 5, zu den Nachrichten der Ziffer 2 rechnen. An diesen Umständen hat sich nichts geändert. Bei standardmäßiger Programmierung mit Dialogfenstern und Dialogelementen rechnen zu den Nachrichten der Ziffer 2 im 32-Bit-Windows nur WM_CLOSE Der Sinn dieser Nachricht wurde eben erklärt. WM_QUERYENDSESSION Das Hintergrundfenster der Anwendung erhält diese Nachricht vom System, wenn die Anwenderin das Ende der Windows-Sitzung über die Startleiste anfordert. Die Anwendung kann widersprechen oder Abschlussarbeiten ausführen. Die Gruppe der Nachrichten vom Typ WM_CTLCOLORxxx, das heißt
92
3 Die Struktur dialogorientierter
Windows-Programme
WMCTLCOLORDLG, WM_CTLCOLORSTATIC, WM_CTLCOLOREDIT USW. Sie dienen dem Einfárben von Fenstern ( - S. 335 ff.). Die überwiegende Mehrheit sind reine Systemnachrichten (Ziffer 1). DefWindowProc erledigt die zugehörigen Arbeiten selbst oder ruft dafür Systemfunktionen auf. DefWindowProc ist selbst materiell in jeder Beziehung eine Systemfunktion. Ohne DefWindowProc wäre das gesamte System überhaupt nicht arbeitsfähig. Windows leitet die Systemnachrichten für ein Fenster auf dem Wege über die Fensterprozedur an DefWindowProc und möglicherweise von dort aus an andere zuständige Systemfunktionen. Wenn Sie die Systemnachrichten nicht an DefWindowProc leiten, ist das Fenster und die gesamte Anwendung sofort nicht mehr ansprechbar. Das ganze System wird gestört. Sie müssen es wieder in einen regulären Zustand bringen, indem Sie die Anwendung über die Taskliste, die mit den Tasten Strg-Alt-Entf aufgerufen wird, abbrechen.
Nach dem Prototypen ist der Rückgabewert der Fensterprozedur vom Typ LRESULT signed long). Sie programmieren schematisch: - Wenn die Fensterprozedur auf die Nachricht selbst reagiert, leitet sie sie nicht an DefWindowProc weiter. Sie gibt den Wert 0 zurück. (Die Umwandlung der 0 in den Typ long geschieht nach der C-Syntax automatisch). Für die Nachrichten WM_QUERYENDSESSION und WM_CTLCOLORxxx, die der Ziffer 2 angehören und für die in DefWindowProc auch eine Standardarbeit vorgesehen ist, sind spezielle Rückgabewerte ungleich 0 vorgeschrieben, die bei den Beschreibungen dieser Nachrichten dargestellt sind. - Wenn die Fensterprozedur nicht selbst auf die Nachricht reagiert, ruft sie DefWindowProc dafür auf. Diese gibt nach ihrem Prototyp auch einen long-Wert zurück. Die Fensterprozedur reicht ihn mit return weiter. Von der Funktionsfähigkeit des Programms her wäre es nicht notwendig, Nachrichten der 3. Gruppe, für die DefWindowProc sowieso keine Arbeiten vorsieht, dorthin weiterzuleiten. Dann müssten Sie aber für jede Nachricht prüfen, ob sie weitergeleitet werden muss oder nicht. Das wäre programmiertechnisch sehr aufwändig. Praktisch kommt nur in Frage, einfach alle nicht beachteten Nachrichten weiterzugeben. DefWindowProc gibt bei allen Nachrichten, die sie nicht in ihrem Katalog für Reaktionen hat, von sich aus 0 zurück. Zur Gliederung einer Fensterprozedur verwendet man eine switchAnweisung. Insgesamt ist so im Kern der Aufbau der Textliste 3.10.1 einzuhalten. Auf eine Nachricht zu reagieren, wird in der Sprachregelung der WindowsEntwicklung als "Verarbeiten" der Nachricht bezeichnet. Halten Sie sich aber vor Augen, dass über den Inhalt der Verarbeitung allgemein nichts gesagt ist,
3 Die Struktur dialogorientierter Windows-Programme
93
und dass man bei einer in der Fensterprozedur "verarbeiteten" Nachricht einheitlich nur weiß, dass sie nicht an DefWindowProc weitergegeben wird.
LRESULT CALLBACK Funktionsname(HWND hWnd, UINT message, WΡARAM wParam, LPARAM lParam)
{
. .. Definition lokaler Variabler ... switch (message) { case Nachricht_l: reagiere auf die Nachricht return 0; case Nachricht_2: reagiere auf die Nachricht return 0; ... usw.
}
}
{
}
// für unverarbeitete Nachrichten: return DefWindowProc(hWnd, message, wParam, lParam); Textliste 3.10.1 Schema einer Fensterprozedur LRESULT DefWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) switch (message) {
}
case Systemnachricht: verarbeite die Nachricht return Rückgabewert; case Nachricht mit Standardreaktion: realisiere die Reaktion return Rückgabewert; default: // Nachrichten für Anwendungen return 0;
Textliste 3.10.2 Schema von DefWindowProc Der von der Entwicklerin eingesetzte Rückgabewert einer Fensterprozedur für eine verarbeitete Nachricht ist tatsächlich ohne Bedeutung. Ausnahmen sind
nur die Nachrichten WM_QUERYENDSESSION und WM_CTLCOLORxxx der Gruppe 2, die Standardreaktionen in DefWindowProc haben. Bei System-
94
3 Die Struktur dialogorientierter Windows-Programme
nachrichten, die in DefWindowProc oder einer von dort aus aufgerufenen Systemfunktion verarbeitet werden, wird der Rückgabewert natürlich oft wesentlich sein und die weiteren Systemreaktionen steuern. Die Regelung, bei einer in der Fensterprozedur verarbeiteten Nachricht 0 zurückzugeben, ist eine von Microsoft vorgesehene Übereinkunft. Auch DefWindowProc gibt wie gesagt bei Nachrichten, die dort keine Beachtung finden, 0 zurück. Davon abzuweichen erschwert die Lesbarkeit des Quelltextes. DefWindowProc hat (abgesehen von dem fehlenden Schlüsselwort CALLBACK) denselben Prototyp wie eine Fensterprozedur, sie nimmt also ein Handle auf ein Fenster und den materiellen Inhalt einer Nachricht entgegen. Dementsprechend ist sie etwa wie die Textliste 3.10.2 konstruiert. Betrachten Sie als Syntaxbeispiel noch einmal die Fensterprozedur des Quelltextes Kuchen ( - S. 43) und die Erklärung dazu auf S. 52 f. Man kann den Inhalt einer Fensterprozedur nicht generell standardisieren. Sie enthält die zu dem Fenster gehörige Programmlogik und muss deshalb von Fenster zu Fenster verschieden sein.
3.11
Die Hauptnachrichtenschleife
Die Hauptnachrichtenschleife wird standardisiert in einer von wenigen Varianten programmiert. Welche Variante zu wählen ist, richtet sich nach den Typen der Dialogfenster, die in der Anwendung vorkommen. Von Bedeutung ist, ob es "offene" Dialogfenster mit der Fensterprozedur DefDlgProc in der Anwendung gibt oder nicht. Sie können Dialogfenster und simultan die Dialogelemente darin auf zweierlei Weise erzeugen: -
mit einer der Funktionen CreateDialog oder CreateDialogParam mit einer der Funktionen DialogBox oder DialogBoxParam
Ein Beispiel für die Verwendung von CreateDialog zeigt die Anwendung Kuchen. CreateDialogParam erlaubt bei der Erzeugung gleich die Übergabe eines Parameters an das Fenster. Im Übrigen unterscheiden sich die mit den beiden Funktionen erzeugten Fenster nicht. Auch bei DialogBoxParam kann ein Parameter übergeben werden. Sonst unterscheiden sich auch die mit DialogBox und DialogBoxParam gebildeten Fenster nicht. Es gibt aber Unterschiede zwischen den Dialogfenstern, die mit den beiden Paaren von Funktionen gebildet werden. Im Zusammenhang mit der Programmierung der Hauptnachrichtenschleife ist von Bedeutung: Die Funktion
J Die Struktur dialogorientierter Windows-Programme
95
DialogBox beziehungsweise DialogBoxParam erzeugt zugleich mit einem Dialogfenster implizit eine eigene "temporäre" Nachrichtenschleife. Der Code dafür ist im Quellcode der Anwendung nicht enthalten. Solange der Dialog existiert, gibt es mithin in der Anwendung mehrere Nachrichtenschleifen: -
die von Ihnen selbst programmierte Hauptnachrichtenschleife, die implizit über DialogBox beziehungsweise DialogBoxParam erzeugte temporäre Nachrichtenschleife.
Nur diese letztere wird während der Lebendauer des Dialogfensters durchlaufen. Sie gibt wie die Hauptnachrichtenschleife eine eintreffende Nachricht immer korrekt an das adressierte Fenster. Integrierte Fensterverwaltung leistet sie aber nur für das Dialogfenster, für das sie gebildet worden ist. Der Sachverhalt einschließlich des Begriffs der temporären Nachrichtenschleife wird später im Einzelnen angesprochen und klargestellt. Dialogfenster mit einer implizit erzeugten temporären Nachrichtenschleife werden abgeschlossen genannt (Diese Bezeichnung wird auf S. 250 ff. diskutiert). Man kann ein abgeschlossenes Fenster außer mit DialogBox oder DialogBoxParam auch mit der Funktion MessageBox erzeugen. Sie bildet ein Meldungsfenster, eine variantenarme Spezialform eines Dialogfensters. Andere Dialogfenster und Hintergrundfenster ohne Dialogelemente heißen offen. Für die Konstruktion der Hauptnachrichtenschleife ist wie eingangs gesagt von Bedeutung, ob es offene Dialogfenster gibt, die die Fensterprozedur Def DlgProc haben. Diese Fensterprozedur haben Dialogfenster -
-
der vordefmierten Klasse #32770. Dies ist die Standardklasse für Dialogfenster. Sie ist aber nur eingeschränkt brauchbar, wenn ein Dialogfenster das Hintergrundfenster der Anwendung sein soll, oder anders ausgedrückt, wenn das Hintergrundfenster der Anwendung Dialogelemente aufnehmen soll. Ein Hintergrundfenster braucht ein Icon. Die Klasse #32770 hat kein Icon und nur in Windows 95 und 98, nicht in Windows NT kann man einem Fenster dieser Klasse ein Icon zuweisen. privat gebildeter Klassen, sofern dies von Ihnen vorgesehen wird. Man kann einer privaten Klasse, die man für ein Hintergrundfenster bildet und der man ein Icon gibt, die Fensterprozedur Def DlgProc geben. Die Klasse verhält sich dann hinsichtlich der Nachrichtenlenkung und Nachrichtenverarbeitung wie #32770 und ist somit als Dialogfensterklasse brauchbar. Die Programmierung wird später dargestellt.
Zur Ergänzung der folgenden Erklärungen demonstriert das Bild 3.11.1 die Wege, über die die Nachrichten an die Fensterprozeduren gelangen.
96
3 Die Struktur dialogorientierter
Windows-Programme
¥
Bild 3.11.1 Die Verteilung der Nachrichten aus der Nachrichtenschlange
3.11.1
Anwendungen ohne offene Dialoge mit Fensterprozedur DefDlgProc
Wenn es keine offenen Dialoge mit Fensterprozedur DefDlgProc in der Anwendung gibt, wählt man die Grundform der Hauptnachrichtenschleife. Man benötigt eine Struktur des Typs MSG (-> S. 83). Man definiert also in WinMain MSG ms g; Der Code für die Hauptnachrichtenschleife lautet:
J Die Struktur dialogorientierter Windows-Programme
97
while (GetMessage (&msg, NULL, 0, 0))
{ TranslateMessage (&msg); DispatchMessage (&msg);
} Die hier mit GetMessage ( - SDK) an das Betriebssystem übergebenen Parameterkonstanten - NULL, 0,0- brauchen in der Hauptnachrichtenschleife einer Anwendung nie durch andere Werte ersetzt zu werden. GetMessage fragt beim System an, ob eine Nachricht in der Nachrichtenschlange der Anwendung liegt. Wenn ja, erhält die Funktion die Nachricht als Eintragung in die angegebene Messagestruktur. Wenn nicht, wartet sie zunächst, bis eine Nachricht da ist. Sie kehrt also erst zusammen mit einer Nachricht zurück. In der Zwischenzeit beschäftigt sich das Betriebssystem mit anderen Aufgaben, wenn es welche hat. GetMessage hat nach ihrem Prototypen einen Rückgabewert vom Typ BOOL. Sie gibt immer einen Wert ungleich FALSE zurück, außer bei der Nachricht WM_QUIT. TranslateMessage vervollständigt die Nachrichten über Tastatureingaben. Wenn eine Taste gedrückt wird, gibt das System zunächst die Nachricht WM_KEYDOWN auf dem Wege über die Nachrichtenschleife an das Fenster mit dem Fokus. Der Parameter wParam enthält die Mitteilung, um welche Taste es sich handelt. Wenn die Taste a gedrückt wird, geht somit diese Nachricht an das Fenster. Sie bezieht sich noch ganz auf die Taste. Sie teilt noch kein Zeichen, also zum Beispiel noch keinen Buchstaben, mit. Noch ist nicht bekannt, ob es sich um ein großes oder kleines a handelt. Um das zu bestimmen, muss ausgewertet werden, ob zugleich die shift-Taste gedrückt worden ist. Auch sie übermittelt, wenn man sie drückt, eine Nachricht an das Fenster. Alternativ lässt sich mit der Funktion GetKeyState ( - SDK) abfragen, ob zu einem Zeitpunkt eine Taste gedrückt ist. Es ist Aufgabe der Funktion TranslateMessage, aus diesen eintreffenden Nachrichten beziehungsweise aus dem Zustand der Umschalttasten den ANSI-Code der Eingabe zu bilden. Erst TranslateMessage erzeugt die druckbaren Zeichen der Tastatur (Buchstaben, Zahlen, Sonderzeichen), und zwar als WM_CHAR-Nachrichten. Der ANSI-Code steht in wParam. TranslateMessage erzeugt neben den Codes der druckbaren Zeichen auch die Codes von Steuerzeichen. In manchen Fällen, zum Beispiel bei Strg-shift-a, bildet TranslateMessage keine zusätzliche Nachricht. Die Funktion gibt die Nachrichten über die Nachrichtenschlange an das Fenster. DispatchMessage wird ebenfalls mit der Nachricht in Form der Messagestruktur aufgerufen. Sie gibt sie in Form einer Parametersammlung
98
3 Die Struktur dialogorientierter Windows-Programme
HWND UINT WΡARAM LPARAM
hWnd message wParam lParam
das heißt ohne Parameter time und pt, an die zuständige Fensterprozedur weiter. Die Arbeit von DispatchMessage besteht nicht nur darin, eine Fensterprozedur mit vorgegebenen Parameterwerten aufzurufen. Sie muss die Fensterprozedur des adressierten Fensters überhaupt erst bestimmen, ehe sie sie aufrufen kann. Die MSG-Struktur enthält nur das Handle. Außerdem bildet sie in vielen Fällen selbst die Inhalte wParam und lParam aus den Originalwerten der Nachricht S. 84 f.). Nachdem die Fensterprozedur ihre Arbeit beendet hat, kehrt auch DispatchMessage zurück. Der Rückgabewert dieser Funktion wird nicht verwendet. Die Regie geht wieder an GetMessage. Die Hauptnachrichtenschleife wird genau dann verlassen, wenn ihr die Nachricht WM_QUIT übermittelt worden ist. Dies programmiert man mit der Funktion PostQuitMessage ( - S. 81). GetMessage nimmt dann nur die Nachricht mit ihren Parametern entgegen und gibt FALSE zurück. Die Nachricht leitet den Programmabschluss ein. PostQuitMessage hat für sich allein gesehen nicht die Wirkung, eine Anwendung zu beenden. Sie gibt nur WM_QUIT in die Nachrichtenschlange. Hier muss mehreres zusammenwirken. GetMessage gibt bei WM_QUIT FALSE zurück, so dass die Nachrichtenschleife verlassen wird. Die Anwendung endet nun, weil die Hauptnachrichtenschleife am Ende von winMain liegt. Eine Anwendung kann außer der ífawptnachrichtenschleife noch andere, temporäre Nachrichtenschleifen haben, die über eine mehr oder weniger lange Zeit durchlaufen werden. Die implizit über DialogBox oder DialogBoxParam gebildeten Schleifen, deren Quellcode nicht im Programm enthalten ist, wurden schon erwähnt. Das Programm kann außerdem explizit temporäre Nachrichtenschleifen bilden, deren Code kaum anders als der der Hauptnachrichtenschleife formuliert wird. Wenn diese mit PostQuitMessage beendet werden, kann die Anwendung weiter existieren, indem sie die Nachrichten über andere Nachrichtenschleifen, vor allem natürlich über die Hauptnachrichtenschleife, aus der Nachrichtenschlange entnimmt. Der Sachverhalt der temporären Nachrichtenschleifen wird wie schon angekündigt später aufgegriffen.
J Die Struktur dialogorientierter
3.11.2
Windows-Programme
99
Anwendungen mit einem offenen Dialog mit Fensterprozedur DefDlgProc
Für offene Dialogfenster mit Fensterprozedur Def DlgProc muss die Hauptnachrichtenschleife modifiziert werden. Das Dialogfenster im Programm Kuchen ist ein Beispiel eines solchen Fensters. Diese Fenster und ihre Dialogelemente erhalten ihre Nachrichten nicht über TranslateMessage, DispatchMessage, sondern über IsDialogMessage. Die Funktion erledigt alle Arbeiten, die andernfalls von TranslateMessage und DispatchMessage gemeinsam übernommen werden. IsDialogMessage realisiert im Zusammenwirken mit Def DlgProc eine integrierte Dialogfensterverwaltung. Die beiden Funktionen bieten für einige Nachrichten Leistungen, die sich auf das Dialogfenster als Ganzes beziehen. Dazu gehören (-> auch S. 49.): -
-
Man kann in dem Dialogfenster den Fokus von Dialogelement zu Dialogelement mit den tab- und Richtungstasten weitergeben. Diese integrierte Fokusverwaltung für die Dialogelemente ist ein besonders wertvoller Teil der integrierten Dialogfensterverwaltung. Ein Standardschaltknopf wird als solcher dargestellt (-» eine spätere Technote).
Das Handle eines offenen Dialoges muss in WinMain bekannt sein. Die Funktionen CreateDialog oder CreateDialogParam geben es bei der Erzeugung des Fensters zurück. Zum Beispiel in Kuchen ist programmiert HWND hDlg = NULL; hDlg = CreateDialog(hlnstance, "#100", hwndH, DlgProc); Wenn es genau einen offenen Dialog in der Anwendung gibt, und wenn die Handlevariable wie in diesem Beispiel hDlg heißt, lautet die Hauptnachrichtenschleife: while (GetMessage (&msg, NULL, 0, 0)) { if (hDlg == NULL || !IsDialogMessage(hDlg, &msg)) { TranslateMessage (&msg); DispatchMessage (&msg); } } Es gibt nun zwei Fälle:
100
3 Die Struktur dialogorientierter
Windows-Programme
a. Eine Nachricht ist für den offenen Dialog bestimmt, das heißt sie ist an das Dialogfenster selbst oder an eines seiner Dialogelemente gerichtet. -
Dann ist bei korrekter Programmierung zum Zeitpunkt der Abfrage hDlg ! = NULL. Die Nachricht wird als weitere Verarbeitung des logischen Ausdrucks an IsDialogMessage gegeben.
IsDialogMessage stellt fest, dass die Nachricht für das offene Dialogfenster oder eines seiner Dialogelemente bestimmt ist. Sie sorgt für die Verarbeitung und gibt TRUE zurück. Die Nachricht wird dann nicht mehr an TranslateMessage und DispatchMessage gegeben. b. Die Nachricht ist nicht für den Dialog bestimmt. Das trifft zu, - wenn der Dialog zum Zeitpunkt der Abfrage nicht existiert. hDlg hat dann bei korrekter Programmierung den Wert NULL. Die Verarbeitung des logischen Ausdrucks wird abgebrochen, ohne dass IsDialogMessage aufgerufen wird. Im Programm Kuchen wird hDlg mit NULL initialisiert und erhält erst bei der Erzeugung des Dialogfensters eine andere Eintragung. Vorher wird schon das Hintergrundfenster gebildet. Es kann sofort Nachrichten empfangen. In dieser Zeit ist hDlg noch NULL. Das Dialogfenster in Kuchen existiert bis zum Programmende. Wenn ein Dialogfenster aber während der Dauer der Anwendung zerstört wird, muss auch die Handievariable auf NULL gesetzt werden. -
wenn der Dialog zwar existiert (hDlg != NULL), IsDialogMessage aber anschließend feststellt, dass die Nachricht ihn nicht anspricht. Sie gibt dann FALSE zurück. Dann werden TranslateMessage und DispatchMessage aufgerufen. Nach der Verhaltenslogik der Hauptnachrichtenschleife brauchen Sie die Prüfung hDlg == NULL nicht in die if-Klausel aufzunehmen. Wenn ein NULL-Wert für das Fensterhandle an IsDialogMessage gegeben wird, gibt sie FALSE zurück. Es ist aber üblich, in der angegebenen Weise zu programmieren. Diese Zeile Quellcode wird für alle Eingabenachrichten durchlaufen. Oft ist das Handle eines offenen Dialogfensters NULL, weil das Fenster gerade nicht existiert. Dann gibt es einen - wenn auch nur winzigen - Zeitvorteil, IsDialogMessage gar nicht erst aufzurufen. Wenn Sie die Zeile mit IsDialogMessage nicht in die Nachrichtenschleife einfügen, gelangen alle Nachrichten - und zwar dann über TranslateMessage, DispatchMessage - an das in ihnen angegebene Adressatenfenster. Die integrierte Dialogfensterverwaltung, unter anderem also die Zuweisung des Fokus mit den tab- und Richtungstasten, entfällt.
J Die Struktur dialogorientierter
Windows-Programme
101
Sehr oft hat eine Anwendung ein Hintergrundfenster ohne Dialogelemente aus einer privat definierten Klasse und sonst nur Dialogfenster der Klasse #32770 und Dialogelemente ( - S. 46 ff.). Dann gehen nur Nachrichten für das Hintergrundfenster den Weg über TranslateMessage und DispatchMessage. Eine Anwendung braucht kein Hintergrundfenster ohne Dialogelemente zu haben. Sie kann zum Beispiel nur aus einem offenen Dialog bestehen, der aus einer privat definierten Klasse mit Icon und mit der Fensterprozedur DefDlgProc gebildet wird und der direkt die Dialogelemente enthält. Dann steht auch für dieses Fenster die integrierte Fensterverwaltung zur Verfügung. In diesem Fall braucht man TranslateMessage, DispatchMessage nicht in die Nachrichtenschleife einzubinden. Alle Nachrichten gehen sowieso an IsDialogMessage. Wenn man freilich mit Textbausteinen programmiert, sind TranslateMessage und DispatchMessage in der Nachrichtenschleife enthalten und sie stören nicht. Man braucht sie nicht ausdrücklich zu streichen. Ein Dialogfenster kann statt der Standarddialogfenster-Prozedur DefDlgProc eine private Fensterprozedur verwenden. Dann hat es nicht die integrierte Fensterverwaltung. Man kann auch ein solches Fenster mit CreateDialog oder CreateDialogParam erzeugen. Es ist dann offen, das heißt die fenstererzeugende Funktion bildet dafür nicht implizit eine Nachrichtenschleife. Die Nachrichten für ein solches Fenster und seine Dialogelemente leitet man nicht über IsDialogMessage. Die Funktion arbeitet sinnvoll nur zusammen mit DefDlgProc. Man gibt die Nachrichten an TranslateMessage und DispatchMessage. Eine Programmierfigur der Form i f (hDlg == NULL | | ! I s D i a l o g M e s s a g e ( h D l g ,
&msg))
{ } ist nach den Prinzipien der Strukturierten Programmierung verpönt. Die Einbindung eines Funktionsaufrufs in die if-Anweisung nutzt den Umstand aus, dass bei einer oderVerknüpfung zweier logischer Ausdrücke der zweite nur ausgeführt wird, wenn der erste nicht bereits TRUE ergeben hat. Dies ist von der C-Sprachdefinition her eine Rationalisierung in der Verarbeitung zusammengesetzter logischer Ausdrücke, die hier, vom Standpunkt der Strukturierten Programmierung her zweckentfremdet, für die Gestaltung des logischen Flusses benutzt wird. Von diesem Standpunkt aus gesehen werden auf diese Weise g o t o Anweisungen simuliert, die einen Block von mehr als einem Eingang aus erreichbar machen. Man muss ja auch zugeben, dass die Konstruktion vergleichsweise schwer lesbar ist. Will man sie vermeiden, muss man die Blockanweisung mehrmals hinschreiben oder eine logische Variable benutzen, zum Beispiel in der Form
102
3 Die Struktur dialogorientierter
BOOL result if (hDlg != result = if (hDlg == {
Windows-Programme
= FALSE; NULL) IsDialogMessage(hDlg, &msg); NULL || result == FALSE)
} Der Vergleich fällt von der Länge des Quelltextes her gesehen aber deutlich zugunsten der unkonventionellen, "nichtstnikturierten" Schreibweise aus. Das trifft besonders zu, wenn wie im nächsten Punkt mehrere offene Dialoge berücksichtigt werden müssen.
3.11.3
Anwendungen mit mehreren offenen Dialogen mit Fensterprozedur DefDlgProc
Wenn die Anwendung mehrere offene Dialoge mit Fensterprozedur DefDlgProc bildet, muss IsDialogMessage für alle aufgerufen werden. Andernfalls geht wieder die integrierte Dialogfensterverwaltung verloren. Sie formulieren zum Beispiel den folgenden Code für 3 Handlevariable hDlgl, hDlg2 und hDlg3: while (GetMessage (&msg, NULL, 0, 0)) { if (hDlgl == NULL || !IsDialogMessage(hDlgl, &msg)) if (hDlg2 == NULL II !IsDialogMessage(hDlg2, &msg)) if (hDlg3 == NULL || !IsDialogMessage(hDlg3, &msg)) {
}
}
TranslateMessage (&msg); DispatchMessage (&msg);
oder vielleicht etwas leichter durchschaubar, aber gleichbedeutend: while (GetMessage (&msg, NULL, 0, 0)) { if( hDlg != NULL && IsDialogMessage(hDlg, &msg)) continue; if( hDlg != NULL && IsDialogMessage(hDlg, &msg)) continue; if( hDlg != NULL && IsDialogMessage(hDlg, &msg)) continue; TranslateMessage (&msg); DispatchMessage (&msg); }
3 Die Struktur dialogorientierter Windows-Programme
103
Sie brauchen nicht so leicht Sorge zu haben, dass die Anwendung wegen der hintereinandergeschalteten if-Klauseln langsam wird. Fast nur primäre Eingabenachrichten nehmen den Weg über die Hauptnachrichtenschleife und aus der Sicht der Maschine kommen sie in sehr großen Zeitabständen an ( - S. 38). Zu den ganz wenigen Ausnahmen gehören die paarig zu den Tastaturanschlägen (WM_KE Y DOWN) gebildeten WM_CHAR-Nachrichten.
3.11.4
Abgeschlossene Dialoge
Die Existenz von abgeschlossenen Dialogen in der Anwendung wirkt sich auf die Formulierung der Hauptnachrichtenschleife nach den Ziffern 3.11.1 bis 3.11.3 nicht aus. Im Allgemeinen bildet eine Anwendung einen abgeschlossen Dialog zu irgendeinem Zeitpunkt im Programmverlauf als temporäres Fenster und zerstört ihn wieder, wenn er seinen Zweck erfüllt hat. Dann ist auch die zugehörige implizit erzeugte Nachrichtenschleife wie schon gesagt temporär. Solange sie existiert, entnimmt sie die Nachrichten für die Anwendung aus der Nachrichtenschlange. Zugleich mit dem Ende des Dialoges verschwindet sie wieder. Es ist aber möglich, einen abgeschlossenen Dialog als Hintergrundfenster einer Anwendung zu wählen. Eine solche Anwendung braucht überhaupt keine explizit definierte Hauptnachrichtenschleife.
104
4
4 Die Fensterzustände für die Nachrichtenannahme
Die Fensterzustände für die Nachrichtenannahme
Dem Anwender stehen im Dialogbetrieb zwei Arten von Eingaben zur Verfügung: -
über die Maus. Die Mauseingaben richten sich immer an das angeklickte Fenster. über die Tastatur. Die Tastatureingaben wenden sich an ein bestimmtes Fenster. Man sagt, dass es zu diesem Zeitpunkt den "Fokus" hat. Jedes Fenster kann den Fokus haben.
Die Adressierung der Mauseingaben und der Tastatureingaben sind insofern getrennt zu erörtern. Für das Anklicken mit der Maus muss das gewünschte Zielfenster auf dem Schirm dargestellt sein. Wenn das nicht der Fall ist, muss der Anwender die Darstellung herbeiführen können. Umgekehrt gibt es aber auch Situationen, in denen es dem Anwender nach dem Sinn des Programmes nicht möglich sein soll, ein auf dem Schirm sichtbares Fenster erfolgreich anzuklicken. Bei den Tastatureingaben muss der Anwender wissen, welches Fenster den Fokus hat. Auch hier muss der Anwender die Möglichkeit haben, gegebenenfalls das gewünschte Fenster auszuwählen. Und auch hier kann es sein, dass ein bestimmtes Fenster gerade einmal keinesfalls den Fokus bekommen soll. Die Aufgabe, die Fenster für die Maus beziehungsweise die Tastatur verfügbar zu machen, wird im Windows-System weitgehend durch ein wohlüberlegtes standardisiertes Fensterverhalten gelöst. Der Entwickler muss den Quellcode an diese Bedingungen anpassen. Wenn Eingaben verhindert werden sollen, ist eine entsprechende Programmierung notwendig. Die folgenden Technoten beziehen sich auf dieses Thema. Dabei sind unter anderem die Begriffe Hierarchischer Status, Z-Ordnung, Aktivität, Sichtbarkeit und Ansprechbarkeit (Sperrstatus) zu erklären. Sie bilden einen zusammenhängenden Komplex. Die zugehörigen programmiertechnischen Mittel (Funktionen, Nachrichten) lernen Sie hier kennen.
4.1
Der hierarchische Status
Die Fenster einer Anwendung sind zu jedem Zeitpunkt hierarchisch in einem Graphen angeordnet, der aus einem oder mehreren Bäumen besteht. Ein Baum hat ein Wurzelfenster (top level-window), das keine Vorgänger hat. Die übrigen
4 Die Fensterzuslände für die Nachrichtenannahme
105
Fenster haben genau einen direkten Vorgänger, der als Eiter bezeichnet wird. Ein Elter kann praktisch unbegrenzt viele Nachkommen der 1. Generation haben. Jedes von ihnen kann wieder praktisch unbegrenzt viele direkte Nachkommen haben. Windows kann auf einer Maschine simultan über 16000 Fenster unterhalten. Die Beziehung zwischen einem Elter und einem unmittelbaren Nachkommen kann eine von zwei Ausprägungen haben: das Nachfolgerfenster ist "zugeordnet" (owned) oder "Kind" (child). Hinsichtlich des hierarchischen Status gibt es somit insgesamt 3 Arten von Fenstern: Wurzelfenster Nachkommenfenster: zugeordnete Fenster Kindfenster Ein Nachkommenfenster kann wieder zugeordnete Nachkommen und Kindnachkommen haben. Die Nachkommen der 1. Generation eines Elters werden in ihrem Verhältnis untereinander als Geschwister bezeichnet, unabhängig davon, ob sie zugeordnet oder Kind sind. Wurzelfenster sind ihrer Funktion nach die Hintergrundfenster der Anwendungen. Wenn im Text bisher irgendwo vom Haupt- oder Hintergrundfenster einer Anwendung die Rede war, war syntaktisch immer ein Wurzelfenster gemeint. Wurzelfenster werden vom System automatisch mit ihrem Icon und ihrem Fenstertext in der Startleiste nachgewiesen. Sie signalisieren so dem Anwender, dass die Anwendung gestartet ist. Die Anwendungen haben regelmäßig nur ein Wurzelfenster, damit nur ein Hintergrundfenster und nur einen Fensterbaum. Das ist praktisch immer der beste entwurfstechnische Ansatz, wenn auch nicht syntaktische Notwendigkeit. Wenn eine Anwendung mehrere Wurzelfenster hat, werden sie alle in der Startleiste aufgeführt. Der Anwender findet dann seine Anwendung dort mehrfach vertreten, wundert sich darüber und weiß nicht, welchen Sinn das haben soll. Tatsächlich hat es auch keinen Sinn. Auch wenn eine einzige Funktion WinMain zwei inhaltlich ziemlich getrennte Anwendungsaufgaben versorgen soll, können Sie sie zugleich oder nacheinander aus demselben Hintergrundfenster aufrufen und dem Anwender damit jede Irritation ersparen. Es ist auch nicht anzuraten, das Hintergrundfenster einer Anwendung während der Laufzeit auszutauschen. Die Wurzelfenster werden in der Startleiste in der Reihenfolge ihrer Entstehung nachgewiesen. Der Austausch von Wurzelfenstern führt deshalb auch bei gleichem Icon und gleichem Text unter Umständen dazu, dass der Anwender den Nachweis seiner Anwendung plötzlich an einer anderen Stelle der Startleiste wiederfindet.
4 Die Fensterzustände fir die Nachrichtenannahme
Der Fensterbaum einer Anwendung verknüpft ihre Fenster zu einer Gesamtheit. Die Fenster einer Anwendung bilden keine gemeinsamen Bäume mit den Fenstern anderer Anwendungen oder mit den Fenstern des Systems. Ein Nachkommenfenster ist in einer ganzen Reihe von Punkten mit seinem Eiter verknüpft und von ihm abhängig. Zum Beispiel kann es erst nach dem Elter erzeugt werden und es wird automatisch zerstört, bevor das Elter zerstört wird. Die Abhängigkeit ist bei zugeordneten Fenstern und Kindfenstern im Einzelnen unterschiedlich geregelt. Schlagwortartig ausgedrückt, ist die Abhängigkeit eines Kindes größer als die eines zugeordneten Fensters. Sehr wesentlich ist, dass Kindfenster nur im Clientbereich ihres Elters dargestellt werden. Man kann sie formal außerhalb des Clientbereiches positionieren, aber dann sind sie im Effekt nicht sichtbar. Wird das Elter auf dem Schirm verschoben, bewegen sich Kindfenster im Clientbereich mit. Im Unterschied dazu sind zugeordnete Fenster über den ganzen Schirm beweglich. Wir haben bisher etwas unscharf gesagt, dass ein Fenster als Dialogfenster bezeichnet wird, wenn es Dialogelemente zur Datenanzeige und -eingäbe "enthält". Durch Rückgriff auf die Statusgruppen lässt sich der Begriff präziser fassen: Ein Fenster ist dann Dialogfenster, wenn es Elter, das heißt direkter Vorfahr von Dialogelementen (mindestens einem) ist. Die Gliederung des Fensterbaumes nach Statusgruppen spiegelt oft die Dreiteilung der Fenster einer Anwendung in Hintergrundfenster, Dialogfenster und Dialogelemente wieder: -
Die Wurzelfenster sind wie gesagt immer die Hintergrundfenster. Die zugeordneten Fenster sind die Dialogfenster und umgekehrt. Die Dialogelemente sind Kindfenster und Kindfenster sind Dialogelemente.
Die beiden Nachkommentypen der zugeordneten Fenster und der Kindfenster sind vorwiegend für die in dieser Aufzählung angegebenen Zwecke in das Windows-System eingeführt. Die zugeordneten Fenster haben Eigenschaften erhalten, die auf die Verwendung als Dialogfenster abstellen. Dialogelemente übernehmen die Aufgaben der Dateneingabe und Ergebnisanzeige und brauchen bestimmte andere Eigenschaften. Sie werden zwar überwiegend erst durch die Fensterprozeduren geboten, teilweise aber auch schon dadurch, dass man Dialogelemente als Kindfenster definiert. Fensterprozedur und Kindfensterstatus ergänzen sich. In den beiden letzten Punkten hat die Aufzählung die Bedeutung einer Generalregel, zu der es unterschiedliche Ausnahmen gibt. Die Statusgruppen sind syntaktische Kategorien und können im Rahmen der formal definierten Möglichkeiten nach Gutdünken eingesetzt werden. Hervorzuheben ist: Sie können ein Dialogfenster manchmal sinnvoll als Kind konstruieren. Das wird im
4 Die Fensterzusländeför die Nachrichtenannahme
107
Folgenden auch noch dargestellt. Ferner können Sie Kindfenster bilden, um Graphiken darzustellen. Dies sind nicht die einzigen Ausnahmen. Es gibt im Windows-System noch Kindfenster für ganz andere Zwecke, darunter die "MDI-Client-Fenster". MDI steht für Multiple Document Interface. Die Fenster werden in Anwendungen eingesetzt, in denen wie zum Beispiel in einem Textverarbeitungsprogramm simultan mehrere Dokumente (Texte) im Zugriff sein sollen. Als betriebswirtschaftlicher Entwickler werden Sie derartige Fenster nicht wählen. Der Fensterbaum eines dateiverarbeitenden Programms benötigt gewöhnlich 6 - 7 Generationen. Er kann aber bei einer Vorliebe des Entwicklers für eine stark gestaffelte Benutzerfiihrung, die sich über viele Fenster erstreckt, auch die doppelte Zahl von Generationen haben. Die Blätter sind im Allgemeinen Dialogelemente und somit Kindfenster. Auf dem Schirm können gleichzeitig mehrere Anwendungen und deshalb auch Fenster mehrerer Fensterbäume zu sehen sein. Der hierarchische Status ist keine Eigenschaft der Fensterklasse. Die Fenster einer Klasse können im Allgemeinen wahlweise Wurzel, zugeordnet oder Kind sein. Er wird durch 2 definitorische Festlegungen bestimmt, die bei der Erzeugung des individuellen Fensters angegeben werden: 1. Hat das Fenster ein Elter? 2. Jedes Fenster hat ein Stilbit für seinen "Beziehungstyp". Welches ist der Wert dieses Stilbits? Der Beziehungstyp kann einen der 3 Werte WS_0VERLAPPED, WS_POPUP oder WS CHILD annehmen. Der Status ergibt sich nach der Tabelle: Gibt es ein Elter? Stilbit
Hierarchischer Status
nein
WS_OVERLAPPED oder WS_POPUP
ja
WS_POPUP
zugeordnet
Wurzel
ja
WS_CHILD
Kind
Bei Wurzelfenstern bestimmen die beiden Stilbits WS_OVERLAPPED und WS_POPUP unterschiedliche Detaileigenschaften. Die Fenster der vordefinierten Dialogelementklassen müssen aber immer als Kind definiert werden. Die Fensterprozeduren dieser Klassen setzen voraus, dass es ein Elter gibt. Insofern kann ein Dialogelement schon einmal kein Wurzelfenster sein. Darüberhinaus unterscheiden sich zugeordnete Fenster und Kindfenster in ihren Eigenschaften so, dass für Dialogelemente nur der Status eines Kindfensters in Frage kommt.
108
4 Die Fensterzustände für die Nachrichtenannahme
Der hierarchische Status wird vom System in der Objektbeschreibung des Fensters geführt, räumlich gesehen in dem vom System verwalteten geschützten Hauptspeicherbereich. Er ändert sich während der Lebenszeit des Fensters nicht. (Grundsätzlich kann das Elter eines Fensters mit der Funktion S e t P a r e n t ausgewechselt werden. Es gibt dafür in standardmäßigen Anwendungen aber keinen Bedarf.)
4.2
Die Z-Ordnung
Zur Beschreibung der Überlagerung der Fenster auf dem Schirm wird der Begriff der Z-Ordnung benutzt. Er bezeichnet die dritte Dimension neben der horizontalen und vertikalen Positionierung auf dem Schirm. Dabei kommt es nicht auf absolute Zahlenwerte, sondern nur auf die relative Anordnung der Fenster an. Alle zu einem Zeitpunkt sichtbaren Fenster sind untereinander linear geordnet. Wir bringen die Reihenfolge hier mit Pfeilen zum Ausdruck. Sind zum Beispiel die Fenster H, K, ZI und Z2 sichtbar, wird ihre Z-Ordnung zu einem bestimmten Zeitpunkt etwa durch H - > K - > Z 1 - > Z 2 beschrieben. Fenster, die näher am Betrachter liegen, sind "oben" oder "vorn", Fenster, die weiter entfernt sind und deshalb leichter verdeckt werden, sind "unten" oder "hinten". In dem eben angegebenen Beispiel ist Z2 das oberste Fenster. Die Z-Ordnung der Fenster ist nicht unmittelbar mit der effektiven Sichtbarkeit gleichzusetzen. Ein Fenster kann rechnerisch außerhalb des Schirms positioniert sein. Dann ist es effektiv niemals sichtbar, egal, wie seine Z-Position ist. Zwei Fenster können auf dem Schirm nichtüberlappend positioniert sein. Dann spielt es für ihre effektive Sichtbarkeit keine Rolle, welches in der Z-Ordnung weiter vorn ist. Die Z-Ordnung unterliegt festen Regeln. Sie stellen in ihrem Leitmotiv auf die Zusammengehörigkeit der Fenster einer Anwendung und auf das Arbeiten mit Dialogfenstern ab: -
Wenn der Anwender gerade mit einem Fenster arbeitet, sind alle Fenster des zugehörigen Baumes höher in der Z-Ordnung als die Fenster eines anderen Baumes. Kein fremdes Fenster kann ein Fenster der Anwendung überdecken, wenn sie nur aus einem einzigen Fensterbaum besteht. (Wenn eine Anwendung entgegen dem bereits angegebenen Entwurfsprinzip zwei Fensterbäume hat, können Fenster eines anderen Programms in der Z-Ordnung zwischen den Fenstern der beiden Bäume liegen.)
-
Wenn der Anwender mit einem Dialogfenster arbeitet, ist es im Vordergrund, das heißt oben in der Z-Ordnung. Seine Dialogelemente müssen dann gleichzeitig in seinem Clientbereich angezeigt werden und dadurch
4 Die Fensterzustünde für die Nachrichlenannahme
109
ihre Zugehörigkeit zu dem Dialogfenster deutlich machen. Die vorgegebenen Regeln sichern das im Allgemeinen automatisch, indem sie festlegen, dass die Dialogelemente immer Kind sind. Kindfenster werden unmittelbar über ihrem Eiter angezeigt. Bei einer speziellen Struktur des Fensterbaumes ist es grundsätzlich möglich, so zu programmieren, dass ein fremdes Fenster zwischen ein Dialogfenster und seine Dialogelemente gerät. Dann liegt ein Programmierfehler vor, der aber - praktisch beurteilt - absichtlich konstruiert werden muss. Die Regeln der Z-Ordnung werden nun aufgeführt, zuerst diejenigen, die den Zustand zu einem bestimmten Zeitpunkt betreffen: 1. Die Fensterbäume sind wie gesagt in der Z-Ordnung nicht gemischt. Alle Fenster eines bestimmten Baumes liegen über beziehungsweise unter denen eines anderen Baumes. Speziell sind alle Fenster des Baumes, der ganz oben in der ZOrdnung liegt, näher am Betrachter als alle übrigen Fenster. 2. Zugeordnete Fenster liegen in der Z-Ordnung immer über ihrem Elter. 3. Kindfenster liegen in der Z-Ordnung auch immer über ihrem Elter. 4. Kindfenster liegen immer unter einem zugeordneten Geschwister. Das Beispiel Bild 4.2.1 zeigt einen Fensterbaum (genauer gesagt einen Teil davon) mit einem Dialogfenster D, das außer seinen Dialogelementen, die wie gesagt immer Kind sind, noch ein zugeordnetes Fenster Ζ als Nachfolger hat. Die ZOrdnung ist vielleicht D - > K 1 - > K 2 ^ K 3 - K 4 - » Z . Die Anordnung zwischen den Kindern kann auch anders sein. Immer wird aber D am Anfang und Ζ am
Bild 4.2.1 Fensterbaum Ende der Folge liegen. Der Sinn ist, dass ein zugeordneter Nachfolger wie Ζ gewöhnlich selbst auch ein Dialogfenster sein wird, in dem zum Beispiel Hilfsarbeiten für D ausgeführt werden sollen. (Im Bild sind hier nur zur Vereinfachung keine Kinder von Ζ eingezeichnet.) Dann dürfen die Kinder von D nicht von ihrem Dialogfenster und die Kinder von Ζ nicht von ihrem getrennt werden. Das Bild 4.2.2 gibt einen Blick auf den Bildschirm.
110
4 Die Fensterzuslände fir die Nachrichtenannahme
5. Aus den Ziffern 1. bis 3. ergibt sich: Das oberste Wurzelfenster in der ZOrdnung hat über sich nur noch seine eigenen Nachkommen (wenn es welche hat), und zwar die Nachkommen über sämtliche Generationen.
D zugeordnet
/ zugeordnet
K1 Kind
K2 Kind
K3 Kind
K4 Kind
Bild 4.2.2 Schirmdarstellung zu Bild 4.2.1
Bild 4.2.3 Fensterbaum 6. Eine lineare Abstammungslinie von ausschließlich Kindfenstern kann in der Z-Ordnung unterbrochen sein, wenn es eine weitere, konkurrierende lineare Abstammungslinie von Kindfenstern gibt, so dass eine von ihnen notwendig unterbrochen sein muss. Andernfalls kann eine solche Abstammungslinie nicht unterbrochen sein. In Bild 4.2.3 kann die Z-Ordnung KO Kl -> K2 -> K3 - K4 oder KO - K3 - K4 - Kl - K2 sein. Entweder die Linie KO - Kl oder KO - K3 ist unvermeidlich unterbrochen. Die Linien Kl - K2 und K3 - K4 sind aber keinesfalls unterbrochen. 7. Innerhalb eines Fensterbaumes können lineare Abstammungslinien gemischt angeordnet sein, soweit die eben angegebenen Regeln 4 und 6 respektiert werden. Dies betrifft somit den Zusammenhalt zwischen Eltern und ihren zugeordneten Nachkommen. In Bild 4.2.4 ist ein Fensterbaum zugleich mit einer möglichen Z-Ordnung eingezeichnet. Ein Wurzelfenster H hat ein Kind Κ und dieses einen zugeordneten Nachfolger ZI. Η hat ferner einen zugeordneten
4 Die Fensterzustände fiir die Nachrichtenannahme
111
Nachfolger Z2. Die Z-Ordnung kann Η - Κ -> Z2 - ZI oder Η - Κ - ZI - Z2 lauten. Wegen Regel 4 muss Κ unmittelbar über Η liegen.
Bild 4.2.4 Fensterbaum mit Z-Ordnung Das Bild 4.2.5 zeigt ein weiteres Beispiel, ebenfalls mit einer möglichen ZOrdnung. Ein zugeordnetes Fenster ZI hat 2 Linien von Kindfenstern Kl - K2 und K3 - K4. K2 hat einen zugeordneten Nachkommen Z3. Ferner hat ZI einen zugeordneten Nachkommen Z2.
Bild 4.2.5 Fensterbaum mit Z-Ordnung
112
4 Die Fensterzuslände fir die Nachrichtenannahme
Die Abstammungslinien Kl - K2 und Κ3 - Κ4 stehen in der Z-Ordnung auf jeden Fall ohne Unterbrechung. Die Z-Teilfolgen Kl - K2 und K3 - K4 stehen unmittelbar hintereinander, und eine von ihnen schließt sich direkt an ZI an. In Bezug auf diese Fenster kann die Z-Folge also ZI - Kl - K2 - K3 - K4 oder ZI - K3 -> K4 -> Kl - K2 lauten. Z2 kann über Z3 liegen oder umgekehrt. Z2 und Z3 werden aber immer über den Fenstern ZI, Kl, K2, K3, K4 liegen. Die Gesamtheit der Fenster, die aus einem Wurzelfenster oder einem zugeordneten Fenster und ausschließlich Kindfensternachfahren besteht und wie geschildert in der Z-Ordnung nicht durch ein zugeordnetes Fenster unterbrochen werden kann, heißt ein Fensterverbund. Hat ein Wurzelfenster oder zugeordnetes Fenster keine Kindfenster, bildet es allein einen Verbund. Hat es Kindfenster, endet der Verbund in jeder Linie mit einem Blatt (einem Endpunkt des Baumes) oder vor einem zugeordneten Nachkommen. Der Begriff des Verbundes zielt im einfachsten Fall, der auch häufig auftritt, auf Dialogfenster und ihre Dialogelemente ab. Ein Dialogfenster, das aus einem Wurzelfenster oder zugeordneten Fenster besteht und nur Dialogelemente enthält, ist ein Verbund. Ein Beispiel ist in Bild 4.2.6 dargestellt.
Bild 4.2.6 Fensterbaum eines Dialogfensters Die Dialogelemente Kl bis K4 liegen alle über dem Dialogfenster D. Untereinander können sie eine beliebige Z-Ordnung haben. Meistens ist sie ohne Bedeutung. Gewöhnlich zeichnen Sie die Dialogelemente so in den Clientbereich des Dialogfensters, dass sie sich nicht überlappen. Dialogelemente können keine Titelleisten haben, so dass ihre Positionierung auch nicht vom Anwender mit der Maus geändert werden kann. Die gegenseitige Z-Ordnung wirkt sich dann auf die effektive Sichtbarkeit nicht aus. Grundsätzlich ist es aber möglich, die Dialogelemente beim Entwurf des Dialogfensters überlappend anzuordnen oder ihre Lage während des Programmverlaufs über entsprechende Anweisungen zu ändern und sie so später überlappend zu positionieren. Es gibt praktische Anwendungsfalle dafür. Dann ist die
4 Die Fensterzustände für die Nachrichtenannahme
113
Z-Ordnung natürlich ein wesentlicher Punkt. Programmiertechnische Fragen dazu werden später aufgegriffen.
Bild 4.2.7 Fensterbaum mit Z-Ordnung zwischen den Dialogen Nicht immer liegt der einfachste Fall vor. Ein Dialogfenster kann außer seinen Dialogelementen weitere Dialogfenster als Nachkommen haben. Ist das Hauptfenster eines Nachkommendialoges Kind, gehört er mit zum Verbund und liegt stets unter einem zugeordneten Geschwisterdialog. Ein Beispiel zeigt das Bild 4.2.7. Dl ist ein Dialogfenster mit den Dialogelementen Kl und K2. Es hat als Nachkommen - außer seinen Dialogelementen - selbst wieder 2 Dialogfenster D2 und D3. D2 ist Kind, D3 zugeordnet. D3 überdeckt in der Z-Ordnung Dl und D2 mit ihren Dialogelementen. Diese Regelung berücksichtigt automatisch, dass die Dialogelemente von D3 bei D3 angeordnet sein sollen. Die Zusammengehörigkeit von Dl mit seinen Dialogelementen Kl und K2 ist aber nicht automatisch geschützt. Nach Regel 6 kann die Z-Ordnung unter anderem Dl - D2 - K3 - K4 - Kl - K2 lauten. Hier handelt es sich um die Situation, die eingangs dieser Technote schon erwähnt wurde, in der nämlich die Regeln der Z-Ordnung grundsätzlich zulassen, dass ein fremdes Fenster zwischen ein Dialogfenster und seine Dialogelemente gerät. Ein praktisches Problem, eine programmiertechnische Gefahrenquelle ergibt sich daraus nicht. Bei der Erzeugung der Dialogfenster liegen die Dialogelemente jeweils unmittelbar an, das heißt die Z-Ordnung ist Dl - Kl - K2 D2 - K5 - K6 (Die Reihenfolge zwischen Kl und K2 und zwischen K5 und K6 kann umgekehrt sein). Der Anwender hat keine Mittel, um die Z-Ordnung zu
114
4 Die Fensterzitstände fur die Nachrichtenannahme
ändern. Die Anwendung kann sie ändern, aber das muss dann ausdrücklich programmiert werden. Soweit also die Organisation der Z-Ordnung zu einem bestimmten Zeitpunkt. Nun zur zeitlichen Entwicklung: 8. Wurzelfenster können ihre Z-Reihenfolge und damit die Z-Reihenfolge ihrer Fensterbäume im Programmverlauf beliebig vertauschen. Mal wird der eine über dem anderen liegen, mal umgekehrt. Das betrifft die Wurzelfenster verschiedener Anwendungen ebenso wie die syntaktisch möglichen mehreren Wurzelfenster einer einzelnen Anwendung. Mit der Änderung stellt sich ein neuer Zustand ein, der wieder der eben gegebenen Beschreibung entspricht. 9. Zugeordnete Fenster eines Baumes, die nicht in direkter Linie voneinander abstammen, können ihre Z-Reihenfolge ebenfalls im Programmverlauf vertauschen. In Bild 4.2.5 (S. 111) können Z2 und Z3 ihre relative Z-Ordnung tauschen. 10. Wenn für ein Elter nacheinander 2 zugeordnete Fenster erzeugt werden, überdeckt das Jüngere bei seiner Entstehung das Ältere. Die Regel berücksichtigt ein Aktualitätsprinzip: Ein im Programmverlauf erzeugtes zugeordnetes Fenster ist in aller Regel ein Dialogfenster. Es wird erzeugt, wenn man damit arbeiten will. Dafür ist am besten, wenn es auch gleich oben in der Z-Ordnung ist. 11. Kindfenster mit demselben Elter (Zwillingskindfenster) können ihre ZReihenfolge im Programmverlauf vertauschen, wenn sie die Eigenschaft (das Stilbit) WS_CLIPSIBLINGS haben. Die zuständigen Funktionen führen die Änderung der Z-Ordnung andernfalls nicht aus. Die von der Änderung betroffenen Kinder brauchen alle das Stilbit. Die Ratio der Regelung ist: Die meisten Kindfenster eines Elters werden in fester Positionierung ohne gegenseitige Überdeckung dargestellt. Es kommt dann auf ihre Z-Reihenfolge nicht an; deshalb wird sie auch sowieso nicht geändert. Die vom System für das Zeichnen von Kindfenstern aufgerufene Menge von Funktionen ist deshalb reduziert. Sie müssen dem System ausdrücklich mitteilen, wenn Sie für bestimmte Kindfenster Unterstützung für die Änderung der ZOrdnung fordern. 12. Wenn für ein Elter nacheinander zwei Kindfenster ohne das Stilbit WS_CLIPSIBLINGS erzeugt werden, liegt das Jüngere bei seiner Entstehung über dem Älteren. Haben die Kinder das Stilbit WS_CLIPSIBLINGS, liegt das Jüngere bei seiner Entstehung jedoch unter dem Älteren. Hier wird also das oben unter Ziffer 10. im Zusammenhang mit zugeordneten Fenstern angegebene Aktualitätsprinzip nicht angewendet. Da Kindzwillinge das Stilbit vor allem bekommen, wenn sie sich zeichnerisch überlappen, kann es sein, dass Sie die ZOrdnung sofort ändern müssen.
4 Die Fensterzustände fur die Nachrichtenannahme
4.3
115
Aktivität und Fokus
Wurzelfenster und zugeordnete Fenster können den Status haben, "aktiv" zu sein. Da man ihnen diesen Status zuweisen kann, sind sie "aktivierbar". Kindfenster können nicht aktiv sein. Sie sind nicht aktivierbar. Zu einem bestimmten Zeitpunkt ist höchstens ein Fenster auf dem Schirm aktiv. Die Aktivität äußert sich in 3 Eigenschaften: 1. Fokus. Der Fokus liegt bei dem aktiven Fenster oder einem Kindfenster seines Verbundes.1 Umgekehrt folgt daraus: Wenn ein Kindfenster den Fokus hat, ist immer der jüngste Vorfahr aktiv, der kein Kindfenster ist. 2. Z-Ordnung. Das aktive Fenster wird auf dem Schirm nicht von anderen Fenstern überlagert, außer - nach den Regeln über die Z-Ordnung - von seinen eigenen Nachkommen. Das aktive Fenster gehört somit zu dem obersten Fensterbaum in der Z-Ordnung. Er liegt mit allen Fenstern über allen anderen Bäumen. Wenn Nachkommen des aktiven Fensters wieder eigene Nachkommen haben, liegen sie alle über dem aktiven Fenster. Das aktive Fenster braucht nicht das höchste aktivierbare Fenster in der ZOrdnung zu sein. Es kann zugeordnete, das heißt aktivierbare Nachkommen haben und die sind in der Z-Ordnung ja immer höher. 3. Kennzeichnung. Wenn das aktive Fenster eine Titelleiste hat, wird sie in hervorgehobener Farbe dargestellt. Wenn es keine Titelleiste hat, sieht man ihm nicht an, dass es aktiv ist. Im Grundsatz ist immer dasjenige aktivierbare Fenster aktiv, mit dessen Verbund der Anwender gerade arbeitet. Der Verbund wird der aktive Verbund genannt. Eines seiner Fenster hat den Fokus. Der Anwender soll sehen können, auf welchen Verbund sich die Arbeit gegenwärtig konzentriert. Daraus ergibt sich das Entwurfsprinzip: Aktivierbare Fenster brauchen eine Titelleiste. Die Dialogelemente und andere Kinder des Verbundes werden im Clientbereich des aktiven Fensters dargestellt. Der Anwender muss in den allermeisten Fällen auch sehen können, welches Fenster im Verbund den Fokus hat. Aktivierbare Fenster zeigen nicht an, ob sie den Fokus haben. Hierin liegt aber, wie Sie jetzt sehen werden, kein Problem. In diesem Zusammenhang ist zuerst ein Begriff einzuführen. Bei den Dialogelementen ist danach zu unterscheiden, ob sie Tastatureingaben verarbeiten können oder nicht. Zum Beispiel nehmen die Texteingabefelder (Editfelder) 1) Die Anwendung kann über die Funktion S e t F o c u s veranlassen, dass der Fokus verschwindet, so dass gar kein Fenster den Fokus hat. Da man den Fokus nicht verschwinden lassen wird, ist das ein praktisch unwichtiger Sonderfall.
116
4 Die Fensterzustände für die Nachrichtenannahme
natürlich Tastatureingaben an und zeigen den veränderten Text an. Schaltknöpfe kann man mit der Entertaste andrücken. Derartige Dialogelemente zeigen dem Anwender immer an, dass sie auf die Eingaben reagieren. Sie heißen "eingabefähig". Ihnen stehen diejenigen Dialogelemente gegenüber, die nur der Anzeige dienen. Zu ihnen gehören die statischen Textanzeigefenster von der Art, wie sie das Dialogfenster von Kuchen hat. Ob ein Dialogelement eingabefähig ist, ist eine Frage der Fensterdefinition. Sie wird durch die Wahl der Fensterklasse entschieden. Editfelder gehören der Klasse Edit, Schaltknöpfe der Klasse Button, statische Textelemente der Klasse Static an. Die Fenster dieser Klassen sind immer grundsätzlich eingabefähig beziehungsweise nicht eingabefähig. Die Eigenschaft kann im Einzelfall über Programm zeitweise unterdrückt werden. Zum Beispiel kann man ein Editfeld, das wie gesagt Element einer Klasse von Eingabefenstern ist, für Eingaben sperren. Es ist dann so lange im tatsächlichen Verhalten nur für Anzeigen zu gebrauchen. Ein eingabefähiges Dialogelement reagiert gewöhnlich auch auf Mauseingaben. Zum Beispiel kann man bei einem Editfeld die Maus benutzen, um den Tastatureingabecursor, den sogenannten Caret, auf eine bestimmte Stelle in der Zeichenkette zu lenken. Nichteingabefahige Dialogelemente verarbeiten dagegen meistens keine Mausklicks. Wenn man ein Textanzeigefenster im Programm Kuchen anklickt, geht die Nachricht an das darunter liegende Dialogfenster. Das Textelement ist für Mausklicks durchsichtig. Es ist Entwurfsprinzip im Windows-System, dass Dialogelemente, sofern sie überhaupt Eingaben verarbeiten können, dem Anwender möglichst die Wahl lassen, ob er seine Eingabe über die Tastatur oder die Maus realisieren will. Das Prinzip wird aber nicht streng durchgeführt. Stattet man zum Beispiel ein statisches Textfenster mit einem besonderen Stilbit (SS_NOTIFY) aus, ist es nicht durchsichtig für Mausklicks, sondern nimmt sie selbst entgegen. Tastatureingaben dagegen verarbeitet es nicht. Dies ist aber in der Entwicklung doch eine Ausnahme. Standardmäßig erhält ein Textelement das besondere Stilbit nicht. Wir geben ausdrücklich an, wenn es in einer Anwendung eingesetzt wird. Die vordefinierten eingabefähigen Dialogelemente zeigen an, ob sie den Fokus haben. Zum Beispiel zeigen Texteingabefelder während dieser Zeit den Tastatureingabe-Cursor. Schaltknöpfe zeigen ihre Beschriftung umrandet usw. Sie programmieren: 1. Wenn im Clientbereich eines aktiven Fensters eingabefähige Dialogelemente gezeigt werden (programmiertechnisch ausgedrückt: wenn der Verbund des aktiven Fensters eingabefähige Dialogelemente enthält), hat immer eines von ihnen den Fokus. Hier gibt es, wie schon erörtert wurde, mehrere Möglichkeiten
4 Die Fensterzustände für die Nachrichtenannahme
117
der Konstruktion des Verbundes. Zwei von ihnen haben besondere praktische Bedeutung. a. Der Standardfall liegt vor, wenn die eingabefähigen Dialogelemente alle Kinder des aktiven Fensters sind. Er ist bereits in Bild 4.2.1 (S. 109) und Bild 4.2.6 (S. 112) dargestellt. Eine weitere Illustration ist das Bild 4.3.1. Sie wählen hier die standardisierte integrierte Fensterverwaltung mit der Fensterprozedur DefDlgProc.
Bild 4.3.1 Fensterbaum Das Dialogfenster Dl in Bild 4.3.1 hat zwar außer seinen Dialogelementen Kl - K3 noch einen Nachkommen D2 mit eigenen Dialogelementen. D2 ist aber selbst aktivierbar und gehört mit seinen Kindern nicht zum Verbund von Dl. Während Dl aktiv ist, haben Kl, K2 und K3 je nach Programmverlauf abwechselnd den Fokus (soweit sie eingabefähig sind). Die integrierte Fensterverwaltung sorgt dafür, dass das Dialogfenster den Fokus nie selbst hat. Zwar wird er im Verlauf der Arbeit mit der Anwendung häufig einmal an das Dialogfenster gelangen. Die Fensterprozedur Def DlgProc erhält dann aber zugleich die Nachricht WM_SETFOCUS, die ihr mitteilt, dass das Fenster den Fokus bekommen hat. Sie wertet sie aus, indem sie den Fokus (mit der Nachricht WM_NEXTDLGCTL) an ein eingabefähiges Dialogelement gibt. Die dafür benötigte Zeitspanne ist infinitesimal kurz. Das Dialogfenster selbst braucht den Fokus nicht. Ein möglicherweise vorhandenes Systemmenü oder eine Menüleiste können zwar auch über Tastatureingaben angesprochen werden. Dafür ist der Fokus aber nicht Voraussetzung. Dieser Punkt wird zum Ende der Technote noch aufgegriffen. b. Die zweite wichtige Konstruktion (neben dem Bild 4.3.1) zeigt das Bild 4.3.2. Es entsteht aus Bild 4.2.7, wenn die unmittelbaren Kinder Kl und K2 von Dl weggelassen werden. D3 ist ein aktivierbares Dialogfenster und hat mit der
118
4 Die Fensterzuslände fur die Nachrichtenannahme
Fokusverwaltung im Verbund von Dl nichts zu tun. Wir müssen uns aber mit dem Kinddialogfenster D2 auseinandersetzen.
Bild 4.3.2 Fensterbaum mit Z-Ordnung zwischen den Dialogen In Windows-Programmen gilt das verbindliche Entwurfsprinzip: Kindfensterdialoge erhalten keine Titelleisten. Die Begründung ist: Das Fenster D2 ist nicht aktivierbar. Wenn es eine Titelleiste hat, wird sie nicht eingefärbt, während der Anwender mit den Dialogelementen arbeitet. Das widerspricht dem durchgängigen Prinzip der Information des Anwenders über seinen aktuellen Arbeitsbereich - ein Dialogfenster mit seinem Clientbereich und den Dialogelementen darin - durch eingefärbte Titelleisten. Die integrierte Dialogfensterverwaltung setzt bei einem Kinddialog voraus, dass er keine Titelleiste hat. Hat er eine Titelleiste, fällt sie in einem wesentlichen Punkt aus: Editfenster, das heißt Fenster zur Dateneingabe können nicht mehr in der normalen Weise den Fokus bekommen. Über diesen Punkt hinaus, der bei Dialogfenstern ohne Editelemente nicht von Bedeutung wäre, sind die Systemfunktionen zur Verwaltung der Fenster generell nicht für den Fall ausgetestet, dass die Regel missachtet wird. Eine Missachtung ist ein Programmierfehler, der ein unerwartetes Verhalten des Programms zur Folge haben kann. Da Kinddialogfenster also keine eigenen Titelleisten bekommen, können sie auch keine selbständigen Dialoge sein. Der Anwender sieht im Bildschirmlayout einen Kinddialog in das aktivierte Fenster eingebettet. Er kann nicht erkennen, dass technisch ein eigener Dialog vorliegt. Er ordnet die dargestellten Dialogelemente dem aktiven Dialogfenster zu. Es ist deshalb verbindliches Entwurfs-
4 Die Fensterzuslände für die Nachrichtenannahme
119
prinzip, einen Kinddialog mit dem aktivierbaren Fenster des Verbundes im Verhalten zu einer Einheit zu verbinden. In Bild 4.3.2 müssen Dl und D2 integriert werden. Sie wählen für D2 die Fensterprozedur DefDlgProc und integrierte Dialogfensterverwaltung. Sie versorgt D2 mit seinen Dialogelementen. Es gibt aber zunächst noch keine integrierte Fensterverwaltung für Dl und D2 zusammen. Dl hat irgendeine Fensterprozedur. Wenn man zum Beispiel das aktivierbare Fenster Dl mit der Maus anklickt, bekommt es den Fokus, und wenn man nichts anderes programmiert, behält es ihn auch. Eine integrierte Fensterverwaltung würde ihn sofort abgeben. Hier müssen Sie selbst programmiertechnische Vorsorge treffen. Sie werden so programmieren, dass das aktivierbare Fenster Dl den Fokus, wenn es ihn bekommen hat, sofort mit der zuständigen Funktion Set Focus an das Dialogfenster D2 gibt, von wo die integrierte Fensterverwaltung ihn selbsttätig an ein Dialogelement weiterleitet. Es handelt sich hier keineswegs um eine aufwändige Komplikation. Der Unterschied zu den Verhältnissen bei aktivierbaren Dialogfenstern ist sehr gering. Die Fensterprozedur von Dl und die Dialogprozedur von D2 brauchen ein paar Zeilen Quellcode, die Sie zur fachgerechten Bildung des Fensterverbundes routinemäßig einfügen. Die Konstruktion hat unter anderem Anwendung, wenn der Arbeitsbereich von Dl während der Programmverlaufs möglicherweise ausgewechselt werden soll. Die Anwendung ersetzt dann also während der Laufzeit D2 durch ein anderes Kinddialogfenster. Der Kinddialog kann dann immer so dimensioniert werden, dass er den Clientbereich von Dl vollständig oder in einer vorgeplanten Weise ausfüllt. Es gibt aber auch andere Anwendungen. 2. Wenn das aktive Fenster nur nichteingabefáhige Dialogelemente hat und wenn es auch keinen Kinddialog mit eingabefahigen Dialogelementen gibt, darf der Fokus bei dem Dialogfenster selbst oder bei einem Dialogelement liegen. Die Frage ist ohne jede Bedeutung. Es gibt dort kein Fenster, das sinnvoll den Fokus beanspruchen kann. Das Dialogfenster darf eine private Fensterprozedur oder die Fensterprozedur DefDlgProc haben. Beides ist, soweit der Fokus betroffen ist, gleichwertig. Wir schließen die Erörterung hier ab, indem wir noch einmal zu Bild 4.2.7 (S. 113) zurückkehren. Es wurde zunächst benutzt, um einen Aspekt der ZOrdnung zu erklären. Ein aktivierbares Dialogfenster Dl hat außer seinen Dialogelementen noch ein Kinddialogfenster D2 mit eigenen Dialogelementen. Die Situation ist unproblematisch, wenn -
die Dialogelemente von D2 nicht eingabefähig sind. Sie programmieren wie unter la. beschrieben. D2, K3 und K4 bekommen nie den Fokus.
120 -
4 Die Fensterzustände für die Nachrichtenannahme
die Dialogelemente von Dl nicht eingabefáhig sind. Sie programmieren dann wie unter lb. beschrieben. Aus Dl und D2 machen Sie ein kombiniertes Dialogfenster. Kl und K2 bekommen nie den Fokus.
Anders ist es aber, wenn Dl und D2 beide eingabefahige Dialogelemente haben. Bild 4.2.7 fällt dann weder unter die Ziffer 1 noch die Ziffer 2 der gegenwärtigen Diskussion. Sie würden dann zwar vielleicht Dl und D2 jeweils für sich mit der Fensterprozedur Def DlgProc und mit integrierter Fensterverwaltung ausstatten. Sie können aber keine integrierte Fensterverwaltung angeben, die Dl und D2 gemeinsam umfasst. Deshalb gibt es unter anderem keinen tabZyklus ( - S. 49), der über die im Clientbereich von Dl dargestellten eingabefahigen Dialogelemente führt. Es gibt keine dafür vorgesehene Systemfunktion und Sie können das als Entwickler nicht in einfacher Weise durch eigenen Code ausgleichen. Hier liegt eine Situation vor, die Sie praktisch nicht bewältigen können und deshalb vermeiden müssen. - Hier geht es aber sachlich nicht um eine Lücke in den Programmiermöglichkeiten. Sie können Dl und D2 ja zu einem einzigen Fenster zusammenfassen. Das Beispiel dient allein der Diskussion. Die vorstehende Erörterung ging davon aus, dass aktivierbare Fenster nicht anzeigen, ob sie den Fokus haben, dass für den Anwender aber im Allgemeinen sichtbar sein muss, welches Fenster gerade den Fokus hat. Dafür hat sich nun die folgende Lösung ergeben, die Sie als Entwickler realisieren werden: -
-
Wenn ein aktives Fenster in seinem Clientbereich eingabefähige Dialogelemente (eigene oder die eines Kindfensters) zeigt, hat immer eines von ihnen den Fokus. Nach der Programmierung der Elemente zeigen sie das an. Ein Dialogfenster mit eingabefähigen Dialogelementen erhält immer die Fensterprozedur Def DlgProc und integrierte Fensterverwaltung. Andernfalls hat das aktive Fenster oder ein möglicherweise vorhandenes nichteingabefahiges Dialogelement den Fokus. Auf die Wahl der Fensterprozedur kommt es dann grundsätzlich nicht an.
Wir kommen nun wie angekündigt noch auf die Menüs zu sprechen. Nur aktivierbare Fenster können Systemmenüs und Menüleisten haben. Eine Menüleiste mit ihren aufklappbaren Pull-down-Menüs ist verwendungstechnisch in jeder Beziehung dasselbe wie ein Dialogelement. Man kann ein Menü auch dasselbe wie eine Sammlung von Dialogelementen nennen. Jeder Menüpunkt entspricht einem Schaltknopf. Dies ist so bei Menüleisten und auch beim Systemmenü eines Fensters. Die Knöpfe für Minimieren, Maximieren und Schließen rechts oben in der Titelleiste wiederholen je einen einzelnen Auswahlpunkt aus dem Systemmenü.
4 Die Fensterzustände für die Nachrichtenannahme
121
Der Anwender spricht ein Menü (Systemmenü, Menüleiste) an, indem er es mit der Maus anklickt. Alternativ kann er ein Menü über ein Tastenkürzel (altTaste + Zusatztaste) ansprechen, wenn eins dafür definiert ist. Das Systemmenü öffnet er über alt-Enter, einen Punkt der Menüleiste adressiert er über alt und das unterstrichen angezeigte Zeichen (im Explorer zum Beispiel alt-d oder alt-b). Die seitlichen Richtungstasten (Pfeil rechts beziehungsweise Pfeil links) führen dann zyklisch über die einzelnen Pull-down-Menüs. Sie beziehen das Systemmenü und die Pull-down-Menüs der Menüleiste ein. Die senkrechten Richtungstasten führen zyklisch über die Auswahlpunkte eines einzelnen Pull-downMenüs. Einen so ausgewählten Menüpunkt kann der Anwender zum Beispiel mit Enter bestätigen. Menüs reagieren also auf Maus- und Tastatureingaben. Syntaktisch ist ein Menü aber kein Dialogelement. Es ist kein eigenes Fenster, sondern ein Teil (ein Element) des Fensters, in dem es dargestellt wird. Es ist eine syntaktische beziehungsweise organisatorische Entscheidung der Entwickler des WindowsSystems, Menüs formal nicht als Dialogelemente auszubilden. Diese funktional nicht begründete Sonderstellung wird durch die Systemprogrammierung wieder ausgeglichen. Die Regelung lautet: Die Arbeit mit Menüs und in Menüs setzt nicht voraus, dass ihr Fenster den Fokus hat. Wenn der Anwender in einem aktiven Verbund ein Menü mit der Maus anklickt, bleibt der Fokus, wo er gerade ist. Liegt er also bei einem eingabefähigen Dialogelement, bleibt er dort. Entsprechend ist es, wenn der Anwender in einem aktiven Verbund ein Menü über die Tastatur anspricht. Er kann ein Pull-downMenü öffnen und dann zum Beispiel darin über die senkrechten Richtungstasten Auswahlaktionen vornehmen. Auch dann bleibt der Fokus, wo er ist. Allerdings wirkt er sich in dieser Zeit auch nicht mehr aus. Um dies zu erreichen, werden Menüs über Eingriffe des Systems in die Nachrichtenverarbeitung erzeugt, gesteuert und auch wieder geschlossen. Liegt der Fokus zum Beispiel gerade bei einem Editfenster und gibt man zu diesem Zeitpunkt eine Tastaturkürzelnachricht zum Eröffnen eines Pull-down-Menüs, fängt das System sie ab, wertet sie aus und eröffnet das Menü. Zugleich erzeugt das System eine temporäre Nachrichtenschleife. Nur sie wird durchlaufen, solange das Menü offen ist. Alle primären Nachrichten werden darüber geleitet. Sie nehmen nicht den Weg über die Hauptnachrichtenschleife. Die temporäre Nachrichtenschleife kümmert sich nicht um die Frage, wo der Fokus ist, sondern leitet alle Tastaturnachrichten an eine vom System unterhaltene Menüverwaltung. Der Fokus wird bei dem Editfenster angezeigt, liegt nach dem effektiven Programmverhalten aber beim Menü. Auch die Mauseingaben werden an die Menüverwaltung gegeben und dort interpretiert. Zum Beispiel führt ein Mausklick in einen Menüpunkt dazu, dass die zugehörige Auswahl realisiert
122
4 Die Fensterzustände für die Nachrichtenannahme
wird. Außer den Tastatureingaben und den Mauseingaben gibt es in Ihren Anwendungen noch einen dritten Typ primärer Nachrichten, die schon erwähnten, aber programmiertechnisch noch nicht verwendeten Zeittaktnachrichten. Nur sie werden von der temporären Nachrichtenschleife an ihr Adressatenfenster gegeben. Sobald das Menü geschlossen wird, zerstört das System die temporäre Nachrichtenschleife. Die Hauptnachrichtenschleife tritt wieder in Aktion. Das Editfenster hat wieder den Fokus und erhält ab sofort nun auch effektiv wieder die Tastaturnachrichten. Wenn ein Menüpunkt über eine Menüleiste ausgewählt worden ist, erhält sein Fenster die Nachricht WM_COMMAND (dies ist der Wert von message; wParam und lParam geben den Nachrichteninhalt näher an). Sie wird in der Fensterprozedur beziehungsweise bei Fenstern mit Prozedur DefDlgProc in der Dialogprozedur verarbeitet. Hier hat man wieder die Übereinstimmung der Menüs mit den Dialogelementen: Vom Typ WM_COMMAND sind auch die Nachrichten, die die Dialogfenster von ihren Dialogelementen erhalten. Das Systemmenü gibt andere Nachrichten. Zum Beispiel der Menüpunkt Schließen gibt die Nachricht WM_CLOSE. Menüs sind also immer in die Arbeit ihres Fensterverbundes integriert. Das Fenster braucht nicht die Fensterprozedur DefDlgProc mit integrierter Fensterverwaltung zu haben. Bei der Konstruktion des Falles la dieser Technote (Bilder 4.2.1, 4.2.6, 4.3.1) verarbeiten Sie die Menünachrichten in der Dialogprozedur. Bei der Konstruktion des Falles lb (Bild 4.3.2) verarbeiten Sie sie in der Fensterprozedur von Dl. Im Fall 2 (Fenster ohne eingabefähige Dialogelemente) verarbeiten Sie sie auch in der Fensterprozedur beziehungsweise Dialogprozedur des aktiven Fensters.
4.4
Die Zuweisung von Aktivität und Fokus
Es gibt Maßnahmen, die Aktivität weiterzugeben, und Maßnahmen, den Fokus weiterzugeben. Nach Regel 1 von S. 115 sind der Fokus und die Aktivität aber nicht unabhängig voneinander. Die Regeln für das Wandern von Aktivität und Fokus lauten: -
Wenn die Aktivität durch eine entsprechende Maßnahme an ein anderes Fenster gegeben wird, geht auch der Fokus an dieses Fenster. Wenn der Fokus an ein anderes Fenster des aktiven Verbundes gegeben wird, hat das auf die Aktivität keine Auswirkung. Wenn der Fokus an ein Fenster eines anderen Verbundes (an ein aktivierbares oder ein Kindfenster) gegeben wird, wechselt die Aktivität
4 Die Fensterzustände für die Nachrichtenannahme
123
und geht an das aktivierbare Fenster des Verbundes. Geht also der Fokus an ein Kindfenster, geht die Aktivität an das zugehörige aktivierbare Vorfahrenfenster. Die Aktivität kann nicht wandern, ohne dass der Fokus mitwandert, der Fokus kann aber begrenzt allein wandern, nämlich zwischen dem aktiven Fenster und den Kindfenstern seines Verbundes. Obgleich eine Maßnahme sich auf beides auswirken kann, ist doch zu beachten, ob sie primär die Aktivität oder den Fokus überträgt. Es gibt mehrere Methoden, um das Wandern der Aktivität und des Fokus zu veranlassen. Sie werden im Folgenden beschrieben. Dabei ist zwischen den Mitteln, die dem Anwender zur Verfügung stehen, und denen, die das Programm anwendet, zu unterscheiden.
4.4.1
Der Mausklick
Aus der Sicht des Anwenders ist der Gebrauch der Maus besonders hervorzuheben. Dieser Punkt wird hier an den Anfang gestellt. Es gibt eine grundsätzliche Regelung im Windows-System über die Auswirkung des Mausklicks. Sie wird allerdings im praktischen Verhalten der Anwendungen durch die vordefinierte Programmierung der Dialogelemente und der Dialogfenster mit integrierter Fensterverwaltung weitgehend abgeändert beziehungsweise ergänzt. Stellen wir zuerst die grundsätzliche Regelung dar: Die Maus überträgt die Aktivität. Ein aktivierbares Fenster wird aktiviert, wenn - es selbst oder - ein Kindfenster seines Verbundes mit der Maus angeklickt wird. Es kommt nicht darauf an, ob es sich um einen linken oder rechten Mausklick handelt. Beide führen zur Aktivierung. Der Mausklick darf das Fenster irgendwo - im Clientbereich oder im Nichtclientbereich treffen. Speziell auch das Anklicken der Menüleiste oder eines ihrer Menüpunkte aktiviert das Fenster. Ein minimiertes Wurzelfenster kann man durch Mausklick in der Startleiste auf Arbeitsgröße bringen und aktivieren. Die Aktivierung über die Maus hat die schon genannten Auswirkungen: 1. Zuweisung des Fokus. Dem mit der Maus aktivierten Fenster wird automatisch der Fokus zugewiesen. Wenn die Aktivierung erfolgt, weil ein Kindfenster angeklickt worden ist, wird er dennoch dem aktivierten Fenster selbst zugewiesen.
124
4 Die Fensterzustände ftir die Nachrichtenannahme
2. Anpassung der Z-Ordnung. 3. Einfárbung der Titelleiste. Während die Ziffern 2 und 3 (Z-Ordnung und Färbung) Auswirkungen der Aktivierung betreffen, die während der Dauer der Aktivität des Fensters aufrechterhalten bleiben, trifft das für die Ziffer 1 (Besitz des Fokus) nicht unbedingt zu. Aktiviert-Werden über die Maus schließt Den-Fokus-Bekommen ein. Aktiv-Sein und Den-Fokus-Haben sind aber nicht dasselbe. Das durch Mausklick aktivierte Fenster braucht den Fokus nicht zu behalten, sondern kann ihn an ein Kindfenster des Verbundes weitergeben. Dies realisiert man über Programm, unter anderem mit der Funktion S e t Focus. Mit der Maus kann man den Fokus nicht von einem Elter auf ein Kindfenster übertragen. Die Maus überträgt nur die Aktivität. Der Fokus geht nur nach der Regel über das gemeinsame Wandern von Aktivität und Fokus mit. Wenn ein Mausklick nicht zur Aktivierung eines Fensters führt, weil das angeklickte Fenster schon aktiv war oder weil es Kindfenster im Verbund des aktiven Fensters ist, hat er keine Auswirkungen auf den Besitz des Fokus. Soweit also die allgemeine Regelung. Sie wäre für sich allein gesehen nicht ausreichend. In der Basiskonstruktion des Systems hat der Anwender überhaupt kein Mittel, um einem Fenster den Fokus zuzuweisen, wenn es ihn nicht als Hauptfenster eines Verbundes mit der Aktivität bekommt. Es gibt sozusagen keine zweite Maus, die den Fokus zuweist. Hier greift das Verhalten der Fenster in zweierlei Weise ein: -
Ein mit einem Mausklick aktiviertes Fenster, das eingabefähige Dialogelemente im Verbund hat, gibt bei fachgerechter Konstruktion (Ziffer 1 der vorigen Technote) wie gesagt den Fokus immer an ein Dialogelement weiter; das aktivierbare Fenster erhält nur die Aktivität.
Das allein wäre noch nicht genug. Damit könnte der Anwender noch nicht befriedigend wählen, welches Dialogelement den Fokus bekommen soll. Er könnte dazu nur den tab-Zyklus benutzen. Das ist in Fenstern mit vielen Dialogelementen aber doch eine umständliche Methode. Deshalb ist außerdem programmiert: -
Die vordefinierten eingabefähigen Dialogelemente weisen sich den Fokus selbst über Code zu, wenn sie mit der Maus angeklickt werden. Dazu gehören unter anderen die Editfenster und die Schaltknöpfe. Das Elter wird dabei nicht beteiligt. Bei diesen Dialogelementen überträgt der Mausklick nicht nur die Aktivität an den aktivierbaren Vorfahr, sondern zugleich den Fokus an das angeklickte Fenster. Die angegebene Basisregel über die Wirkung des Mausklicks wird hier durch standardisierte Programmierung durchbrochen.
4 Die Fensterzustände für die Nachrichtenannahme
125
Damit insgesamt ergibt sich nun ein wünschenswertes Verhalten von Dialogfenstern. -
-
Die allgemeine Regelung über Mausklicks bleibt in der tatsächlichen Wirkung erhalten, wenn es keine eingabefähigen Dialogelemente in einem Verbund gibt. Der Fokus geht an das aktivierbare Fenster. Wenn der Anwender in einem Verbund mit eingabefähigen Dialogelementen eines von ihnen anklickt, bekommt es den Fokus. Das aktivierbare Fenster erhält die Aktivität. Wenn er den Verbund anklickt, ohne ein eingabefahiges Dialogelement auszuwählen, geht der Fokus an ein vom Dialogfenster ausgewähltes Dialogelement.
Die integrierte Dialogfensterverwaltung und die Fensterprozeduren der vordefinierten eingabefähigen Dialogelemente ergänzen die Basisfunktionen des Systems und sind der Sache nach ein wesentlicher Teil des Systems. Insgesamt ergibt sich so, dass der Anwender mit einer Maus und auch ohne Unterscheidung nach Maustasten passend nach seinen Wünschen die Aktivität und den Fokus übertragen kann. Detailunterschiede ergeben sich daraus, ob ein Dialogfenster aktivierbar ist (Ziffer la der vorigen Technote) oder nicht (Ziffer lb der vorigen Technote). Wenn ein Dialogfenster mit eingabefähigen Dialogelementen erzeugt wird, bestimmt die Fensterdeklaration in der Ressourcedatei, welches Dialogelement als Erstes den Fokus bekommt. Danach gilt: -
-
Wenn ein aktivierbares Dialogfenster die Aktivität abgibt, speichert die Dialogfensterverwaltung, welches Dialogelement zuletzt den Fokus hatte. Wird das Dialogfenster durch Mausklick in den Clientbereich oder in ein nichteingabefähiges Dialogelement wieder aktiviert, wird der Fokus demselben Dialogelement wieder zugewiesen. Wird es durch Mausklick in ein eingabefähiges Dialogelement aktiviert, geht der Fokus wie schon gesagt an dieses Element. Bei einem Kinddialog ist die integrierte Fensterverwaltung etwas schwächer als bei einem aktivierbaren Dialog. Sie merkt sich nicht, welches Dialogelement als Letztes den Fokus hatte. Bei einem Mausklick in den Clientbereich oder in ein nichteingabefähiges Dialogelement gibt sie den Fokus stets an das Dialogelement, das nach der Ressourcedeklaration dafür an erster Stelle steht. Wenn der Verbund durch Mausklick in ein eingabefähiges Dialogelement aktiviert wird, geht er auch hier an dieses Element.1
1) Editfenster in einem Kinddialog nehmen sich den Fokus nur, wenn das Elter keine Titelleiste hat.
126
4 Die Fensterzuslände flir die Nachrichlenannahme
Für den Anwender ist die grundsätzliche Regelung über die Wirkung des Mausklicks (Übertragung der Aktivität mit Fokuszuweisung an das aktivierte Fenster) nicht erkennbar. Aus seiner Sicht fuhrt das Anklicken eines eingabefähigen Dialogelementes zur Zuweisung des Fokus an das Element. Das Anklicken eines nichteingabefähigen Dialogelementes oder des Clientbereiches eines Dialogfensters führt zur Zuweisung des Fokus an ein gegenwärtig nicht ausdrücklich gewähltes eingabefähiges Element, sofern es eines gibt. Wenn es keines gibt, stellt sich für den Anwender die Frage nach dem Verbleib des Fokus nicht. Die Titelleiste des umgebenden Fensters wird eingefarbt. Das Fenster als Ganzes wird aus einer möglicherweise verdeckten Darstellung in den Vordergrund gebracht.
4.4.2
Das Erzeugen eines sichtbaren Fensters
Für Sie als Entwickler gliedert sich das Thema der Weitergabe von Aktivität und Fokus nach den Programmsituationen: -
-
Was sind die Wirkungen, wenn ein Fenster sichtbar erzeugt wird und damit zu den auf dem Schirm schon dargestellten Fenstern hinzukommt? Bei einem Fenster, das schon existiert hat, aber unsichtbar war: Wie verhält es sich, wenn das Fenster in Arbeitsform, das heißt in normale oder maximierte Darstellung umgeschaltet wird? Grundsätzlich hat man die entsprechende Frage, wenn ein Fenster bisher minimiert war. Dem Minimieren aus dem Programm heraus, das heißt über Code, kommt in Standardanwendungen aber wenig oder keine Bedeutung zu. Der Anwender minimiert das Wurzelfenster einer Anwendung über die zugehörige Option in der Titelleiste und räumt damit Platz auf dem Schirm frei. Wenn er die Anwendung wieder vollständig darstellt, wird sie sofort aktiviert. Beide Vorgänge werden durch das vordefinierte Verhalten von Fenstern gesteuert. Sie als Entwickler haben dazu keine Aufgaben. Andere Fenster als das Wurzelfenster einer Anwendung werden in aller Regel nicht minimiert. Wir gehen auf die Programmierung zum Minimieren von Fenstern nur ergänzend und erst später ein. Wie geben Sie aus dem Programm heraus Aktivität und Fokus weiter, wenn Fenster schon normal oder maximiert auf dem Schirm sind?
Diese Fragen werden jetzt betrachtet. Dabei beginnen wir mit den sichtbar erzeugten Fenstern.
4 Die Fensterzuslände für die Nachrichtenannahme
127
Wird ein Fenster als sichtbar (mit dem Stilbit WS_VISIBLE) erzeugt, wird es sofort mit normalen Abmessungen angezeigt. Die Größe ergibt sich im Einzelnen aus der Fensterdefinition. (In den Headerdateien ist zwar ein Stilbit für maximierte Darstellung - WS_MAXIMIZE - definiert. Es ist aber unwirksam.)
1.
Aktivierbare Fenster
Wird ein aktivierbares Fenster (ein Wurzelfenster oder zugeordnetes Fenster) sichtbar erzeugt, wird es sofort aktiviert. Diese Eigenschaft, nämlich das Fenster zu aktivieren, haben alle Funktionen, mit denen man ein aktivierbares Fenster erzeugen und sofort anzeigen kann, aus der Sicht des Entwicklers also vor allem CreateWindow und die Funktionen, mit denen man Dialogfenster auf der Basis einer Ressource bildet. Entsprechend den Erklärungen zur Aktivierung durch Mausklick braucht das Fenster den Fokus nicht zu behalten, sondern kann ihn über Code an ein Kindfenster weitergeben. Die aktivierbaren Dialogfenster mit integrierter Fokusverwaltung geben ihn immer an ein eingabefahiges Dialogelement weiter, wenn sie eines haben.
2.
Kindfenster
Wenn Kindfenster sichtbar erzeugt werden, hängt es von der Sachlage ab, wohin der Fokus gelangt. (Die Aktivität kann ein Kindfenster nicht bekommen.) Bei der Bildung eines Kindfensters mit CreateWindow bleiben Aktivität und Fokus, wo sie sind. Das Kindfenster erhält den Fokus nicht. Dieselbe Regelung gilt bei der Erzeugung eines Kinddialogfensters ohne Dialogprozedur mit CreateDialog oder CreateDialogParam. Erzeugen diese Funktionen ein als Kind deklariertes Dialogfenster mit integrierter Fokusverwaltung und einer (dann regelmäßig vorhandenen) Dialogprozedur, geben sie gleich den Fokus dorthin. Bei korrekter Programmierung wird er dort sofort einem eingabefähigen Dialogelement zugewiesen, wenn es eines gibt. Wenn die Aktivität bis dahin schon bei dem aktivierbaren Fenster des Verbundes lag, bleibt sie dort (andernfalls könnte das Dialogfenster ja auch nicht den Fokus bekommen). Wenn sie bisher woanders lag, geht sie nun neu an das aktivierbare Fenster des Verbundes. Mit DialogBox oder DialogBoxParam kann man kein Kinddialogfenster bilden. Diese Funktionen bilden bei korrekter Programmierung nur aktivierbare Fenster.
128
4 Die Fensterzustände für die Nachrichtenannahme
Der Vorgang der Erzeugung und gleichzeitigen Anzeige eines Dialogfensters mit integrierter Fensterverwaltung stimmt also bei aktivierbaren und nicht aktivierbaren Dialogfenstern im Wesentlichen überein: das aktivierbare Fenster erhält die Aktivität, ein eingabefähiges Dialogelement, ersatzweise das Dialogfenster, erhält den Fokus.
4.4.3
Sichtbarmachen eines Fensters
Oft wird ein Fenster zunächst unsichtbar (ohne das Stilbit WS_VISIBLE) erzeugt. Das gilt vor allem, wenn man ein schirmfallendes, das heißt maximiertes Hintergrundfenster für eine Anwendung haben möchte. Wenn man ein Fenster gleich sichtbar erzeugt, wird es wie gesagt normal dargestellt. Wenn ein Fenster von Anfang an maximiert angezeigt werden soll, erzeugen Sie es am besten unsichtbar. Andernfalls wird es erst normal gezeichnet. Die sofort folgende Anweisung zur Maximierung führt zu einem für den Anwender sichtbaren, flimmernden Wachstum des Fensters auf dem Schirm. Ein noch unsichtbares Fenster wird mit ShowWindow in normaler oder maximierter Größe sichtbar gemacht ( - S. 76). Zum Beispiel für ein Fenster hWnd formuliert man: ShowWindow (hWnd, SW_SHOWNORMAL) ; beziehungsweise ShowWindow (hWnd, SW_SHOWMAXIMIZED); So ist im Quelltext Kuchen programmiert: hwndH = CreateWindow ( . . . . ) ; // ohne WS_VISIBLE ShowWindow (hwndH, SW SHOWMAXIMIZED);
1.
Aktivierbare Fenster
Bei den Parametern SW_SHOWNORMAL und SW_SHOWMAXIMIZED gibt ShowWindow an aktivierbare Fenster die Aktivität. Nach den Regeln über das gemeinsame Wandern von Aktivität und Fokus geht dabei auch der Fokus an das Fenster. Ein aktivierbares Fenster mit eingabefähigen Dialogelementen im Verbund weist ihn sofort einem solchen Element zu. Die Auswirkungen für Aktivität und Fokus sind also bei der Erzeugung eines unsichtbaren aktivierbaren Fensters mit anschließendem Sichtbarmachen insgesamt exakt wie bei der Erzeugung mit sofortiger Sichtbarkeit.
4 Die Fensterzustände für die Nachrichtenannahme
2.
129
Kindfenster
ShowWindow überträgt den Fokus nicht. Anders als ein Mausklick überträgt ShowWindow bei einem Kindfenster auch die Aktivität nicht an das aktivierbare Fenster des Verbundes. Die Funktion ist bei Kindfenstern neutral in Bezug auf Aktivität und Fokus. Beide bleiben, wo sie sind. Wenn man einen sichtbaren Kinddialog mit integrierter Fensterverwaltung über CreateDialog oder CreateDialogParam erzeugt, geben diese Funktionen wie gesagt gleich den Fokus dorthin. Das trifft auch zu, wenn der Dialog unsichtbar erzeugt wird. Wenn man ein unsichtbares Kinddialogfenster sofort sichtbar macht, hat es den Fokus noch. Andernfalls werden Sie ihn durch eine zusätzliche Anweisung über Set Focus an das Dialogfenster geben. Nach den Regeln über das gemeinsame Wandern von Aktivität und Fokus geht dabei die Aktivität an das zugehörige aktivierbare Fenster. Man kann ein schon sichtbares Fenster unsichtbar machen. Die Anweisung lautet: ShowWindow (hWnd, SW_HIDE); Wenn man ein aktivierbares Fenster dann wieder normal oder maximiert anzeigt, wird es zugleich aktiviert. Die Auswirkungen sind genau wie bei der ersten Anzeige. Einem Kindfensterdialog muss erneut der Fokus zugewiesen werden. In diesem Zusammenhang ist auch der Parameter SW SHOW zu erwähnen. Mit ShowWindow (hWnd, SW_SHOW); bringt man das Fenster in den Anzeigezustand (normal oder maximiert), den es zuletzt hatte, bevor es unsichtbar gemacht wurde. Auch bei ihm wird ein aktivierbares Fenster aktiviert. Ein Aufruf von ShowWindow ist immer wirksam, wenn ein Fenster noch nicht in dem Zustand ist, in den es nach dem Text des Aufrufs gebracht werden soll. Man kann ein normal angezeigtes Fenster maximiert anzeigen oder umgekehrt. Dabei wird es dann zugleich aktiviert, wenn es aktivierbar ist. Man kann ShowWindow aber nicht allgemein als Funktion zum Aktivieren benutzen. Wenn man für ein aktivierbares aber nicht aktives, normal angezeigtes Fenster angibt, es solle normal angezeigt werden, ändert sich nichts. Das Fenster bekommt nicht die Aktivität.
130
4 Die Fensterzustände für die Nachrichtenannahme
4.4.4
Aktivieren und Fokus-Zuweisen mit SetFocus
Sie brauchen als Entwickler programmiertechnische Mittel, um ein beliebiges normal oder maximiert dargestelltes Fenster der Anwendung zu aktivieren (wenn es aktivierbar ist) oder ihm den Fokus zuzuweisen. Um ein aktivierbares Fenster hWnd zu aktivieren, gibt es mehrere Möglichkeiten. Sie können programmieren: SetForegroundWindow (hWnd); Diese Funktion wird in WinMain benutzt, wenn verhindert werden soll, dass eine Anwendung mehrfach gestartet werden kann. Sie ist dazu geeignet, weil sie prozessübergreifende Reichweite hat (~ S. 79). SetActiveWindow (hWnd) ; Die Funktion unterscheidet sich von SetForegroundWindow, indem sie voraussetzt, dass das aufgerufene Fenster dem Thread (und damit auch der Anwendung) angehört. Diese Funktion setzt voraus, dass BringWindowToTop (hWnd) ; das aufgerufene Fenster der Anwendung angehört. Sie können aber auch immer die in dieser Aufzählung noch nicht enthaltene Funktion SetFocus verwenden: SetFocus(hWnd); Sie überträgt den Fokus. Es ist nach der Regel über das gemeinsame Wandern von Aktivität und Fokus aber nur zwischen dem aktivierbaren Fenster und den Kindern des Verbundes möglich, den Fokus weiterzugeben, ohne dass die Aktivität mitwandert. Man kann deshalb ein Fenster über Zuweisung des Fokus mit SetFocus auch aktivieren. SetFocus ist für Sie als Entwickler ungefähr, was für den Anwender der Mausklick ist. Es gibt aber den Unterschied: SetFocus überträgt den Fokus, der Mausklick die Aktivität. Die andere Eigenschaft wird jeweils nur mitgeführt. Wenn man einem Kindfenster mit SetFocus den Fokus zuweist, geht der Fokus tatsächlich an das Kind. Das zugehörige aktivierbare Fenster wird aktiviert (wenn es nicht schon aktiv ist), aber es erhält den Fokus nicht. Ein Fenster, das den Fokus bekommt, erhält vom System die Nachricht WM_SETFOCUS. Ein Fenster, das den Fokus abgibt, erhält vom System die Nachricht WM_KILLFOCUS. Die Prototypen sind sind in Textliste 4.4.1 auf der folgenden Seite angegeben. Beide Nachrichten werden vom System übergeben. Ihr Zweck ist zu informieren. Das Programm kann sie abfragen, übergibt sie aber niemals selbst. SetFocus ist nicht zuständig, um bei integrierter Fensterverwaltung den Fokus einem eingabefähigen Dialogelement zuzuweisen. Zwar können Sie
4 Die Fensterzuslände für die Nachrichtenannahme
131
Set Focus verwenden. Wenn aber zum Beispiel Schaltknöpfe auf diese Weise den Fokus erhalten, werden sie nicht wie sonst mit verstärktem Rand gezeichnet. Um den Fokus über Programm zuzuweisen, verwendet man die Nachricht WM_NEXTDLGCTL, die später noch beschrieben wird. Die Funktion GetFocus liefert das Handle des Fensters, das den Fokus hat. Die Funktion GetActiveWindow erlaubt zu fragen, welches Fenster aktiv ist. Beide Funktionen schauen nicht in fremde Threads. Sie geben NULL zurück, wenn Fokus beziehungsweise Aktivität nicht im anfragenden Thread sind. message
wParam
lParam
WM_SETFOCUS
Handle des Fensters, das den Fokus abgibt Handle des Fensters, das den Fokus erhält
uninteressant
WM KILLFOCUS
uninteressant
Textliste 4.4.1 Prototypen von WMJSETFOCUS und WM_KILLFOCUS
4.4.5
Das Programm KuchenKind
Jetzt folgt ein Beispielsprogramm zur Verwendung von SetFocus. Wir müssen weiter Code-Abschnitte zulassen, die noch nicht verständlich sind, wenn wir auf vollständige Programmbeispiele nicht ganz verzichten wollen. Für das vorliegende Beispiel wähle ich noch einmal die Kuchenaufgabe, so dass kein neuer unerklärter Code erscheint. Die Funktion SetFocus wurde schon mehrfach im Zusammenhang mit der Aufgabe, einen Kindfensterdialog mit seinem aktivierbaren Elter zu einem gemeinsamen Dialog zu integrieren, erwähnt ( - Bild 4.3.2, S. 118, und zugehöriger Text). Wir wollen also das Programm Kuchen mit einem Kinddialog statt einem zugeordneten Dialog konstruieren. Es ist hier nicht notwendig, zwei Fenster, eins für den Hintergrund und eins für die Dialogelemente, zu bilden. Man kann gleich ein Dialogfenster als Hintergrund wählen. Das wird später auch noch durchgeführt. Wir brauchen aber ein einfaches programmiertechnisches Beispiel für die Integration eines aktivierbaren Fensters mit einem Kinddialog. Es gibt mehrere Möglichkeiten der Ausführung. Wir programmieren hier in einer Weise, bei der der Anwender gar nicht sieht, dass es zwei verschiedene
132
4 Die Fensterzustände für die Nachrichtenannahme
Fenster gibt. Dazu bekommt das Hintergrundfenster im Clientbereich dieselbe blassgrüne Farbfüllung wie der Dialog und der Dialog erhält keinen Rand.
Bild 4.4.1 Fensterbaum in Kuchen Kind
^ ^ ^ M J n l x l i
I I S Kinderparty
Kinder 1
Kuchenstücke
«
3
_J I
6 Stücke je Kind
1 Stücke übrig
'
OKI il
i 1
Bild 4.4.2 Programm Kuchen_Kind
Insgesamt ändert man die Anwendung Kuchen so ab: 1. Für die neue Clientfärbung des Hintergrundfensters wird in der Definition der Fensterklasse in WinMain eine Zeile variiert: wndclass.hbrBackground = CreateSolidBrush (RGB(192, 2 2 0 , 1 9 2 ) ) ; 2. Das Handle h D l g des Dialoges wird in prog.c global definiert, damit es in der Prozedur des Hintergrundfensters verfügbar ist. Der Code der Prozedur wird ergänzt und lautet nun:
4 Die Fensterzustände für die Nachrichtenannahme
133
LRESULT CALLBACK HProc(HWND hWnd, UINT message, WΡARAM wParam, LPARAM lParam) { switch (message) { case WM_SETFOCUS: if (hDlg != NULL) SetFocus (hDlg); return 0; case WM_CLOSE: PostQuitMessage(0); return 0;
}
} return DefWindowProc (hWnd, message, wParam, 1Param);
Die Zeilen case WM_SETFOCUS: if (hDlg != NULL) SetFocus (hDlg); return 0; bilden also das Codestück, um das man die Fensterprozedur des aktivierbaren Elters eines Kinddialoges ergänzen muss und das auf S. 119 angesprochen wurde. 3. In der Deklaration des Dialoges in der Ressource wird das Fenster als Kind (Stilbit WS CHILD) eingestellt. Die Titelleiste entfällt. Der Dialog bekommt keinen eigenen Rand. Die Positionierung wird neu überlegt. Die Deklaration ist nun: 100 DIALOG 0, 0, 180, 100 STYLE WS_CHILD | WS_VISIBLE FONT 10, "System" BEGIN END Die Quelltextliste 4.4.1 enthält den vollständigen Programmtext.
134
4 Die Fensterzustände für die Nachrichtenannahme
Textliste 4.4.1 Programm Kuchen Kind NULL, II Elter NULL, II individuelles Menu hlnstance, II Programminstanz NULL); II betrifft MDI-Fenster ShowWindow (hwndH, SW_SHOWNORMAL) ;
PROG.H für Kuchen Kind
hDIg = CreateDialog(hlnstance, "#100", hwndH, DlgProc); while (GetMessage (&msg, NULL, 0, 0)) {
leere Datei
II II
if( hDIg == NULL || NsDialogMessage (hDIg, &msg))
PROG.C für Kuchen Kind
{
>
#include «windows.h> #include "prog.h"
return msg.wParam ;
LRESULT C A L L B A C K HProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); BOOL C A L L B A C K DlgProc(HWND hDIg, UINT message, W P A R A M wParam, L P A R A M IParam);
} LRESULT C A L L B A C K HProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam)
{
HWND hDIg;
HWND MSG WNDCLASS
hwndH; msg; wndclass;
case WM_CLOSE: PostQuitMessage(O) ; return 0 ;
II Fensterhandle II Messagestruktur II Fensterklassenstruktur
return DelWindowProc (hWnd, message, wParam, IParam) ;
hwndH = FindWindow(NULL, "Kinderparty"); if(hwndH l= NULL) { ShowWindow(hwndH,SW_RESTORE); SetForegroundWindow(hwndH); return 0;
} wndclass.style wndclass. IpfnWndProc wndclass.cbCIsExtra wndclass.cbWndExtra wndclass.hlnstance wndclass.hlcon
switch (message) { case WM_SETFOCUS: if (hDIg != NULL) SetFocus (hDIg); return 0;
int C A L L B A C K WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR IpszCmdLIne, int nCmdShow)
{
}
TranslateMessage (&msg) ; DispatchMessage (&msg) ;
BOOL C A L L B A C K DlgProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam)
{ = 0;
= HProc ; = 0;
BOOL bZahl; int ¡Kinder, iKuchenstuecke; static HBRUSH hbrWeiss, hbrBlassgruen;
= 0;
= hlnstance ; = Loadlcon(hlnstance, "Proglcon"); = LoadCursor (NULL, wndclass. hCursor IDC_ARROW) ; wndclass. hbrBackground = CreateSolidBrush (RGB(192, 220, 192)); wndclass.IpszMenuName = NULL; wndclass.IpszClassName = "HCIass" ; RegisterClass (&wndclass) hwndH = CreateWindow ( "HCIass", // Fensterhandle "Kinderparty", II Titeltext WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, C W J J S E D E F A U L T , CW_USEDEFAULT,
switch (message) { case WMJNITDIALOG: hbrBlassgruen = CreateSolidBrush (RGB(192, 220, 192)); hbrWeiss = CreateSolidBrush (RGB(255, 255, 255)); return 1; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: iKuchenstuecke = GetDIgltemlnt (hWnd, 102, &bZahl, TRUE); if (bZahl == FALSE) {
4 Die Fensterzustände für die Nachrichtenannahme
SendMessage (hWnd, WM_NEXTDLGCTL, (WPARAM) GetDlgltem(hWnd, 102), 1); return 1;
}
)
SetDlgitemlnt(hWnd, 201, iKuchenstuecke / ¡Kinder, TRUE); SetDlgltemlnt(hWnd, 202, iKuchenstuecke % ¡Kinder, TRUE); return 1 ;
return 0; case WM_CTLCOLORSTATIC: { int id; id = GetDlgCtrllD((HWND)IParam); switch (id) { case 201: case 202: SetBkCoior((HDC)wParam, RGB(255, 255, 255)); return (BOOL) hbrWeiss; default: SetBkCoior((HDC)wParam, RGB( 192,220,192)); return (BOOL) hbrBlassgruen;
)
}
case WM_CTLCOLORDLG: return (BOOL) hbrBlassgruen;
> II* II II II II
} // switch (message) return 0;
PROGRES.RC für KuchenKInd
#include #include "prog.h" PROGICON ICON standard.ico 100 DIALOG DISCARDABLE 0, 0, 180, 100 STYLE WS_CHILD | WS_VISIBLE FONT 10, "System" BEGIN CTEXT "Kinder",-1,24,20,25,12 CTEXT "Kuchenstücke",-1,59,20,50,12 EDITTEXT 101,24,32,25,12,ES_RIGHT I ES_MULTILINE | WS_GROUP | ES_NUMBER EDITTEXT 102,72,32.25,12,ES_RIGHT I ES_MULTILINE | WS_GROUP | ES_NUMBER DEFPUSHBUTTON "OK",IDOK, 120, 44, 30, 10, WS_GROUP RTEXT "',201,24,54,25,12,WS_BORDER RTEXT '"',202,72,54,25,12,WS_BORDER CTEXT "Stücke\nje Kind",-1,24,70,25,20 CTEXT "St0cke\nübrig",-1,72,70,25,20 END
135
136
4.4.6
4 Die Fensterzustände für die Nachrichtenannahme
Auswirkungen der Deaktivierung eines aktiven Fensters
Wenn ein aktives Fenster zerstört wird, aktiviert das System automatisch ein anderes Fenster. Wenn ein zugeordnetes Fenster, das heißt nicht die Wurzel, zerstört worden ist, bleibt die Aktivität in der Anwendung. Ihre Fenster bleiben also die vordersten in der Z-Ordnung. Aktiviert wird der jüngste aktivierbare Vorfahr. Wenn die Wurzel zerstört wird, kann die Aktivität nicht in der Anwendung bleiben, weil alle übrigen Fenster des Baumes als Folge mit zerstört werden. Ein Hintergrundfenster kann man nicht unsichtbar machen, ohne das System zu stören. Wenn ein anderes aktives Fenster unsichtbar gemacht wird, wird es deaktiviert. Die Aktivität bleibt in der Anwendung. Wenn ein aktives Fenster über den Systemknopf in der Titelleiste minimiert wird, wird es deaktiviert. Die Aktivität bleibt in der Anwendung. Wenn ein aktives Hintergrundfenster auf diese Weise minimiert wird, wird ein Fenster eines anderen Baumes aktiviert.
4.4.7
Änderungen der Z-Ordnung bei Kindgeschwistern
Kindgeschwister sind meistens Dialogelemente und werden im Allgemeinen nicht überlappend positioniert. Wenn das doch einmal der Fall ist, kommt es vor, dass Sie die Z-Reihenfolge im Programmverlauf ändern wollen. Die Funktion BringWindowToTop bringt ein Kindfenster (auch ein aktivierbares Fenster, - S. 130) in der Z-Ordnung so hoch, wie das nach der Konstruktion des Fensterbaumes möglich ist. Es hat dann nur noch seine eigenen Nachkommen über sich. Sie überträgt nicht den Fokus. Die Formulierung für ein Fenster hwndKind ist: BringWindowToTop
(hwndKind);
Die Funktion SetWindowPos kann ein Kindfenster auch nach oben in der Z-Ordnung bringen und dabei zugleich seine Position auf dem Schirm ändern. Ein Beispiel ist das Demoprogramm Kind_Z_Ordnung.
4 Die Fensterzuslände für die
4.4.8
Nachrichtenannahme
137
Das Programm KindZOrdnung
Vor einem Hintergrundfenster wird ein aktivierbarer Dialog mit dem Titel BringWindowToTop erzeugt, der unter anderem drei farbige, sich überlappende statische Textfenster zeigt. Der Dialog ist aus der Klasse #32770 und hat integrierte Fensterverwaltung. Der tab-Zyklus erstreckt sich über drei Schaltknöpfe. Durch Andrücken können Sie wahlweise eines der farbigen Textfenster nach oben bringen. Sie brauchen und haben auch alle das Stilbit WS CLIPSIBLINGS.
Bild 4.4.3 Fensterbaum von Kind Z Ordnung
Bild 4.4.4 Programm Kind Z Ordnung Nach der Quelltextliste folgen einige weitere Demoprogramme zu den Themen Z-Ordnung, Aktivität und Fokus.
4 Die Fensterzustände für die Nachrichtenannahme
138
RGB(166, 202, 240)); return (BOOL)hbrBlassblau; Prog.h für Klnd_Z_Ordnung
}
return 0; case WM_CTLCOLORDLG: return (BOOL) hbrBlassblau;
leere Datei caseWMCOMMAND: switch (LOWORD(wParam)) { Prog.c für Klnd_Z_Ordnung
WinMain bis auf den Titeltext wie Quelltext S. 42 HProc wie Quelltext S. 43 BOOL CALLBACK DlgProc(HWND hOlg, UINT message, W P A R A M wParam, LPARAM IParam)
{ static HBRUSH static HBRUSH static HBRUSH static HBRUSH int id;
hbrBlassblau; hbrMagenta; hbrRot; hbrGruen;
>
case 110: BringWindowToTop(GetDlgltem(hDlg, 10)); return 1; case 120: BringWindowToTop(GetDlgltem(hDlg, 20)); return 1; case 130: BringWindowToTop(GetDlgltem(hDlg, 30)); return 1;
return 0; } // switch (message) return 0;
Progres.rc für Klnd_Z_Ordnung
switch (message) { case WMJNITDIALOG: hbrBlassblau = CreateSolidBrush (RGB(166, 202, 240)); hbrMagenta = CreateSolidBrush (RGB(255, 0, 255)); hbrRot = CreateSolidBrush(RGB(255, 0, 0)); hbrGruen = CreateSolidBrush(RGB(0,255, 0)); return 1 ; case WM_CTLCOLORSTATIC: id = GetDlgCtrllD((HWND)IParam); switch (id) { case 10: SetBkColor((HDC)wParam, RGB(255, 0, 0)); return (BOOL)hbrRot; case 20: SetBkColor((HDC)wParam, RGB(255, 0, 255)); return (BOOL)hbrMagenta; case 30: SetBkColor((HDC)wParam, RGB(0, 255, 0)); return (BOOL)hbrGruen; case 40: SetBkColor((HDC)wParam,
#include "afxres.h" #include "prog.h" PROGICON ICON "standard.ico" 100 DIALOG DISCARDABLE 10, 20, 180, 100 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE I WS_CAPTION CAPTION "BringWindowToTop" FONT 10, "System" BEGIN CTEXT "\n10", 10,27,46,39,23,WS_BORDER I WS_CLIPSIBLINGS CTEXT "\n20",20,48,33,39,23,WS_BORDER I WS_CLIPSIBLINGS CTEXT "\n30",30,55,53,39,23,WS_BORDER I WS_CLIPSIBLINGS PUSHBUTTON "10",110,118,29,35,12,WS_GROUP PUSHBUTTON "20",120,118,48,35,12,WS_GROUP PUSHBUTTON "30",130,118,67,35,12 CTEXT "Bringe nach vorne",40,106,14,64,12 END
Textliste 4.4.2 Programm Kìnd Z Ordnung
4 Die Fensterzustände fir die Nachrichtenannahme
4.4.9
139
Das Programm ZOrder
Das Demoprogramm Z Order schließt an die Erklärungen zur Fensterhierarchie an. Es zeigt den Wechsel von Z-Ordnung und Aktivität -
beim Erzeugen und Anzeigen von Fenstern und beim Aktivieren mit der Maus.
Die Demonstrationsmöglichkeiten werden durch die Programme Fokus ( - S. 145) und SetFocus ( - S. 149) ergänzt. Die Darstellung der Fenster dieser Anwendung in Baumform zeigt das Bild 4.4.5.
Bild 4.4.5 Baumdarstellung für Z Order Zuerst wird ein Hintergrundfenster mit Namen Hl erzeugt. Es gehört einer privaten Klasse mit privater Fensterprozedur und mit einem Icon an. Es hat keine Dialogelemente. Mit seinem Menü können 3 Nachkommen ZI, Z2, Kl erzeugt und auch wahlweise wieder zerstört werden. ZI und Z2 sind zugeordnet, Kl ist Kind. Diese Fenster sind Dialogfenster. Sie enthalten statische Textdialogelemente. Sie gehören einer gemeinsamen privaten Klasse an. Die Fensterprozedur ist privat. Eine integrierte Fensterverwaltung hat hier keine Bedeutung, weil es keine eingabefähigen Dialogelemente gibt. Sie gehört nicht zu den Demonstrationszielen des Programms. Ferner kann mit dem Menü von Hl ein weiteres Hintergrundfenster H2 gebildet werden, so dass die Demo-Anwendung dann zwei Fensterbäume hat. Dies ist unter dem Gesichtspunkt praktischer Anwendungsprogrammierung unerwünscht und deshalb unrealistisch, hier dient es aber der Darstellung der allgemeinen Verwaltung von Fensterbäumen nach den Regeln der Z-Ordnung unter Windows. H2 gehört einer eigenen privaten Klasse an. Sie hat ein Icon, damit das Fenster in der Startleiste nicht ohne Icon erscheint. Es hat ein statisches Textelement und ist somit ein Dialogfenster. Die Fensterprozedur ist wieder privat. Eine integrierte Fensterverwaltung wäre ohne Auswirkungen.
140
4 Die Fensterzustände fur die Nachrichtenannahme
Bei der Bildung von H2 wird sofort ein ihm zugeordnetes Nachkommenfenster Z3 erzeugt. Es wird aus derselben privaten Klasse mit privater Fensterprozedur gebildet, aus der auch ZI, Z2 und Kl erzeugt werden. Z3 enthält ein statisches Textfenster. Die Textdialogelemente sind standardmäßige statische Textanzeigefenster mit vordefinierter Fensterprozedur ( - S. 116). Sie werden hier mit Rand dargestellt, so dass Sie erkennen können, ob Sie ein Textelelement oder den Clientbereich des Dialogfensters anklicken. Für das gegenwärtige Programm läuft hier beides auf dasselbe hinaus.
Bild 4.4.6 Das Programm Z Order vor dem Bildschirmhintergrund Die Demo-Anwendung hat also die folgenden 5 Fensterverbunde: H1, K l , ZI, Z2, H2, Z3,
dessen Dialogelement
4 Die Fensterzuslände fiir die Nachrichtenannahme
141
Die aktivierbaren Fenster haben Titelleisten, so dass man sie mit der Maus bewegen kann. Man kann infolgedessen auch immer prüfen, welches Fenster in der Z-Ordnung über dem anderen liegt. Verhaltensproben an dem Programm zeigen unter anderem: - Wurzelfenster und zugeordnete Fenster können mit der Maus aktiviert werden. - Das Anklicken eines Kindfensters (hier des Kinddialogfensters Kl oder eines Textelementes) aktiviert das aktivierbare Fenster des Verbundes. - Anklicken mit der Maus ändert die Z-Reihenfolge zwischen zugeordneten Geschwisterfenstern. - Ein Kindfenster liegt immer unter seinen zugeordneten Geschwistern. Der Verbund aus H l , Kl und dessen Dialogelement kann in der Z-Ordnung nicht durch ein anderes Fenster unterbrochen werden. (Genau genommen wird nur gezeigt, dass dies nicht über die Maus möglich ist.) - Man kann kein aktivierbares Fenster zwischen ein Dialogfenster und seine Dialogelemente einordnen. (Auch diese Demonstration bezieht sich nur auf die Wirkung der Maus.) - Zugeordnete Fenster können überall auf dem Schirm angezeigt werden. (Kindfenster können nur im Clientbereich des Eiters angezeigt werden. Sie werden am Rand des Clientbereiches abgeschnitten. Das wird hier aber nicht demonstriert.) - Die beiden Fensterbäume liegen in der Z-Ordnung immer ganz voneinander getrennt. H2 oder Z3 können in keiner Weise zwischen Hl einerseits und ZI oder Z2 andererseits positioniert werden. (Die Demonstration betrifft nur die Wirkung der Maus.) - Die Z-Ordnung zwischen Fensterbäumen wird durch Aktivieren eines Mitglieds eines anderen Baumes geändert. Aktiviert man ein Fenster im Baum von H l , während H2 auf dem Gebiet von Hl positioniert ist, wird H2 auf dem Schirm ganz unsichtbar. Man kann es sichtbar machen, indem man es mit der Startleiste aktiviert, oder indem man Hl ausreichend verschiebt oder verkleinert. - Minimiert man H l , verschwindet der gesamte Baum. Der Baum von H2 (wenn er angelegt ist) bleibt erhalten. H2 wird sogar aktiviert, was aber unter dem Gesichtspunkt normaler Anwendungsentwicklung nicht interessant ist. Wenn man Hl wieder herstellt, wird es aktiviert. - Die Startleiste zeigt die Wurzelfenster Hl und H2. Sie zeigt ferner die Wurzelfenster aller anderen gestarteten Anwendungen des Systems.
142
4 Die Fensterzustände fur die Nachrichtenannahme
Textliste 4.4.3 Programm ZOrder In WinMain wird erst die Fensterklasse für das Hintergrund- HWND hwndH2; fenster H1 erzeugt. Dann werden private Dialogfensterklassen H2Class für das zweite Hintergrundfenster H2 und int C A L L B A C K WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR IpszCmdLine, DlgClass für die Fenster Z1, Z2, K1 und Z3 gebildet. int nCmdShow) Die private Dialagfensterklasse DlgClass ware nicht notwen- { dig. Die Standard-Dialogfensterklasse #32770 wurde in dieHWND hwndHI; //Fensterhandle sem Programm kein anderes Verhalten zeigen. Ihre FensterMSG msg; II Messagestruktur prozedur DefDIgProc regelt die integrierte Verwaltung des W N D C L A S S wndclass; //Fensterklassenstruktur Fokus für eingabefähige Dialogelemente. Die Fokusverwaltung wird in den Dialogfenstern dieses DemonstrationshwndHI = FlndWindow(NULL, "H1"); Programms nicht gezeigt. DefDIgProc würde sich gar nicht if(hwndH1 l= NULL) { bemerkbar machen. Das ware aber für den Anwender nicht von außen erkennbar. Deshalb wurde DlgClass mit einer einfachen Fensterprozedur DlgProc gebildet Sie hat als alleinige Aufgabe, die Dialogfenster elnzufärben.
ShowWindow(hwndH1 ,SW_RESTORE); SetForegroundWindow( hwndHI ); return 0;
} hlnst = hlnstance;
Das Hintergrundfenster H1 hat das Stilbit WS_OVERLAPPED. Für die beabsichtigten Demonstrationen dürfte es auch das Stilbit W S _ P O P U P haben. Das Fenster H2 hat das Stilbit W S _ P O P U P . Es dürfte auch das Stilbit WS_OVERL A P P E D haben.
// // II
Prog.h für Z_Order
II
«define »define #define #define »define »define »define »define »define
ID_Z1_EROEFFNEN ID_Z1_SCHLIESSEN ID_Z2_EROEFFNEN ID_Z2_SCHLIESSEN ID_K1_EROEFFNEN ID_K1_SCHUESSEN ID_H2_EROEFFNEN ID_H2_SCHLIESSEN ID_SITZUNG_BEENDEN
/ " " " · " " " " · · " ·
*
101 102 103 104 105 106 107 108 109
" " "
Prog.c für Z Order »-"»"-»»»"-·"/
»include «windows.h> »include "prog.h" HINSTANCE hlnst; LRESULT C A L L B A C K H1Proc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); LRESULT C A L L B A C K H2Proc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); LRESULT C A L L B A C K DlgProcfHWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); HMENU hMenu;
wndclass.style = 0; wndclass.lpfnWndProc = HIProc; wndclass.cbCIsExtra = 0; wndclass.cbWndExtra =0; wndclass.hlnstance = hlnstance; wndclass.hlcon = Loadlcon(hlnstance, "Proglcon"); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush (RGB(255, 255, 255)); wndclass.IpszMenuName = "HMenu"; wndclass.IpszClassName = Ή 1 Class"; RegisterClass (Swnddass); wndclass.style - 0; wndclass.lpfnWndProc = H2Proc; wndclass.cbCIsExtra = 0; wndclass.cbWnd Extra = DLGW1NDOWEXTRA; wndclass.hlnstance = hlnstance; wndclass.hlcon = Loadlcon(hlnstance, "Proglcon"); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush (RGB(166, 202, 240)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "H2Class"; RegisterClass (&wndclass); wndclass.style = 0; wndclass.lpfnWndProc = DlgProc; wndclass.cbCIsExtra = 0; wndclass.CbWndExtra = DLGWINDOWEXTRA; wndclass.hlnstance = hlnstance; wndclass.hlcon = (HICON) NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush (RGB(166, 202, 240)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "DlgClass"; RegisterClass (&wndclass);
4 Die Fensterzustände für die Nachrichtenannahme hwndHI - CreateWindow ( Ή 1 Class", Ή1", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL. NULL, hlnstance, NULL); ShowWindow(hwndHI, SW_SHOWMAXIMIZED);
143
DestroyWindow(hwndZ2); hwndZ2 = NULL; EnableMenultem(hMenu, ID_Z2_EROEFFNEN, MFS_ENABLED); EnableMenultem(hMenu, ID_Z2_SCHLiESSEN, MFS_GRAYED); return 0; case ID_K1_EROEFFNEN: hwndKl = CreateDialog(hlnst, "K1Res", hWnd, NULL); EnableMenultem(hMenu, ID_K1_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, ID_K1_SCHL1 ESSEN, MFS_ENABLED); return 0;
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg);
}
return msg.wParam; case ID_K1_SCHLIESSEN: DestroyWindow(hwndK1 ); hwndKl = NULL; EnableMenultem(hMenu, ID_K1_EROEFFNEN, MFS_ENABLED); EnableMenultem(hMenu, ID_K1_SCHLIESSEN, MFS_GRAYED); return 0;
LRESULT CALLBACK H1Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
{ static HWND hwndZI = NULL; static HWND hwndZ2 = NULL; static HWND hwndKl = NULL; switch (message) {
case ID_H2_EROEFFNEN: hwndH2 = CreateDialog(hlnst, "H2Res", NULL, NULL); EnableMenultem(hMenu, ID_H2_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, ID_H2_SCHLIESSEN, MFS_ENABLED); CreateDialogfhlnst, "Z3Res", hwndH2, NULL); return 0;
case WM_CREATE: hMenu = GetMenu(hWnd); return 0; case WM_COMMAND: switch(LOWORD(wParam)) { case ID_Z1_EROEFFNEN: hwndZI = CreateDialog(hinst, "Z1Res", hWnd, NULL); EnableMenultem(hMenu, ID_Z1_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, ID_Z1_SCHLIESSEN, MFS_ENABLED); return 0;
case ID_H2_SCHLIESSEN: DestroyWindow(hwndH2); hwndH2 = NULL; EnabieMenultem(hMenu, ID_H2_EROEFFNEN, MFS_ENABLED); EnableMenultem(hMenu, ID_H2_SCHLIESSEN,MFS_GRAYED); return 0;
case ID_Z1_SCHLIESSEN: DestroyWindow(hwndZ1 ); hwndZI = NULL; EnableMenultem(hMenu, ID_Z1_EROEFFNENMFS_ENABLED); EnableMenultem(hMenu, ID_Z1_SCHLIESSEN, MFS_GRAYED); return 0; case ID_Z2_EROEFFNEN: hwndZ2 = CreateDialog(hlnst, "Z2Res", hWnd, NULL); EnabieMenultem(hMenu, ID_Z2_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, ID_Z2_SCHLIESSEN, MFS_ENABLED), return 0; case ID_Z2_SCHLI ESSEN:
case ID_SITZUNG_BEENDEN: SendMessage(hWnd, WM_CLOSE, 0, 0); return 0;
} return DelWindowProc (hWnd, message, wParam, IParam); case WM_CLOSE: PostQuitMessage(O); return 0;
) return DelWindowProc (hWnd, message, wParam, IParam);
144
4 Die Fensterzustände für die Nachrichtenannahme
BEGIN MENUITEM "«.Eröffnen", ID_Z1_EROEFFNEN MENUITEM "«.Schließen", ID_Z1_SCHLIESSEN, , { MFS_GRAYED static HBRUSH hbrBlassblau; END POPUP "Fenster Z&2" Switch (message) { BEGIN MENUITEM "«.Eröffnen", ID_Z2_EROEFFNEN case W M C R E A T E : MENUITEM "«.Schließen", ID_Z2_SCHLIESSEN,, if (hbrBlassblau == NULL) MFS_GRAYED hbrBlassblau = CreateSolidBrush END (RGB(166, 202, 240)); POPUP "Fenster «.K1" return 0; BEGIN MENUITEM "«.Eröffnen", ID_K1_EROEFFNEN case WMCTLCOLORSTÄTIC. MENUITEM "»Schließen", ID_K1_SCHLIESSEN,, SetBkColor((HDC)wParam, RGB(166,202 , 240)); MFS_GRAYED return (LRESULT) hbrBlassblau; END POPUP "Fenster S.H2" case WM_DESTROY: BEGIN hwndH2 = NULL; MENUITEM "&Eröffnen", ID_H2_EROEFFNEN EnableMenultem(hMenu, ID_H2_EROEFFNEN, MENUITEM "«.Schließen", ID_H2_SCHLIESSEN, , MFS_ENABLED); MFS_GRAYED EnableMenultem(hMenu, ID_H2_SCHLIESSEN, END MFS_GRAYED); MENUITEM "B«.eenden!", return 0; ID_SITZUNG_BEENDEN } END return DefWindowProc (hWnd, message, wParam, IParam); Z1RES DIALOG 46, 50, 104, 61 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION LRESULT CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam) CAPTION "Z1" CLASS "DlgClass" { FONT 10, "System" static HBRUSH hbrBlassblau; BEGIN CTEXT "Z1\nmit Elter H1,\nzugeordnet",-1,14,17,74,27, switch (message) { WS_BORDER END case WM_CREATE: if (hbrBlassblau == NULL) Z2RES DIALOG 66, 87, 104, 61 hbrBlassblau = CreateSolidBrush STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | (RGB(166, 202, 240)); WS_CAPTION return 0; CAPTION "Z2" CLASS "DlgClass" case WM.CTLCOLORSTATIC; SetBkColor((HDC)wParam, RGB(166, 202,240)); FONT 10, "System" BEGIN return (LRESULT) hbrBlassblau; CTEXT "Z2\nmit Elter H1,\nzugeordnet",-1,14,17,74,27, } WS_BORDER return DefWindowProc (hWnd, message, wParam, END IParam); LRESULT CALLBACK H2Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
>
> *
Progres.rc für Z_Order
#include "afxres.h" #include "winuser.h" #include "prog.h" PROGICON ICON standard.ico HMENU MENUEX BEGIN POPUP "Fenster Z&1"
K1RES DIALOG 20, 70, 104, 61 STYLE DS_MODALFRAME | WS_CHILD | WS_VISIBLE CLASS "DlgClass" FONT 10, "System" BEGIN CTEXT "K1\nmit Elter H1,\nKind",-1,14,17,74,27, WS_BORDER END H2RES DIALOG 100, 190, 104, 61 STYLE DS_MODALFRAME | WS_MINlMIZEBOX | WS_POPUP I WS.VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "H2" CLASS "H2Class" FONT 10, "System" BEGIN
4 Die Fensterzustände für die Nachrichtenannahme
145
CTEXT "H2\nohne Elter,\nWurzel",-1,14,17,74,27, WS_BORDER END
CLASS "DlgClass" FONT 10, "System" BEGIN CTEXT "Z3\nmit Elter H2,\nzugeordnef',-1,14,17,74,27, Z3RES DIALOG 46, 50, 104, 61 WS_BORDER STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | END WS_CAPTION CAPTION "Z3"
4.4.10
Das Programm Fokus
Dieses Demoprogramm soll zeigen, wie der Fokus beim Aktivieren von Fenstern über die Maus zugeteilt wird.
Bild 4.4.7 Das Programm Fokus Erst wird ein Dialog-Hintergrundfenster H einer privaten Klasse und privater Fensterprozedur erzeugt und angezeigt. Das Fenster H ist Wurzel. Danach haben Sie über ein Menü die Möglichkeit, 3 Dialogfenster ZI, Z2 und Kl zu erzeugen und auch wieder zu zerstören. Die Fenster gehören privaten Klassen an und verwenden nicht die Fensterprozedur Def DlgProc für integrierte Fokusverwaltung. ZI und Z2 sind zugeordnet, Kl ist Kindfenster.
146
4 Die Fensterzustände für die Nachrichtenannahme
Die leeren Kästchen im Fensterbaum symbolisieren eines oder mehrere Dialogelemente. Der Baum entspricht dem ersten der beiden Fensterbäume von Z_Order, ist aber in den Dialogelementen geändert. Die Wurzel hat - im Bild rechts oben - ein standardmäßiges, nichteingabefähiges statisches Textanzeigefenster, in dem angegeben ist, ob sie gerade den Fokus hat. Auch die drei Nachkommen-Dialogfenster haben je ein solches Fenster. Sie haben außerdem Textfenster, um die Dialogfenster zu kennzeichnen. Die Ränder der Beschriftungen sind dieses Mal nicht eingezeichnet.
Bild 4.4.8 Fensterbaum in Fokus Die Nachkommen-Dialogfenster haben außerdem je ein eingabefähiges Dialogelement. Es weist sich selbst den Fokus zu, wenn es angeklickt wird. ZI und Z2 haben Editfenster (Texteingabefenster). Sie nehmen Text an, wenn sie den Fokus haben. Die Eingaben werden hier vom Programm nicht weiter beachtet. Kl hat einen Schaltknopf. Auch für ihn gibt es keine Wirkungsprogrammierung. Sie können ihn anklicken und er gibt dann eine Nachricht an sein Elter. Sie wird aber nicht ausgewertet. Ob ein Editfenster den Fokus hat, ist am Tastaturcursor (Caret) erkennbar. Ob ein Schaltknopf den Fokus hat, sehen Sie an der Einrahmung seines Fenstertextes. Die Konstruktion der Dialogfenster folgt nicht den weiter vorn entwickelten Richtlinien. Wenn es nach ihnen ginge, müssten ZI und 72 integrierte Fensterverwaltung haben, und wenn sie aktiv sind, müsste der Fokus immer bei ihrem Editfenster liegen. H und Kl müssten zu einem Dialog integriert sein, in dem immer der Schaltknopf den Fokus hätte. Das Programm ist aber geschrieben, um Systemeigenschaften zu demonstrieren, die dann überdeckt wären. Es zeigt sich unter anderem: -
Ein Kindfenster erhält nicht über Mausklick den Fokus. Wenn man den Clientbereich von Kl außerhalb des Schaltknopfes anklickt, gehen Aktivi-
4 Die Fensterzuslände fir die Nachrichtenannahme
-
147
tät und Fokus an H, das aktivierbare Fenster des Verbundes. Kl hat in diesem Programm niemals den Fokus. Eine Ausnahme machen auf Grund ihrer Sonderprogrammierung die Editfenster und die Schaltknöpfe (und überhaupt die eingabefahigen Dialogelemente). Die Textdialogelemente machen keine Ausnahme. Statische Textfenster nehmen sich beim Anklicken mit der Maus nicht den Fokus.
Textliste 4.4.4 Programm Fokus Fokus benutzt Dialogfenster einer identischen privaten Klasse mit Fensterprozedur DlgProc. Sie fragen laufend die Nachrichten WM_SETFOCUS und WM_KILLFOCUS ab, um zu verfolgen, ob sie gerade den Fokus haben. Außerdem enthalt DlgProc nur noch Färtoungsanweiungen. Die Funktion SetClientRGB, die hier zum Färben des Hintergrundfensters herangezogen wird, ist in myEnv.h deklariert und wird in myEnv.c eingebunden ( - Anhang).
Prog.h für Fokus
#define «define #define #define #define #define
ID_Z1.. E R O E F F N E N ID_Z1.. S C H L I E S S E N ID_Z2.. E R O E F F N E N ID_Z2.. S C H L I E S S E N ID_K1.. E R O E F F N E N ID_K1.. S C H L I E S S E N
101 102 103 104 105 106
Prog.c für Fokus
#include #include "myEnv.h" #include "prog.h" HWND hwndZI, hwndZ2, hwndKl;
HWND hwndH; // Fensterhandle MSG msg; // Messagestruktur W N D C L A S S wndclass; // Fensterklassenstruktur hlnst = hlnstance; wndclass.style = 0; wndclass. IpfnWndProc = HProc; wndclass.cbCIsExtra = 0; wndclass.cbWndExtra = DLGWINDOWEXTRA; wndclass.hlnstance = hlnstance; wndclass.hlcon = Loadlcon(hlnstance, "Proglcon"); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass. hbrBackground = CreateSolidBrush (RGB(230, 230, 230)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "HCIass"; RegisterClass (&wndclass); wndclass.style = 0; wndclass.lpfnWndProc = DlgProc; wndclass.cbCIsExtra = 0; wndclass.cbWndExtra = DLGWINDOWEXTRA: wndclass.hlnstance = hlnstance; wndclass.hlcon = NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass. hbrBackground = CreateSolidBrush (RGB(192, 220, 192)); wndclass. IpszMenuName = NULL; wndclass. IpszClassName = "DlgClass"; RegisterClass (Swndclass); hwndH = CreateDialog(hlnst, "HRes", NULL, NULL); ShowWindow (hwndH, SW_SHOWMAXIMIZED);
LRESULT C A L L B A C K HPrac(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); L R E S U L T C A L L B A C K DlgProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam);
while (GetMessage (&msg, NULL, 0, 0)) {
HINSTANCE hlnst;
}
TranslateMessage (&msg); DispatchMessage (&msg); return msg.wParam;
Int C A L L B A C K WinMaln (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR IpszCmdLIne, Int nCmdShow)
148
4 Die Fensterzustände fur die
Nachrichtenannahme
case ID_K1_EROEFFNEN: hwndKl = CreateDialog(hlnst, "K1 Res", hWnd, NULL); EnableMenultem(hMenu, ID_K1_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, ID_K1_SCHLIESSEN, MFS_ENABLED); return 0;
II Fensterprozedur des Hintergrundfensters: LRESULT CALLBACK HProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
{ static HMENU hMenu; PSTR textl = Ή ohne Fokus"; PSTR text2 = Ή hat den Fokus"; Switch (message) {
case ID_K1_SCHLIESSEN: DestroyWindow( hwndKl); hwndKl = NULL; EnableMenultem(hMenu, ID_K1_EROEFFNEN, MFS_ENABLED); EnableMenultem(hMenu, ID_K1_SCHLIESSEN, MFS_GRAYED); return 0;
case WM_CREATE: hMenu = GetMenu(hWnd); return 0; case WM_CTLCOLORSTATIC: return SetClientRGB(wParam, rgbWeiss); case WM_SETFOCUS: SetDIgltemTextfhWnd, 10, text2); return 0;
} return DefWindowProc (hWnd, message, wParam, IParam);
case WM_KILLFOCUS: SetDlgltemText(hWnd, 10, textl); return 0; case WM_COMMAND: switch(LOWORD(wParam)) {
case W M C L O S E : PostQuitMessage(O); return 0;
)
return DefWindowProc (hWnd, message, wParam, case ID_Z1_EROEFFNEN: IParam); hwndZI = CreateDialog(hlnst, "Z1Res", } hWnd, NULL); LRESULT CALLBACK DlgProc(HWND hWnd, UINT EnableMenultem(hMenu, message, WPARAM wParam, LPARAM IParam) ID_Z1_EROEFFNEN, MFS_GRAYED); EnableMenultem(hMenu, { ID_Z1_SCHLIESSEN, PSTR textl = "Dialog ohne Fokus"; MFS_ENABLED); PSTR text2 = "Dialog hat den Fokus"; return 0; switch (message) { case ID_Z1_SCHLIESSEN: case WM_CTLCOLORSTATIC: DestroyWindow(hwndZ1 ); < hwndZI = NULL; int id; EnableMenultem(hMenu, id = GetDlgCtrllD((HWND)IParam); ID_Z1_EROEFFNENMFS_ENABLED); EnableMenultem(hMenu, switch (id) { ID_Z1_SCHLIESSEN, MFS_GRAYED); case 10: return 0; return SetClientRGB(wParam, rgbWeiss); default: case ID_Z2_EROEFFNEN: return SetClientRGB(wParam, rgbBlassgruen); hwndZ2 = CreateDialog(hlnst, "Z2Res", hWnd, NULL); ) EnableMenultem(hMenu, ) ID_Z2_EROEFFNEN, MFSJ3RAYED); case WM_SETFOCUS: EnableMenultem(hMenu, SetDlgltemText(hWnd, 10, text2); ID_Z2_SCHLIESSEN, return 0; MFS_ENABLED); return 0; case WM_KILLFOCUS: SetDlgltemText(hWnd, 10, textl); return 0; case ID_Z2_SCHLIESSEN: DestroyWindow(hwndZ2); } hwndZ2 = NULL; return DelWindowProc (hWnd, message, wParam, EnableMenultem(hMenu, IParam); ID_Z2_EROEFFNEN,MFS_ENABLED); EnableMenultem(hMenu, ID_Z2_SCHLIESSEN, MFS_GRAYED); return 0;
4 Die Fensterzustände für die Nachrichtenannahme
* *
149
FONT 10, "System" BEGIN CTEXT '"',10,200,25,84,12,WS_BORDER END
Progres.rc für Fokus
.............. »include "afxres.h" #include "winuser.h" #include "prog.h" PROGICON ICON standard.ico HMENU MENUEX BEGIN P O P U P "Fenster Z&1" BEGIN MENUITEM "&Eröffnen", ID_Z1_EROEFFNEN MENUITEM "«Schließen", ID_21_SCHLIESSEN, , MFS_GRAYED END POPUP "Fenster Z&2" BEGIN MENUITEM "«.Eröffnen", ID_Z2_EROEFFNEN MENUITEM "«Schließen", ID_Z2_SCHLIESSEN,, MFS_GRAYED END P O P U P "Fenster &K1" BEGIN MENUITEM "«Eröffnen", ID_K1_EROEFFNEN MENUITEM "«Schließen", ID_K1_SCHLIESSEN, , MFS_GRAYED END END
Z1RES DIALOG 80, 50, 142, 74 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Z1" CLASS "DlgClass" FONT 10, "System" BEGIN CTEXT "Z1 mit Elter H, zugeordnet",-1,30,8,84,12 CTEXT "",10,29,25,84,12,WS_BORDER EDITTEXT -1,29,42,84,12, ES_CENTER | ES_MULTILINE END Z2RES DIALOG 165, 90, 142, 74 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Z2" CLASS "DlgClass" FONT 10, "System" BEGIN CTEXT "Z2 mit Elter H, zugeordnet",-1,30,8,84,12 CTEXT "", 10,29,25,84,12, WS_BORDER EDITTEXT -1,29,42,84,12, E S _ C E N T E R | ES_MULTILINE END
K1 RES DIALOG 20, 15, 142, 74 STYLE DS_MODALFRAME | WS_CHILD | WS_VISIBLE CLASS "DlgClass" FONT 10, "System" BEGIN HRES DIALOG 0, 0, 200, 200 CTEXT "K1 mit Eiter H, Kind",-1,30,8,84,12 STYLE WS_THICKFRAME | WS_POPUP | WS_CAPTION CTEXT "Dialog ohne Fokus", 10,29,25,84,12, I WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_BORDER WS_SYSMENU PUSHBUTTON "Schaltknopf',-1,29,42,84,12 CAPTION "H" MENU HMENU END CLASS "HCIass"
4.4.11
Das Programm SetFocus
Das Demoprogramm soll demonstrieren, wie die Funktion S e t F o c u s mit dem Fokus auch die Aktivität weitergibt. Hier gibt es ähnliche Fenster wie in Fokus: ein Hintergrundfenster, bezeichnet mit H, 2 dem H zugeordnete Fenster ZI und Z2, dieses Mal ohne Editelemente, ein Kindfenster Κ von H. Die Detailprogrammierung ist aber verändert. Die Fenster H, ZI, 72 (alle aktivierbaren Fenster) haben Menüs, durch die Sie den Fokus einem anderen gerade existierenden Fenster zuweisen können. Das Anklicken löst den Aufruf
150
4 Die Fensterzuslände fiir die
Nachrichtenannahme
der Funktion SetFocus aus. Sie können zum Beispiel sehen, wie die Zuweisung des Fokus von ZI an Κ das Fenster Κ mit dem Fokus ausstattet und das Fenster Η aktiviert.
Bild 4.4.9 Das Programm SetFocus vor dem Bildschirmhintergrund Kindfenster haben in Windows generell keine Menüleisten. Wenn Sie aber den Schaltknopf in Κ andrücken, gibt er eine Nachricht (WM_COMMAND) an sein Eiter. Die Fensterprozedur von Κ wertet sie aus, indem sie den Fokus mit SetFocus an Η gibt.
Textliste 4.4.5 Programm SetFocus Das Menü von Η wird mit UpdateHmenu angepasst, wenn eines der Nachfolgerfenster erzeugt oder zerstört werden ist. Die Menüs von Z1 und 72 sind nicht immer auf dem neuesten Stand. Zum Beispiel wird das Menü von Z1 nicht angepasst, wenn Κ zerstört worden ist. Z1 passt sein Menü durch Aufruf von UpdateZIMenu in Verarbeitung von WM_SETFOCUS an. Es bekommt diese Nachricht, wenn es den Fokus erhält Sofern es den Fokus durch Anklicken des Menüknopfes erhalt, bekommt und verarbeitet es die Nachrieht vor dem Anzeigen des Pull-down-Menüs.
/*·····**···*··**·*—*··***·***—************ * * Prog.h * für SetFocus * *************•**************·*•************·*«*«/ «define ID_Z1_EROEFFNEN «define ID_Z1_SCHLIESSEN
«define «define Die Anweisungen zum Färben der Fenster sind nicht mit «define abgedruckt. «define
ID_Z2_EROEFFNEN ID_Z2_SCHLIESSEN ID_K_EROEFFNEN ID_K_SCHLIESSEN
«define ID_FOCUSH
101 102 103 104 105 106 111
4 Die Fensterzustände für die Nachrichtenannahme #define ID_FOCUSZ1 #deflne ID_FOCUSZ2 «define ID_FOCUSK
* *
117 110 112
151
wndclass.lpszClassName = "Z1 Class"; RegisterClass (&wnddass) ; wndclass.style = 0; wndclass.lpfnWndProc = Z2Proc ; wndclass.cbCIsExtra = 0 ; wndclass.cbWndExtra = DLGWINDOWEXTRA; wndclass.hlnstance = hlnstance ; wndclass.hlcon = NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = CreateSolidBrush (RGB(192, 220, 192)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "Z2Class"; RegisterClass (Swndclass) ;
Prog.c für SetFocus
...............................................^ «include «windows. h> «include "myEnv.h" «include "prog.h" LRESULT C A L L B A C K HProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); LRESULT C A L L B A C K Z1 Proc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); LRESULT C A L L B A C K Z2Proc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam); LRESULT C A L L B A C K KProc(HWND hWnd, UINT messa! W P A R A M wParam, L P A R A M IParam);
wndclass.style = 0; wnddass.IpfnWndProc = KProc ; wndclass.cbCIsExtra - 0 ; wndclass.cbWndExtra = DLGWINDOWEXTRA; wndclass.hlnstance = hlnstance ; wndclass.hlcon = NULL; wnddass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = CreateSolidBrush (RGB(192, 220, 192)); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "KCIass"; RegisterClass (&wndclass) ;
void UpdateHmenu(HWND hWnd); void UpdateZI Menu(HWND hWnd); void UpdateZ2Menu(HWND hWnd); HINSTANCE hlnst; HWND hwndH, hwndZI, hwndZ2, hwndK; int C A L L B A C K WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR IpszCmdLine, int nCmdShow)
hwndH = CreateDialog(hlnst, "HRes", NULL, NULL); ShowWindow (hwndH, SW_SHOWMAXIMIZED) ;
{
while (GetMessage (&msg, NULL, 0, 0)) {
MSG msg; W N D C L A S S wnddass;
II Messagestruktur //Fensterklassenstruktur
TranslateMessage (&msg); DispatchMessage (&msg) ;
}
hlnst = hlnstance;
return msg.wParam ;
wndclass.style = 0; wndclass.lpfnWndProc = HProc ; wndclass.cbCIsExtra = 0 ; wndclass.cbWndExtra = DLGWIΝ DOWEXTRA; wndclass.hlnstance = hlnstance ; wnddass.hlcon = Loadicon(hinstance, "Proglcon"); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass. hbrBackground = CreateSolidBrush (RGB(230,230, 230)); wndclass. IpszMenuName = NULL; wndclass.lpszClassName = "HCIass"; RegisterClass (Swndclass) ; wndclass.style = 0; wndclass.lpfnWndProc = ZIProc ; wndclass.cbCIsExtra = 0 ; wndclass.cbWndExtra = DLGWI Ν DOWEXTRA; wndclass.hlnstance = hlnstance ; wndclass.hlcon = NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wnddass.hbrBackground = CreateSolidBrush (RGB(192, 220, 192)); wndclass.lpszMenuName = NULL;
// Fensterprozedur des Hintergrundfensters: LRESULT C A L L B A C K HProc(HWND hWnd, UINT message, W P A R A M wParam, L P A R A M IParam)
{ PSTR textl = Ή ohne Fokus"; PSTR text2 = Ή hat den Fokus"; switch (message) { case WM_SETFOCUS: SetDlgltemText(hWnd, 10, text2); if (hwndK != NULL) EnableWindow(GetDlgltem(hwndK, 11), FALSE); return 0; case WM_KILLFOCUS: SetDlgltemText(hWnd, 10, textl); if (hwndK 1= NULL) EnableWindow(GetDlgltem(hwndK, 11), TRUE); return 0;
152
4 Die Fensterzustände für die
Nachrichtenannahme
case WM.COMMAND: switch(LOWORD(wParam)) {
PSTR text2 = "Z1 hat den Fokus"; switch (message) {
case ID_Z1_ER0EFFNEN: hwndZI = CreateDialog(hlnst, "Z1Res",
case WM_SETFOCUS: SetDIgltemTextfhWnd, 10, text2); UpdateZIMenu(hWnd); return 0;
hWnd, NULL); UpdateHmenu(hWnd); return 0; case ID_Z1_SCHLIESSEN: DestroyWindow(hwndZ1 ); hwndZI = NULL; UpdateHmenu(hWnd); return 0;
case WM_KILLFOCUS: SetDlgltemText(hWnd, 10, textl); return 0; case WM_COMMAND: switch (LOWORD(wParam)) {
case ID_Z2_EROEFFNEN: hwndZ2 = Crea(eDialog(hlnst, "Z2Res", hWnd, NULL); UpdateHmenu(hWnd); return 0;
case ID_FOCUSH: SetFocus(hwndH); return 0; case ID_FOCUSZ2: SetFocus(hwndZ2); return 0; case ID_FOCUSK: SetFocus( hwndK); return 0;
case ID_Z2_SCHLIESSEN: OestroyWindow(hwndZ2); hwndZ2 = NULL; UpdateHmenu(hWnd); return 0;
}
case ID_K_EROEFFNEN: hwndK = CreateDialog(hlnst, "KRes", hWnd, NULL); UpdateHmenu(hWnd); return 0; case ID_K_SCHLIESSEN: DestroyWindow(hwndK); hwndK = NULL; UpdateHmenu(hWnd); return 0; case ID_FOCUSZ1: SetFocus(hwndZI); return 0; case ID_FOCUSZ2; SetFocus(hwndZ2); return 0; case ID_FOCUSK: SetFocus(hwndK); return 0; ) II switch(LOWORD(wParam)) return DefWindowProc (hWnd, message, wParam, IParam) ;
}
return DelWindowProc (hWnd, message, wParam, IParam) ;
return DefWindowProc (hWnd, message, wParam, IParam) ; LRESULT CALLBACK Z2Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
{ PSTR textl = "22 ohne Fokus"; PSTR text2 = "22 hat den Fokus"; switch (message) { case WM_SETFOCUS: SetDigltemText(hWnd, 10, text2); UpdateZ2Menu(hWnd); return 0; case WM_KILLFOCUS: SetDlgltemText(hWnd, 10, textl); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_FOCUSH: SetFocus(hwndH); return 0; case ID_FOCUSZ1 : SetFocus( hwndZI); return 0; case ID_FOCUSK: SetFocus(hwndK); return 0;
case WM_CLOSE: PostQuitMessage(O) ; return 0 ;
} return DefWindowProc (hWnd, message, wParam, IParam) ; LRESULT CALLBACK Z1 Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
{
PSTR textl = "Z1 ohne Fokus";
} return DefWindowProc (hWnd, message, wParam, IParam) ;
4 Die Fensterzustände für die Nachrichtenannahme return DefWindowProc (hWnd, message, wParam, IParam) ;
eise {
}
EnabieMenultem(hMenu, ID_Z2_EROEFFNEN, MFS_GRAYED); EnabieMenultem(hMenu, ID_Z2_SCHLIESSEN, MFS_ENABLED); EnableMenultem(hMenu, ID_FOCUSZ2, MFS_ENABLED);
LRESULT CALLBACK KProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
if (hwndZ2 == NULL) EnableMenultem(hMenu, MFS_GRAYED); eise EnableMenultem(hMenu, MFS_ENABLED); If (hwndK == NULL) EnableMenultem(hMenu, MFS_GRAYED); eise EnableMenultem(hMenu, MFS_ENABLED);
ID_FOCUSZ2,
ID_FOCUSZ2,
ID_FOCUSK,
ID_FOCUSK,
void UpdateZ2Menu(HWND hWnd)
{
HMENU hMenu; hMenu = GetMenu(hWnd); if (hwndZI == NULL) EnableMenultem(hMenu, MFS_GRAYED); eise EnabieMenuitem(hMenu, MFS_ENABLED); If (hwndK == NULL) EnableMenultem(hMenu, MFS_GRAYED); eise EnableMenultem(hMenu, MFS_ENABLED);
)
if (hwndZ2 == NULL) < EnableMenultem(hMenu, ID_Z2_EROEFFNEN, MFS_ENABLED); EnabieMenuitem(hMenu, ID_Z2_SCHLIESSEN, MFS_GRAYED); EnabieMenultem(hMenu, ID_FOCUSZ2, MFS_GRAYED);
)
}
void UpdateZI Menu(HWND hWnd) {
void UpdateHmenu(HWND hWnd) {
EnableMenultemfhMenu, ID_Z1_EROEFFNEN, MFS_ENABLED); EnableMenultem(hMenu, ID_Z1_SCHLIESSEN, MFS_GRAYED); EnableMenultem(hMenu, ID_FOCUSZ1, MFS_GRAYED);
NULL)
}
ID_FOCUSZ1,
ID_FOCUSZ1,
ID_FOCUSK,
ID_FOCUSK,
153
154
* *
4 Die Fensterzustände für die Nachrichtenannahme
Progres.rc für SetFocus
................................................Ζ »include "afxres.h" #¡nclude "winuserh" #¡nclude "prog, h"
Z2MENU MENUEX BEGIN POPUP "Fokus" BEGIN MENUITEM "Fokus an H", ID_FOCUSH MENUITEM "Fokus an Z1", ID_FOCUSZ1,, MFS_GRAYED MENUITEM "Fokus an K", ID_FOCUSK,, MFS_GRAYED END END
PROGICON ICON standard.ico HMENU MENUEX BEGIN P O P U P "Fenster Z&1" BEGIN MENUITEM "ÄEröffnen", ID_Z1_EROEFFNEN MENUITEM "SSchlietten", ID_21_SCHLIESSEN,, MFS_GRAYED END P O P U P "Fenster Z&2" BEGIN MENUITEM "&Er6ffnen", ID_Z2_EROEFFNEN MENUITEM "&Schließen", ID_Z2_SCHLIESSEN,, MFS_GRAYED END P O P U P "Fenster &K" BEGIN MENUITEM "&Ereffnen", ID_K_EROEFFNEN MENUITEM "SSchließen", ID_K_SCHLIESSEN,, MFS_GRAYED END POPUP "Fokus" BEGIN MENUITEM "Fokus an Z1", ID_FOCUSZ1,, MFS_GRAYED MENUITEM "Fokus an Z2", ID_FOCUSZ2,, MFS_GRAYED MENUITEM "Fokus an K", ID.FOCUSK,, MFS_GRAYED END END Z1MENU MENUEX BEGIN P O P U P "Fokus" BEGIN MENUITEM "Fokus an H", ID_FOCUSH MENUITEM "Fokus an Z2", ID_FOCUSZ2,, MFS_GRAYED MENUITEM "Fokus an K", ID_FOCUSK, , MFS_GRAYED END END
HRES DIALOG 0, 0, 200, 200 STYLE WS_THICKFRAME | WS_POPUP | WS_CAPTION I WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU CAPTION "H" MENU HMENU CLASS "HCIass" FONT 10, "System" BEGIN CTEXT "",10,200,25,84,12,WS_BORDER END Z1RES DIALOG 80, 50, 142, 54 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Z1" CLASS "Z1 Class" MENU ZIMenu FONT 10, "System" BEGIN CTEXT "Z1 mit Elter H, zugeordnet",-1,30,8,84,12 CTEXT "", 10,29,25,84,12,WS_BORDER END Z2RES DIALOG 165, 90, 142, 54 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION CAPTION "Z2" MENU Z2Menu CLASS "Z2Class" FONT 10, "System" BEGIN CTEXT "Z2 mit Elter H, zugeordnet",-1,30,8,84,12 CTEXT '"',10,29,25,84,12,WS_BORDER END KRES DIALOG 20, 15, 142, 74 STYLE DS_MODALFRAME | WS_CHILD | WS_VIS1BLE CLASS "KCIass" FONT 10, "System" BEGIN CTEXT "K mit Elter H, Klnd",-1,30,8,84,12 CTEXT "K ohne Fokus", 10,29,25,84,12,WS_BORDER PUSHBUTTON "Fokus an H", 11,29,42,84,12 END
4 Die Fensterzustände für die Nachrichtenannahme
4.5
155
Unsichtbare und minimierte Fenster
In früheren Technoten war immer wieder von Sichtbarkeit und Unsichtbarkeit von Fenstern und von der Funktion ShowWindow die Rede (vor allem S. 128). Hier wird das Thema noch einmal aufgegriffen. Offene Punkte werden ergänzt. Dabei sprechen wir auch die Programmierung zur Minimierung von Fenstern an, von der an früherer Stelle schon gesagt wurde, dass sie in den Anwendungen sehr selten gebraucht wird. Unabhängig von der Verwendungshäufigkeit ist es wohl nicht anzuraten, eine Basisregelung des Fensterverhaltens ganz zu übergehen. Ein existierendes Fenster braucht nicht den Status der Sichtbarkeit zu haben. Es hat dann alle Eigenschaften eines Fensters, auch eine normale Schirmposition mit normalen Abmessungen, wird aber auf dem Schirm nicht dargestellt. Unsichtbarkeit ist einer von 4 möglichen Darstellungszuständen eines Fensters. Die Darstellungszustände sind auf S. 18 aufgeführt. Ein Kindfenster ist nicht sichtbar, wenn sein Elter nicht sichtbar ist. Wenn ein Eiterfenster unsichtbar gemacht wird, macht das System zugleich alle Kindfenster unsichtbar. Eine wichtige Anwendung dieser Regel bilden die Dialogfenster. Wird ein Dialogfenster unsichtbar, verschwinden auch alle Dialogelemente vom Schirm. Wird ein aktivierbares Fenster unsichtbar, verschwindet der gesamte Verbund. Zugeordnete Fenster sind in der Sichtbarkeit selbständig. Sie bleiben sichtbar, wenn ihr Elter unsichtbar gemacht wird. Somit ist der folgende Zustand möglich: Z2 zugeordnet
ZI zugeordnet
H Wurzel
sichtbar
unsichtbar
sichtbar
Bild 4.5.1 Fensterbaum Das System unterstellt als verbindliche Vorschrift, dass das Hintergrundfenster einer Anwendung immer sichtbar ist. Wenn es den Schirm freigeben soll, wird es minimiert. Fenster unsichtbar zu machen, hat meistens den Sinn, dem Anwender Programmelemente nicht zu zeigen, mit denen er gegenwärtig sowieso nicht arbeiten kann.
156
4 Die Fensterzustände für die Nachrichtenannahme
-
Ein unsichtbares Fenster ist für Mauseingaben durchlässig. Die Eingaben gehen an das darunter liegende Fenster. - Ein unsichtbares Fenster ist nicht aktiv. Wird ein aktives Fenster unsichtbar, überträgt das System die Aktivität an den jüngsten aktivierbaren Vorfahr. Ein unsichtbares Kindfenster kann grundsätzlich den Fokus haben. Wird ein aktives Fenster unsichtbar, verliert das Fenster im Verbund, das den Fokus hat, als Folge des Entzugs der Aktivität den Fokus. Wird dagegen zum Beispiel ein einzelnes Kindfenster im Verbund, das den Fokus hat, unsichtbar, behält es den Fokus. Als Folge hat zunächst kein sichtbares Fenster den Fokus. Im Prinzip müssen Sie ihn nun über Code einem anderen Fenster zuteilen. Hier ist aber wieder ein Punkt, bei dem eine grundsätzliche Regelung in den meisten praktischen Fällen durch standardisierte Sonderprogrammierung aufgehoben wird. Wenn Sie nämlich in einem Dialogfenster mit integrierter Fensterverwaltung ein Dialogelement, das den Fokus hat, unsichtbar machen, erhält das nächste Dialogelement im tab-Zyklus den Fokus. (Der tab-Zyklus wird vom Entwickler programmiert und enthält eingabefähige Dialogelemente. Wenn es nur ein Dialogelement im tab-Zyklus gibt, so dass nun das einzige in Frage kommende Dialogelement unsichtbar ist, erhält das Dialogfenster des Fokus, sofern es aktivierbar ist. Wenn es Kind ist, erhält zunächst sein aktivierbares Elter vom System den Fokus. Dort haben Sie programmiert, dass es ihn sofort an das Kinddialogfenster abgibt. Auch in diesem Fall erhält also das Dialogfenster den Fokus. Dies ist insgesamt eine gutartige Lösung. Sie entspricht dem allgemeinen Prinzip, dass der Fokus bei Fehlen eingabefähiger Dialogelemente wahlweise beim Dialogfenster oder einem Dialogelement liegen soll.) Bei korrekter P r o g r a m m i e r u n g ergibt
sich damit die Regel: -
Ein unsichtbares Fenster hat nicht den Fokus.
Ob ein Fenster sichtbar ist, richtet sich nach dem Stilbit WS_VISIBLE. Während es nicht gesetzt ist, ist das Fenster unsichtbar. Bei der Erzeugung wird ein Fenster nur dann sofort sichtbar, wenn das Stilbit WS_VISIBLE ausdrücklich gesetzt wird. Standard ist sonst, dass es nicht gesetzt ist und dass das Fenster bei der Erzeugung nicht sichtbar ist. Eine Ausnahme gilt für die in einer DIALOG-Anweisung in einer Ressource deklarierten Dialogelemente. Sie werden standardmäßig sichtbar erzeugt. Die Verneinung lässt sich mit der Stilbitangabe NOT WS_VISIBLE zum Ausdruck bringen. Im Effekt können Sie jedes Fenster nach Wunsch sichtbar oder unsichtbar erzeugen. Was nun die Minimierung von Fenstern angeht: Ein minimiertes Fenster ist als Erinnerungselement dargestellt. Der Anwender kann nicht damit arbeiten. Gezeigt wird nur ein Teil der Titelleiste. Es ist aber terminologisch nicht unsichtbar.
4 Die Fensterzustände für die Nachrichtenannahme
157
Während ein Fenster minimiert ist, werden seine Nachkommen nicht dargestellt, weder zugeordnete Fenster noch Kinder. Hier liegt ein Unterschied zu unsichtbaren Fenstern und eine Anpassung der Regelung an das praktische Erfordernis bei der Minimierung von ganzen Anwendungen. Wenn der Anwender ein Hintergrundfenster minimiert, verschwindet der ganze Fensterbaum von der Arbeitsfläche des Schirms. Aktivierbare Fenster haben nach einem allgemeinen Entwurfsprinzip eine Titelleiste mit einem Titeltext. Kindfenster haben keine Titelleiste. Sie können trotzdem einen Titeltext in ihren Objektdaten haben. In minimiertem Zustand wird er beziehungsweise wird sein Anfang angezeigt. Es ist nicht sinnvoll, ein Kindfenster zu minimieren, das keinen Titeltext hat. In minimiertem Zustand wird dann eine leere Leiste abgebildet. Minimierte zugeordnete Fenster werden links unten auf der Arbeitsfläche des Schirms, Kindfenster links unten im Clientbereich des Elters abgebildet.
Bild 4.5.2 Hauptfenster einer Anwendung mit minimiertem aktivem Dialogfenster Ein zugeordnetes Fenster kann in minimiertem Zustand aktiv sein. Dann ist die rudimentäre Titelleiste hervorgehoben eingefärbt. Es wird unter anderem aktiviert, wenn man es mit der Maus anklickt. Diese arbeitstechnisch unbrauchbare Situation kann auch zugleich mit dem Minimieren über ShowWindow mit einem besonderen Parameter herbeigeführt werden. Er ist nicht in die Testliste 4.5.1 auf der folgenden Seite aufgenommen. Praktisch programmieren Sie so, dass einem aktiven Fenster beim Minimieren (falls das bei Ihnen überhaupt vorkommt) die Aktivität entzogen wird.
158
4 Die Fensterzustände fur die Nachrichtenannahme
Textliste 4.5.1 Darstellungsparameter bei ShowWindow Parameter
Zahlenwert
SW_SHOWNORMAL
1
SW SHOWMAXIMIZED 3
SW HIDE
SW MINIMIZE
SW SHOW
SW SHOWNA
SW RESTORE
Bedeutung Zeige ein maximiertes, unsichtbares oder minimiertes Fenster im normalen Zustand an. Für ein aktivierbares Fenster: Aktiviere es. Maximiere ein bisher normal angezeigtes, unsichtbares oder minimiertes Fenster. Für ein aktivierbares Fenster: Aktiviere es. Mache ein bisher normal angezeigtes, maximiertes oder minimiertes Fenster unsichtbar. Entziehe einem aktiven Fenster die Aktivität. Minimiere ein bisher normal angezeigtes, maximiertes oder minimiertes Fenster. Entziehe einem aktiven Fenster die Aktivität. Zeige ein unsichtbares Fenster in dem Zustand (normal, maximiert oder minimiert) an, den es hatte, bevor es unsichtbar gemacht wurde. Für ein aktivierbares Fenster: Aktiviere es. Zeige ein unsichtbares Fenster in dem Zustand (normal, maximiert oder minimiert) an, den es hatte, bevor es unsichtbar gemacht wurde. Für ein aktivierbares Fenster: Aktiviere es nicht. Zeige ein minimiertes Fenster in dem Zustand an, in dem es zuletzt vollständig (normal oder maximiert) dargestellt wurde. Für ein aktivierbares Fenster: Aktiviere es.
Wenn ein Fenster minimiert wird, entspricht die Regelung zum Verbleib des Fokus dem Unsichtbar-Machen. Wird einem ganzen Verbund die Aktivität entzogen, weil er minimiert wird, verliert er auch den Fokus. Ein einzelnes minimiertes Kindfenster oder einer seiner Kindnachkommen behält den Fokus aber. Sie werden ihn mit SetFocus einem anderen Fenster zuteilen. Eine Minimierung von einzelnen Dialogelementen kommt sachlich nicht in Frage, so dass eine Sonderregelung für Dialogfenster mit integrierter Verwaltung wie beim Unsichtbar-Machen (automatische Zuweisung des Fokus an ein anderes Dialogelement) nicht vorgesehen werden musste. Für ein schon bestehendes Fenster schaltet die Funktion ShowWindow zwischen den Darstellungszuständen um. Man kann die Darstellungszustände nicht über Nachrichten einstellen. Sie programmieren für ein Fenster hWnd:
4 Die Fensterzustände für die Nachrichtenannahme
159
ShowWindow(hWnd, Darstellungsparameter); Den Rückgabewert (vom Typ BOOL) werden Sie nicht abfragen. Der Parameter bestimmt nicht nur den Darstellungszustand, sondern betrifft bei aktivierbaren Fenstern auch die Aktivität. ShowWindow ist neutral in Bezug auf den Fokus. Wenn er vom System nicht zusammen mit der Aktivität weitergegeben wird, bleibt er, wo er ist. Die Textliste 4.5.1 enthält fast alle Parameter. Es gibt Auswirkungen von ShowWindow auf die Z-Ordnung der Fenster. Bei aktivierbaren Fenstern ist das schon zum Ausdruck gebracht, wenn gesagt wird, dass ein Fenster aktiviert oder deaktiviert wird. Es gibt aber bei 2 Darstellungsparametern auch Auswirkungen, wenn ein Kindfenster mit ShowWindow angesprochen wird. -
-
Wenn es mit SW_SHOWMAXIMIZED dazu gebracht wird, den Clientbereich des Elters auszufüllen, wird seine Z-Ordnung im Verhältnis zu seinen Kindgeschwistern geändert, wenn es welche hat. Es überdeckt nun diese Geschwister und - sofern vorhanden - damit auch deren Kindnachkommen. Das ist eine zweckmäßige Regelung, weil man ein Kindfenster nur maximieren wird, wenn es auch Arbeits- oder Demonstrationsfläche sein soll. Die beteiligten Geschwister brauchen das Stilbit WS_CLIPSIBLINGS, weil das System sonst die Änderung der ZOrdnung nicht unterstützt. Auch der Parameter SW SHOWNORMAL führt unter Umständen zu einer Änderung der Z-Ordnung zwischen Kindgeschwistern. Hier ist aber eine Fallunterscheidung notwendig, die zudem keine intuitiv naheliegende Begründung hat. Statt das Gedächtnis mit einem selten gebrauchten Detail zu belasten, ist es einfacher, wenn Sie im Bedarfsfall die gewünschte Reihenfolge über BringWindowtoTop ( - S. 136) herbeiführen.
Mit der Funktion IsWindowVisible kann das Programm fragen, ob ein Fenster sichtbar ist.
4.5.1
Das Programm Visib
Das Demoprogramm zeigt die Wirkung der Funktion ShowWindow bei Kindgeschwistern. Ein Hintergrundfenster mit dem Titeltext Visib gehört einer privaten Klasse an. Es hat eingabefahige Dialogelemente (Schaltknöpfe) und integrierte Fensterverwaltung. Die private Klasse ist gewählt, damit das Fenster ein Icon haben kann. Für die integrierte Fensterverwaltung hat es die Fensterprozedur DefDlgProc. - Visib verarbeitet keine betriebswirtschaftlichen Daten. In formaler
160
4 Die Fensterzustände für die Nachrichtenannahme
Hinsicht ist es aber ein Beispiel für ein Dialogfenster mit integrierter Fensterverwaltung als Hintergrundfenster. (Das Buch enthält mehrere Beispiele.)
K1
K2
fSWjSHOWNÖRMAL |
SW_SHOWNORMAL I
SW_SHOWMAXIMIZED
SW_SHOWMAXIMIZED |
SW_SHOW
"* ; < ~
}
SW_HIDE
·
.
jV*'
1
-
SW_SHOW ?»
SW_HIDE
- F .
1 j
Ende
Bild 4.5.1 Programm Visib Vor Visib wird ein Kindfenster mit der Bezeichnung F aus einer privaten Klasse mit privater Fensterprozedur gebildet. F erhält 2 farbige Kinddialogfenster Kl und K2 aus der Klasse #32770. Sie zeigen ihre Namen in nichteingabefahigen statischen Textelementen. Das Programm demonstriert die Anwendung von ShowWindow auf Kl und K2. Visib enthält Schaltknöpfe, die ShowWindow für diese Fenster aufrufen. Sie geben der Funktion unterschiedliche Parameter mit, deren Wirkung Sie betrachten können. Visib enthält ferner statische Textelemente. Kl und K2 sind als Kinder von F gebildet, damit sie beim Maximieren auf dessen Größe beschränkt sind. Sie haben das Stilbit WS_CLIPSIBLINGS, SO dass ihre Z-Reihenfolge im Programmverlauf geändert werden kann.
4 Die Fensterzustände flir die
Nachrichtenannahme
161
Schaltknöpfe sollen gesperrt sein, wenn die Aktion, die sie anzeigen, nicht verfügbar ist. Als angezeigte Aktion wird hier zu Demonstrationszwecken der Aufruf von ShowWindow selbst, nicht seine Wirkung, verstanden. Er ist immer verfügbar, auch wenn er unwirksam ist, weil das Fenster schon in dem angegebenen Zustand ist.
Bild 4.5.2 Baumdiagramm für Visib Das Fenster F und seine Kinder sind in die integrierte Fensterverwaltung von Visib nicht einbezogen. Sie könnten auch nicht einbezogen werden. Die integrierte Fensterverwaltung umfasst immer nur Dialogelemente. Diese Fenster haben niemals den Fokus. (Nach den auf S. 127 dargestellten Regeln wird er ihnen bei der Erzeugung mit CreateDialog zugewiesen. Unmittelbar danach, noch in WinMain, wird er aber mit SetFocus an Visib zurückgegeben und nun können sie ihn nicht mehr bekommen.)
Textliste 4.5.1 Programm Visib II II II II
PROG.H für Visib
«define «define «define «define «define «define «define «define «define
ID_NORMALK1 ID_MAXK1 ID_SHOWK1 ID_HIDEK1 ID_NORMALK2 ID_MAXK2 ID_SHOWK2 ID_HIDEK2 ID_ENDE
11 12 13 14 21 22 23 24 15
PROG.C für Visib
«include «windows.h> «include "prog.h" BOOL CALLBACK HProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam); LRESULT CALLBACK FProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam); BOOL CALLBACK K1Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam); BOOL CALLBACK K2Proc(HWND hWnd, UINT message,
162
4 Die Fensterzustände für die Nachrichtenannahme
WPARAM wParam, LPARAM IParam);
hwndK2 = CreateDialog(hlnstance, "#202", hwndF, K2Proc); SetFocus(hwndH);
HINSTANCE hlnst; HWND hwndF, hwndKl, hwndK2;
while (GetMessage (&msg, NULL, 0, 0)) {
int CALLBACK WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR pszCmdLine, int nCmdShow) { HWND hwndH; MSG msg; WNDCLASS wndclass;
if( hwndH == NULL || !lsDialogMessage(hwndH, 4msg))
{
// Fensterhandle // Messagestruktur // Fensterklassenstnjktir
}
}
TranslateMessage (&msg); DispatchMessage (&msg);
return msg.wParam;
hwndH = FindWindow(NULL, "Visib"); if (hwndH 1= NULL) { ShowWindow(hwndH, SW_RESTORE); SetForegroundWindow(hwndH); return 0;
} hlnst = hlnstance; wndclass.style = 0; wndclass.lpfnWndProc = DefDIgProc; wndclass.cbCIsExtra = 0; wndclass.cbWnd Extra = DLGWINDOWEXTRA; wndclass.hlnstance = hlnstance; wndclass.hlcon = Loadlcon(hlnstance, "Proglcon"); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = NULL; wndclass. IpszMenuName = NULL; wndclass.lpszClassName = "HCIass"; RegisterClass (Swndclass); hwndH = CreateDialog(hlnstance, "#100", NULL, HProc); wndclass.style = 0; wndclass.lpfnWndProc = FProc; wndclass.cbCIsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hlnstance = hlnstance; wndclass.hlcon = NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = CreateSolidBrush (RGB(166, 202, 240)); wndclass. IpszMenuName = NULL; wndclass.lpszClassName = "FCIass"; RegisterClass (&wndclass); hwndF = CreateWindow ( "FCIass", // Klassenname NULL, // Titeltext WS_CHILD I WS_DLGFRAME | WS_VISIBLE, 138, 40, 232, 267, II Fenstertioordinaten hwndH, // Eiter NULL, II individuelles Menu hlnstance, II Programminstanz NULL); II betrifft MDI-Fenster hwndKl = CreateDialog(hlnstance, "#201", hwndF, K1 Proc);
BOOL CALLBACK HProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
{
static H BRUSH hbrBlassblau; switch (message) { case WMJNITDIALOG: hbrBlassblau = CreateSolidBrush (RGB(166, 202, 240)); return 1 ; case WM_CTLCOLORDLG: return (BOOL) hbrBlassblau; case WM_CTLCOLORSTATIC: SetBkColor ((HDC)wParam, RGB(166,202,240)); return (BOOL) hbrBlassblau; case WM.COMMAND: switch LOWORD(wParam) { case ID_NORMALK1 : ShowWindow (hwndKl, SW_SHOWNORMAL); return 1 ; case ID_MAXK1: ShowWindow( hwndKl, SW_SHOWMAXIMIZED); return 1; case ID_SHOWK1: ShowWindow(hwndK1, SW_SHOW); return 1 ; case IDHIDEK1: ShowWlndow(hwndK1, SW_HIDE); return 1 ; case ID_NORMALK2: ShowWindow (hwndK2, SW_SHOWNORMAL); return 1 ; case ID_MAXK2: ShowWindow(hwndK2, SW_SHOWMAXIMIZED); return 1 ; case ID_SHOWK2: ShowWindow(hwndK2, SW_SHOW); return 1 ; case ID_HIDEK2: ShowWindow(hwndK2, SW_HIDE); return 1 ;
4 Die Fensterzustände fur die Nachrichtenannahme
163
case ID_ENDE: SendMessage(hWnd, WM_CLOSE, 0, 0); return 1 ;
CLASS "HCLASS" FONT 10, "System" BEGIN PUSHBUTTON "SW_SHOWNORMAL", } return 0; ID_NORMALK1, 17,148,77,12, WS_GROUP PUSHBUTTON "SW_SHOWMAXIMIZED", ID_MAXK1, 17,166,77,12,WS_GROUP case WM_CLOSE: PUSHBUTTON "SW_SHOW", ID_SHOWK1, PostQuitMessage(O); retum 1 ; 17,184,77,12, WS_GROUP PUSHBUTTON "SW_HIDE", ID_HIDEK1, 17,202,77, } // switch (message) 12,WS_GROUP PUSHBUTTON "SW_SHOWNORMAL", return 0; ID_NORMALK2, 108,148,77,12,WS_GROUP ) PUSHBUTTON "SW_SHOWMAXIMIZED", ID_MAXK2, 108,166,77,12,WS_GROUP LRESULT CALLBACK FProcfHWND hWnd, UINT message, PUSHBUTTON "SW_SHOW", ID_SHOWK2, W P A R A M wParam, LPARAM IParam) 108,184,77,12, WS_GROUP PUSHBUTTON "SW_HIDE", ID_HIDEK2, return DefWindowProc (hWnd, message, wParam, 108,202,77,12, WS_GROUP IParam); CTEXT "K1 ", IDC_STATIC, 17,133,77,12 } CTEXT "K2", I DC_STATIC, 108,133,77,12 K1 Proc und K2Proc enthalten nur Färbungsanweisungen PUSHBUTTON "Ende", ID_ENDE, 62,224,77,12, WS_GROUP END
"time.h" "prog.h" "myEnv.h" "stdio.h"
{
BOOL CALLBACK DlgProc(HWND hDIg, UINT message, WPARAM wParam, LPARAM IParam); int CALLBACK WinMain (HINSTANCE hlnstance, HINSTANCE hPrevlnstance, PSTR IpszCmdLine, int nCmdShow)
{ HWND hwndH; MSG msg; WNDCLASS wndclass;
II Fensterhandle II Messagestruktur II Fensterklassenstruktur
hwndH = FindWindow(NULL, "Weltzeit"); iffhwndH != NULL) { ShowWindow(hwndH,SW_RESTORE); SetForegroundWindow(hwndH); return 0;
} wndclass.style wndclass.IpfnWndProc wndclass.cbCIsExtra wndclass.cbWnd Extra
= 0; = DefDIgProc; = 0; = DLGWINDOWEXTRA;
}
}
TranslateMessage (&msg) ; DispatchMessage (&msg) ;
return msg.wParam ;
typedef struct { char stadtname[100]; int situnde; int rríinute; } Stadt ;
«define Ν 20 Stadt a[N] = { "London ", "Kalkutta ", 'Tokio ", "Sydney ", "Adelaide ", "Jakutsk ", "Daressalam ", "Johannesburg ", "Rio de Janeiro ", "Edmonton ", "Denver ", "Chicago ",
-1, 4,
0, 30,
8,
0,
9, 8, 7,
0, 30, 0,
2,
0,
1,
0,
-4,
0,
-8,
0,
-8,
0,
-7,
0,
384
14 Timer
"Rochester MN ", -7, "Lima ", -6, "Buenos Aires ", -5, "Kairo ", 1, "San Francisco ", -9, "Santiago de Chile ", -5, "New York ", -6, "Honolulu ", -11,
0, 0, 0, 0, 0, 0, 0, 0};
case IDLIST: if (HIWORD(wParam) == LBN_SELCHANGE)
{ index = LBGetCurSel(hWnd, IDLIST); LBGetText(hWnd, IDLIST, index, szText); SetDlgltemText(hWnd, 12, szText); sec = -1 ;
BOOL CALLBACK DlgProcfHWND hWnd, UINT message, WPARAM wParam, LPARAM IParam)
}
return 1 ;
{
case 15: ShowCtrl (hWnd, IDLIST); SetCtrlFocus(hWnd, IDLIST); ShowCtrl (hWnd, 16); ShowCtrl (hWnd, 17); SetCtrlFocus(hWnd, IDLIST); DisableCtrl (hWnd, 15); return 1 ;
char szText[100); int i, index; static int sec = -1 ; switch (message) { case WMJNITDIALOG: SetTimer(hWnd, 1,100, 0); for (i = 0; i < N; ++i) {
case 16: EnableCtrl (hWnd, 15); SetCtrlFocusfhWnd, 15); HideCtrl (hWnd, IDLIST); HideCtrl (hWnd, 16); HideCtrl (hWnd, 17); return 1 ;
index = LBAddString(hWnd, IDLIST, a[i].stadtname); LBSetltemData(hWnd, IDLIST, index, i);
} LBSelectStringfhWnd, IDLIST, -1, "London"); SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(IDLIST, LBN_SELCHANGE),
case IDCANCEL: PostQuitMessage(O); return 1 ;
0);
return 1 ;
}
case WM_TIMER: {
return 0;
time_t tnum; struct tm * ts; int hour, min; time(&tnum); ts = localtime(&tnum); if (sec == ts->tm_sec) return 1 ; hour = ts->tm_hour; min = ts->tm_mln; sec = ts->tm_sec; sprintf(szText, "%02d %02d %02d", hour, min, sec); SetDlgltemTextfhWnd, 10, szText); index = LBGetCurSel(hWnd, IDLIST); i = LBGetltemData(hWnd, IDLIST, index); min = min + a[i).minute; if (min > 59) min = min - 60, ++hour; hour = hour + a[i].stunde; if (hour > 23) hour = hour - 24; if (hour < 0) hour = hour + 24; sprintf(szText, "%02d %02d %02d", hour, min, sec); SetDlgltemText(hWnd, 11, szText);
}
case WMCTLCOLORSTATIC: switch (GetDlgCtrllD((HWND)IParam)) { case 10: // Statische Textergebnisfelder case 11 : case 12: return SetClientRGB(wParam,rgbWeiss);
} return SetClientRGB(wParam,rgbBlassgruen); // Statische Texterklärungsfelder
}
case WM_CTLCOLORDLG: return SetClientRGB(wParam,rgbBlassgruen); } // switch (message) return 0;
........................................ *
Progrès.rc für Weltzelt
/ #¡nclude "afxres.h" #¡nclude "prog.h"
return 1; PROGICON ICON case WM_COMMAND: switch (LOWORD(wParam)) {
//Welches ID?
standard.ico
100 DIALOG EX 10, 20, 232, 94 STYLE DS_MODALFRAME | WS_MINIMIZEBOX |
14 Timer
385
WS_CLIPSIBLINGS, WS_EX_DLGMODALFRAME CTEXT "", 10,89,18,64,12,WS_BORDER CTEXT "",11,89,37,64,12,WS_BORDER , RTEXT ™,12,13,37,71,12,WS_CLIPSIBLINGS I WS_BORDER RTEXT "MEZ", IDC.STATIC, 13,18,71,12, WS_CLIPSIBLINGS LISTBOX IDLIST,12,29,71,42,LBS_SORT | PUSHBUTTON "Stadt wählen", 15,17,55,64,12, NOT WS_VISIBLE | WS_CLIPSIBLINGS | WS_VSCROLL I WS.GROUP | WS_TABSTOP WS_CLIPSIBLINGS | WS_GROUP PUSHBUTTON "Stadt gewähr,16,12,13,71,12,NOT PUSHBUTTON "Ende",IDCANCEL,168,37,50,12 WS_VISIBLE I WS_CLIPSIBLINGS | WS_GROUP END LTEXT "", 17,7,7,79,70, NOT WS_VISIBLE |
WS_POPUP I WS_VISIBLE | WS.CAPTION | WS_SYSMENU CAPTION 'Weltzeit" CLASS "DlgCIass" FONT 10, "System" BEGIN
386
15
15 Messageboxen
Messageboxen
•
I Rückfrage
Φ
Wollen Sie die Transaktion abbrechen?
f
1
Ja
Nein
1
Meldung Die Verbindung zur Datenbank konnte nicht hergestellt werden! Abbrechen
ignorieren
Wiederholen
Hl!
lïestwerte
2Λ32 -1 Zeichen FILE_BEGIN); // Startpunkt der Positionsberechnung if (uiPosition == OxFFFFFFFF) Fehlerbehandlung Textliste 16.4 Die Positionierung des Zeichenzeigers
16 Die Plauenprogrammierung
393
Sie können den Zeichenzeiger mit SetFilePointer frei auf eine gewünschte Stelle mit einer nichtnegativen Nummer positionieren. Die Stelle braucht nicht innerhalb der Datei zu liegen. Wenn die Positionierung gelingt, gibt SetFilePointer die neue Position zurück. Andernfalls gibt sie OxFFFFFFFF zurück. Das Codebeispiel der Textliste 16.4 setzt den Zeichenzeiger auf den Datensatz mit der Platznummer i. Dabei hat i einen Wert > 0. Für die Positionierung geben Sie einen Startpunkt und eine Distanz vom Startpunkt an. Außer FILE_BEGIN sind FILE_END (Dateiende) und FILE_CURRENT (gegenwärtige Position des Zeichenzeigers) zulässige Startwerte. Die Distanz hat den Datentyp long. Negative Werte kommen in Frage, wenn FILE_END oder FILE_CURRENT Startpunkt ist. WriteFile und ReadFile arbeiten mit Puffern, wenn das nicht durch Programmierung in CreateFile ausdrücklich ausgeschlossen wird. SetFilePointer leert den Puffer. Wenn Ihre Anwendung zwischen Lesen und Schreiben wechselt, muss sie immer vorher SetFilePointer geben. Beim wahlfreien Zugriff auf Datensätze ist sowieso immer ein Paar SetFilePointer, WriteFile beziehungsweise SetFilePointer, ReadFile eine Codeeinheit. WriteFile schreibt auf eine gewünschte Stelle jenseits der bisherigen Dateigrenze, wenn der Dateizeiger vorher dorthin positioniert worden ist. Wenn es einen Zwischenraum zwischen der bisherigen Dateigrenze und dem neu geschriebenen Datensatz gibt, wird er vom System reserviert und der Datei hinzugefügt. Er wird inhaltlich nicht gelöscht. Es ist nicht notwendig zu schreiben, um eine Datei zu vergrößern. Mit dem Funktionsaufruf SetEndOf File (hFile) wird die Datei auf die Größe verkürzt oder verlängert, die sich aus dem Stand des Zeichenzeigers ergibt. Verweist der Zeichenzeiger gerade auf den Platz Nr. 100, wird der Platz Nr. 99 der letzte in der Datei. SetEndOf File gibt TRUE zurück, wenn sie erfolgreich war. Sie können die Größe der Datei zu jedem Zeitpunkt mit GetFileSize bestimmen: unsigned int ui; ui = GetFileSize(hFile, NULL); CloseHandle schließt eine Datei: CloseHandle (hFile) ; DeleteFile löscht eine Datei (in Windows NT nur, wenn sie nicht geöffnet ist): DeleteFile ( "Namen. dat" ). MoveFile gibt ihr einen anderen Namen. Bei der Übernahme von älteren Datenbeständen können zunächst Fehlanpassungen der Zeichenbelegung bestehen. Windows verwendet einen ANSI-,
394
16 Die Plattenprogrammierung
ISO- und DIN-genormten Zeichensatz. In der Windows-Literatur wird er als ANSI-Zeichensatz bezeichnet. Die im DOS auf den PCs oft eingestellte Codeseite 437 wird in der Windows-Literatur als OEM-Zeichensatz bezeichnet. Eine Fehlanpassung wird gewöhnlich nur die char-Werte im Bereich von 128 bis 255 betreffen. Dort liegen die Sonderzeichen. Im ANSI-Zeichensatz haben die deutschen Sonderzeichen die Zahlenwerte der Texttabelle 16.5. Wenn Sie Daten aus einer Datei übernehmen, in der Zeichen in einem anderen Zeichensatz dargestellt werden, können Sie unter anderem diese Tabelle heranziehen, um Umwandlungsfunktionen zu schreiben. Wenn die Daten der Codeseite 437 entsprechen, gibt es auch eine fertige Windows-Funktion für die Umwandlung (OemToCharBuf f). dezimal hex ä Ä Ö ö ü Ü ß
228 185 246 214 252 220 223
\xE4 \xB9 \xF6 \xD6 \xFC \xDC \xDF
Texttabelle 16.5 Zahlenwerte der deutschen Sonderzeichen im ANSI-Zeichensatz
16.1
Speicherbasierte Dateien
Das Lesen und Schreiben im Array einer Plattendatei mit SetFilePointer, ReadFile und WriteFile ist längst nicht so bequem gelöst wie die entsprechende Arbeit bei einem Hauptspeicherarray. Dort braucht man kein Gegenstück zu SetFilePointer und statt ReadFile beziehungsweise WriteFile verwendet man einfache Übertragungsoperationen (Wertzuweisungen). Windows bietet die Möglichkeit, auf eine Plattendatei wie auf ein Hauptspeicherarray zuzugreifen. Dazu stellt sich die Anwendung ein Abbild der Datei als Array im Hauptspeicher bereit. Sie wird zu diesem Zweck nicht vollständig geladen. Das virtuelle System der Hauptspeicherverwaltung sorgt dafür, dass ein Teil der Datei, mit dem die Anwendung gerade arbeiten will, dort verfügbar ist. Die anderen Teile sind auf ihrem Platz auf der Platte. Das System zieht dafür nicht die Auslagerungsdatei heran. Die Realisierung verlangt, ein Filesharing auszuschließen. Das ist aber auch betriebswirtschaftlich immer geboten, wenn
/ 6 Die Plattenprogrammierung
395
HANDLE hFile; HANDLE hAbbildungsObjekt; int n; typedef struct { int iMatrikelNr; char szName[20]; } satz; satz * pNamen; hFile = CreateFile( "Namen.dat", // Evtl. mit Pfadangabe GENERIC_READ | GENERIC_WRITE, // Lesen und Schreiben 0, // Kein Filesharing zulassen! NULL, // Systemabhängige Sicherheitsattribute CREATE_ALWAYS, II Create ?, Exist ?, Trunc ? Fl LE_ATTRI BUTE_NORMAL, II Datei-Attribute NULL); II Handle auf ein TemplateFile if (hFile == INVALID_HANDLE_VALUE) II (HANDLE) -1 Fehlerbehandlung hAbbildungsObjekt = CreateFileMapping( hFile, NULL, II Standardsicherheitsattribute PAGE_READWRITE, II Lesen und Schreiben 0, II Ein anderer Wert nur für Dateien mit > 2 Λ 32 - 1 Zeichen (n + 1 ) * sizeof (satz), II Dateigröße NULL); II Name des Abbildungobjektes if(hAbbildungsObjekt == NULL) Fehlerbehandlung pNamen = MapViewOfFile( hAbbildungsObjekt, FILE_MAP_WRITE, II Lesen und Schreiben 0, II Ein anderer Wert nur für Dateien mit > 2 Λ 32 - 1 Zeichen 0, II Abbilden der 0); II ganzen Datei if (pNamen == NULL) Fehlerbehandlung
Textliste 16.1.1 Die Abbildung einer Plattendatei in den Hauptspeicher eine Anwendung in die Datei schreiben muss. Die Methode ist so elegant, dass man in seiner betriebswirtschaftlichen Anwendung SetFilePointer, ReadFile und WriteFile am besten tatsächlich überhaupt nicht heranzieht, es sei denn, man wolle nur lesen und müsse Filesharing zulassen.
396
16 Die Plattenprogrammierung
Zum Beispiel können Sie eine Datei mit einem Hauptspeichersortierverfahren sortieren. Mit Zeitproblemen brauchen Sie dabei nicht zu rechnen. 100.000 Datensätze der Struktur s a t z der vorigen Technote (eine Datei von 2.3 MB), die Zufallszahlen als Matrikelnummern enthielten, wurden auf einem PC unter Aufruf einer Mischsortierfunktion in 1,5 Sekunden sortiert. Die Zeit für das Sichern durch Rückschreiben aller zwischengespeicherten Dateianteile ist dabei mitgerechnet. Sie können in der Datei auch binär suchen und andere Operationen ausführen, die traditionell als Hauptspeicherarbeiten angesehen werden. Eine derartig im Hauptspeicher verfügbare Plattendatei wird als speicherbasiert bezeichnet (memory-mapped; der deutsche Ausdruck folgt dem Buch von Richter in der deutschen Ausgabe). Sie wird zuerst mit CreateFile eröffnet. Dann wird sie in den Hauptspeicher abgebildet. Ein Codebeispiel, bei dem wir weiter die Datensatzstruktur der vorigen Technote verwenden, gibt die Textliste 16.1.1.
Hier wird eine speicherbasierte Datei zum Lesen und Schreiben eröffnet. Der Aufruf von CreateFile verlangt nicht Neues. Filesharing wird nicht zugelassen. Die Abbildung in den Speicher erfolgt über zwei Funktionsaufrufe. Zuerst erzeugt CreateFileMapping ein "Abbildungsobjekt", das durch ein unspezifisches Handle identifiziert wird. Als Anwendungsentwickler nehmen wir ohne weitergehende Fragen zur Kenntnis, dass ein solches Abbildungsobjekt benötigt wird. Hier wird außer dem Dateihandle der Zugriffsmodus angegeben. PAGE_READWRITE steht für Lese- und Schreibrecht. Das Recht wird unverändert von CreateFile übernommen. Es muss aber neu angegeben werden und wird hier anders bezeichnet. In CreateFileMapping wird ferner die Dateigröße festgelegt. Der hier eingetragene Wert ist für die Lebensdauer der Speicherabbildung unveränderlich. Man kann nicht nachträglich noch SetEndOf File aufrufen. Darin liegt aber im Grunde keine Einschränkung, weil Sie die Dateigröße jetzt mit einer ausreichenden Platzreserve versehen können. Sie können sie zum Ende der Arbeit wieder reduzieren. CreateFile hat in diesem Beispiel mit CREATE_ALWAYS eine Datei von 0 Bytes Größe bereitgestellt. CreateFileMapping schafft jetzt Platz für η + 1 Datensätze. Man hat sich vorzustellen, dass die Funktion zur Realisierung intern SetFilePointer und SetEndOf File aufruft. Sie können in dem Dateigrößenparameter von CreateFileMapping 0 angeben. Dann bleibt die Größe der Datei unverändert. In diesem Beispiel hätte sie dann unveränderlich Größe 0; CreateFileMapping würde den Wert NULL für Misserfolg zurückgeben. Das Abbildungsobjekt wird im nächsten Schritt an MapViewOf File übergeben. Hier teilt man nun noch einmal den Zugriffsmodus mit. Er muss wieder
16 Die Plattenprogramm ierung
397
unverändert sein und hat wieder eine neue Bezeichnung (FILE_MAP_WRITE). Damit ist die Speicherabbildung erzeugt. MapViewOf File gibt die Adresse des Array-Anfangs zurück. Sie ist vom unspezifischen Datentyp void *. Im Falle des Misserfolges ist der Rückgabewert NULL. Die Speicherabbildung ist nun eröffnet und man kann mit ihr arbeiten. Im Beispiel wird die Array-Adresse sofort in den Typ sat ζ * umgewandelt. Damit steht die Datei programmiertechnisch nach Sätzen strukturiert zur Verfügung. pNamen [ i ] ist der Datensatz auf Platz i, mit i à 0. Das Füllen der Plätze 1 bis η mit Inhalten lässt sich etwa so formulieren: int i ; satz Datensatz; for
(i = 1; i < = η; ++ i)
{ pNamen[i]
=
Datensatz;
} Der neueste Inhalt der Datei ist normalerweise nicht auf die Platte übertragen, sondern wird teilweise nur im Hauptspeicher dargestellt. Um den Datenbestand gegen Verlust durch Ausfall der Maschine zu sichern, können Sie aufrufen: F l u s h V i e w O f F i l e ( p N a m e n ,
0).
Der Zugriff auf eine Stelle außerhalb des Arrays ist wie sonst in C eine Fehladressierung. Bei einer speicherbasierten Datei führt er gewöhnlich zu einem Systemfehler. Das System bricht die Anwendung mit einer unspezifischen Fehlermeldung ab. Während eine Speicherabbildung eröffnet ist, wird die Plattendatei über deren Funktionalität verwaltet. Aus diesem Grund soll kein Filesharing zugelassen werden. Die Funktionen ReadFile und WriteFile dürfen in dieser Zeit nicht benutzt werden. Sie schließen die Speicherabbildung mit zwei Funktionsaufrufen: UnmapViewOfFile(pNamen); CloseHandle (hAbbildungsObjekt);
Danach ist die Datei selbst noch geöffnet. Wenn Sie die Abbildung mit einer zu reichlichen Platzreserve gebildet haben, können Sie sie nun noch mit SetFilePointer, SetEndOfFile auf die richtige Größe reduzieren. Dann geben Sie CloseHandle
(hFile);
Bei Programmende schließt das System die Speicherabbildung und die Datei auch selbsttätig.
398
16 Die Platlenprogrammierung
Wenn Sie eine speicherbasierte Datei nur zum Lesen eröffnen wollen, ändern Sie den Zugriffsmodus in MapViewOf File auf FILE_MAP_READ ab. Außerdem reicht es dann, in CreateFile GENERIC_READ und in CreateFileMapping PAGE_READONLY anzugeben. Sie werden dann in CreateFileMapping die Dateigröße nicht verändern, also 0 eintragen. Gewöhnlich haben Sie allerdings keinen Grund, das Schreiben auszuschließen und sich die Mühe zu machen, den Textbaustein extra dafür abzuändern. Ihre Anwendung schreibt schließlich sowieso nicht, wenn das nicht ausdrücklich programmiert wird. Zum Schluss wollen wir noch anmerken: Die Programmierung betriebswirtschaftlicher Tabellen mit Plattendateien kann immer nur einen speziellen Grund haben. Sie ist grundsätzlich softwaretechnisch überholt. Sie können zwar für Ihre Anwendung dieselbe Sicherheit wie mit einer SQL-Datenbank erreichen. Der zusätzliche Arbeitsaufwand wird aber auch mit speicherbasierten Dateien in jedem Fall erheblich sein und selbst bei zurückhaltender wirtschaftlicher Bewertung den Preis einer Datenbank deutlich übersteigen.
17 Die Druckerausgabe
17
Die Druckerausgabe
17.1
Übersicht
399
Die Druckerausgabe in Windows ist insgesamt ein sehr umfangreiches und kompliziertes Thema. Wir haben es hier grundsätzlich mit einer programmiertechnischen Umgebung zu tun, die alle denkbaren Anwendungsbereiche abdecken muss. Mit den zugehörigen API-Funktionen werden zum Beispiel Textverarbeitungsprogramme entwickelt, in denen man Bücher mit Hunderten von Seiten Text in ständig wechselnden Formatierungen, mit Kopf- und Fußleisten, mit Querverweisen und automatisch erstellten Sachindizes schreiben kann. In die Schrift können Graphiken eingebunden und dort in begrenztem Umfang auch noch bearbeitet werden. Die Druckseiten werden fortlaufend auf dem Schirm dargestellt. Mit denselben API-Funktionen werden die großen Graphikanwendungen wie Designer, Corel Draw und die CAD-Programme entwickelt. Sie sind auch die programmiertechnische Basis für die Gestaltung der Drucker-Setupdialoge, die vom System geführt werden und dazu dienen, Einstellungen, zum Beispiel zur Papiergröße, für die Drucker vorzunehmen. Um alle Gesichtspunkte der Druckerausgabe zu verstehen, ist unter anderem eine mehr oder weniger umfangreiche Ausbildung im Buch- und Zeitungsdruck und im technischen Zeichnen Voraussetzung, ferner in der Systemprogrammierung für Windows. Der gewaltige Stoffumfang des Gebietes braucht uns aber keine Sorge zu bereiten, da wir uns in diesem Buch von vornherein auf die Zielgruppe von Betriebswirtinnen und vergleichbaren Entwicklerinnen einstellen. Was für Druckausgaben haben wir zu entwickeln? Es sind Listen mit Rechenergebnissen betriebswirtschaftlicher Auswertungen, Auftragsformulare, Lieferscheine, Rechnungen, Mahnungen, Kundenlisten, Artikellisten, Etiketten, Werbe-Serienbriefe für die in einer Tabelle erfassten Kunden usw. Da kann von einer besonderen Schwierigkeit der Druckerausgabe nicht mehr die Rede sein. Den Druck dieser Papiere entwickeln wir vielleicht im Einzelnen mit einem gewissen Zeitaufwand, aber jedenfalls ohne alle Probleme. Die API-Funktionen zur Programmierung verteilen sich auf zwei Arbeitsbereiche. Der erste bezieht sich auf die Gestaltung der einzelnen Seiten eines Druckauftrages. Hier legen Sie vor allem fest, welcher Text in welcher Schrift an welcher Stelle auf der Seite gedruckt werden soll. Sie verwenden auch Funktionen für die Ausgabe von Linien und anderen einfachen Graphiken zur Gliederung einer Seite. Außerdem können Sie aufwändige Figuren zeichnen, aber das kommt nach dem Zweck der Anwendung nicht so oft in Frage. Die Ausgabe wird Seite für Seite programmiert. Dieser Teil der Programmierung ist gewissermaßen das, was uns inhaltlich für die Anwendung, die wir entwickeln,
400
/ 7 Die Druckerausgabe
interessiert. Die zu diesem Bereich gehörenden API-Funktionen sind für die Entwicklerin meistens direkt verwendbar, hinsichtlich der ausgeführten Einzelschritte hoch integriert ("high level") und sofort verständlich. Damit Sie diese Funktionen einsetzen können, müssen Sie in einem zweiten Bereich formale Voraussetzungen schaffen. Sie müssen den Drucker verfügbar machen, Möglichkeiten für die Auswahl von Druckoptionen bieten, einen Druckauftrag ordnungsmäßig abschließen und anderes. Hier gibt es eine Vielzahl von API-Mitteln zur Auswahl, deren Sinn oft nur mühsam zu erkennen ist, die auch zum Teil nur kleine Schritte erledigen und damit programmiertechnisch auf einer weit von den Arbeitswünschen einer Entwicklerin entfernten Ebene liegen. Nach vergleichender Beurteilung bleiben am Ende dann aber auch hier ein paar Funktionen übrig, die zusammen die gesamte Verwaltung eines Druckjobs erledigen können. Sie sind auch hoch integriert und müssen nur noch in den richtigen Zusammenhang gebracht werden. Man kann die API-Anweisungen zu den beiden Bereichen im Quellcode ineinandermischen. Damit verzichtet man aber auf leicht erreichbare Übersicht. Auch bei der Erörterung von Beispielscode muss man dann ständig zwischen Funktionen zur Seitenaufbereitung und zur Verwaltung des Druckauftrags hinund herwechseln. Es ist auf der anderen Seite möglich, sie im Code und in der Erklärung vollständig voneinander zu trennen. Wir haben den notwendigen Verwaltungscode in 3 "private" Kapselfunktionen zusammengefasst (StartPrintJob, PrintPage, EndPrintJob). Diese Funktionen kann man wie API-Funktionen einsetzen. Sie verwenden ihrerseits weitere Funktionen als Hilfsfunktionen und statische globale (nur in ihrer eigenen Datei sichtbare) Variable. Zum Hilfscode gehört ferner die gebrauchsfertig formulierte Deklaration einer Dialogfenster-Ressource. Außerdem haben wir eine Funktion SetDCFont gebildet. Ähnlich wie in anderem Zusammenhang SetCtrlFont ( - S. 288) fasst sie die Schritte zur Auswahl einer Schrift in einem Baustein zusammen. Wir nehmen an, dass dieser gesamte Code, soweit er in C formuliert ist, außerhalb der speziellen Quellcodedateien der Anwendung in einer zur wiederholten Verwendung bestimmten Datei definiert ist. Sie ist wie eine Bibliotheksdatei aufzufassen. Ob sie formal in eine Bibliothek compiliert wird, ist gleichgültig. Eine Headerdatei enthält die Prototypen der 4 Funktionen und wird in die C-Quelldateien der Anwendung eingebunden. Bei der konkreten Realisierung für die Beispielsprogramme dieses Buches wurden myEnv.c und myEnvh verwendet ( - Anhang). Die Ressourcedatei der Anwendung muss die Dialogfensterdeklaration übernehmen. Die damit vorbereitete Organisation läuft identisch auf Windows 95, 98 und Windows NT. Vielleicht übernehmen Sie sie in Ihre persönliche Arbeitsumgebung.
17 Die Druckerausgabe
401
Wir befassen uns also jetzt zunächst und schwerpunktmäßig mit der Druckerausgabe auf einer Ebene gebrauchsfertiger Funktionen. Den in die Kapselfunktionen eingelagerten Code zur Druckauftragsverwaltung brauchen Sie bei der Entwicklungsarbeit nicht präsent zu haben. Wie bei jeder anderen Funktion reichen die zu den Kapselfunktionen selbst gegebenen Erklärungen aus. Wir erklären ihn anschließend. Nach diesen vorbereitenden Bemerkungen kommen wir zur praktischen Arbeit. Ein Quelltextabschnitt für die Druckerausgabe hat den Coderahmen der Textliste 17.1.1. HDC hdcDrucker; char * szDocName = "Name des Druckauftrages"·, BOOL bDruckOK; StartPrintJob (hWnd, & hdcDrucker, szDocName, TRUE); if (hdcDrucker == NULL) return 1; Ermittlung der Druckerpunkte je mm for (... ) / / j e ein Durchlauf für jede Druckseite
{
StartPage (hdcDrucker); // Aufbereiten einer Druckseite bDruckOK = PrintPage (hdcDrucker); if (bDruckOK == FALSE) break;
} EndPrintJob (& hdcDrucker);
Textliste 17.1.1 Coderahmen für die Druckerausgabe hdcDrucker ist hier ein Beispielsname für ein Handle auf einen Displaykontext. Dieser Begriff und der Datentyp HDC sind uns bereits im Zusammenhang mit der Funktion SetCtrlFont begegnet. Ein Displaykontext (engl, auch device context, deutch auch Gerätekontext) ist ein umfangreiches Daten- und Methodenobjekt, dessen genaue Konstruktion nicht dokumentiert ist und der Entwicklerin für ihre Zwecke auch nicht bekannt sein muss. In Windows erfordert jede graphische Ausgabe einen Displaykontext. Wenn ein Programm drucken soll, muss es einen Displaykontext an den Druckertreiber binden. Der Treiber gibt dann Informationen über den Drucker und seine Einstellungen an den Kontext. Aus der Sicht der Entwicklerin bilden Displaykontext, Druckertreiber und Drucker fortan eine Einheit. Jede Anweisung, mit der wir inhaltlich
402
17 Die Druckerausgabe
gesehen den Drucker oder den Treiber ansprechen wollen, geben wir an den Kontext. Man kann in einen Kontext Objekte laden oder sie an ihn binden, die er dann zum Drucken aufrufen kann. Dazu rechnen die Schrift, eine Linienart, ein Flächenmuster (brush) und ein Bitmap. Für die Anwendungsentwicklerin ist der Kontext statt durch eine Beschreibung seiner Interna durch die Funktionen erklärt und definiert, die ihn ansprechen können. Der Code definiert also zunächst ein Handle auf einen Displaykontext. Ein Druckauftrag erhält als Namen einen Textstring, den wir hier szDocName genannt haben. Während der Druckaufbereitung wird der Auftrag mit seinem Namen (zum Beispiel "Lieferschein") im Fenster des Druckmanagers angezeigt. Nach der Organisation, über die wir hier reden, erzeugt die Anwenderin einen Druckauftrag in einem Dialogfenster, normalerweise durch Andrücken eines Schaltknopfes. Wir nennen es den Druckauftragsdialog. Unser Coderahmen steht somit in einer Dialogprozedur. Das Fensterhandle hWnd im Aufruf von St art Print Job ist das Handle des Druckauftragsdialoges. Es ist keineswegs notwendig, einen Druckauftrag aus einem Fenster zu geben. Er kann zum Beispiel auch in einer Konsole-Anwendung erteilt werden, in der es keine Fenster gibt. Für ein solches Programm sollte man die Teile der Kapselfiinktionen, die sich auf Fenster beziehen, herausstreichen. Tatsächlich ist der Hilfscode in einer MS-DOS-Anwendung zwar auch unverändert lauffähig. Das System ignoriert die Teile, die mit einer Konsole-Anwendung nicht verträglich sind. Man wird das aber nicht als saubere Programmierung auffassen. I Drucken
S3 IS :;
r~Drucker £Jame:
H P LaserJet 5L (PCL)
Status:
Standarddruckec Bereit
Typ:
H P LaserJet 5L (PCL)
Ort:
LPT1:
•
Γ" Ausdruck jr Datei
Kommentar: Druckbereich Ρ
Alles
Kopien——Exemplare:
1