Rozdział IV - Malowanie tekstu


Na tej lekcji nauczymy się, jak "maluje" się tekst na obszarze roboczym okna. Dowiemy się również o kontekście urządzenia (device context).

 

 

Kod źródłowy możesz pobrać {z tego archiwum}.

 

Teoria

W systemie Windows tekst jest pewnym typem obiektu GUI (Graphic User Inteface - Graficzny Interfejs Użytkownika). Każda litera zbudowana jest z licznych pikseli (punktów), które razem tworzą rozróżnialny kształt. Dlatego umieszczanie tekstu na ekranie nazywamy "malowaniem" zamiast "wypisywaniem". Zwykle malujesz tekst w swoim własnym obszarze roboczym (właściwie mógłbyś malować poza tym obszarem, lecz to już inna historia). Umieszczanie tekstu na ekranie w systemie Windows różni się zasadniczo od takiej samej operacji w systemie DOS. Tam ekran można było potraktować jak matrycę 80 kolumn na 25 wierszy. Lecz w systemie Windows ekran jest współdzielony przez kilka programów. Muszą być wymuszone pewne reguły dostępu do tego zasobu, aby programy nie zapisywały sobie nawzajem ekranu. Windows rozwiązuje ten problem przez ograniczenie obszaru rysunkowego każdego okna tylko do jego obszaru roboczego (client area). Rozmiar tego obszaru roboczego również nie jest stały. Użytkownik może go zmienić w każdej chwili. Należy więc dynamicznie określać rozmiary własnego obszaru roboczego.

Zanim będziesz mógł namalować coś na obszarze roboczym, musisz uzyskać na to pozwolenie od systemu Windows. Zgadza się, nie masz już całkowitej kontroli nad ekranem, tak jak w systemie DOS. Musisz prosić Windows o pozwolenia na malowanie po własnym obszarze roboczym. System Windows określi rozmiar twojego obszaru roboczego, czcionkę, kolory oraz inne atrybuty GDI (Graphic Device Interface - Interfejs Urządzenia Graficznego) i prześle uchwyt do kontekstu urządzenia z powrotem do twojego programu. Możesz następnie wykorzystać ten kontekst urządzenia jako paszport do rysowania po swoim własnym obszarze roboczym okna.

Co to jest kontekst urządzenia? Jest to po prostu pewna struktura danych zarządzana wewnętrznie przez system Windows. Kontekst urządzenia powiązany jest z określonym urządzeniem, takim jak drukarka lub ekran wideo. W przypadku tego ostatniego kontekst urządzenia związany jest z określonym oknem na ekranie.

Niektóre z wartości w kontekście urządzenia są atrybutami graficznymi, takimi jak kolory, czcionki, itp. Są to standardowe wartości, które możesz zmieniać wedle woli. Istnieją one po to, aby zmniejszyć ilość danych, które należałoby każdorazowo przesyłać do wywołań funkcji GDI.

Kontekst urządzenia możesz potraktować jak standardowe środowisko przygotowane dla ciebie przez system Windows. Później będziesz mógł zmienić niektóre standardowe wartości, jeśli będziesz sobie tak życzył.

Gdy program chce malować, musi otrzymać uchwyt kontekstu urządzenia. Zwykle można tego dokonać na kilka sposobów.

Musisz pamiętać o jednej rzeczy - po wykorzystaniu uchwytu kontekstu urządzenia musisz go zwolnić w czasie przetwarzania pojedynczej wiadomości. Nie pobieraj tego uchwytu w odpowiedzi na jedną wiadomość i zwalniaj w odpowiedzi na inną.

System Windows wysyła wiadomości WM_PAINT do okna w celu powiadomienia go, iż nadszedł czas przemalowania jego obszaru roboczego. Zawartość tego obszaru nie jest zapamiętywana. Zamiast tego, jeśli powstanie sytuacja wymagająca przemalowania obszaru roboczego (np. gdy okno było zakryte przez inne a teraz zostało odkryte), Windows umieszcza w kolejce wiadomości dla tego okna wiadomość WM_PAINT. Okno powinno się zatroszczyć o odświeżenie swojej powierzchni. Musisz zebrać wszystkie informacje o sposobie zamalowania powierzchni roboczej okna w sekcji WM_PAINT procedury twojego okna, aby procedura ta mogła przemalować obszar roboczy, gdy nadejdzie wiadomość WM_PAINT.

Następnym pojęciem, z którym powinieneś się zaznajomić, jest nieaktualny prostokąt (invalid rectangle). System Windows definiuje go jako najmniejszy prostokątny obszar w polu roboczym, który musi zostać przemalowany. Gdy Windows wykryje nieaktualny prostokąt w obszarze roboczym okna, wysyła wiadomość WM_PAINT do tego okna. W odpowiedzi na wiadomość WM_PAINT okno może otrzymać strukturę PAINTSTRUCT, która zawiera m.in. współrzędne nieaktualnego prostokąta. W odpowiedzi na wiadomość WM_PAINT wywołujesz BeginPaint, aby zatwierdzić ten prostokąt. Jeśli nie przetwarzasz wiadomości WM_PAINT, to powinieneś co najmniej wywołać DefWindowProc lub ValidateRect, aby zatwierdzić nieaktualny prostokąt, w przeciwnym razie Windows będzie cyklicznie wysyłać wiadomość WM_PAINT.

Poniżej podaję kolejne kroki, które powinieneś wykonać w odpowiedzi na wiadomość WM_PAINT:

Zauważ, iż nie musisz jawnie zatwierdzać nieaktualnego prostokąta. Jest to robione automatycznie przez wywołanie BeginPaint. Pomiędzy parą BeginPaint-EndPaint możesz wywoływać dowolne funkcje GDI, aby malować po swoim obszarze roboczym. Większość z nich wymaga jako parametru uchwytu kontekstu urządzenia.

Treść

Napiszemy program wyświetlający łańcuch tekstu "Asembler Win32 jest wspaniały i łatwy" na środku obszaru roboczego.

 

.386
.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD

INCLUDE    \masm32\include\windows.inc
INCLUDE    \masm32\include\user32.inc
INCLUDE    \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib

.DATA

ClassName DB "SimpleWinClass",0
AppName   DB "Nasze okno z tekstem",0
OurText   DB "Asembler Win32 jest wspaniały i łatwy!",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, 0
    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

Analiza

Większość kodu jest taka sama jak w rozdziale 3. Wyjaśnię tylko ważne różnice.

 

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


Są to zmienne lokalne używane przez funkcje GDI w sekcji WM_PAINT.

RECT STRUCT
    left   LONG ?
    top    LONG ?
    right  LONG ?
    bottom LONG ?
RECT ENDS


Pola left i top są współrzędnymi lewego górnego rogu prostokąta. right i bottom są współrzędnymi prawego dolnego rogu. Zapamiętaj jedną rzecz. Początek układu współrzędnych znajduje się w lewym górnym rogu obszaru roboczego, a oś y jest zwrócona w dół. Zatem punkt o współrzędnej y=10 jest PONIŻEJ punktu o współrzędnej 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


W odpowiedzi na wiadomość WM_PAINT wywołujesz BeginPaint z uchwytem okna, które chcesz malować oraz z niezainicjowaną strukturą PAINTSTRUCT jako parametrami. Jeśli wywołanie powiodło się, to rejestr eax zawiera uchwyt kontekstu urządzenia. Następnie wywołujesz GetClientRect, aby pobrać wymiary obszaru roboczego. Wymiary zostają zwrócone w zmiennej rect, którą przekazujesz do DrawText jako jeden z parametrów. Składnia DrawText jest następująca:


DrawText PROTO hdc:      HDC,\
               lpString: DWORD,\
               nCount:   DWORD,\
               lpRect:   DWORD,\
               uFormat:  DWORD
 

DrawText jest wysokopoziomową funkcją API wyprowadzania tekstu. Abyś mógł się skoncentrować na malowanym łańcuchu tekstowym, obsługuje ona niektóre niewygodne szczegóły, takie jak przenoszenie wyrazów, wyśrodkowywanie, itp. Jej niskopoziomowa siostra, TextOut, zostanie omówiona w następnej lekcji. DrawText formatuje łańcuch tekstowy, aby dopasować go do granic prostokąta. Wykorzystuje ona bieżąco wybraną czcionkę, kolor oraz tło (w kontekście urządzenia) do narysowania tekstu. Wiersze są zawijane, aby zmieścić się w tym prostokącie. Zwraca wysokość wyjściowego tekstu w jednostkach urządzenia, w naszym przypadku będą to piksele. Przyjrzyjmy się jej parametrom:

Po zakończeniu malowania na obszarze roboczym okienka musisz wywołać funkcję EndPaint, aby zwolnić uchwyt kontekstu urządzenia.

I to wszystko. Podsumujmy główne punkty tutaj:

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program TextPaint;

uses Windows;

function WndProc(h:HWND;uMsg:UINT;wP:WPARAM;lP:LPARAM) : longint;
const
  OurText = 'FreePascal jest wspaniały i łatwy';
var
  HDevCnt: HDC;
  ps     : PAINTSTRUCT;
  r      : RECT;
begin
  WndProc := 0;
  case uMsg of
    WM_DESTROY: PostQuitMessage(0);
    WM_PAINT   : begin
                   HDevCnt := BeginPaint(h,ps);
                   GetClientRect(h,r);
                   DrawText(HDevCnt,OurText,-1,r,DT_SINGLELINE or DT_CENTER or DT_VCENTER);
                   EndPaint(h,ps);
                 end;
    else  WndProc := DefWindowProc(h,uMsg,wP,lP);
  end;
end;

function WinMain(hI,hPI:HINST;CmdLine:LPSTR;cmdShow:longint) : longint;
const
  ClassName = 'SimpleWinClass';
var
  wc : WndClassEx;
  ms : msg;
  h  : HWnd;
begin
  wc.cbSize        := SizeOf(WndClassEx);
  wc.style         := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc   := @WndProc;
  wc.cbClsExtra    := 0;
  wc.cbWndExtra    := 0;
  wc.hInstance     := hI;
  wc.hbrBackground := COLOR_WINDOW + 1;
  wc.lpszMenuName  := 0;
  wc.lpszClassName := ClassName;
  wc.hIcon         := LoadIcon(0,IDI_APPLICATION);
  wc.hIconSm       := wc.hIcon;
  wc.hCursor       := LoadCursor(0,IDC_ARROW);
  RegisterClassEx(wc);
  h := CreateWindowEx(0,ClassName,'Nasze okno z tekstem',
                      WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
                      CW_USEDEFAULT,CW_USEDEFAULT,0,0,hI,0);
  ShowWindow(h,CmdShow);
  UpdateWindow(h);
  while GetMessage(ms,0,0,0) do
  begin
    TranslateMessage(ms);
    DispatchMessage(ms);
  end;
  WinMain := ms.wParam;
end;

begin 
  ExitProcess(WinMain(GetModuleHandle(0),0,0,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.