Die WinAPI Plattform

Tutorials

(11.) Das Child Window

In diesem Tutorial zeige ich dir, wie man mit Hilfe von Child Fenstern die Aufgaben eines Programmes in mehrere Teilaufgaben zerlegen kann. Dazu schreiben wir ein Programm, indem man die Hintergrundfarbe des Hauptfensters in einem Child (unter Fenster) verändern kann. Der Einfachheit halber beschränken wir uns auf Grautöne. Man könnte allerdings mit Hilfe einer Farbtabelle auch leicht Farben hinzunehmen.

#include <windows.h>

Da ein Child Window (fast) ein vollwertiges Fenster ist, braucht es auch eine eigene Nachrichtenberarbeitungs Funktion (ein Child Window ist nur von der Position an den Anwendungsbereich des Parent Fensters gebunden). Die Parameter der ChildProc Funktion sind die gleichen, wie bei der normalen WndProc Funktion. Weil wir für das Child Window eine eigene Window Class registrieren, brauchen wir auch einen eigenen lpszClassName (also einen Namen, der das Child Fenster identifiziert, wie der szAppName für das Hauptfenster) Diesen habe ich global deklariert, weil wir ihn in zwei verschiedenen Funktionen brauchen.

Für die Kommunikation zwischen Haupt und Child Fenster benutzen wir das allgemeine Nachrichtensystem von Windows. Für diesen Zweck benutzen wir unsere eigenen Nachrichten. Um nicht mit den Windows Nachrichten zu kollidieren müssen die Werte dieser Nachrichten größer als WM_APP (Application) sein.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildProc(HWND, UINT, WPARAM, LPARAM);

const char szChildName[] = "Farbtabelle";
const UINT PM_COLORCHANGED = WM_APP + 1;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   MSG        msg;
   HWND       hWnd;
   WNDCLASS   wc;
   
   char       szAppName[] = "Das Child Window";
   
   wc.cbClsExtra          = 0;
   wc.cbWndExtra          = 0;
   wc.hbrBackground       = (HBRUSH) GetStockObject(WHITE_BRUSH);
   wc.hCursor             = LoadCursor(NULL, IDC_ARROW);
   wc.hIcon               = LoadIcon(NULL, IDI_APPLICATION);
   wc.hInstance           = hInstance;
   wc.lpfnWndProc         = WndProc;
   wc.lpszClassName       = szAppName;
   wc.lpszMenuName        = NULL;
   wc.style               = CS_HREDRAW | CS_VREDRAW;
   
   RegisterClass(&wc);

Da es mir zu umständlich ist eine neue WNDCLASS Struktur zu deklarieren und auch die gemeinsamen Werte (unseres Hauptfensters und des Child Fensters) neu einzutragen, benutzte ich die selbe WNDCLASS Struktur und ändere nur die relevanten Stellen. So soll unser Child Fenster einen Hellgrauen Hintergrund bekommen und ein Icon brauchen wir auch nicht. Den Klassennamen und die Nachrichtenbearbeitungs Funktion passen wir entsprechend an. Danach wird die Child Fenster Klasse genauso registriert, wie auch das Hauptfenster.

   wc.hbrBackground       = (HBRUSH) GetStockObject(LTGRAY_BRUSH);
   wc.hIcon               = NULL;
   wc.lpfnWndProc         = ChildProc;
   wc.lpszClassName       = szChildName;
   
   RegisterClass(&wc);
   
   hWnd = CreateWindow(   szAppName,
                          szAppName,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT,
                          CW_USEDEFAULT,
                          CW_USEDEFAULT,
                          CW_USEDEFAULT,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

   ShowWindow(hWnd, iCmdShow);
   UpdateWindow(hWnd);

   while (GetMessage(&msg, NULL, 0, 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

Als ständige Variablen in unserer WndProc Funktion brauchen wir einmal den Handle zu unserem Child Window, um mit ihm kommunizieren zu können. Dann wieder eine RECT Struktur, in der wir die Werte des Anwendungsbereiches speichern werden. Und zum Schluss noch die iColor Varialbe, in der wir speichern welche Farbe der Hintergrund gerade hat. Diesen Variable belgen wir mit 'der weißen Farbe' vor.

   static HWND    hChild;
   static RECT    rect;
   static int     iColor = RGB(255, 255, 255);
   
   switch (message)
   {
   case WM_CREATE:
      {
         GetClientRect(hWnd, &rect);

In der WM_CREATE Nachricht erstellen wir das Child Fenster mit der CreateWindow Funktion. Hier signalisieren wir über den Window Style WS_CHILD, dass es sich bei diesem Fenster um ein ein Child Fenster geht. WS_VISIBLE gibt an, dass das Fenster sofort auf dem Bildschirm erscheinen soll (und nicht erst mit ShowWindow daran erinnert werden muss) und WS_DLGFRAME gibt dem Child Fenster einen Rahmen, der einem Dialog gleicht. Nach den Positionswerten geben wir den Handle zum Hauptfenster (hWnd) an, damit Windows weiß, in welchem Anwendungsbereich das Child Fenster liegen soll. lParam ist in der WM_CREATE Nachricht ein Zeiger auf die CREATESTRUCT Strukur. Diese Struktur hat eine Membervariable, die einen Handle zu unserem Programm gespeichert hat.

         hChild = CreateWindow(  szChildName,
                                 NULL,
                                 WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                                 5,
                                 rect.bottom - 35,
                                 rect.right - 10,
                                 30,
                                 hWnd,
                                 NULL,
                                 ((LPCREATESTRUCT) lParam)->hInstance,
                                 NULL);
         return 0;
      }
   case WM_SIZE:
      {

Wenn sich die Größe unseres Fensters verändert hat, verändern wir mit MoveWindow die Position des Child Fensters. Der letzte Parameter gibt an, ob der 'Anwendungsbereich' des Child Windows neu gezeichtnet werden soll.

         rect.right  = LOWORD(lParam);
         rect.bottom = HIWORD(lParam);
         
         MoveWindow(hChild, 5, rect.bottom - 35, rect.right - 10, 30, TRUE);
         
         return 0;
      }

PM_COLORCHANGED ist die von uns selbst geschickte Nachricht. Damit signalisiert das Child Window dem Parent Window, dass sich die Farbe des Hintergrundes ändern soll. In wParam haben wir den Farbwert schon in RGB Form gespeichert. Also setzen wir einfach noch unsere static Variable auf diesen Wert und lassen dann den Bildschirm neu zeichnen.

   case PM_COLORCHANGED:
      {
         iColor = wParam;
         InvalidateRect(hWnd, NULL, FALSE);
         return 0;
      }

In der WM_PAINT Nachricht ist eigentlich nichts neues. Wir zeichenen einfach den Hintergrund mit der Farbe in iColor aus.

   case WM_PAINT:
      {
         PAINTSTRUCT    ps;
         HDC            hDC;
         
         hDC = BeginPaint(hWnd, &ps);
         {
            HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, 
                                              CreateSolidBrush(iColor));
            
            Rectangle(hDC, 0, 0, rect.right, rect.bottom);
            
            DeleteObject(SelectObject(hDC, hOldBrush));
         }
         EndPaint(hWnd, &ps);
         return 0;
      }
   case WM_DESTROY:
      {
         PostQuitMessage(0);
         return 0;
      }
   }
   
   return DefWindowProc(hWnd, message, wParam, lParam);
}

Nun müssen wir noch die Funktion zur Bearbeitung der Nachrichten für das Child Window schreiben. Als Namen habe ich ChildProc gewählt, der ist jedoch beliebig (muss natürlich mit der in WNDCLASS übereinstimmen). In der ChildProc Funktion brauchen wir zwie statische Variablen. Einmal ist das die RECT Struktur, in der wir die Größe des Child Fensters gespeichert haben (ohne Rand, ist also nicht gleich den in CreateWindow angegebenen Werte). Dann die iAnzFarben Variable, in der wir speichern, aus wie vielen Farben der Benutzer auswählen kann (hängt von der Größe des Fensters ab). Die nächsten beiden Werte sind Konstanten, die die Größe eines Farbkästchens und der Abstand zwischen zwei linken Seiten des Kästchens (also iKasten + Abstand) festlegt.

LRESULT CALLBACK ChildProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   static RECT    rect;
   static int     iAnzFarben;
   const  int     iKasten        = 12;
   const  int     iKastenOffset  = 15;
   
   switch (message)
   {

Die Bearbeitung der WM_SIZE Nachricht ist eigentlich die gleiche, die wir sonst auch bei einem Hauptfenster benutzen. Nur, dass wir noch die Anzahl der Farbkästchen neu berechnen. Diese Nachricht bekommt das Fenster übrigens nicht, wenn sich die Größe des Hauptfensters geändert hat, sondern die MoveWindow Funktion mit dem Handle dieses Child Fensters benutzt wurde.

   case WM_SIZE:
      {
         rect.right  = LOWORD(lParam);
         rect.bottom = HIWORD(lParam);
         iAnzFarben  = (rect.right / iKastenOffset) - 2;
         
         return 0;
      }

Dann prüfen wir in der WM_LBUTTONDOWN Nachricht für jedes Farbkästchen, ob es getroffen wurde (du erinnerst dich: Mousepostition in lParam). Wenn dem so war, dann errechnen wir noch die Farbe, die dieses Farbekästchen hat und schicken dann dem Hauptfenster unsere selbst erstellte Nachricht PM_COLORCHANGED mit dem neuen Farbwert in wParam. Da der Handle hWnd nur der Handle zu unserem Child Fenster ist, müssen wir mit GetParent dazu das entsprechende Hauptfenster herausfinden.

   case WM_LBUTTONDOWN:
      {
         for (int i = 0; i < iAnzFarben; i++)
         {
            if (15 + i * iKastenOffset                  <= LOWORD(lParam) &&
                15 + i * iKastenOffset + iKasten        >= LOWORD(lParam) &&
                rect.bottom / 2 - iKasten / 2           <= HIWORD(lParam) &&
                rect.bottom / 2 - iKasten / 2 + iKasten >= HIWORD(lParam))
            {
               int iColor = 255 * i / (iAnzFarben - 1);
               SendMessage(GetParent(hWnd), PM_COLORCHANGED, 
                           RGB(iColor, iColor, iColor), 0);
               return 0;
            }
         }
         return 0;
      }

In der WM_PAINT Nachricht zeichnen wir in unserem Child Fenster jedes einzelne Farbkästchen mit der entsprechenden Farbe.

   case WM_PAINT:
      {
         HDC          hDC;
         PAINTSTRUCT  ps;
         
         hDC = BeginPaint(hWnd, &ps);
         {
            for (int i = 0; i < iAnzFarben; i++)
            {
               int    iColor    = 255 * i / (iAnzFarben - 1);
               HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, 
                                  CreateSolidBrush(RGB(iColor, iColor, iColor)));
               Rectangle(hDC,  15 + i * iKastenOffset,
                               rect.bottom / 2 - iKasten / 2,
                               15 + i * iKastenOffset + iKasten,
                               rect.bottom / 2 - iKasten / 2 + iKasten);
               DeleteObject(SelectObject(hDC, hOldBrush));
            }
         }
         EndPaint(hWnd, &ps);
         return 0;
      }
   }
   return DefWindowProc(hWnd, message, wParam, lParam);
}

In diesem Tutorial kam villeicht ein bisschen viel Stoff auf einem Mal dran, aber ich denke, dass ich dem Anspruch etwas sinnvolles zu programmieren sonst nicht gerecht geworden wäre. Wenn du meinst, dass irgend eine Stelle in diesem Tutorial ungenau ist, oder etwas nicht erklärt wird, dann schreibe mir einfach eine E-Mail.

webmaster@win-api.de