Domovská stránka Programovanie v C++ OOP a Windows Kontakt
  4. Okná vo Windows

Na prednáške:

Úvod do programovania na úrovni operačného systému

Pojmy, s ktorými sa stretneme pri práci na na úrovni operačného systému:

Kde hľadať podrobnejšie informácie:

Pomocou funkcie WinAPI zmeníme pozíciu a rozmery formulára:

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetWindowPos(                         //
volanie funkcie WinAPI
    Handle,                             //
handle (číslo) okna - formulára
    0,                                  //
tento parameter teraz nepoužívame
    0, 0, 300, 200,                     //
nová pozícia a rozmery okna
    SWP_NOZORDER
or SWP_NOACTIVATE);    // atribúty - okno nezmení svoje poradie a nebude sa meniť jeho aktívnosť
end;

Správy vo Windows

Správy slúžia na komunikáciu:

Správy sa posielajú dvoma spôsobmi:

Mechanizmus spracovania správ zakrývajú Delphi pomocou objektov, metód a udalostí.

Poslanie správy priamo

Ukážka: Priame poslanie správy formuláru, ktorou odstránime jeho nadpis (Caption):

procedure TForm1.Button2Click(Sender: TObject);
begin
  SendMessage(                          //
volanie funkcie WinAPI
    Button1.Handle,                     //
handle (číslo) okna, ktorému je správa určená
    WM_SETTEXT,                         //
číselná konštanta - druh správy
    0,                                  //
prvý parametre správy - tzv. WParam
    0);                                 //
druhý parametre správy - tzv. LParam
end;

Význam jednotlivých parametrov funkcie SendMessage je teda nasledujúci:

  SendMessage(handle_okna, typ_správy, WParam, LParam)

Každá správa má svoj význam, ktorý je daný:

V OS existuje veľa druhov správ. Tie sú číslované, avšak namiesto čísel radšej používame preddefinované a pomenované konštanty. Napríklad:

  WM_SETTEXT =     $000C
  WM_MOUSEMOVE =   $0200
  WM_LBUTTONDOWN = $0201
  WM_PAINT =       $000F

Vidíme, že správa môže mať ešte dva parametre, v ktorých sa dajú prenášať dodatočné údaje. Význam parametrov závisí od druhu správy a dá sa nájsť, napríklad v Help-e.  Parametre sú historicky označované ako WParam a LParam. V našom OS sú to 32 bitové čísla a väčšinou ich zvykneme pretypovať. Ak má správa priveľa parametrov, zvykne sa niektorý z parametrov pretypovať na smerník, ktorý ukazuje na väčší záznam, pole, blok údajov

Napríklad, parametre správy WM_SETTEXT nasledujúci význam:

  WParam = 0   ... tento parameter sa nepoužíva a musí byť nastavený na nulovú hodnotu,
  LParam = reť ... adresa nulou ukončeného reťazca (reťazca typu PChar).

Ukážka: Pomocou správy WM_SETTEXT zmeníme nadpis formulára:

procedure TForm1.Button2Click(Sender: TObject);
begin
  SendMessage(
    Button1.Handle,
    WM_SETTEXT,
    0,
    Integer(PChar('Novy nadpis')));  //
pretypovanie reťazca na typ PChar, a potom na číslo
end;

Pretypovaním reťazca na PChar získame smerník, čiže adresu, kde začína postupnosť znakov 'Novy nadpis' ukončená znakom s ASCII kódom 0 (reťazec používaný v programovacom jazyku C). Ďalším pretypovaním sa zo smerníka stane celé číslo.

Poslanie správy so zaradením do frontu

Každá aplikácia má svoj front správ. Do frontu správ sa väčšinou zaraďujú správy, ktoré vygenerovali rôzne zariadenia - pri pohybe myš, pri stlačení klávesu... Aplikácia postupne číta front správ (vyberá z neho postupne správy) a správy spracováva.

Ukážka:, Do frontu správ zaradíme správu pre zatvorenie okna:

procedure TForm1.Button2Click(Sender: TObject);
begin
  PostMessage(                          //
volanie funkcie WinAPI - správa sa zaradí do frontu správ
    Handle,                             //
handle (číslo) okna, ktorému je správa určená
    WM_CLOSE,                           //
správa
    0,                                  // WParam
    0);                                 // LParam
end;

Spracovanie správ

Správy sa spracovávajú pomocou takzvanej oknovej procedúry. Aj komponenty v Delphi, ktoré sú odvodený od triedy triedy TControl, sú pripravená na prijímanie a spracovanie správ. Trieda TControl zavádza virtuálnu metódu WndProc(var Message: TMessage). Objekt, ktorému sa správa pošle, začína spracovávanie správy v metóde WndProc. Táto metóda správu ďalej distribuuje iným metódam, čím vzniká postupnosť volaní rôznych metód, ktoré správu spracujú. Správa tak postupne "putuje" medzi rôznymi metódami a prípadne až ak udalostiam. Napríklad, dôsledkom správy WM_MOUSEMOVE býva udalosť OnMouseMove.

Delphi nám umožňujú vstupovať do procesu spracovania správ na viacerých miestach. V nasledujúcej ukážke zmeníme virtuálnu metódu WndProc:

type
  TForm1 =
class(TForm)
   
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
 
protected
   
procedure WndProc(var Message: TMessage); override;
 
end;

...
implementation
...

procedure TForm1.WndProc(var Message: TMessage);
begin
 
if Message.Msg=WM_MOUSEMOVE then
    TSmallPoint(Msg.LParam).X:=ClientWidth-TSmallPoint(Msg.LParam).X;
 
inherited;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  Canvas.Brush.Color:=clRed;
  Canvas.Ellipse(X-10, Y-10, X+10, Y+10);
end;

Štruktúra TMessage obsahuje (zjednodušene) tieto položky:

V procedúre WndProc si všímame správu WM_MOUSEMOVE. Ak táto správa príde, je v parametri lParam správy WM_MOUSEMOVE zakódovaná x-ová a y-ová pozícia myši. Preto lParam pretypujeme a pozrieme sa naň ako na hodnotu typu TSmallPoint. Tento typ je record s dvoma 16 bitovými číselnými položkami x a y. V poslanej správe tak zmodifikujeme x-ovú súradnicu.

Prvá oknová aplikácie bez použitia VCL

Programovanie vo Windows vyzerá tak, že:

Vytvoríme program, ktorý nepoužije VCL formuláre a pritom zobrazí jednoduché okno. Vytvoríme aplikáciu, odstránime z nej formulár a upravíme hlavný program tak, že:

  1. Zaregistrujeme novú oknovú triedu (tzv. "window class" - nemýliť si to s class v Delphi). Oknová trieda je akýsi vzor, predloha pre okná a obsahuje popis ich základných vlastností. Niektoré oknové triedy sú vo Windows už definované, napríklad: 'BUTTON', 'COMBOBOX', 'EDIT' a ďalšie.
  2. Po zaregistrovaní novej oknovej triedy môžeme vytvárať okná, čím získame ich handle.
  3. Spustíme mechanizmus na čítanie správ z frontu a ich spracovávanie.
  4. Ak okno nepotrebujeme, treba ho zrušiť tak, že uvoľníme jeho handle.

Ukážka - nový projekt, z ktorého odstránime formulár (projekt nemá Unit1, ale iba hlavný program - pozor, nie je to konzolová apilikácia):

program Project1;

uses
  Windows, Messages;

var
  WindowClass: TWndClass;
  MainWindow: HWND;                                        //
handle hlavného okna aplikácie
  Msg: TMsg;                                               //
správa

function WindowProc(Handle: HWND; Msg, wParam, lParam: Longint): Longint; stdcall;
begin                                                      // oknová procedúra (funkcia)
 
if (Msg=WM_DESTROY) and (Handle=MainWindow) then begin
    MainWindow:=0;
    PostQuitMessage(0);
 
end;
  Result:=DefWindowProc(Handle, Msg, wParam, lParam);      //
volanie základne oknovej procedúry
end;

begin
  FillChar(WindowClass, SizeOf(WindowClass), 0);           //
registrácia novej oknovej triedy:
  WindowClass.lpfnWndProc:=@WindowProc;                    //
adresa oknovej procedúry
  WindowClass.hInstance:=HInstance;                        //
handle aplikácie
  WindowClass.hCursor:=LoadCursor(0, IDC_ARROW);           //
nad oknom má byť kurzor - šípka
  WindowClass.hbrBackground:=GetStockObject(WHITE_BRUSH);  //
okno má mať biele pozadie
  WindowClass.lpszClassName:='TestWindow';                 //
názov oknovej triedy je 'TestWindow'
  if RegisterClass(WindowClass)=0 then Exit;               // pokus o registráciu oknovej triedy
  MainWindow:=CreateWindow(                                //
vytvorenie okna:
    'TestWindow',                                          //
okno má byť z našej novej oknovej triedy
    'Nadpis okna',                                         //
text na lište okna
    WS_OVERLAPPEDWINDOW
or WS_CLIPCHILDREN or WS_VISIBLE,  // atribúty - ďalšie vlastnosti okna
    200, 100, 400, 300,                                    //
poloha a rozmery okna
    0,                                                     //
handle rodičovského okna
    0, HInstance,
nil);                                    // ďalšie parametre
 
if MainWindow=0 then Exit;                               // test, či vytvorenie okna dopadlo úspešne
 
while GetMessage(Msg, 0, 0, 0) do begin                  // message loop - číta a spracováva správy z frontu
    TranslateMessage(Msg);                                 //
niektoré správy treba "prekladať"
    DispatchMessage(Msg);                                  //
správu môžeme distribuovať oknu
 
end;
end.

Vidíme, že oknová procedúra s značne líši od procedúry, ktorú sme programovali v formulári. Je to preto, že Delphi a objekty zakrývajú mnohé technické detaily, o ktoré sme sa nemuseli starať.

V programe:

V každej aplikácii beží špeciálny cyklus - tzv. message loop:

  while GetMessage(Msg, 0, 0, 0) do begin
   
TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;

Funkcia GetMessage:

Funkcia TranslateMessage vygeneruje z niektorých klávesnicových správ ešte iné, nové správy a zaradí ich do frontu (funkcia nemodifikuje Msg).
Funkcia DispatchMessage zavolá oknovú procedúru.

V oknovej procedúre si všímame správu WM_DESTROY, ktorá je určená hlavnému oknu (MainWindow):

V oknovej procedúre voláme funkcia operačného systému - DefWindowProc. Tá sa postará o základne spracovanie správy, prípadne neznámu správu ignoruje.

Poznámka: Vyberanie správ z frontu, v normálnom programe v Delphi, má za úlohu trieda TApplication. V hlavnom programe nájdeme príkaz, ktorý vytvorí formuláre a zavolá metódu Application.Run. V nej prebieha message loop. Ak chceme do tohto procesu rozposielania správ zasiahnuť, môžeme reagovať na udalosť Application.OnMessage, ktorá vznikne predtým, ako sa správa pošle oknu.

Tlačidlá ako okná

Pridanie tlačidiel do okna:

begin
  WindowClass
...
 
...
  if RegisterClass...
  MainWindow:=CreateWindow
...
 
if MainWindow=0 then Exit;
  CreateWindow(
    'BUTTON', 'Tlacidlo',
    WS_VISIBLE
or WS_CHILD,
    0, 0, 100, 50,
    MainWindow,
                                         // rodičovské okno
    1, HInstance,
nil);
  CreateWindow(
    'BUTTON', 'Tlacidlo 2',
    WS_VISIBLE
or WS_CHILD,
    0, 50, 100, 50,
    MainWindow,
    2, HInstance,
nil
);
 
while GetMessage(Msg, 0, 0, 0) do
 
...
end
.

Na kliknutie na tlačidlo budeme reagovať v oknovej procedúre:

function WindowProc(Handle: HWND; Msg, wParam, lParam: Longint): Longint; stdcall;
begin
 
case Msg of
    WM_DESTROY:
     
...
    WM_COMMAND:
     
if HiWord(wParam)=BN_CLICKED then
        MessageBox(
          MainWindow,
          PChar('Click '+IntToStr(LoWord(wParam))),
          'správa',
          0);
 
end;
  Result:=DefWindowProc(Handle, Msg, wParam, lParam);
end;

© 2015 Ľ. SALANCI