Iczelion - 04 - Zeichnen mit Text

Tutorial 4: Zeichnen mit Text



In diesem Tutorial werden wir lernen, wie wir Text in die Client Area des Fensters 'zeichnen'. Wir werden ebenso was über den Device Context lernen.

Sie können den Source Code hier herunterladen.

Theorie:

Text in Windows ist eine Art GUI-Objekt. Jeder Buchstabe setzt sich auf einer Anzahl von Pixeln (Punkten) zusammen, die zusammengewürfelt verschiedene Muster ergeben. Das ist auch der Grund, warum es "zeichnen" und nicht von "schreiben" heißt. In der Regel zeichnen Sie ihren Text in ihre eigene Client Area (Sie können auch außerhalb der Client Area zeichnen, aber das ist eine andere Geschichte). Text unter Windows auf den Bildschirm zu bringen unterscheidet sich drastisch von DOS. Unter DOS können Sie den Bildschirm in eine 80x25 große Dimension teilen. Aber unter Windows teilen sich mehrere Programme den Bildschirm. Einige Regeln müssen eingehalten werden, damit es vermieden wird, dass Programme nicht andere auf dem Bildschirm überschreiben. Windows gewährleistet das, indem es die Fläche zum Zeichnen bei jedem Fenster auf die eigene Client Area beschränkt. Die Größe der Client Area eines Fensters ist auch nicht konstant. Der Benutzer kann die Größe jederzeit ändern. Deshalb müssen Sie die Dimensionen ihrer Client Area dynamisch ermitteln.

Bevor Sie etwas in die Client Area zeichnen können, müssen Sie Windows um Erlaubnis bitten. Es ist richtig, Sie haben nicht mehr die absolute Kontrolle über den Bildschirm wie noch unter DOS. Sie müssen Windows um Erlaubnis bitten, ihre eigene Client Area zu zeichnen. Windows wird die Größe der Client Area, Font, Farben und andere GDI-Attribute ermitteln und sendet ein Handle des Device Context (=Gerätekontext) zurück zu ihrem Programm. Sie können dann den Device Context als einen Passierschein benutzen, um in ihre Client Area zu zeichnen.

Was ist ein Device Context? Es ist nur eine Datenstruktur die intern von Windows benutzt wird. Ein Device Context wird mit einem einzelnen Gerät (Device) verbunden, wie zum Beispiel einem Drucker oder Bildschirm. Bei einem Bildschirm wird ein Device Context normalerweise mit einem einzelnen Fenster auf dem Bildschirm verbunden.

Einige Werte im Device Context sind Grafikattribute wie Farben, Font, etc. Das sind Standardwerte, welche Sie ändern können, wenn Sie wollen. Sie existieren, um den Aufwand zu reduzieren, damit diese Attribute nicht bei jedem GDI-Funktionsaufruf spezifiziert werden müssen.

Sie können bei einem Device Context an eine Standardumgebung denken, die für Sie von Windows gemacht wurde. Sie können später einige der Standardeinstellungen überschreiben, wenn Sie wollen.

Wenn ein Programm zeichnen muss, muss es ein Handle auf den Device Context haben. Normalerweise gibt es verschiedene Wege, das zu erreichen.

rufen Sie BeginPaint als Antwort auf eine WM_PAINT Nachricht auf.

rufen Sie GetDC als Antwort auf eine andere Nachricht auf.

rufen Sie CreateDC auf, um ihren eigenen Device Context zuerstellen.

An eins müssen Sie denken, nachdem Sie mit dem Device Context Handle fertig sind, müssen Sie es während der Abarbeitung einer einzelnen Nachricht wieder freigeben. Behalten Sie nicht das Handle, in der Antwort auf eine Nachricht und geben Sie es nicht in einer Antwort auf eine andere Nachricht, wieder frei.

Windows sendet WM_PAINT Nachrichten an ein Fenster, um ihm mitzuteilen, dass es Zeit ist, die Client Area neuzuzeichnen. Windows sichert nicht den Inhalt der Client Area eines Fensters. Stattdessen, wenn eine Situation auftritt, die das Neuzeichnen der Client Area garantiert (wenn ein Fenster z.B. von einem anderen Fenster verdeckt war und jetzt wieder sichtbar ist), fügt Windows eine WM_PAINT Nachricht in die Message Queue des Fensters ein. Es ist die Verantwortlichkeit des Fensters, seine Client Area neuzuzeichnen. Sie müssen alle Informationen zusammentragen, wie Sie ihre Client Area in der WM_PAINT Sektion der Fenster-Prozedur neuzeichnen, so dass die Fenster-Prozedur die Client Area neuzeichnen kann, wenn eine WM_PAINT Nachricht ankommt.

Ein anderes Konzept, mit dem Sie sich vertraut machen müssen, ist das ungültige Rechteck. Windows definiert ein ungültiges Rechteck als das kleinste rechteckige Fläche in der Client Area, die neugezeichnet werden soll. Wenn Windows ein ungültiges Rechteck in der Client Area eines Fensters entdeckt, sendet es eine WM_PAINT Nachricht an das Fenster. Als Antwort auf die WM_PAINT Nachricht, kann das Fenster eine Paintstruct-Struktur erhalten, die, neben anderen Dingen, die Koordinaten des ungültigen Rechtecks enthält. Sie rufen als Antwort auf die WM_PAINT Nachricht BeginPaint auf, um das ungültige Rechteck wieder gültig zu machen. Wenn Sie die WM_PAINT Nachricht nicht bearbeiten, müssen Sie mindestens DefWindowProc oder ValidateRect aufrufen um das ungültige Rechteck wieder gültig zu machen, ansonsten sendet Windows ihnen immer wieder eine WM_PAINT Nachricht.

Folgende Schritte sollten Sie ausführen, um auf eine WM_PAINT Nachricht zu antworten:

Holen Sie ein Handle des Device Context mit BeginPaint.
Zeichnen Sie die Client Area.
Geben Sie das Handle des Device Context mit EndPaint wieder frei.

Beachten Sie, dass Sie nicht explizit das ungültige Rechteck für gültig erklären müssen. Das wird automatisch vom BeginPaint-Aufruf übernommen. Zwischen dem BeginPaint-Endpaint Paar können Sie jede GDI-Funktion aufrufen um in ihre Client Area zu zeichnen. Fast alle von ihnen benötigen das Handle des Device Context als Parameter.

Inhalt:

Wir werden ein Programm schreiben, dass den Text-String "Win32 Assembler ist grossartig und einfach" in der Mitte der Client Area ausgibt.

.386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 OurText db "Win32 Assembler ist grossartig und einfach!",0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ? .CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax, eax ret WndProc endp end start


Analyse:

Der größte Teil des Codes ist der selbe wie in Tutorial 3. Ich werde nur die wichtigsten Änderungen erklären.

LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT


Das sind lokale Variablen, die von GDI-Funktionen in unserer WM_PAINT Sektion benutzt werden. hdc wird benutzt, um das Handle des Device Context zu speichern, das nach dem BeginPaint-Aufruf zurückgegeben wird. ps ist eine PAINTSTRUCT Struktur. Normalerweise benutzen Sie die Werte in ps nicht. Sie wird der BeginPaint-Funktion übergeben und Windows füllt sie mit den entsprechenden Werten. Sie übergeben dann ps der EndPaint-Funktion wenn Sie mit dem Zeichnen der Client Area fertig sind. rect ist eine RECT-Struktur, die wie folgt definiert ist:

RECT Struct left LONG ? top LONG ? right LONG ? bottom LONG ? RECT ends


Left und top sind die Koordinaten der oberen linken Ecke eines Rechtecks. Right und Bottom sind die Koordinaten der unteren rechten Ecke. Nochmal zu Erinnerung: Der Ursprung der x-y-Achsen ist in der oberen linken Ecke der Client Area. Demanch ist der Punkt y=10 UNTER dem Punkt y=0.

invoke BeginPaint,hWnd, ADDR ps mov hdc,eax invoke GetClientRect,hWnd, ADDR rect invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \ DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd, ADDR ps


Als Antwort auf eine WM_PAINT-Nachricht, rufen Sie BeginPaint mit dem Handle des zu zeichnenden Fensters und einer uninitialisierten PAINTSTRUCT-Struktur als Parameter auf. Nach erfolgreichem Aufruf enthält EAX das Handle des Device Context. Als nächstes rufen Sie GetClientRect auf, um die Dimension der Client Area zu bekommen. Die Dimension wird in der rect Variable zurückgegeben, die Sie DrawText als einen Parameter übergeben. Die Syntax von DrawText ist:

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD


DrawText ist eine umfangreiche Textausgabe-API-Funktion. Sie händelt einige widerwärtige Details wie Wörterumbruch, Zentrierung etc., so dass Sie sich auf den String konzentrieren sollten, den Sie ausgeben wollen. Der weniger umfangreiche Bruder, TextOut, wird im nächsten Tutorial behandelt. DrawText formatiert einen Text-String so, dass er in die Grenzen des Rechtecks passt. Sie benutzt den gerade gewählten Font, Farbe und Hintergrund (im Device Context) um den Text auszugeben. Zeilen werden so umgebrochen, dass sie in die Grenzen des Rechtecks passen. Sie liefert die Höhe des Ausgabetextes in Geräteeinheiten, in unserem Fall Pixel, zurück. Schauen wir uns die Parameter an:

hdc  Handle des Device Context
lpString  Der Zeiger auf den String, den Sie in dem Rechteck ausgeben wollen. Der String muss nullterminiert sein, ansonsten müssten Sie im nächsten Parameter, nCount, seine Länge angeben.

nCount  Die Anzahl der Buchstaben, die ausgegeben werden sollen. Wenn der String nullterminiert ist, muss nCount -1 sein. Ansonsten muss nCount die Anzahl der Buchstaben, die Sie ausgeben wollen, enthalten.

lpRect  Der Zeiger auf das Rechteck (eine Struktur vom Typen RECT), in das Sie den String ausgeben wollen. Beachten Sie, dass Sie den String nicht außerhalb dieses Rechtecks ausgeben können.

uFormat Dieser Wert spezifiziert, wie der String in dem Rechteck ausgegeben werden soll. Wir benutzen drei Werte, kombiniert mit dem "oder"-Operator:

    • DT_SINGLELINE
    •   spezifiziert eine einzelne Zeile an Text
    • DT_CENTER
    •   zentriert den Text horizontal.
    • DT_VCENTER
    • zentriert den Text vertikal. Muss zusammen mit DT_SINGLELINE benutzt werden.
Nachdem Sie mit dem Zeichnen der Client Area fertig sind, müssen Sie die EndPaint-Funktion aufrufen, um das Handle des Device Context wieder freizugeben.

Das ist alles. Wir können die wichtigsten Punkte hier zusammenfassen:

  • Sie rufen das BeginPaint-EndPaint Paar in Antwort auf eine WM_PAINT Nachricht auf.
  • Machen Sie mit der Client Area was Sie wollen, zwischen den Aufrufen von BeginPaint und EndPaint.
Wenn Sie ihre Client Area neuzeichnen wollen, als Antwort auf eine andere Nachricht, haben Sie zwei Möglichkeiten:

    • Benutzen Sie das GetDC-ReleaseDC Paar und machen Sie ihre Zeichnungen zwischen diesen beiden Aufrufen.
    • Rufen Sie InvalidateRect oder UpdateWindow auf, um die komplette Client Area für ungültig zu erklären und Windows dazu zu zwingen, eine WM_PAINT Nachricht in die Message Queue ihres Fensters einzureihen und machen Sie ihre Zeichnungen in der WM_PAINT Sektion.

Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage