6.4 Malen und Zeichnen
Bevor Sie einige Funktionen zum Malen und Zeichnen kennen lernen, folgt hierbei erst mal die WinMain()-Funktion, da sich diesmal etwas Neues darin befindet.
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#define PIXEL 1
#define LINIE 2
#define RECHTECK 3
#define VIELECK 4
#define ELLIPSE 5
#define INVALIDATE 6
#define BEENDEN 7
HWND bPixel, bLinie, bRechteck, bVieleck, bEllipse, bInvalidate, bBeenden;
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
LPCSTR MainClassName = "Malen und Zeichnen";
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
WNDCLASSEX wc;
HWND hWnd;
MSG wmsg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(GetModuleHandle(NULL), IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = MainClassName;
wc.lpszClassName = MainClassName;
wc.hIconSm = LoadIcon(GetModuleHandle(NULL), IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Windows Registrations Fehler", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, MainClassName,
"Gerätekontext Beispiel",
WS_SYSMENU | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 300, NULL, NULL, hInstance, NULL);
bPixel = CreateWindow("button", "Pixel", WS_CHILD | WS_VISIBLE |
BS_DEFPUSHBUTTON, 310, 0, 80, 30,
hWnd, (HMENU)PIXEL, hInstance, NULL);
bLinie = CreateWindow("button", "Linie", WS_CHILD | WS_VISIBLE |
BS_DEFPUSHBUTTON, 310, 30, 80, 30,
hWnd, (HMENU)LINIE, hInstance, NULL);
bRechteck = CreateWindow("button", "Rechtecke", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 60, 80, 30, hWnd,
(HMENU)RECHTECK, hInstance, NULL);
bVieleck = CreateWindow("button", "Vielecke", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 90, 80, 30, hWnd,
(HMENU)VIELECK, hInstance, NULL);
bEllipse = CreateWindow("button", "Ellipse", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 120, 80, 30, hWnd,
(HMENU)ELLIPSE, hInstance, NULL);
bEllipse = CreateWindow("button", "Säubern", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 150, 80, 30, hWnd,
(HMENU)INVALIDATE, hInstance, NULL);
bInvalidate = CreateWindow("button", "Säubern", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 150, 80, 30, hWnd,
(HMENU)INVALIDATE, hInstance, NULL);
bBeenden = CreateWindow("button", "Beenden", WS_CHILD |
WS_VISIBLE | BS_DEFPUSHBUTTON,
310, 180, 80, 30, hWnd,
(HMENU)BEENDEN, hInstance, NULL);
if(hWnd == NULL)
{
if(MessageBox(NULL, "Fehler beim Erstellen des Fensters!",
"Error!", MB_ICONEXCLAMATION | MB_OK) == IDOK);
return 0;
}
while(GetMessage(&wmsg,NULL,0,0))
{
TranslateMessage(&wmsg);
DispatchMessage(&wmsg);
}
return wmsg.wParam;
}
Außer dem Hauptfenster werden hier auch Fenster mithilfe existierender Klassen erzeugt. Dabei handelt es sich um vordefinierte Klassen, welche von MS-Windows bereitgestellt werden. Diese Klassen werden mit der Verwendung der Funktion CreateWindow() nacheinander als abgeleitetes Fenster erzeugt.
bPixel = CreateWindow("button", "Pixel", WS_CHILD | WS_VISIBLE |
BS_DEFPUSHBUTTON, 310, 0, 80, 30,
hWnd, (HMENU)PIXEL, hInstance, NULL);
Im ersten Parameter geben Sie dabei die existierende Klasse an. Hier "button" für eine Schaltfläche. Weitere existierende Klassen, die Sie damit erzeugen können wären: LISTBOX, SCROLLBAR, COMBOBOX, STATIC, EDIT und MDICLIENT. Mehr dazu entnehmen Sie bitte aus der MSDN-Dokumentation. Mit dem zweiten Parameter beschriften Sie die Schaltfläche und dem dritten Parameter geben Sie den Stil der Fensterklasse an. Danach folgt die Position und Größe des Buttons (@Jürgen, Du schriebst anstatt "Buttons" Klasse). Dann der Handle für das Stammfenster, worin diese Klasse angezeigt werden soll. Der neunte Parameter enthält dann die ID des zu erstellenden Steuerelements, was hier die Konstante PIXEL wäre. Dann folgt noch der Intstanz-Handle der Anwendung. Der letzte Parameter wird kaum verwendet und auf NULL gesetzt.
Wenn das Fenster erstellt wurde, ergibt sich durch die neue WinMain()-Funktion folgendes Bild:
Buttons mit vordefinierten Fensterklassen erstellen
Nachdem Sie das Fenster erstellt haben, wollen Sie sicherlich wissen, wie man die einzelnen Funktionen, welche hier mit den Schaltflächen angegeben wurden, erstellen kann. Das ist einfacher, als Sie vielleicht denken. Zuerst die komplette WndProc()-Prozedur.
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
HDC hDC;
HPEN pen;
RECT rc;
long i=0, j, x, y, x2, y2;
POINT aptTriangle[4]= {
{50, 2 },
{98, 86},
{ 2, 86},
{50, 2 }
},
aptPentagon[6]= {
{50, 2 },
{98, 35},
{79, 90},
{21, 90},
{ 2, 35},
{50, 2 }
},
aptHexagon[7] = {
{50, 2 },
{93, 25},
{93, 75},
{50, 98},
{ 7, 75},
{ 7, 25},
{50, 2 }
};
srand( (unsigned)time( NULL ) );
switch( uMsg )
{
case WM_COMMAND :
switch( LOWORD( wParam ) )
{
case PIXEL :
hDC = GetDC(hWnd);
for(i=0; i < 900000; i++)
{
x = rand()%300;
y = rand()%299;
SetPixel(hDC, x, y, RGB(x, y, y));
}
ReleaseDC(hWnd, hDC);
return 0;
case LINIE :
hDC = GetDC(hWnd);
for(i=0; i < 1000; i++)
{
pen = CreatePen( PS_SOLID, 1, RGB(rand()%255,
rand()%255,
rand()%255)
);
SelectObject(hDC, pen);
x = rand()%300;
y = rand()%299;
x2 = rand()%300;
y2 = rand()%299;
MoveToEx(hDC, x, y, NULL);
LineTo(hDC, x2, y2);
DeleteObject(pen);
//Verzögerung
for(j=0; j<1000000; j++);
}
ReleaseDC(hWnd, hDC);
return 0;
case RECHTECK :
hDC = GetDC(hWnd);
for(i=0; i < 200; i++)
{
pen = CreatePen( PS_SOLID, 1, RGB(rand()%255,
rand()%255,
rand()%255)
);
SelectObject(hDC, pen);
x = rand()%300;
y = rand()%299;
x2 = rand()%300;
y2 = rand()%299;
Rectangle(hDC, x, y, x2, y2);
DeleteObject(pen);
//Verzögerung
for(j=0; j<4000000; j++);
}
ReleaseDC(hWnd, hDC);
return 0;
case VIELECK :
hDC = GetDC(hWnd);
pen = CreatePen( PS_SOLID, 1, RGB(255, 0, 0));
SelectObject(hDC, pen);
SetRect(&rc, 0, 0, 300, 299);
//Dreieck
if (RectVisible(hDC, &rc))
Polyline(hDC, aptTriangle, 4);
//Fünfeck
SetViewportOrgEx(hDC, 0, 100, NULL);
if (RectVisible(hDC, &rc))
Polyline(hDC, aptPentagon, 6);
//Sechseck
SetViewportOrgEx(hDC, 100, 100, NULL);
if (RectVisible(hDC, &rc))
Polyline(hDC, aptHexagon, 7);
DeleteObject(pen);
ReleaseDC(hWnd, hDC);
return 0;
case ELLIPSE :
hDC = GetDC(hWnd);
for(i=0; i < 200; i++)
{
pen = CreatePen( PS_SOLID, 1, RGB(rand()%255,
rand()%255,
rand()%255)
);
SelectObject(hDC, pen);
x = rand()%300;
y = rand()%299;
x2 = rand()%300;
y2 = rand()%299;
Ellipse(hDC, x, y, x2, y2);
DeleteObject(pen);
//Verzögerung
for(j=0; j<4000000; j++);
}
ReleaseDC(hWnd, hDC);
return 0;
case INVALIDATE :
InvalidateRect(hWnd, 0, TRUE);
return 0;
case BEENDEN :
PostQuitMessage(0);
return 0;
}
case WM_DESTROY :
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN :
InvalidateRect(hWnd, 0, TRUE);
break;
default :
return( DefWindowProc( hWnd, uMsg, wParam, lParam ));
}
return( 0L );
}
Die Variablen zu Beginn der Prozedur lassen wir jetzt erst mal außen vor und steigen gleich beim Eintreten der Nachricht WM_COMMAND ein. Diese Nachricht wird hier gesendet, wenn eine der Schaltflächen betätigt wurde.
switch( uMsg )
{
case WM_COMMAND :
switch( LOWORD( wParam ) )
{
Welche Nachricht gesendet wurde, befindet sich in der niedrigeren zwei Bytes von wParam. Zuerst überprüfen Sie, ob die Schaltfläche für "Pixel" gedrückt wurde.
case PIXEL :
hDC = GetDC(hWnd);
for(i=0; i < 900000; i++)
{
x = rand()%300;
y = rand()%299;
SetPixel(hDC, x, y, RGB(x, y, y));
}
ReleaseDC(hWnd, hDC);
return 0;
Wurde hier der Button "Pixel" betätigt, holen Sie sich erst mal mit GetDC() den Gerätekontext, um überhaupt etwas zeichnen zu können. In der jetzt folgenden Schleife werden 900000 Pixel auf einer Fläche von 300x299 gesetzt. Die x und y Position, wo der Pixel gesetzt wird, überlassen wir hier dem (Pseudo) Zufallsgenerator (welcher leider nicht ganz zufällig ist). Mit der Funktion SetPixel() können Sie jetzt einen Pixel auf eine bestimmte Farbe setzen. Der erste Parameter ist dabei der Gerätekontext, mit dem zweiten Parameter geben Sie die x und y-Position des betreffenden Pixels an. Mit dem letzten Parameter geben Sie die Farbe des Pixels vom Typ COLORREF an. Hierbei wird das Makro RGB() verwendet, womit wir den Rot-Grün-Blau-Anteil des Pixels angeben können. Für jeden dieser drei Farbwerte können Sie einen Wert zwischen 0 und 255 angeben. Hier einige Farbbeispiele:
RGB(255, 0, 0) //Rot
RGB(0, 255, 0) //Grün
RGB(0, 0, 255) //Blau
RGB(0, 0, 0) //Schwarz
RGB(255, 255, 255) //Weiss
RGB(255, 255, 0) //Gelb
Sie können gerne selbst mit den Werten herumspielen. Nach den 900000 gezeichneten Pixeln, geben Sie am Ende den Gerätekontext wieder frei und diese Aktion wäre beendet. Dabei könnte sich folgendes Bild ergeben:
Zeichnen von einzelnen Pixeln
Wenn Sie jetzt den "Säubern"-Button oder mit der linken Maustaste in die Malfläche klicken, wird der Bildschirm wieder neu gezeichnet und leer gemacht. Zu dieser Funktion später mehr. Die nächste Aktion ist das Zeichnen von Linien. Der zweiten Fallunterscheidung also:
case LINIE :
hDC = GetDC(hWnd);
for(i=0; i < 1000; i++)
{
pen = CreatePen( PS_SOLID, 1, RGB(rand()%255,
rand()%255,
rand()%255)
);
SelectObject(hDC, pen);
x = rand()%300;
y = rand()%299;
x2 = rand()%300;
y2 = rand()%299;
MoveToEx(hDC, x, y, NULL);
LineTo(hDC, x2, y2);
DeleteObject(pen);
//Verzögerung
for(j=0; j<1000000; j++);
}
ReleaseDC(hWnd, hDC);
return 0;
Auch hier holen Sie sich erst mal den Gerätekontext zum Zeichnen mit GetDC(). Anschließend werden in der for-Schleife 1000 Linien gezeichnet. Zuerst wird mit der Funktion CreatePen() ein logischer Stift vom Type HPEN (HPEN ist ein Handle) erzeugt. Mit dem ersten Parameter geben Sie den Stil des Stiftes an. PS_SOLID bedeutet das eine feste Linie gezeichnet wird. Eine gestrichelte Linie erzeugen Sie mit PS_DASH und eine Gepunktete mit PS_DOT. Weitere Stiftstile wären: PS_NULL, PS_DASHDOT, PS_DASHDOTDOT und PS_INSIDEFRAME. Probieren erlaubt ;-). Mit dem zweiten Parameter geben Sie die Stiftbreite an und mit dem dritten Parameter die Farbe vom Typ COLORREF. Auch hier verwenden Sie wieder das Makro RGB() und den Zufallsgenerator. Anschließend erzeugen Sie wieder zufällige Positionen für x, y, x2 und y2 im Bereich von 300x299 Pixeln. Jetzt legen Sie den Anfangspunkt des Stiftes mit der Funktion MoveToEx() fest. Damit geben Sie im Gerätekontext (erster Parameter) die x und y Position an (zweiter und dritter Parameter). Mit dem vierten Parameter von MoveToEx() könnten Sie noch einen Zeiger auf die POINT-Struktur angeben. Dazu kommen wir noch. Von der Position x und y ziehen Sie jetzt mit der Funktion LineTo() einen Strich zur Position x2 und y2. Natürlich müssen Sie auch bei dieser Funktion den Gerätekontext angeben. Damit nach dem Zeichnen der Linie und dem Erzeugen eines neuen Stiftes nicht unnötig Ressourcen verschwendet werden, wird der Stift mit DeleteObject() wieder gelöscht.
Hier das Ergebnis dieser Operation:
Zeichnen von Linien
Nach dem Ende des Zeichnens wird der Gerätekontext mit ReleaseDC() wieder freigegeben.
Im nächsten Beispiel folgt ein ähnlicher Vorgang mit dem Zeichnen von Rechtecken, wobei sich im Gegensatz zum Beispiel mit den Linien nur die Funktion zum Zeichnen geändert hat.
Rectangle(hDC, x, y, x2, y2);
Mit dieser Funktion wird auf den Gerätekontext hDC ein Rechteck gezeichnet. Die linke obere Ecke stellt dabei die Position x und y da und die rechte Untere die Position x2 und y2. Hier das Ergebnis:
Zeichnen von Rechtecken
Der nächste Fall wäre das Zeichnen von Dreiecken, Fünfecken oder gar Sechsecke. Da es dafür keine speziellen Funktionen gibt, wird die Funktion Polyline() verwendet. Hierzu erst mal der Code für das Zeichnen von Vielecken.
case VIELECK :
hDC = GetDC(hWnd);
pen = CreatePen( PS_SOLID, 1, RGB(255, 0, 0));
SelectObject(hDC, pen);
SetRect(&rc, 0, 0, 300, 299);
//Dreieck
if (RectVisible(hDC, &rc))
Polyline(hDC, aptTriangle, 4);
//Fünfeck
SetViewportOrgEx(hDC, 0, 100, NULL);
if (RectVisible(hDC, &rc))
Polyline(hDC, aptPentagon, 6);
//Sechseck
SetViewportOrgEx(hDC, 100, 100, NULL);
if (RectVisible(hDC, &rc))
Polyline(hDC, aptHexagon, 7);
DeleteObject(pen);
ReleaseDC(hWnd, hDC);
return 0;
Zuerst holen Sie sich den Gerätkontext mit GetDC(), dann wird der logische Stift mit der Funktion CreatePen() erzeugt und mit SelectObject() zum Zeichnen ausgewählt. Soweit nichts Neues mehr.
Zuerst setzen Sie mit der Funktion SetRect() die Koordinaten einer RECT-Struktur, welche zu Beginn der Prozedur mit rc deklariert wurde. Der erste Parameter dieser Funktion ist dabei ein Zeiger auf die RECT-Struktur, welche auf die folgenden angegebenen Werte gesetzt wird. Der zweite und dritte Parameter gibt die Koordinate der linken oberen Ecke an und der dritte und vierte Parameter gibt die rechte untere Ecke an.
Mit der Funktion RectVisible() überprüfen Sie, ob das angegebene Rechteck rc innerhalb des Clippingbereichs des Gerätekontextes liegt. Oder einfach ausgedrückt, ob, der angegebene Bereich innerhalb des darstellbaren Fensterbereichs liegt und nicht ein Teil davon außerhalb.
In diesem rechteckigen Bereich von rc zeichnen Sie jetzt mit der Funktion PolyLine() ein Dreieck. Der erste Parameter ist der Gerätekontext. Der zweite Parameter ist ein Zeiger auf ein Array mit POINT-Strukturen, welche Sie zu Beginn des Programms folgendermaßen definiert haben:
POINT aptTriangle[4]= {
{50, 2 },
{98, 86},
{ 2, 86},
{50, 2 }
},
Hier wurde eine POINT-Struktur mit vier Werten versehen. Der erste Punkt ist dabei der Startpunkt (50,2). Von diesem Startpunkt wird eine Linie zum Punkt (98, 86) gezogen. Die zweite Linie wird zum Punkt (2,86) gezogen und die letzte Linie wird wieder zum Startpunkt (50, 2) gezogen, womit Sie ein Dreieck gezeichnet hätten. Im letzten Parameter von PolyLine() geben Sie die Anzahl der Punkte vom zweiten Parameter an.
Kurz darauf wird mit der Funktion SetViewportOrEx() der Ansichtspunkt des Gerätekontext hDC um 100 Pixel nach unten gesetzt. Mit dem Ansichtspunkt ist hierbei der rechteckige Bereich rc gemeint. Den zu versetzenden Ansichtspunkt des Gerätekontextes geben Sie mit dem zweiten und dritten Parameter an. Mit dem letzten Parameter könnten Sie einen Zeiger auf eine POINT-Struktur verwenden, welcher den Ursprung der Ansicht in Geräteeinheiten aufnimmt.
Nachdem der Ansichtspunkt verschoben wurde, kann das nächste Vieleck (Fünfeck) in diesen Ansichtspunkt gezeichnet werden. Dies verläuft ebenso, wie schon beim Zeichnen des Dreiecks. Zum Schluss wird dasselbe mit einem Sechseck gemacht. Hier das Ergebnis dieser Operationen:
Zeichnen von Vielecken mit Polyline()
Zum Schluss folgt noch das Zeichen von Ellipsen, welches äquivalent wie beim Zeichen von Rechtecken ausfällt.
case ELLIPSE :
hDC = GetDC(hWnd);
for(i=0; i < 200; i++)
{
pen = CreatePen( PS_SOLID, 1, RGB(rand()%255,
rand()%255,
rand()%255)
);
SelectObject(hDC, pen);
x = rand()%300;
y = rand()%299;
x2 = rand()%300;
y2 = rand()%299;
Ellipse(hDC, x, y, x2, y2);
DeleteObject(pen);
//Verzögerung
for(j=0; j<4000000; j++);
}
ReleaseDC(hWnd, hDC);
return 0;
Nur das anstatt der Funktion Rectangle() die Funktion Ellipse() verwendet wurde. Hier das Prinzip bei einer Ellipse:
Koordinaten bei einer Ellipse
Und hier noch das Ergebnis zu diesem Beispiel:
Zeichnen von Ellipsen
Schließlich wurde in diesem Beispiel auch noch die Funktion InvalidateRect() verwendet.
case INVALIDATE :
InvalidateRect(hWnd, 0, TRUE);
return 0;
Mit dieser Funktion legen Sie einen rechteckigen Aktualisierungsbereich des Fensters fest, der neu gezeichnet werden muss. Mit dem ersten Parameter geben Sie den Fenster-Handle an. Der zweite Parameter ist in der Regel ein Zeiger auf eine RECT-Struktur. Durch die Angabe von 0 wird hierbei gesorgt, dass das ganze Fenster neu gezeichnet werden muss. Geben Sie im letzten Parameter TRUE an, wird dem Fenster die Nachricht WM_ERASEBKGND geschickt. Wollen Sie bspw. von der linken oberen Ecken des Fensters einen Bereich von 100x100 Pixeln neu Zeichnen, so können Sie dies folgendermaßen realisieren:
case INVALIDATE:
SetRect(&rc, 0, 0, 100, 100);
InvalidateRect(hWnd, &rc, TRUE);
return 0;
Dadurch erhalten Sie folgendes Ergebnis:
Bestimmten rechteckigen Bereich neu Zeichnen
Ich denke die Funktionsweise dürft klar sein. Die Funktion IncalidateRect() wurde auch verwendet, wenn die linken Maustaste in einem Bereich des Fensters außerhalb einer Schaltfläche betätigt wurde. Dann wird das komplette Fenster ebenso neu gezeichnet:
case WM_LBUTTONDOWN :
InvalidateRect(hWnd, 0, TRUE);
break;
Die Auswertung von Maus- und Tastatureingaben wird noch behandelt.
Weiter mit 7. Eingabe von Maus und Tastatur verarbeiten
