Um zu verstehen, warum man in C einen Standard eingeführt hat bedarf es ein wenig mehr der Geschichte zu dieser Sprache.C C++ C/C++ Programmieren C Kurs Programmierung Systemprogrammierung Win32 api Win-Api Windows Systemprogrammierung C Programmierung Win32 api Win-Api Windows Systemprogrammierung Kapitel 6: GDI, Textausgabe und Malen und Zeichnen

In diesem Kapitel soll das Windows-GDI (Graphics Device Interface) etwas genauer betrachtet werden. Die GDI-Schnittstelle stellt eine geräteunabhängige Schnittstelle für Bildschirm und Drucker zur Verfügung. Dank GDI müssen Sie sich nicht selbst um die verwendete Hardware kümmern, da diese Schnittstelle die Hardwarekonstellation berücksichtigt. Man stelle sich vor, Sie wollen in Ihrer erstellten WIN32-Anwendung einen Text ausdrucken lassen und müssten für jeden Drucker erst mal einen Treiber schreiben. Die GDI-Schnittstelle ist somit die Schicht zwischen Ihrer Anwendung unter Windows und den Treibern. Wollen Sie bspw. einen Text bei Ihrer Anwendung ausgeben, leitet GDI diese Aufgabe an den Treiber der Grafikkarte weiter. Der Treiber wiederum hat die Aufgabe den Text auf dem Bildschirm auszugeben.

Das hierbei die Textausgabe und das Zeichnen in einem Kapitel behandelt werden, lässt den Schluss zu, dass Windows nicht mehr zwischen Grafik und Text unterscheidet, wie dies unter MS-DOS noch der Fall war.

6.1. Gerätekontex            zurück  zum Inhaltsverzeichnis

Damit Sie also geräteunabhängige Anwendungen erstellen können, müssen Sie den Gerätekontex (Device Contex kurz DC) für das Fenster ermitteln. Dieser Gerätekontex ist eine interne Struktur von Windows für die Verwaltung von Informationen über ein Ausgabegerät. Solch ein Gerätekontex enthält immer einen Stift zum Zeichen, einen Pinsel zum Füllen von Flächen, eine Schriftart zur Ausgabe von Zeichen und weitere Informationen, die den Gerätekontex regeln. Für jede GDI-Funktion, die Sie verwenden wollen, müssen Sie einen zugehörenden Gerätekontex mit angeben. Diesen können Sie sich mit der Funktion GetDC() ermitteln lassen.

HDC GetDC(HWND hWnd); 

Als Parameter übergeben Sie der Funktion den Fenster-Handle und bekommen, wenn alles glatt verlief, den Gerätekontex dazu zurück. Mit diesem Gerätekontex können Sie jetzt Grafiken oder Texte auf das Fenster hWnd ausgeben bzw. zeichnen. Übergeben Sie der Funktion GetDC(), NULL als Parameter, bekommen Sie den Gerätkontex des Desktop zurück. Damit könnten Sie praktisch direkt auf den Desktop von Windows zeichnen.

Wenn Sie den Gerätkontex nicht mehr benötigen, wird dieser mit der Funktion ReleaseDC() wieder freigegeben:

int ReleaseDC(HWND hWnd, HDC hDC); 

6.2. WM_PAINT            zurück  zum Inhaltsverzeichnis

Die Nachricht WM_PAINT ist eine sehr wichtige Nachricht in puncto Text- und Grafikausgabe. Diese Nachricht kann bei mehreren Anwendungsfällen eintreten. Die Aufgabe dieser Nachricht ist, dass bei Eintreffen dieser Nachricht, der Fensterinhalt oder Teile davon neu gezeichnet werden müssen. WM_PAINT wird gesendet wenn …

Wenn Sie einen Text oder eine Grafik ausgeben wollen, müssen Sie die Nachricht WM_PAINT behandeln. Nur wenn Sie diese Nachricht behandeln, können Sie auch wirklich sicher gehen, dass der Inhalt des Fensters immer aktuell ist.

6.3. Text ausgeben            zurück  zum Inhaltsverzeichnis

Um Ihnen das Thema ein wenig schmackhafter zu machen, folgt hier ein einfaches Listing, womit ein einfacher Text in einem neu erstellten Fenster ge"schrieben" wird (richtig währe wohl gezeichnet).

Ausgabe eines Textes mit GDI

Ausgabe eines Textes mit GDI

Hierzu das WIN32-Listing:

#include <windows.h>
#include <string.h>

LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam );

LPCSTR MainClassName = "Device Contex";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   WNDCLASSEX wc;
   MSG wmsg;
   HWND hWnd;

   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ätekontex Beispiel",
                          WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          300, 200,NULL,NULL,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;
}


LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg,
                          WPARAM wParam, LPARAM lParam )
{
  PAINTSTRUCT ps;
  HDC hDC;
  char szPuffer[] = "Ein Teststring";

   switch( uMsg )
   {
     case WM_PAINT :
              hDC = BeginPaint(hWnd, &ps);
              TextOut(hDC, 0, 0, szPuffer, sizeof(szPuffer)-1);
              EndPaint(hWnd, &ps);
              return 0;
      case WM_DESTROY :
              PostQuitMessage(0);
              break;

      default :
            return( DefWindowProc( hWnd, uMsg, wParam, lParam ));
   }
   return( 0L );
}

Außer der Nachrichtenbehandlung von WM_PAINT enthält dieses Listing nichts Neues. Daher soll auch gleich damit begonnen werden.

  PAINTSTRUCT ps;
  HDC hDC;
…
…
     case WM_PAINT :
              hDC = BeginPaint(hWnd, &ps);
              TextOut(hDC, 0, 0, szPuffer, sizeof(szPuffer)-1);
              EndPaint(hWnd, &ps);
              return 0;

Mit der Funktion BeginPaint() initialisieren Sie den Zeichenprozess im Fenster. Dabei wird ein Speicher für den Gerätekontex angefordert und zurückgegeben. Der erste Parameter der Funktion ist der Handle des zu zeichnenden Fensters. Der zweite Parameter ist ein Zeiger auf die Windows-Struktur PAINTSTRUCT. In dieser Struktur befinden sich Informationen über den Aktuallisierungsbereich des Fensters hWnd.

War die Funktion erfolgreich, wird der Handle (HDC) des Gerätekontex zurückgegeben, der zum Zeichnen des Fensters verwendet werden soll.

In der nächsten Zeile können Sie nun mit der Funktion TextOut() einen String in den alllozierten Gerätekontex schreiben. Der erste Parameter ist dabei der Gerätekontex selbst. Mit dem zweiten und dritten Parameter geben Sie die logische x bzw. y Position des Referenzpunktes für die Ausrichtung des Strings an. Der vierte Parameter ist der auszugebene String selbst und der letzte Parameter beinhaltet die Anzahl der Zeichen des Strings.

Haben Sie die gewünschten Zeichenfunktionen aufgerufen, müssen Sie am Ende das Ende des Malvorgangs mit der Funktion EndPaint() angeben. Die Parameter sind dabei dieselben, wie bei BeginPaint(). Die Funktionen BeginPaint() und EndPaint() werden in der Regel nur in Verbindung der der WM_PAINT-Nachricht verwendet.

Wollen Sie bspw. den String anstatt im Fenster auf den Windows-Desktop schreiben, können Sie die Behandlung von WM_PAINT ein wenig verändern:

     case WM_PAINT :
              hDC = GetDC(NULL);
              BeginPaint(NULL, &ps);
              TextOut(hDC, 0, 0, szPuffer, sizeof(szPuffer)-1);
              EndPaint(NULL, &ps);
              return 0;

Und schon wird links oben auf dem Desktop der String ausgegeben. Wollen Sie auch noch die Textfarbe verändern können Sie sich die Funktion SetTextColor() in der MSDN-Dokumention ansehen.

6.4. Malen und Zeichnen            zurück  zum Inhaltsverzeichnis

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;
   MSG wmsg;
   HWND hWnd;

   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ätekontex 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ähren: 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 der 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ähre. Dann folgt noch der Intstanz-Handle der Anwendung. Der letzte Parameter wird nicht kaum verwendet und auf NULL gesetzt.

Wenn das Fenster erstellt wurde, ergibt sich durch die neue WinMain()-Funktion folgendes Bild:

Buttons mit vordefinierte Fensterklassen erstellt

Buttons mit vordefinierte Fensterklassen erstellt

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 )
{
  PAINTSTRUCT ps;
  HDC hDC;
  long i=0, j , x, y, x2, y2;
  HPEN pen;
  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};
   RECT rc;
   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 als der Button "Pixel" betätigt, holen Sie sich erst mal dem GetDC() den Gerätekontex, 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ätekontex, 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 die 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 Farbenbeispiele:

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 Pixel geben Sie am Ende den Gerätekontex wieder frei und diese Aktion währe beendet. Dabei könnte sich folgendes Bild ergeben:

Zeichnen von einzelnen Pixeln

Zeichnen von einzelnen Pixeln

Wenn Sie jetzt den "Säubern"-Button oder mit der linken Maustaste in der 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ätekontex 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ähren: 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 Pixel. Jetzt legen Sie den Anfangspunkt des Stiftes mit der Funktion MoveToEx() fest. Damit geben Sie im Gerätekontex (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ätekontex angeben. Damit nach dem Zeichnen der Linie und dem Erzeugen eines neuen Stiftes nicht unnötig Resourcen verschwendet werden, wird der Stift mit DeleteObject() wieder gelöscht.

Hier das Ergebnis dieser Operation:

Zeichnen von Linien

Zeichnen von Linien

Nach dem Ende des Zeichnens wird der Gerätekontex 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 dem Gerätekontex 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

Zeichnen von Rechtecken

Der nächste Fall währe 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ätkontex mit GetDC(), dann wird der logische Stift mit der Funktion CreatePen() erzeugt und mit SelectObject() zum Zeichnen ausgewählt. Soweit nicht 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ätekontex 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ätekontex. 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ätekontex hDC um 100 Pixel nach unten gesetzt. Mit dem Ansichtspunkt ist hierbei der rechteckige Bereich rc gemeint. Den zu versetzenden Ansichtspunkt des Gerätekontex 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()

Zeichnen von Vielecken mit Polyline()

Zum Schluss folgt noch das Zeichen von Ellipsen, was Ä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++);
                   }

Nur das anstatt der Funktion Rectangle() die Funktion Ellipse() verwendet wurde. Hier das Prinzip bei einer Ellipse:

Koordinaten bei einer Ellipse
Koordinaten bei einer Ellipse

Und hier noch das Ergebnis zu diesem Beispiel:

Zeichnen von Ellipsen

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

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 Kapitel 7: Eingabe von Maus und Tastatur verarbeiten            zum Inhaltsverzeichnis