
Der Grund, warum in diesem Kapitel die Ausgabe von Text und Grafik gemeinsam behandelt wird, ist offensichtlich: Windows unterscheidet nicht mehr zwischen Text und Grafik, wie man es von MS-DOS gewohnt war. Die Routinen von Windows, die der graphischen Ausgabe dienen, werden unter dem Begriff GDI (Graphic Device Interface) zusammengefaßt. Hier werden dem Programmierer eine Vielzahl von Funktionen zur Ausgabe von Texten, Punkten, Linien oder Bitmaps zur Verfügung gestellt. Ein großer Vorteil des GDI ist es, daß die Ausgabe von Grafik geräteunabhängig erfolgt. Es ist also ziemlich gleichgültig, die Ausgabe auf dem Bildschirm oder einem Laserdrucker erfolgt. Durch entsprechende Treiber wird die nötige Umsetzung von Windows besorgt.
Hinter dem Begriff Gerätekontext verbirgt sich ein Konzept, daß man zuerst verstanden haben muß, um irgend etwas auf dem Bildschirm ausgeben zu können. Die Schwierigkeit liegt auch hier wieder darin, daß man sich in die Gedanken der Entwickler von Windows versetzen muß. Hat man deren Absichten erst einmal verstanden, ist der Rest dann wieder einfacher.
Ein Gerätekontext (device context, DC) ist die Verbindung zwischen einem Windows-Programm und einem Gerätetreiber, der seinerseits ein Ausgabegeräte wie zum Beispiel einem Laserdrucker anspricht. Einfach gesagt: Jedesmal, wenn ein Programm etwas auf Bildschirm oder Drucker ausgeben will, braucht es einen Gerätekontext!
Ein Gerätekontext ist eine Datenstruktur, in der folgende Informationen gespeichert sind. Die Spalte "GDI-Funktionen" gibt an, welche Funktionen die entsprechenden Attribute ändern oder benutzen.
Zeichenattribute |
Standardwert |
GDI-Funktionen |
Hintergrundfarbe |
weiß |
SetBkColor |
Hintergrundmodus |
OPAQUE |
SetBkMode |
Bitmap |
kein Standardwert |
CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, SelectObject |
Farbmuster |
WHITE_BRUSH |
CreateBrushIndirect, CreateDIBPatternBrush, CreateHatchBrush CreatePatternBrush, CreateSolidBrush SelectObject |
Farbmuster-Ursprung |
(0,0) |
SetBrushOrg, UnrealizeObject |
Clipping Gebiet |
Anzeigefläche |
CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRoundRectRgn, ExcludeClipRect, IntersectClipRect, OffsetClipRgn, SelectClipRgn |
Farbpalette |
DEFAULT_PALETTE |
CreatePalette, RealizePalette, SelectPalette, UnrealizeObject |
Aktuelle Stiftposition |
(0,0) |
LineTo, MoveTo |
Zeichenmodus |
R2_COPYPEN |
SetROP2 |
Schriftart |
SYSTEM_FONT |
CreateFont, CreateFontIndirect, SelectObject |
Zeichenabstand |
0 |
SetTextCharacterExtra |
Abbildungsmethode |
MM_TEXT |
SetMapMode |
Stift |
BLACK_PEN |
CreatePen, CreatePenIndirect, SelectObject |
Polygon-Füll-Modus |
ALTERNATE |
SetPolyFillMode |
Dehnungsmodus |
BLACKONWHITE |
SetStretchBltMode |
Textfarbe |
Black |
SetTextColor |
Abbildungsstreckung |
(1,1) |
SetViewportExt |
Abbildungsursprung |
(0,0) |
SetViewportOrg |
Ausgabestreckung |
(1,1) |
SetWindowExt |
Ausgabeursprung |
(0,0) |
SetWindowOrg |
Tabelle 4.1: Informationen des Gerätekontexts
Ein Grund für den Einsatz eines Gerätekontextes ist folgender: Stellen Sie sich vor, Sie wollten einen Text ausgeben. Dazu müßten Sie einer entsprechenden Funktion sehr viele Informationen mitgeben, wie zum Beispiel die Vorder- und Hintergrundfarbe, Schriftart, Farbmuster usw. Das würde den Aufwand für den Programmierer bei jedem Funktionsaufruf beträchtlich erhöhen. Dadurch, daß man eine vordefinierte Parameterliste in Form des Gerätekontextes hat, spart man eine ganze Menge Arbeit. Jetzt kann man einen relativ einfachen Funktionsaufruf benutzen und den Rest der Parameter aus dem Gerätekontext entnehmen.
Hinweis:
Beachten Sie, daß nicht jede vorhandene Information auch von jeder Funktion benötigt wird!
Möchte man einen Gerätekontext verwenden, muß man ihn mit dem Befehl CreateDC anlegen. Da der Vorgang des Anlegens ziemlich zeitaufwendig ist, werden für die schnelle Ausgabe auf dem Bildschirm fünf vordefinierte Kontexte benutzt. Sie werden mit der Funktion GetDC angefordert und mit ReleaseDC wieder freigegeben. Da Windows nur diese beschränkte Anzahl von Gerätekontexten für Fenster hat, leiht sich das Programm gewissermaßen den Kontext nur vorübergehend, um seine Ausgaben zu erledigen. Nach dem Zeichnen der Figur oder des Textes wird der Gerätekontext wieder zurückgegeben und steht dann an anderer Stelle wieder zur Verfügung. Neben der Funktion GetDC stehen speziell für den Gebrauch mit der Meldung WM_PAINT noch das Funktionspaar BeginPaint und EndPaint zur Verfügung. In unserer Einführung wollen wir uns mit diesen beiden letzten Funktionen begnügen.
Um auf einen Gerätekontext Bezug nehmen zu können, braucht man wie üblich einen Handle. Dieser wird von CreateDC, GetDC oder BeginPaint zurückgeliefert. Diese Bezüge haben die gleiche Aufgabe, wie Sie es auch bei Bezügen für Fenster, Cursor oder Symbole kennengelernt haben. Auch hier handelt es sich wieder um eine vorzeichenlose 16-Bit-Zahl, die Windows als interne Kennziffer dient. Die Bezeichnung für diesen Datentyp ist HDC.
Zusammenfassung:
(1) Alle Ausgaben auf Bildschirm oder Drucker benötigen eine Gerätekontext.
(2) Für die Ausgabe im Fenster existieren aus Geschwindigkeitsgründen 5 vordefinierte Gerätekontexte.
(3) Um auf einen Gerätekontext zugreifen zu können, wird ein Handle benutzt.
Sie mögen es vielleicht nicht mehr hören können, aber man kann es nicht oft genug wiederholen: In Windows läuft der ganze Informationsfluß über Nachrichten. Das kann die Nachricht sein, daß eine Taste losgelassen (WM_KEYUP), ein Befehl aus einem Menü gewählt (WM_COMMAND) oder das Anwendungsfenster zerstört wurde (WM_DESTROY). Die Hauptaufgabe der Nachricht WM_PAINT besteht darin, dem Programm mitzuteilen, daß es seinen Fensterinhalt oder Teile davon neu ausgeben muß. Die Nachricht WM_PAINT wird bei verschiedenen Anlässen erzeugt. Dazu zählen:
(1) Die Funktion UpdateWindow, die beim erstmaligen Anlegen des Fensters die Meldung WM_PAINT erzeugt.
(2) Das Sichtbarwerden eines Fensterteils, das durch ein anderes Fenster verdeckt war.
(3) Der Aufruf der Funktion InvalidateRect.
(4) Scrollen des Fensters.
(5) Die Veränderung der Fenstergröße. Vorausgesetzt, beim Anlegen der Fensterklasse wurden die Angaben CS_HREDRAW oder CS_VREDRAW für den Fensterstil (wc.style) gemacht. Diese beiden Angaben legen fest, daß bei jeder Änderung der Fenstergröße der gesamte Fensterinhalt neu gezeichnet wird.
Bei der Auflistung der Anlässe, die ein Neuzeichnen nötig machen, ist Ihnen wahrscheinlich die Funktion InvalidateRect aufgefallen. Was es damit auf sich hat, wollen wir kurz erläutern.
Ungültigkeitsgebiet
Da das Neuzeichnen eines Fensterinhalts unter Umständen recht zeitraubend ist, versucht Windows, den dazu nötigen Aufwand möglichst gering zu halten. Daher macht Windows folgendes: Jedesmal wenn ein Teil des Fensterinhaltes zerstört wird, zum Beispiel durch ein überlagerndes Fenster, merkt sich Windows genau diesen Bereich und markiert ihn als ungültig (engl.: update region). In Bild 4.1 ist der Bereich ungültig geworden, den die Uhr überdeckt.
Bild 4.1: Ungültiges Rechteck
Wird das überlagernde Fenster wieder zur Seite geschoben, und erhält die Anwendung die Nachricht WM_PAINT, liefert Windows den rechteckigen ungültigen Bereich. Im Beispiel ist das der Bereich, der von der Uhr verdeckt war. Zum Reparieren dieser Stelle verwendet Windows nun zwei Techniken. Erstens teilt es dem Programm den ungültigen Bereich mit und erlaubt ihm so, nur die Zeichen auszugeben, die wirklich zerstört sind. Viele Programme kümmern sich jedoch nicht darum, sondern geben prinzipiell alles aus. Daher macht Windows noch ein zweites, auch ohne die Hilfe der Anwendung. Gibt die Anwendung im obigen Beispiel nicht nur die zerstörten Buchstaben neu aus, sondern jeweils eine ganze Zeile, sorgt Windows dafür, daß die tatsächliche Bildschirmausgabe nur in dem Bereich erfolgt, der ungültig ist. Die hierzu verwendete Technik nennt sich Clipping. Unter Clipping versteht man allgemein das Abschneiden der Bildschirmausgabe am Rand eines Ausgabebereiches bzw. Fensters. Es wird dadurch sichergestellt, daß keine Fenster über seinen Rand, bzw. kein Fenster in ein anderes hineinschreiben kann.
Nach dieser langen Vorrede können wir nun zur Beschreibung der Funktion InvalidateRect kommen. Sie erlaubt es einem Programm, sich selbst eine Nachricht vom Type WM_PAINT zu schicken, damit der Fensterinhalt wieder ausgegeben wird. Das könnte zum Beispiel sinnvoll sein, wenn das Programm eine Berechnung durchgeführt hat und nun sein Fenster mit dem Ergebnis darstellen möchte. Um den gesamten Fensterinhalt für ungültig zu erklären, ruft die Anwendung die Funktion mit folgenden Parametern auf:
InvalidateRect(hWnd, NULL, TRUE)
hWnd ist hier der Bezug auf das angesprochene Fenster. Soll nur ein bestimmter Ausschnitt ungültig werden, wird statt NULL eine Variable vom Typ RECT eingesetzt, die die Koordinaten des ungültigen Rechtecks enthält.
Nach so viel Theorie wollen wir jetzt endlich etwas in einem Fenster darstellen. Als Grundlage für unser Programm benutzen wir das Programm prg3_1, dessen Hauptprogrammteil Sie fast unverändert übernehmen können. Hier muß nur die Zeile 10 gelöscht werden, da dieses Programm keine Dialogbox verwendet. Die anderen Änderungen werden im Anschluß an das Listing beschrieben.
Das Programm erlaubt es, über ein Menü die Darstellung von Linien, einem Kreis und einer Reihe von Punkten zu wählen.
prg4_1.c
/*prg4_1.c*/
#include <windows.h>
/* 3*/
/*==========================< Globale Variable >=========================== */
/* 5*/ HANDLE Instanz;
/* 6*/
/*=============================< Prototypen >============================== */
/* 8*/ int PASCAL WinMain(HANDLE, HANDLE, LPSTR, int);
/* 9*/ LONG FAR PASCAL FensterFunktion(HWND,WORD,WORD,LONG);
/*10*/
/*===========================< Hauptprogramm >============================= */
/*12*/ int PASCAL WinMain(HANDLE dieseInstanz,
/*13*/ HANDLE vorigeInstanz,
/*14*/ LPSTR kommando,
/*15*/ int fenstertyp)
/*16*/ {
/*17*/ MSG meldung;
/*18*/ HWND Hauptfenster;
/*19*/ Instanz = dieseInstanz;
/*20*/ if (!vorigeInstanz)
/*21*/ {
/*22*/ WNDCLASS wc;
/*23*/ wc.style= CS_HREDRAW | CS_VREDRAW;
/*24*/ wc.lpfnWndProc= FensterFunktion;
/*25*/ wc.cbClsExtra= 0;
/*26*/ wc.cbWndExtra= 0;
/*27*/ wc.hInstance= dieseInstanz;
/*28*/ wc.hIcon= LoadIcon(dieseInstanz,"grossesA");
/*29*/ wc.hCursor= LoadCursor(NULL,IDC_ICON);
/*30*/ wc.hbrBackground=GetStockObject(WHITE_BRUSH);
/*31*/ wc.lpszMenuName= "MenueFuerPrg4_1";
/*32*/ wc.lpszClassName="Einfach";
/*33*/ if (!RegisterClass(&wc))
/*34*/ {
/*35*/ return 255;
/*36*/ }
/*37*/ }
/*38*/ Hauptfenster=CreateWindow( "Einfach",
/*39*/ "Zeichnen",
/*40*/ WS_OVERLAPPEDWINDOW,
/*41*/ CW_USEDEFAULT,
/*42*/ CW_USEDEFAULT,
/*43*/ CW_USEDEFAULT,
/*44*/ CW_USEDEFAULT,
/*45*/ NULL,
/*46*/ NULL,
/*47*/ dieseInstanz,
/*48*/ NULL);
/*49*/ if (!Hauptfenster)
/*50*/ {
/*51*/ return 255;
/*52*/ }
/*53*/ ShowWindow(Hauptfenster, fenstertyp);
/*54*/ UpdateWindow(Hauptfenster);
/*55*/ while (GetMessage(&meldung,0,0,0))
/*56*/ {
/*57*/ TranslateMessage(&meldung);
/*58*/ DispatchMessage(&meldung);
/*59*/ }
/*60*/ return meldung.wParam;
/*61*/ }
/*62*/
/*===========================< Fensterfunktion >=========================== */
/*64*/ LONG FAR PASCAL FensterFunktion( HWND fenster,
/*65*/ WORD nachricht,
/*66*/ WORD parameter1,
/*67*/ LONG parameter2)
/*68*/ {
/*69*/ static int MenueBefehl=10;
/*70*/ int x,y;
/*71*/ HDC hdc;
/*72*/ PAINTSTRUCT ps;
/*73*/
/*74*/ switch (nachricht)
/*75*/ {
/*76*/ case WM_PAINT:
/*77*/ hdc=BeginPaint (fenster, &ps);
/*78*/ switch(MenueBefehl)
/*79*/ {
/*80*/ case 10:
/*81*/ LineTo(hdc,30,30);
/*82*/ MoveTo(hdc,60,30);
/*83*/ LineTo(hdc,30,60);
/*84*/ break;
/*85*/ case 20:
/*86*/ Ellipse(hdc, 10,10,50,50);
/*87*/ Ellipse(hdc, 20,20,40,40);
/*88*/ break;
/*89*/ case 30:
/*90*/ y=0;
/*91*/ for(x=0; x<=255; x++)
/*92*/ {
/*93*/ SetPixel(hdc,x,y,RGB(x,0,0));
/*94*/ y++;
/*95*/ }
/*96*/ break;
/*97*/ }
/*98*/ EndPaint(fenster, &ps);
/*99*/ return 0;
/*100*/ case WM_COMMAND:
/*101*/ switch(parameter1)
/*102*/ {
/*103*/ case 10:
/*104*/ case 20:
/*105*/ case 30:
/*106*/ MenueBefehl=parameter1;
/*107*/ InvalidateRect(fenster,NULL,TRUE);
/*108*/ return 0;
/*109*/ case 99:
/*110*/ SendMessage(fenster,WM_CLOSE,0,0L);
/*111*/ return 0;
/*112*/ }
/*113*/ break;
/*114*/ case WM_DESTROY:
/*115*/ PostQuitMessage(0);
/*116*/ return 0;
/*117*/ }
/*118*/ return DefWindowProc(fenster,nachricht,parameter1,parameter2);
/*119*/}
Nach dem Übersetzen und Starten des Programms erscheint das Fenster aus Bild 4.2. Zu erkennen sind zwei Linien, die quer über den Bildschirm verlaufen.
Bild 4.2 Das Programm prg4_1 nach dem Start
Das Menü Formen wird im Bild 4.3 dargestellt.
Bild 4.3: Geöffnetes Menü
Nach Wahl des Befehls Kreis erscheint die in Bild 4.4 gezeigte Ausgabe.
Bild 4.4: Zwei Kreise
Wie sieht nun der Programmcode aus, der diese Ausgaben auf dem Bildschirm erzeugt? Beginnen wir mit den Änderungen im Hauptprogramm gegenüber dem Programm prg3_1.
Zeile 23: Die Angaben CS_HREDRAW und CS_VREDRAW bei der Festlegung des Fensterstils geben an, daß der Fensterinhalt (engl.: client area) jedesmal neu gezeichnet werden soll, wenn das Fenster in der Größe horizontal oder vertikal verändert wurde. Eine solche Einstellung ist ganz nützlich, wenn man zum Beispiel immer in der Mitte des Fensters etwas ausgeben will. Ändert sich die Größe des Fensters, muß die alte Zeichnung gelöscht und in der Mitte neu erstellt werden. Für unser erstes Beispiel hätten wir diese Änderung allerdings nicht machen müssen.
Zeile 29+31: Hier wird ein kleines Rechteck (siehe auch Anhang) als Cursor und eine neue Überschrift fürs Fenster definiert.
Jetzt zur Beschreibung der Fensterfunktion, in der das Wesentliche geschieht.
Zeile 69: Die Variable MenueBefehl merkt sich den zuletzt ausgewählten Menüpunkt. Als Vorbelegung haben wir den Wert 10 gewählt, was dem Befehl Linie entspricht (Die Zahl 10 hatten wir in der Ressourcedatei willkürlich als Kennziffer für diesen Menüpunkt gewählt). Als static muß die Variable deklariert werden, damit sie ihren Wert auch zwischen den Aufrufen der Funktion FensterFunktion behält.
Zeile 70: Zwei Hilfsvariable, die zum Zeichnen einer Reihe von Punkten verwendet werden.
Zeile 71: Die Variable hdc merkt sich den Bezug auf den Gerätekontext. Die Bezeichnung hdc wird häufig gewählt, wenn es sich um einen handle auf einen device context handelt.
Zeile 72: Die Funktion BeginPaint in Zeile 77 erwartet als einen ihrer Parameter eine Variable vom Typ PAINTSTRUCT. Wir haben die Variable ps genannt, wie es bei den meisten Programmierern üblich ist. Die Struktur PAINTSTRUCT enthält folgende Felder
typedef struct tagPAINTSTRUCT
{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[16];
} PAINTSTRUCT;
und wird zum Zeichnen im Fenster benötigt. In der Variablen hdc wird der Bezug zum Fenster gespeichert. fErase teilt dem Programm mit, ob der Fensterhintergrund neu gemalt werden muß. Diese Angabe wird nur selten benötigt. Der wichtigste Wert ist rcPaint. rcPaint ist selber eine Struktur und zwar vom Typ RECT. (Zur Erinnerung: Wir haben hier ein Beispiel einer geschachtelten Struktur in C). In dieser Variablen werden die vier Eckpunkte des ungültigen Rechtecks gespeichert (siehe Kapitel 4.2). Die restlichen drei Variablen sind für die interne Benutzung durch Windows reserviert.
Zeile 76: Der Programmteil, der zur Nachricht WM_PAINT gehört, ist der wichtigste in unserem Beispiel. Das erste Mal wird diese Nachricht ausgelöst, wenn in Zeile 54 die Funktion UpdateWindow aufgerufen wird. Und da die Static-Variable MenueBefehl mit dem Wert 10 initialisiert wurde, werden direkt zu Beginn die Anweisungen ausgeführt, die auf "case 10" folgen. Wie Sie wohl erraten können, dient die Funktion LineTo dem Linienziehen. Und damit ist auch erklärt, warum zu Beginn des Programms immer die beiden Linien erscheinen.
Zeile 77+98: BeginPaint liefert den Gerätekontext, der in hdc gespeichert wird. Die Funktionen BeginPaint und EndPaint müssen immer zusammen verwendet werden. Sie umschließen den gesamten Anweisungsblock, der irgendeine Ausgabe auf dem Bildschirm macht. Die Aufgabe von BeginPaint ist es, alles für die Ausgabe vorzubereiten (Bezug auf Gerätekontext liefern, die Struktur PAINTSTRUCT ausfüllen usw.). EndPaint räumt zum Schluß wieder auf.
Zeile 80+85+89: Die Werte 10, 20 und 30 entsprechen den Menüpunkten Linie, Kreis und Punkte. Sehen Sie sich dazu auch die Ressourcedatei prg4_1.rc an.
Zeile 81-83: Die Funktion LineTo zeichnet eine Linie zu den angegebenen Koordinaten. Der erste Wert steht dabei für den Gerätekontext, hier also die Fensterfläche. Der zweite Wert ist der x-Wert und der dritte der y-Wert. Wie man in Bild 4.5 sieht, beginnen die Koordinaten oben links mit dem Werte 0,0. Der x-Wert wächst nach rechts und der y-Wert nach unten.
Bild 4.5: Das Standard-Koordinatensystem
Und woher weiß LineTo, von wo aus die Linie zu zeichnen ist? Aus dem Gerätekontext! Wenn Sie sich nochmals Tabelle 4.1 ansehen, finden sie dort das Zeichenattribut "Aktuelle Stiftposition" mit dem Standardwert 0,0. Das ist der Koordinatenursprung, der für die erste Linie benutzt wird. Die Funktion MoveTo in Zeile 82 macht jetzt nichts anderes, als den Wert für die Stiftposition auf die angegebenen x- und y-Werte zu setzen. Und der zweite LineTo-Befehl zieht seine Linie dann mit dieser Koordinate als Ursprung.
Hinweis:
Beachten Sie, daß LineTo die Koordinaten ebenfalls verändert! Die Stiftposition ist nach dem Linienzeichnen auf die Endposition dieser Linie gesetzt!
Zeile 86+87: Die Funktion Ellipse zeichnet eine gefüllte Figur. Wenn Sie die Zeilen 86 und 87 vertauschen, werden Sie feststellen, daß nur ein Kreis sichtbar ist, da in diesem Fall der eine durch den anderen übermalt wird! Die Größe der Ellipse wird durch ein gedachtes Rechteck bestimmt, dessen linke obere Ecke (20,20 und 10,10) und rechte untere Ecke (40,40 und 50,50) der Funktion als Parameter übergeben werden. Innerhalb dieses gedachten Rechtecks wird die Ellipse eingezeichnet. Der erste Parameter ist wieder der benötigte Gerätekontext. Da in unserem Beispiel jeweils ein Quadrat durch die Koordinaten festgelegt ist, werden zwei Kreise - als Sonderform der Ellipse - dargestellt. Bild 4.6 verdeutlicht nochmals, welche Bedeutung die Koordinaten für diese Funktion haben.
Bild 4.6: Koordinaten für eine Ellipse
Zeile 93: Die Funktion SetPixel benötigt vier Parameter: als ersten den übliche Gerätekontext, dann die x- und y-Koordinaten und zum Schluß die Farbe dieses Punktes. Um die Farbe zu setzen, haben wir das Makro RGB benutzt. Dieses Makro erhält seinerseits drei Werte, die die Farbe bestimmen. Dabei ist der erste Wert der Rot-Anteil der Farbe, der zweite der Grün-Anteil und der dritte der Blau-Anteil. Der Wert 0 bedeutet die geringste Intensität und 255 die höchste. Sind alle drei Werte gleich 0, ergibt das Schwarz, haben alle drei Parameter den Wert 255, ergibt das Weiß. Im Beispielprogramm wird innerhalb der Schleife die Rot-Intensität bis zum Maximalwert von 255 gesteigert. Und da sich auch die Koordinaten kontinuierlich erhöhen, wird eine Linie dargestellt, deren Farbe vom Beginn zum Ende langsam in Rot übergeht.
Zeile 100+101: Die Nachricht WM_COMMAND wird immer dann erzeugt, wenn der Benutzer einen Befehl aus einem Menü wählt. Ist das der Fall, kann man anhand des Parameters parameter1 feststellen, welcher Befehl das war. parameter1 wird der Fensterfunktion in Zeile 66 übergeben. Der übergebene Wert entspricht den Kennziffern, die man den einzelnen Befehlen in der Ressourcedatei gegeben hat. In vielen Programmen finden Sie übrigens nicht diese Zahlenwerte, wie wir sie verwenden, sondern Konstante. Gebräuchlich sind z.B. IDM_NEW, IDM_EXIT oder ähnliches. Die Definition erfolgt in einer separaten Header-Datei (*.h), die dann natürlich per include in das C-Programm und die Ressource-Datei eingebunden werden muß.
Beispiel:
#define IDM_NEW 100 #define IDM_EXIT 150
Der Vorteil dieser Methode ist, daß man mit aussagekräftigen Namen arbeiten kann und sich nicht mit bloßen Zahlen abgeben muß. Wir haben diese Vorgehensweise im Beispielprogramm nicht gewählt, um die Anzahl der benötigten Dateien so gering wie möglich zu halten.
Zeile 106: Diese Zeile ist für das Funktionieren des Programms besonders wichtig. In der Variablen MenueBefehl wird hier der Wert von parameter1 gespeichert, was nichts anderes bedeutet, als daß diese Variable jetzt die Kennziffer des gewählten Befehls enthält. Da sie eine static-Variable ist und somit ihren Wert auch beim nächsten Aufruf von FensterFunktion noch hat, kann dann in Zeile 78 das entsprechende case ausgewählt werden.
Zeile 107: Die Funktion InvalidateRect sorgt dafür, daß Windows die Nachricht WM_PAINT an das Fenster schickt. InvalidateRect benötigt als ersten Parameter den Bezug auf das Fenster. Der zweite Parameter gibt normalerweise die Koordinaten des ungültigen Rechtecks in Form einer Variablen von Typ RECT (siehe auch Erläuterung zu Zeile 72) an. Der Wert NULL bedeutet, daß der gesamte Fensterinhalt ungültig ist. Der letzte Parameter bestimmt, ob die Fensterfläche gelöscht werden soll oder nicht.
Zeile 110: Die letzte neue Funktion, die Sie in diesem Programm kennenlernen, ist SendMessage. Sie dient dazu, einem oder mehreren Fenstern eine Nachricht zu schicken. Der erste Parameter bestimmt das angesprochene Fenster, der zweite die zu sendende Nachricht. Die beiden letzten Parameter dienen, wie bereits erläutert, der genaueren Beschreibung bei speziellen Nachrichten, die wir im Beispiel nicht benötigen. SendMessage wir hier benutzt, um der eigenen Anwendung die Nachricht WM_CLOSE zu schicken. WM_CLOSE veranlaßt die Anwendung, sich zu beenden.
Zusammenfassung:
(1) Die Funktion UpdateWindows in Zeile 54 erzeugt die erste WM_PAINT-Nachricht. Da zu diesem Zeitpunkt die Variable MenueBefehl von der Initialisierung her den Wert 10 hat, erscheint das Fenster zu Beginn mit den beiden Linien. Würden Sie in Zeile 69 die Variable auf 20 setzen, wäre die erste Ausgabe zwei Kreise.
(2) Wählen Sie z.B. den Befehl Kreis, wird WM_COMMAND gesendet. In Zeile 106 wird der Variable MenueBefehl die 20 zugewiesen (20 steht in diesem Programm bekanntlich für den Befehl Kreis).
(3) Damit der ausgewählte Befehl überhaupt eine Wirkung zeigt, muß der Bildschirm gelöscht und müssen die Kreise gemalt werden. Zu diesem Zweck wird daher in Zeile 107 der gesamte Fensterinhalt für ungültig erklärt.
(4) Hat ein Fenster einen Ungültigkeitsbereich, stellt Windows die Nachricht WM_PAINT in die Warteschlange des Fensters. Wird die Nachricht schließlich abgearbeitet, können die gewünschten Kreise gezeichnet werden (den Menüpunkt hatten wir uns ja in Zeile 106 gemerkt).
Diese Art der Programmierung - sozusagen von hinten herum - ist sicherlich gewöhnungsbedürftig. Haben Sie sich aber einmal an die ereignisorientierte Programmierung gewöhnt, kommt Ihnen Ihr alter Programmierstil genauso seltsam vor!
Zur Vervollständigung des Listings von prg4_1.c finden Sie hier noch die zugehörige Ressource- und die Definitionsdatei.
prg4_1.rc
#include <windows.h>
GROSSESA ICON DISCARDABLE "A.ICO"
MenueFuerPrg4_1 MENU
{
POPUP "&Formen"
{
MENUITEM "&Linie", 10
MENUITEM "&Kreis", 20
MENUITEM "&Punkte", 30
MENUITEM SEPARATOR
MENUITEM "&Beenden", 99
}
}
prg4_1.def
;Definitionsdatei PRG4_1.DEF NAME PRG3_1 EXETYPE Windows DESCRIPTION "Programm prg4_1" STUB "WINSTUB.EXE" CODE PRELOAD MOVABLE DISCARDABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 1024 STACKSIZE 5120 EXPORTS FensterFunktion @1
Bei der Ausgabe der Grafiken im Programm prg4_1 haben wir zwei Punkte noch nicht behandelt. Das wollen wir an dieser Stelle kurz nachholen.
Abbildungsmethoden
Bei den Funktionen Ellipse, LineTo oder SetPixel sind wir stillschweigend davon ausgegangen, daß die Koordinaten, die den Funktionen übergeben wurden, auf dem Bildschirm genau einem Pixel entsprachen. Für das Beispiel stimmte die Annahme auch. Wenn Sie sich die Tabelle 4.1 ansehen, finden Sie unter dem Zeichenattribut Abbildungsmethode den Eintrag MM_TEXT. Dieser Wert besagt, daß eine logische Einheit einem Pixel entspricht. MM_TEXT ist die Standardeinstellung, Windows erlaubt jedoch auch andere Modi. Dazu gehören z.B. MM_LOMETRIC oder MM_TWIPS. Bei diesen Modi entspricht eine logische Einheit 0,1 mm bzw. 1/1440 Zoll. Die insgesamt acht unterschiedlichen Abbildungsmethoden dienen dazu, die grafische Darstellung möglichst geräteunabhängig zu halten.
Wir wollen dieses Thema hier nicht weiter vertiefen, sondern Sie nur auf den Umstand aufmerksam machen, so daß sie bei Bedarf wissen, wo Sie weitere Informationen herholen müssen.
Linien ziehen
Beachten Sie, daß beim Linienziehen zwar der Startpunkt, den Sie angeben, zur Linie gehört, der Endpunkt jedoch nicht! Wenn also eine Linie von Koordinate 10 bis 20 gezogen werden soll, endet sie bereits bei 19. Dieses auf den ersten Blick seltsame Verhalten hat den Vorteil, daß eine neue Zeichenfigur automatisch da anfangen kann, wo die vorherige aufhörte.
Der nächste Schritt nach dem Zeichnen von Grafiken ist die Ausgabe von Text. Windows stellt dafür fünf Funktionen zur Verfügung, von denen wir TextOut, die am häufigsten gebrauchte, benutzen werden. Zu diesem Zweck schreiben wir ein weiteres Übungsprogramm, prg4_2. Da dieses Programm nur eine Erweiterung von prg4_1 ist, werden wir es nicht komplett abdrucken, sondern nur die Änderungen mit den entsprechenden Zeilennummern auflisten.
Als erstes sorgen Sie dafür, daß die Textausgabe auch über das Menü erfolgen kann. Dazu fügen Sie in der Ressourcedatei prg4_2.rc hinter dem Menüpunkte "Punkte" diese Zeile ein:
MENUITEM "&Text", 40
In das Programm prg4_2.c werden die folgenden Zeilen eingefügt
/*96A*/ case 40: /*96B*/ SetTextColor(hdc,RGB(0,255,0)); /*96C*/ SetBkColor(hdc,RGB(0,0,255)); /*96D*/ TextOut(hdc,0,0,"Die Funktion TextOut.",14); /*96E*/ break; /*105A*/ case 40:
Compilieren Sie das Programm, und starten Sie es. Wählen Sie den Menüpunkt Text. Das Ergebnis sollte so aussehen:
Bild 4.7: Textausgabe mit TextOut
In diesen wenigen Zeilen lernen Sie drei neue Funktionen kennen. Die wichtigste, TextOut, soll als erstes dargestellt werden.
Zeile 96D: Die Funktion TextOut gibt Texte auf dem Bildschirm aus. Sie ist in etwa mit der Funktion printf zu vergleichen, die unter Windows ja nicht benutzt werden kann. TextOut braucht als ersten Parameter den Gerätekontext. Der zweite und dritte geben die Koordinaten an, wo der Text zu drucken ist. Parameter vier ist ein Zeiger auf die darzustellende Zeichenkette, und fünf gibt die Anzahl der Zeichen an, die von diesem String gedruckt werden sollen. Dieser letzte Parameter ist nötig, da TextOut sich nicht um eine binäre Null kümmert, die normalerweise das Ende einer Zeichenkette markiert. Im Bild 4.7 sehen Sie, daß der Text abgeschnitten ist: "Die Funktion T". Das liegt offensichtlich daran, daß bei der Längenangabe der willkürliche Wert 14 angegeben wurde.
Zeile 96B+96C: Wie Sie wissen, dient der Gerätekontext unter anderem dazu, Standardparameter für die Grafikausgabe bereitzustellen. Meist reichen die Standardeinstellungen auch aus. Möchte man diese jedoch ändern, tut man das mit Hilfe von speziellen Funktionen. In Tabelle 4.1 finden Sie Funktionen, die zur Veränderung der Standardwerte verwendet werden müssen. SetTextColor(hdc,RGB(0,255,0)) dient dazu, die Farbe des Textes einzustellen. Im Beispiel haben wir wieder das Makro RGB benutzt, um die Farben auf grün einzustellen. SetBkColor(hdc,RGB(0,0,255)) setzt die Farbe des Hintergrunds hier auf blau.
Wenn Sie nicht nur eine, sondern mehrere Zeilen Text ausgeben wollen, müssen Sie u.a. Informationen über Höhe und Breite der Zeichen ermitteln, um die Zeilen im richtigen Abstand zu setzen. Die Funktion, die für diese Aufgabe gedacht ist, heißt GetTextMetrics. Der Aufruf dieser Funktion legt in einer von Ihnen zu definierenden Struktur vom Typ TEXTMETRIC alle Informationen ab. Danach haben Sie Zugriff auf ca. 20 Werte, die den Font beschreiben. Eine detaillierte Beschreibung zu GetTextMetrics mit Beispiel finden Sie in Kapitel 7.5.
This Web page was created using a Trial Version of HTML Transit 3.0.