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.
OwnDrawText
zur Verfügung stellt. Die
Funktion soll folgende Parameter haben:
HDC hDC
const char* szText
RECT* rect
unsigned int iFlags
OTD_ALIGNLEFT
OTD_CENTER
OTD_ALIGNRIGHT
\n
Zeichen soll als Zeilenvorschub berücksichtig werden. Die Funktion
soll hinter einer Schnittstelle versteckt werden. Schreibe dazu ein Windows Programm,
welches die Funktion testet.
DrawTextf
. Die Funktion soll
die Vorzüge des printf
mit den Vorzügen der DrawText
Funktion verbinden. Die Funktion muss also eine Variable Parameterliste haben, wie
printf
.