Die WinAPI Plattform

Tutorials

Tutorial Nummer 04

(04.) Text positionieren/formatieren

In dem letzten Tutorial habe ich dir gezeigt, wie man Text in den Anwendungsbereich zeichnet, aber der Text war einfach irgendwo platziert. Wenn du das Fenster noch verkleinert hast, dann konntest du den Text unter Umständen gar nicht mehr sehen. In diesem Tutorial zeige ich dir, wie man Text zentriert bzw. die Größe des Textes in Pixeln bestimmt.

Um die neuen Funktionen zu demonstrieren, schreiben wir ein Programm, welches eine Überschrift "Der ASCII Zeichensatz" oben zentriert ausgibt. Darunter soll zu jedem ASCII Code das entsprechende Zeichen ausgegeben werden. Und dies nicht einfach so, sondern genau in das Fenster eingepasst.

#define STRICT
#include <windows.h>

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

const char  szAppName[] = "Text positionieren/formatieren";

Die Konstanten benutzen wir, um den Rand, den horizontalen Abstand zwischen zwei Codes und den vertikalen Abstand zwischen zwei Zeilen festzulegen.

const int   iRand      = 20;
const int   iSpace     = 8;
const int   iVSpace    = 2;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
   MSG       msg;
   HWND      hWnd;
   WNDCLASS  wc;
   
   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_VREDRAW | CS_HREDRAW;
   
   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)
{

In der RECT Struktur (im MSDN: RECT) speichern wir die Maße unseres Anwendungsbereiches.

   static RECT rect;

   switch (message)
   {

Die WM_SIZE Nachricht kommt immer, wenn sich die Größe des Fensters verändert hat (auch am Anfang einmal). Da das Koordinatensystem erst in der linken, oberen Ecke beginnt, müssen, die Membervariablen left und top immer Null bleiben. Die Zuweisung hätte man sich hier sparen können, da static Variablen mit Null vorbelegt werden. Größere Y Werte sind weiter unten und größ X Werte weiter rechts. Im niederwertigen Wort (zwei Byte) des lParam ist die Breite des Anwendungsbereiches gespeichert. Im höherwertigen Wort ist die Höhe gespeichert. Mit den Makros LOWORD und HIWORD, die in windef.h definiert sind, kann man diese Werte leicht extrahieren.

   case WM_SIZE:
      {
         rect.left    = 0;
         rect.top     = 0;
         rect.right   = LOWORD(lParam);
         rect.bottom  = HIWORD(lParam);
         
         return 0;
      }
   case WM_PAINT:
      {
         PAINTSTRUCT   ps;
         HDC           hDC;

Um die Funktionen Übersichtlich zu halten, speichern wir die Überschrift in einer Konstanten.

         const char  szUeberschrift[] = "Der ASCII Zeichensatz";
         
         hDC = BeginPaint(hWnd, &ps);
         {

Jetzt brauchen wir ein paar Variablen, um den Zeichenvorgang zu steuern, denn die einzelnen ASCII Zeichen sollen schön hintereinander und wenn rechts der Rand überschritten wird, soll eine neue Zeile begonnen werden. iXPos speichert die aktuelle horizontale Position. iYPos speichert die vertikale Position. i ist der Schleifenzähler (von Null bis 128). In szText wird jeder einzelne ASCII Code mit dem dazugehörigen Zeichen formatiert zwischengespeichert. In der iStrLen Variablen speichern wir die Länge des aktuellen Strings. size ist eine Instanz der Struktur SIZE, welche benutzt wird um Größen zu speichern. Die Struktur hat nur zwei Elementvariablen cx und cy, welche die Größe in X und Y Koordinaten speichern (c steht für count).

            int   iXPos      = iRand;
            int   iYPos      = 2 * iRand;
            int   i          = 0;
            char  szText[30];
            int   iStrLen    = 0;
            SIZE  size;

Mit der DrawText Funktion (im MSDN: DrawText) kann man Text formatiert ausgeben. Der erste Parameter ist, wie immer bei GDI Funktionen, der Handle zu unserem Device Context. Der zweite Parameter ist ein Zeiger auf den auszudruckenden Text. Im dritten Parameter muss man die Länge des Textes angeben. Wir berechnen die Länge erst mit der Funktion lstrlen (im MSDN: lstrlen), das Windows gegenstück zu der Dos Funktion strlen aus string.h. Der vierte Parameter ist ein Zeiger auf unsere RECT Struktur. DrawText wird den Text innerhalb dieses Rechteckes ausrichten. Mit dem vierten Parameter kann man festlegen, wie der Text formatiert werden soll. Wir benutzen die Konstanten DT_SINGLELINE und DT_CENTER welche den Text einzeilig und zentriert ausgeben lassen.

            DrawText(hDC, szUeberschrift, lstrlen(szUeberschrift), &rect, 
                                              DT_SINGLELINE | DT_CENTER);

Nun berechnen wir in einer Schleife die Position jedes einzelnen ASCII Zeichens und geben es danach mit der TextOut Funktion aus. Wir könnten den ganzen Text auch an einander reihen und dann mit der DrawText Funktion ausgeben, jedoch ist so der Lerneffekt höher.

            for (; i < 128; ++i)
            {

Die wsprintf (im MSDN: wsprintf) Funktion kann man mit der DOS Funktion sprintf vergleichen. Es arbeitet eigentlich, wie printf, nur dass der Text nicht auf dem Bildschirm ausgegeben wird, sondern in szText abgespeichert wird. Die Funktion gibt die Länge des geschriebenen Textes zurück, welche wir in iStrLen speichern.

               iStrLen = wsprintf(szText, "%i: %c", i, (char)i);

Mit der GetTextExtentPoint32 (im MSDN: GetTextExtentPoint32) Funktion kann man die Maße eines bestimmten Textes bestimmen Da die Größe des Textes von der Schriftgröße abhängt, müssen wir den Handle zu unserem Device Context als ersten Parameter übergeben, damit die Funktion sich die Maße der Schrift besorgen und so die Größe des gesamten Textes ausrechnen kann. Der zweite Parameter ist ein Zeiger auf den Text, dessen Maße bestimmt werden sollen. Im dritten Parameter muss die Länge des Stringes stehen. Mit dem vierten Parameter übergeben wir einen Zeiger auf die SIZE Struktur, in der dann die Maße des Textes gespeichert werden.

               GetTextExtentPoint32(hDC, szText, iStrLen, &size);

Wenn die Breite des Textes über den rechten Rand hinausgehen würde, machen wir einen Zeilenvorschub. Setzen also iXPos auf den linken Rand zurück und erhöhen iYPos um eine Zeile plus den Zeilenabstand.

        
               if (rect.right <= (iXPos + size.cx + iRand))
               {
                  iXPos  = iRand;
                  iYPos += size.cy + iVSpace;
               }

Haben wir alles genau berechnet, geben wir den Text mit der TextOut Funktion aus.

               TextOut(hDC, iXPos, iYPos, szText, iStrLen);

Und vor dem nächsten Zeichenvorgang setzen wir die horizontale Zeichenposition hinter den gerade gezeichneten Text.

               iXPos += size.cx + iSpace;
            }
         }
         EndPaint(hWnd, &ps);
         return 0;
      }
   case WM_DESTROY:
      {
         PostQuitMessage(0);
         return 0;
      }
   }
   return DefWindowProc(hWnd, message, wParam, lParam);
}

Das Programm gibt den ASCII Zeichensatz genau in das Fenster gepasst auf dem Bildschirm aus (Screenshot). Es zeigt außerdem, dass die Funktion TextOut nicht alle ASCII Zeichen darstellen kann, es benutzt in diesem Fall ein standard Zeichen.

Übungen

webmaster@win-api.de