|
8. Okná vo Windows |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Na prednáške:
dokončenie o správach - používateľom definované správy;
práca s oknami vo Windows bez použitia VCL;
spracovanie správ v cykle message loop.
Používateľom definované správy
Môžeme používať vlastné správy z rozsahu $0400 ... $7FFF. Väčšinou sa odvolávame na konštantu WM_USER=$0400;
const WM_CHANGESIZE=WM_USER+1; type TWMChangeSize=packed record Msg: Cardinal; M: Longint; D: Longint; Result: Longint; end; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; Label1: TLabel; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); protected procedure WMChangeSize(var Msg: TWMChangeSize); message WM_CHANGESIZE; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin Perform(WM_CHANGESIZE, 2, 1); end; procedure TForm1.Button2Click(Sender: TObject); begin Perform(WM_CHANGESIZE, 1, 2); end; procedure TForm1.WMChangeSize(var Msg: TWMChangeSize); var Index: Integer; begin SetBounds( Left, Top, MulDiv(ClientWidth, Msg.M, Msg.D)+Width-ClientWidth, MulDiv(ClientHeight, Msg.M, Msg.D)+Height-ClientHeight); for Index:=0 to ControlCount-1 do Controls[Index].Perform(WM_CHANGESIZE, Msg.M, Msg.D); end; |
Namiesto cyklu, ktorým rozposielame správy komponentom, ktoré ležia vo formulári, môžeme použiť metódu Broadcast:
TForm1.WMChangeSize(var Msg: TWMChangeSize);procedure
Ako by mal komponent spracovať správu WM_CHANGESIZE:
type TMyLabel=class(TLabel) protected procedure WMChangeSize(var Msg: TWMChangeSize); message WM_CHANGESIZE; end; procedure TMyLabel.WMChangeSize(var Msg: TWMChangeSize); begin SetBounds( MulDiv(Left, Msg.M, Msg.D), MulDiv(Top, Msg.M, Msg.D), Width, Height); Font.Size:=MulDiv(Font.Size, Msg.M, Msg.D); Msg.Result:=1; end; |
Komponenty, ktoré správu nevedia spracovať, vrátia ako výsledok správy hodnotu 0. Takéto komponenty dokážeme rozlíšiť a môžeme ich rozmery upraviť:
procedure TForm1.WMChangeSize(var Msg: TWMChangeSize); var Control: TControl; Index: Integer; begin SetBounds( Left, Top, MulDiv(ClientWidth, Msg.M, Msg.D)+Width-ClientWidth, MulDiv(ClientHeight, Msg.M, Msg.D)+Height-ClientHeight); Font.Height:=MulDiv(Font.Height, Msg.M, Msg.D); for Index:=0 to ControlCount-1 do begin Control:=Controls[Index]; if Control.Perform(WM_CHANGESIZE, Msg.M, Msg.D)=0 then Control.SetBounds( MulDiv(Control.Left, Msg.M, Msg.D), MulDiv(Control.Top, Msg.M, Msg.D), MulDiv(Control.Width, Msg.M, Msg.D), MulDiv(Control.Height, Msg.M, Msg.D)); end; end; |
Okná vo Windows
Vo Windows sú objekty identifikované nejakým číslom - hande (32 bitové číslo). Ak vytvoríme okno, windows nám vrátia jeho handle. Do funkcií, ktoré s oknom pracujú, potom posielame handle okna. Podobne, ak potrebujeme kresliť do grafickej plochy, musíme si od Windows vypýtať jej handle. Rovnaké je to aj s inými objetami - perami (pen) výplňami (brush), bitmapami atď.
Vytvorenie okna
Zaregistrujeme oknovú triedu (tzv. "window class" - nemýliť si to s class v Delphi, je to úplne iná vec). Oknová trieda je akýsi vzor/predloha pre okná, ktoré v aplikácii vytvoríme a obsahuje popis ich základných vlastností. Napríklad, okná z jednej oknovej triedy dostávajú správy do spoločnej oknovej procedúry. Adresa tejto procedúry je uvedená v popise triedy;
Po zaregistrovaní oknovej triedy môžeme vytvárať okná - tak získame ich handle;
Pomocou oknovej procedúry spracovávame správy;
Ak okno nepotrebujeme, treba ho zrušit (uvoľniť jeho handle).
Ukážka (projekt nemá Unit1, ale iba hlavný program):
program
Project1; uses Windows, Messages, SysUtils; const WindowClassName='TestWindow'; var MainWindow: HWND; function WindowProc(Handle: HWND; Msg, wParam, lParam: Longint): Longint; stdcall; begin if (Handle=MainWindow) and (Msg=WM_DESTROY) then begin MainWindow:=0; PostQuitMessage(0); end; Result:=DefWindowProc(Handle, Msg, wParam, lParam); end; function Create( Parent: HWND; X, Y, W, H: Integer; Caption: string; Style: DWord): HWND; var WindowClass: TWndClass; IsRegistered: Boolean; begin IsRegistered:=GetClassInfo(HInstance, WindowClassName, WindowClass); if not IsRegistered or (WindowClass.lpfnWndProc<>@WindowProc) then begin if IsRegistered then UnregisterClass(WindowClassName, WindowClass.HInstance); WindowClass.style:=CS_DBLCLKS; WindowClass.lpfnWndProc:=@WindowProc; WindowClass.cbClsExtra:=0; WindowClass.cbWndExtra:=0; WindowClass.hInstance:=HInstance; WindowClass.hIcon:=0; WindowClass.hCursor:=LoadCursor(0, IDC_ARROW); WindowClass.hbrBackground:=GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName:=nil; WindowClass.lpszClassName:=WindowClassName; if RegisterClass(WindowClass)=0 then begin Result:=0; Exit; end; end; Result:=CreateWindow( WindowClassName, PChar(Caption), Style, X, Y, W, H, Parent, 0, HInstance, nil); end; var Msg: TMsg; begin MainWindow:=Create( 0, 200, 100, 320, 240, 'Nadpis okna', WS_OVERLAPPEDWINDOW or WS_VISIBLE); if MainWindow=0 then Exit; while GetMessage(Msg, 0, 0, 0) do begin TranslateMessage(Msg); DispatchMessage(Msg); end; end. |
V programe:
MainWindow //
handle hlavného okna aplikácie.
PostQuitMessage(0) //
pošle správu WM_QUIT,
0 je výsledok vykonávania programu.
GetClassInfo
(HInstance, WindowClassName, WindowClass): LongBooltouto funkciou otestujeme, či oknová TestWindow trieda existuje, ak áno, potom skontrolujeme, či je to oknová trieda, ktorú sme už raz sami zaregistrovali;
HInstance // je handle aplikácie - premenná je zadeklarovaná v systémovej knižnici a Delphi je nastavia pri spustení aplikácie;
WindowClassName // je typu PChar - smerník na reťazec znakov ukončených nulou;
WindowClass // je záznam typu TWndClass.
TWndClass
má položky:style: Cardinal; // štýl, môže sa kombinovať: CS_DBLCLKS or CS_NOCLOSE;
lpfnWndProc: TFNWndProc; // adresa oknovej procedúry;
cbClsExtra: Integer;
cbWndExtra: Integer;
hInstance: HINST; // handle aplikácie, ktorá štýl registrovala;
hIcon: HICON; // handle ikony okna a aplikácie;
hCursor: HCURSOR; // handle kurzora;
hbrBackground: HBRUSH; // handle pre výplne pre pozadie okna;
lpszMenuName: PChar; // smerník na názov ponuky, ktorá je uložená v resource;
lpszClassName: PChar; // smerník na názov triedy.
LoadCursor(HandleAplikácie, MenoKurzora): HCURSOR
ak treba, nahrá kurzor z resource a vráti jeho handle;
ak HandleAplikácie =
HInstance, nahrá sa kurzor z resourcov aplikácie:
LoadCursor(HInstance, 'SPECIALNASIPKA');
ak HandleInstanice=0, MenoKurzora môže byť iba štandardný kurzor Windows (šípka, hodinky a pod.).
GetStockObject
(ČísloObjektu): HBRUSHvráti handle preddefinovaného objektu Windows;
pre GetStockObject(BLACK_BRUSH) funkcia vráti handle čiernej výplne.
Ak chceme používať špeciálne, nie preddefinované farby,
musíme si vytvoriť vlastnú výplň tak, že zavoláme funkciu
CreateSolidBrush(ČísloFarby):
HBRUSH. Potom je postup nasledujúci:
...
WindowClass.hbrBackground:=CreateSolidBrush(ColorToRGB(clBtnFace));
...
if RegisterClass(WindowClass)=0
then begin
DeleteObject(WindowClass.hbrBackground);
//
DeleteObject ... triedu sa nepodarilo
zaregistrovať, výplň musíme zrušiť
Result:=0;
Exit;
end;
...
CreateWindow(
lpClassName: PChar; // názov
okennej triedy
lpWindowName: PChar; //
nadpis okna
dwStyle: DWORD;
// štýl okna
X, Y, nWidth, nHeight: Integer;
hWndParent: HWND; //
rodič okna
hMenu: HMENU;
// handle menu okna (ak = 0, použije sa menu
z oknovej triedy)
// alebo identifikátor prvku, ktorý leží v
okne
hInstance: HINST;
// handle aplikácie
lpParam: Pointer
): HWND;
Vo Windows sú aj: menu, tlačidlá, posuvné lišty a ďalšie známe prvky, tiež okná (aj keď trochu špecializované), takže neskôr ich budeme pomocou tejto funkcie vytvárať.
Spracovanie správ
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 vyberieme správu z frontu:
kým je front aplikácie prázdny, funkcia čaká;
inak vyberie z frontu správu a naplní ňou premennú Msg;
funkcia vráti hodnotu false, ak z frontu vybrala správu WM_QUIT, inak vracia hodnotu true.
TranslateMessage vygeneruje
z niektorých klávesnicových správ ešte iné, nové správy a zaradí ich do frontu
(funkcia nemodifikuje Msg).
DispatchMessage
zavolá oknovú procedúru.
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.
Oknová procedúra
V našom príklade reagujeme na správu WM_DESTROY, ktorá je určená hlavnému oknu:
táto správa sa posiela predtým, ako sa okno zruší (ako zaniká handle okna);
vtedy zavoláme funkciu PostQuitMessage, ktorá vloží správu WM_QUIT do frontu (jej vybratím ukončíme message loop);
okrem toho si poznačíme, že handle okna už nemáme - MainWindow:=0.
Pridanie tlačidiel do okna:
begin MainWindow:=Create( 0, 200, 100, 320, 240, 'Main Window', WS_OVERLAPPEDWINDOW or WS_VISIBLE); if MainWindow=0 then Exit; CreateWindow( 'BUTTON', 'Tlacidlo', WS_VISIBLE or WS_CHILD, 0, 0, 100, 50, MainWindow, 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 begin TranslateMessage(Msg); DispatchMessage(Msg); end; end. |
Ako reagovať na kliknutie tlačidla:
function
WindowProc(Handle: HWND; Msg, wParam, lParam: Longint): Longint; stdcall; begin case Msg of WM_DESTROY: if Handle=MainWindow then begin MainWindow:=0; PostQuitMessage(0); end; WM_COMMAND: if wParam shr 16=BN_CLICKED then MessageBox( 0, PChar('Click '+IntToStr(wParam and $FFFF)), 'správa', 0); end; Result:=DefWindowProc(Handle, Msg, wParam, lParam); end; |
© 2003 Ľubomír SALANCI