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

WinApi - Lekcja 2

Bardzo waznym pojeciem jest uchwyt (handle). Jest to liczba 32-bitowa identyfikujaca w systemie Windows okreslony obiekt: okno, pedzel, czcionke itd. ZAPAMIETAJ: uchwyt przydziela system w czasie tworzenia (create) danego obiektu i nie masz na niego wplywu. Typ okreslajacy uchyt latwo rozpoznac po pierwszej literze 'H'. I tak mamy: HWND, HBRUSH, HFONT, HBITMAP itd. Jesli jakis obiekt nie ma zdefiniowanego typu uchwytu to stosujemy typ ogolny THandle. Wartosc zero nie jest nigdy przydzielana i oznacza brak uchwytu. Uchwyt wykorzystywany jest prawie przy każdej operacji. Żeby przeslac komunikat nalezy znac uchwyt okna do ktorego komunikat ma dotrzec.

Skad przychodza komunikaty?

Pod pojeciem 'meldunek' albo 'komunikat' bedziesz rozumial wywolanie przez system Windows funkcji okienkowej naszego programu - na razie poprzestanmy na jednym oknie i jednej funkcji. Sa dwa zrodla meldukow: - zdarzenia zewnetrzne z punktu widzenia programu:

 
- komunikaty klawiaturowe (uzytkownik nacisnal/zwolnil) klawisz,
- komunikaty myszy (uzytkownik wykonal jakas czynnosc mysza),
- komunikaty od zegara oznaczajace uplyw okreslonego odcinka czasu,
- komunikaty od systemu - tworzenie okna , zmiana rozmiaru i polozenia okna, zwijanie i rozwijanie okna, zmiana kolorow systemowych itp. 
- komunikaty wewnetrzne wysylane przez inne okna utworzone w naszym programie;

Glownym zrodlem sa okna potomne zwane kontrolkami:przyciski, suwadla, pola edycyjne itp.

Wyslac komunikat do okna oznacza wywolac funkcje okienkowa tego okna. Sa dwie metody:

- funkcja SendMessage - metoda 'przez poslanca';
- funkcja PostMessage - metoda 'poczta'.
 
Parametry kazdej z tych funkcji odpowiadaja dokladnie parametrom funkcji okienkowej. Pierwsza wywoluje bezposrednio funkcje okienkowa okna i zwraca to co funkcja okienkowa zwrocila jak LRESULT. Druga wstawia meldunek do kolejki komunikatow i niczego nie zwraca.

PCHar

Jest to typ wprowadzony w TP7.0 wskazujacy na znak/ciag znakow zakonczony zerem tzw. ASCIIZ. Powszechnie stosowany w Windows do przekazywania tekstow
(stringow) do i z procedur i funkcji. Wyroznia sie tym sposrod innych typow wskazujacych, ze mozna wykonywac na nim operacje matematyczne np.:


canst  p1: PChar = 'Ala ma kota';
var    p2: PChar;
begin  p2 := p1 + 7;

W powyzszym przykladzie do zmienej p2 zostala przypisana stala p1 i od tej wartosci zostalo odjete 7 poczatkowych liter. Takie dzialanie da napis "kota".

W programach pisanych w API korzystac bedziemy wlasnie z typu PChar lub talibcy znakow Char - np:

var Zmianna : array[0..255] of char;

32-bitowe Delphi laczy wygode dzialania na lancuchach (typu String) w Turbo Pascalu z wymogami Windows, ktore operuja na lancuchach ASCIIZ i wskaznikach. Dlugi typ 'String' jest w istocie czterobajtowym wskaznikiem na ciag znakow zakonczony zerem i moze w prosty sposob byc konwertowany (rzutowane) na typ PChar. Jesli uzyjesz deklaracji: s:String[n]; (n<=255) to zostanie utworzony zwykly (pascalowy) string. Natomiast deklaracja: s:String; tworzy dlugi string. System przydziela pamiec na taki lancuch w sposob dynamiczny. Dlugi String mozna traktowac jak tablice znakow z wyjatkiem elementu zerowego. Zamiast tego nalezy uzywac funkcji: Length, SetLength. Standardowy 255-znakowy lancuch mozna utworzyc deklaracja: s:ShortString lub uzyc przelacznika: {$H-}
W prostych, krotkich programach nie bedziemy stosowac dlugich Stringow.

wvsprintf

Ta funkcja na pewno kojarzy się osobą piszącym wcześniej w C. Sluzy ona bowiem do konwersji tekstu. Jej dzialanie jest podobne do dzialania funkcji Format. Oto prosty przyklad jej uzycia:

uses Windows;

var Lesson : Integer;
    Buff : array[0..255] of char; // bufor przechowujacy tekst...

begin
  Lesson := 2; // przypisz wartosc zmienenj
  wvsprintf(Buff, 'Witaj! ' + #13#13+
                  'Jest to %d lekcja kursu WinAPI. '+ #13#13+
                  'Baw się dobrze!.', @Lesson);
  MessageBox(0, Buff, 'Witaj!', MB_OK); // wyswietl wartosc zmiennej
end.

W WinAPI nie ma przydatnej funkcji konwertujacej zmienne typu funkcja IntToStr, czy StrToInt. Jest za to wvsprintf. Funkcja ma prostą budowe. Pierwszym parametrem jest bufor tekstowy, ktory przechowywac bedzie tekst, kolejny to tekst do konwersji, ktory moze zawierac tak jak w przypadku polecenia Format parametry:

- %d - zmienna Integer;
- %c - znak typu char;
- %s - string;

Spis pozostalych parametrow mozesz znalezc w pliku pomocy. Trzecim parametrem jest zmienna, ktora ma byc wstawiona w miejsce znaku %d. Zamiast pojedynczej zmiennej moze to byc rekord:

uses Windows;

var
{  rekord zawierajacy informacje o szerokosci i wysokosci ekranu }
  Pix : packed record
    CX, CY : Integer;
  end;
  Buff : array[0..255] of char; // bufor przechowujacy tekst...

begin
{  pobierz szerokosc i wysokosc ekranu oraz przypisz te dane do rekordu }
  with  Pix do
  begin
    CX := GetSystemMetrics(SM_CXSCREEN);
    CY := GetSystemMetrics(SM_CYSCREEN);
  end;
{  przeksztalc dane do postaci tablicy znakow Buff }
  wvsprintf(Buff,
 'Szerokosc ekranu: %d pikseli.'#13#10+
 'Wysokosc ekranu: %d pikseli.'#13#10, @Pix);
  MessageBox(0, Buff, 'Wymiary ekranu', 0);
end.

W tym wypadku w okienku zostanie wyświetlona informacja o rozdzielczości ekranu.


MessageBox

MessageBox jest funkcja przeznaczona do wyswietlania krotkich komunikatow. Definicja w pliku Windows.pas:

function MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT):
Integer; stdcall;

hWnd - uchwyt 'okna rodzicielskiego'; u nas nie ma wiec zero
lpText - tekst wyswietlany w oknie
lpCaption - tekst wyswietlany w pasku tytulu
uType - styl na ktory sklada sie liczba i rodzaj przyciskow oraz rodzaj
 ikony i dzwieku towarzyszacego. I tak:

MB_OK = 0;
MB_OKCANCEL = 1;
MB_ABORTRETRYIGNORE = 2;
MB_YESNOCANCEL = 3;
MB_YESNO = 4;
MB_RETRYCANCEL = 5;

MB_ICONHAND = MB_ICONERROR = MB_ICONSTOP = $10;
MB_ICONQUESTION = $20;
MB_ICONEXCLAMATION = MB_ICONWARNING = $30;
MB_ICONASTERISK = MB_ICONINFORMATION = $40;
MB_USERICON = $00000080;
MB_HELP = $4000;

Sa jeszcze inne ale niewazne.

Parametry pierwszej grupy mozna mieszac z parametrami drugiej grupy przy pomocy dodawania lub lepiej (czesciej) operatora 'OR' np:
MB_YESNO or MB_ICONQUESTION

Wartosc typu Integer zwracana przez funkcje MessageBox informuje ktory z przyciskow zostal uzyty do zamkniecia okna i moze byc:

IDOK = ID_OK = 1;
IDCANCEL = ID_CANCEL = 2;
IDABORT = ID_ABORT = 3;
IDRETRY = ID_RETRY = 4;
IDIGNORE = ID_IGNORE = 5;
IDYES = ID_YES = 6;
IDNO = ID_NO = 7;
IDCLOSE = ID_CLOSE = 8;
IDHELP = ID_HELP = 9;

Zakonczenie klawiszem Esc zwraca wynik IDCANCEL (jesli jest) lub IDOK.

Okno komunikatu wyswietlane funkcja MessageBox mozna wykorzystac w prostym programie informacyjnym.


GetSystemMetrics

Funkcja ta dostarcza rozne parametry konfiguracyjne systemu Windows. Niebawem poznamy je prawie wszystkie. Teraz uzyjemy tej funkcji aby przy pomocy MessageBox wyswietlic dwa najwazniejsze. Znaczenie poszczegolnych parametrow mozesz znalezc w pliku pomocy.

Tworzenie obiektów

Tworzenie komponentów na formularzu nie jest niczym nadzwyczajnym. Sluzy do tego funkcja CreateWindow. Tworzenie komponentow musi odbywac sie najlepiej w podczas tworzenia formularza, a wiec nalezy obsluzyc komunikat WM_CREATE. Oto jak powinno odbyć się stworzenie na formularzu przycisku:

  CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, H, 0, hInstance, nil)

Funkcja ta co prawda zawiera sporo parametrów, ale bez obaw. Oto znaczenie poszczegolnych parametrow;


lpClassName - klasa. Inaczej mowiac jaki komponent ma byc umieszczony. Mozliwe sa takie oto: Button ( przycisk ), COMBOBOX ( lista rozwijalna ), EDIT ( pole tekstowe ), LISTBOX ( lista z polami ), MDICLIENT ( okno MDI ), SCROLLBAR ( pasek sluzacy do przesuwania ), STATIC ( zwykla etykieta - Label ).
lpWindowName  - nazwa komponentu, czyli tekst, ktora ma na nim widniec.
dwStyle - styl komponentu. Obszerny spis mozesz znalezc w pliku pomocy. Powiem tylko, ze WS_CHILD ( komponent jest umieszczony na formie [ jest dzieckiem ] ), WS_VISIBLE [ komponent jest widoczny ].
X - polozenie w poziomie komponentu;
Y - polozenie w pionie;
nWidth - szerokosc komponentu;
nHeight - wysokosc komponentu
hWndParent - parametr ten okresla uchwyt okna - rodzica dla komponentu.
hMenu - identyfikator dla komponentu
hInstance - instancja aplikacji
pParam - wskaznik to tworzonego komponntu - to pole moze pozostac puste

Powyzszy kod mozna umiescic w komunikacie OnCreate - caly kod programu powinien wygladac tak:

program komponenty;

uses
  Windows,
  Messages;

const
  Caption : PChar = 'Program 2';

function Window(H : HWND; Msg: UINT; wPar : WPARAM; lPar: LPARAM):
  LRESULT; stdcall;
begin
  Result := 0;
  case Msg of
    WM_DESTROY: PostQuitMessage(0);

    WM_CREATE: //... podczsa uruchamiania programu...
    begin
    {  stworz komponent button }
      CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 100, 100, 120, 25, H, 0, hInstance, nil)
    end;

  else
    Result := DefWindowProc(H, Msg, wPar, lPar);
  end;
end;

procedure InitApp;
var
  WNDCLASS : TWndClass;
  WND : HWND;
begin
  WndClass.style := CS_CLASSDC;
  WndClass.lpfnWndProc := @Window;
  WndClass.cbClsExtra := 0;
  WndClass.cbWndExtra := 0;
  WndClass.hInstance := hInstance;
  WndClass.hIcon := LoadIcon(0, IDI_APPLICATION);
  WndClass.hCursor := LoadCursor(0, IDC_ARROW);
  WndClass.hbrBackground := COLOR_WINDOW;
  WndClass.lpszMenuName := nil;
  WndClass.lpszClassName := Caption;

  RegisterClass(WNDCLASS);

  Wnd := CreateWindow(Caption,
  Caption,
  WS_SYSMENU,
  100,
  100,
  400,
  250,
  0, 0,
  hInstance, nil);
  ShowWindow(wnd, SW_SHOW);

end;

procedure Run;
var
  Msg: TMsg;
begin
  while GetMessage(msg, 0, 0, 0) do
  begin
     TranslateMessage(msg);
     DispatchMessage(msg);
  end;
  Halt(msg.wParam);
end;

begin
  InitApp;
  Run;

end.

Co teraz gdybysmy chcieli obsluzyc zdarzenie nacisniecia na przycisk? Do tego wlasnie sluzy ten dodatkowy parametr - identyfikator. Napiszmy jakis program. Powiedzmy bedzie on sie skladal z pola tekstowego i przycisku. Po nacisnieciu przycisku w okienku wyswietli sie zawartosc pola tekstowego.

Przede wszystkim nalezy stworzyc dodatkowy komponent typu Edit.

{ ... }
    WM_CREATE: //... podczas uruchamiania programu...
    begin
    {  stworz komponent button }
      CreateWindow('BUTTON', 'Naciśnij mnie', WS_CHILD or WS_VISIBLE, 50, 100, 120, 25, H, 100, hInstance, nil);
    { stworz komponent Edit }
      Edit := CreateWindow('EDIT', 'Wpisz jakiś tekst', WS_CHILD or WS_VISIBLE or WS_BORDER, 10, 50, 220, 20,
      H, 0, hInstance, nil);
    end;
{ ... }

Jak widzisz tym razem zadeklarowalismy osobna zmienna, ktora identyfikuje komponent Edit. Zauwaz takze, ze podczas tworzenia Edit'a przypisany zostal dodatkowy parametr - WS_BORDER, ktory doda do komponentu obramowanie. Zauwaz takze, ze stworzonemu Buttonowi przypisany zostal identyfikator o numerze 100.

Trzeba jeszcze obsluzyc zdarzenie nacisniecia na komponent. Trzeba osbluzyc komunikat WM_COMMAND:

WM_COMMAND:
      case wPar of
      100:  // w przypadku nacisniecia na komponent Button...
        begin
          GetWindowText(Edit, Buff, SizeOf(Buff)); //... pobierz tekst z Edit'a i przypisz zmiennej Buff
          MessageBox(H, Buff, 'Tekst z Edit''a...', MB_OK); // wyswietl w okienku zmienna
        end;
      end;

Jak widzisz parametr wPar zawiera informacje o identyfikatorze nacisnietego klawisza. Odbywa sie to tak: podczas nacisniecia przycisku do aplikacji zostaje wyslany komunikat WM_COMMAND, ktory zawiera w polu wPar identyfikator nacisnietego klawisza ( o szczegolach mozesz poczytac nizej ). Tak wiec my obslugujemy komunikat WM_COMMAND oraz zdarzenie w przypadku nacisniecia na klawisz, czyli inaczej mowiac zdarzenia 100. No i co ma robic program po nacisnieciu przycisku? Otoz pobiera ona tekst z pola tekstowego Edit. Pobieranie tekstu z kontrolki odbywa sie za pomoca polecenia GetWindowText. Pierwsze polecenia okresla kontrolke, z ktorej pobrany bedzie tekst, kolejny to bufor, w ktorym tekst bedzie umieszczony, no i ostatni okresla wiekosc bufora. Na koniec caly bufor zostaje wyswietlony w okienku.

Mala powtorka

Funkcja okienkowa ma cztery parametry:


hOkno - uchwyt okna, do ktorego komunikat jest skierowany,
uKomunikat - kod meldunku: WM_CosTam,
wParametr, lParametr - wykorzystywane w rozny sposob w roznych meldunkch,
 czesto nie wykorzystane i rowne zero, czasem przekazujace dwie
 rozne liczby jako mlodsze i starsze slowo. Bardzo czesto lParametr
 jest adresem/wskaznikiem i wymaga konwersji typu (!!!).

W funkcji okienkowej zwykle stosuje sie instrukcje 'case' w celu rozgalezienia zaleznie od kodu komunikatu. Komunikaty, ktorych funkcja okienkowa nie obsluguje
musza byc skierowane do funkcji domyslnej DefWindowProc (Def - default).

  Result := 0;
  case uKomunikat of
    WM_CREATE: {obsluga komunikatu wm_Creatte};
    WM_SIZE: {obsluga komunikatu wm_Size};
    WM_PAINT: {obsluga komunikatu wm_Paint};
    WM_COMMAND: {osluga komunikatu wm_Command - zwykle ma taka postac}
      case LoWord(wParametr) of
 ID_1: {obsluga komunikatu od kontrolki 1};
 ID_2: {obsluga komunikatu od kontrolki 2};
        . . . .
      end;
    . . . .
    WM_DESTROY: {obsluga komunikatu wm_Destroy (obowiazkowa)};
  else Result:=DefWindowProc(hOkno, uKomunikat, wParametr, lParametr);
  end;


Komunikaty klawiaturowe:

Na poczatek wystarczy nam trzy komunikaty klawiaturowe:
WM_KEYDOWN, WM_KEYUP:
 wParametr zawiera kod tzw. klawisza wirtualnego,
 lParametr dodatkowe dane jak licznik powtorzen, scan-code itp
   raczej nie uzywany w praktyce.
WM_CHAR: meldunek wyprodukowany przez funkcje TranslateMessage
 wParametr zawiera kod ASCII klawisza
 lParametr - jak poprzednio.

Sa jeszcze meldunki:
WM_SYSKEYDOWN, WM_SYSKEYUP - kombinacja z klawiszem Alt,
WM_SYSCHAR, WM_DEADCHAR, WM_SYSDEADCHAR - poprzedzajacy znak
 diakrytyczny na niektorych klawiaturach narodowych.

Przyklad: nacisniecie i zwolnienie klawisza 'A' wygeneruje meldunki:
WM_KEYDOWN wParametr = $41 (wirtualny kod klawisza A)
WM_CHAR  wParametr = $61 - kod ASCII znaku 'a'
WM_KEYUP wParametr = $41

Klawiatura jest jedna a okien moze byc duzo. Wiec ktore okno otrzyma komunikat klawiaturowy? To, ktore ma fokus, albo mowiac inaczej, to, ktore jest 'w ognisku'. Tylko jedno okno w systemie moze miec fokus. Jest to okno aktywne lub jego potomek.

Komunikaty myszy:

WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_RBUTTONDOWN, WM_RBUTTONUP,
WM_MBUTTONDOWN, WM_MBUTTONUP: wysylane w momencie naciskania i zwalniania przyciskow myszy.
WM_MOUSEMOVE: wysylany gdy kursor myszy zmieni polozenie w oknie.
 wParametr informyje o jednoczesnie wcisnietych klawiszach i przyciskach
 poszczegolne bity 0..4 oznaczaja:
 MK_LBUTTON = 1;
 MK_RBUTTON = 2;
 MK_SHIFT = 4;
 MK_CONTROL = 8;
 MK_MBUTTON = $10;

Oczywiscie fukcja okienkowa otrzymuje komunikaty myszy tylko wtedy, gdy kursor myszy znajduje sie w obszarze roboczym (client) okna.
Wszystkie komunikaty myszy przekazuja przy pomocy lParametr wspolrzedne kursora myszy, pozioma X w mlodszym slowie, pinowa Y w starszym slowie. Wspolrzedne liczone sa wzgledem lewego gornego rogu pola roboczego okna.


Komunikaty od zegara (timera)

To wlasciwie tylko jeden komunikat WM_TIMER. Ale zeby system wysylal ten komunikat do naszego  okna nalezy funkcja SetTimer utworzyc timer. Ma ona cztery parametry:

hOkno - uchwyt okna, ktore bedzie otrzymywac meldunki WM_TIMER,
nID - numer identyfujacy timera (bo mozna utworzyc kilka),
uMiliSecs - przedzial czasy w milisekundach co jaki beda naplywaly meldunki.
lpTimerFunc - NIL lub adres innej funkcji, ktora bedzie wywolywana
 zamiast glownej funkcji okienkowej w celu obslugi WM_TIMER.

wParametr komunikatu WM_TIMER zawiera numer identyfikacyjny timera, ktory
wygenerowal meldunek.

Na zakonczenie programu lub gdy juz nie jest potrzebny, timer trzeba usunac funkcja: KillTimer(hOkno, nID);


Wazniejsze komunikaty od systemu

WM_CREATE: pierwszy komunikat, jaki otrzymuje nowo utworzone okno
 gdy jeszcze nie jest widoczne na ekranie. Dobre miejsce aby
 na umieszczenie czynnosci inicjujacych, wykonywanych tylko raz.
WM_SIZE: gdy okno zmieni rozmiar poziomy lub pionowy. Pierwszy komunikat
 pojawia sie zanim okno zostanie narysowane. lParametr zawiera
 nowe wymiary okna - w mlodszym slowie szerokosc, ww starszym wysokosc.
WM_MOVE: gdy okno zmieni polozenie na ekranie. lParametr zawiera wspolrzedne
 ekranowe lewego gornego rogu czesci roboczej okna.
WM_PAINT: gdy system oczekuje odmalowania zawartosci pola roboczego okna.
 Obsluga tego meldunku zajmiemy sie w nastepnym odcinku. Teraz ze
 wzgledu na to, ze nie umiemy umiescic niczego w polu roboczym okna
 uzywamy wylacznie paska tytulu do wyswietlania tekstow funkcja
 SetWindowTekst. Podobna funkcja GetWindowText mozna pobrac tekst
 z paska tytulu okna.