Piątek 11 Kwiecień 2025r. Godz 00:00:00      
Postów: 251      

WinApi - Lekcja 3

W tym rozdziale omowimy proces rysowania po formularzu, czyli grafika w WinAPI. Przede wszystkim jezeli chcesz, aby ikona EXE miala jakis porzadny wyglad musisz w edytorze graficznym dostepnym wraz z Delphi stworzyc ikone o nazwie MAINICON i w programie dodac zasob za pomoca dyrektywy:

{$R NAZWAPLIKUZASOBU.RES}

Od tej pory nadasz plikowi EXE jakis wyglad...


Cos napisac, narysowac....

Zasada pracy Windows jest taka, ze wszelkie dzialania wizualne (pisanie, rysowanie) powinno odbywac sie na zyczenie systemu podczas obslugi komunikatu  WM_PAINT. Dlaczego? Zawartosc ekranu programu pracujacego pod DOS-em nie zmieni sie "sama", bez dzialania programu. A na zawartosc okna w systemie Windows czyhaja rozne kataklizmy. Okno moglo zmienic rozmiary, zostac przysloniete przez inne okno, zostac przesuniete czesciowo poza ekran,
zostac zminimalizowane do paska zadan itp. Co wtedy z zawartoscia okna? System niestety nie przechowuje zawartosci okna w pamieci bo ile trzeba by tej pamieci
dla wielu niejednokrotnie okien nalozonych na siebie lub zwinietych. Jedyne co system robi to informuje okno poprzez wysylanie komunikatu WM_PAINT gdy trzeba odmalowac zawartosc obszaru robczego lub jego czesci. Wyjatkiem jest rozwijanie menu, kiedy to Windows przechowuje obrazek zaslanianej czesci okna i odtwarza ja bez wysylania komunikatu WM_PAINT.

KONTEKST urzadzenia graficznego

Wszystkie funkcje graficzne (pisanie tekstu to tez grafika) jako pierwszy parametr wymagaja uchwytu kontekstu urzadzenia graficznego (Device Context). Czym jest ten kontekst? Jest to pewna struktura danych opisujaca rozne parametry rysowania, malowania i pisania np. kolor linii, tekstu, tla, grubosc linii, ksztalt i wymiary czcionki itd. itd. czyli cos takiego o czym wiedza znawcy grafiki BGI tylko wiele wiecej. Nie moga byc to dane globalne systemu, bo w systemie pracuje wiele programow (system wielozadaniowy) i ustawienia jednego nie moga zaklocac pracy innego. Nie jest to tez po prostu uchwyt okna, bo okno ma wiele elementow (ramke, pasek tytulu) i czynione zmiany w kontekscie dotycza tylko czesci roboczej okna.

Uchwyt kontekstu jest formalnie typu HDC i moze byc pobrany (nie utworzony) funkcja GetDC np: dcHandle:=GetDC(hOkno); UWAGA: uchwyt trzeba zwolnic
funkcja ReleaseDC zanim zakonczy sie wykonywanie funkcji okienkowej.

Korzystanie z funkcji GDI wyglada mniej wiecej tak:

  dc := GetDC(hOkno);
  { wywolanie funkcji GDI }
  ReleaseDC(hOkno, dc);

Jesli pobierzemy uchwyt kontekstu funkcja: GetWindowDC (zamiast GetDC) to bedziemy mogli rysowac po calym oknie nie tylko w polu roboczym. Dla urzadzen graficznych innych niz okno na ekranie np. dla drukrki nalezy utworzyc kontekst funkcja: CreateDC i po wykorzystaniu usunac go funkcja DeleteDC - ale o tym opowiem znacznie dalej.

Obsluga WM_PAINT

Jest nieco odmienna od opisanej wyzej. Nalezy utworzyc (zadeklarowac) rekord typu TPaintStruct: 

var dc: HDC; ps: TPaintStruct;

i podczas obslugi WM_PAINT wykonac:

  dc := BeginPaint(hOkno, ps);
  { inne linie programu }
  EndPaint(hOkno, ps);

Poprzez rekord typu TPaintStruct system przekazuje pewne informacje, ktore mozna wykorzystac podczas obslugi WM_PAINT; sa tam m.in. wspolrzedne prostokata wymagajacego odmalowania bo czesto nie cale okno trzeba odswiezyc.

BeginPaint oprocz tego, ze dostarcza uchwyt kontekstu (jak GetDC) dodatkowo:

 - czysci obszar roboczy okna czyli zamalowuje go pedzlem podanym jako parametr hbrBackground rekordu TWndClass,
 - informuje system, ze cale pole robocze okna ma juz wlasciwa zawartosc.

Meldunek WM_PAINT ma niski priorytet i jesli system jest czyms zajety zwleka z odmalowaniem okna. Jesli w tym czasie zajdzie jakies inne zdarzenie wymagajace odmalowania okna to system nie umieszcza w kolejce komunikatow nowego WM_PAINT a tylko ewentualnie powieksza wymiary prostokata wymagajacego odmalowania (w strukturze TPaintStruct). Gdybysmy zapomnieli o BeginPaint to system natychmiast wygeneruje nastepny WM_PAINT bo nie bedzie wiedzial, ze
zawartosc okna jest juz wlasciwa. EndPaint zwalnia uchwyt urzadzenia. Jesli nie obslugujemy WM_PAINT to, jak wiadomo, trzeba skierowac go do DefWindowProc, ktora wykonuje tylko BeginPaint i EndPaint. Pierwszy meldunek WM_PAINNT zostaje doreczony (przez poslanca a nie poczta) funkcji okienkowej przez wywolanie UpdateWindow zaraz po utworzeniu okna.

InvalidateRect

Nawet jesli nie jestes dociekliwy ta informacja jest Ci potrzebna. Jesli cos jest rysowane lub pisane na zyczenie uzytkownika to program musi byc przygotowany na powtorzenie tej operacji na zyczenie systemu czyli po otrzymaniu komunikatu WM_PAINT. Praktycznie robi sie to tak, ze zapamietuje sie tylko informacje o tym,
ze dany napis (obrazek) ma sie pojawic i 'zmusza' system do wyslania WM_PAINT wlasnie funkcja InvalidateRect - uniewaznij prostokat. Niewazny znaczy wymagajacy odmalowania. Jesli jako drugi parametr podamy NIL to zostanie uniewaznione cale pole robocze okna. Jest to sposob najprostszy ale niezbyt efektywny wiec stosowany przez poczatkujacych programistow.


Podsumowujac prezentujemy kod, ktory laduje bitmape z asobow i oczywiscie wyswietla ja:

var
  PS : TPaintStruct;
  DC : HDC;
  Bitmap : HBITMAP;
  _Bitmap : HDC;
begin
  DC := BeginPaint(Handle, PS);

  Bitmap := LoadBitmap(hInstance, 'NAZWABITMAPY');
  _Bitmap := CreateCompatibleDC(DC);
  SelectObject(_Bitmap, Bitmap);
  BitBlt(dc, 10, 10, 300, 300, _Bitmap, 0, 0, SRCCOPY);
  DeleteDC(_Bitmap);

  EndPaint(Handle, PS);
end;

Do wyswietlenia obrazku na formularzu sluzy polecenie BitBlt. Parametry tam podawane to oczywiscie rozmiary bitmapy.

Podobnie ma sie sprawa z rysowaniem. W API rysowanie po formularzu jest podobne jak w przypadku oferowanej przez Delphi klasy TCanvas. 

 { .. }  
   WM_PAINT:
      begin
        DC := BeginPaint(H, PS);

        TextOut(DC, 10, 10, 'www.jakisadres.pl', Length('www.jakisadres.pl'));
        EndPaint(H, PS);
      end;
  { ... }

Jako zmienna musisz zadeklarowac:

var
  PS : TPaintStruct;
  DC : HDC;

Teraz jednak nie wyglada to najlepiej. Nalezy dodac jakies tlo - najlepiej zmienic je na przezroczyste:

      SetBkMode(DC, TRANSPARENT);   // ustaw tlo na przezroczyste

Mozna takze ustawic kolor dla czcionki:

      SetTextColor(DC, RGB(0, 100, 150));   // ustaw kolor czcionki

Pierwszym parametrem jest oczywiscie kontekst, a kolejny to kolor czcionki zmieszany z kolorow czerownych, zielonych oraz niebieski.

Inne funkcje zwiazane z grafika mozesz znalezc w plikach pomocy.