Na prednáške:
- údov do WinAPI,
- správy v operačnom systéme,
- práca s oknami na nízkej úrovni (bez použitia VCL).
Ú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:
- okno sa v OS Windows chápe pomerne všeobecne:
- okno je každý vizuálny prvok - formulár, ale aj tlačidlo alebo posuvná
lišta,
- okno môže byť obdĺžnik, výrez na obrazovke bez rámiku,
- okno môže byť aj neviditeľné (nemusí sa zobrazovať na obrazovke),
- handle je číselný identifikátor objektu (okna,
grafickej plochy, pamäťového priestoru, súboru, procesu...):
- operačný systém skrýva svoje vnútorné údajové štruktúry a sprístupňuje
ich iba pomocou tzv. handlov,
- handle je celé číslo (napríklad 32 bitové číslo),
- s handlami sa veľmi často pracuje, napríklad:
- ak chceme zmeniť pozíciu okna, musíme poznať handle okna a volať funkciu
SetWindowPos(Handle, ...),
- ak potrebujeme kresliť do grafickej plochy, musíme získať jej handle,
- Delphi nám niektoré handle sprístupňuje aj na úrovni programovacieho jazyka,
- WinAPI = Windows
Application Programming
Interfaces - funkcie pre prácu s pamäťou, súbormi, procesmi a ďalšími
prostriedkami.
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:
- medzi oknami navzájom (okno pošle správu inému oknu),
- medzi oknami a ovládačmi (ovládač pošle správu oknu o pohybe myši),
- medzi oknami a operačným systémom (operačným systémom pošle správu oknu,
aby sa okno premiestnilo).
Správy sa posielajú dvoma spôsobmi:
- buď priamo
oknu pomocou funkcie SendMessage a
takéto správy sa obslúžia okamžite,
- alebo sa radia do frontu správ pomocou funkcia
PostMessage, kde čakajú na spracovanie.
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ý:
- číslom správy,
- parametrami správy.
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:
- Msg: Cardinal; ... číslo,
druh správy
- WParam: Longint; ... hodnota parametra
WParam
- LParam: Longint; ... hodnota parametra
LParam
- Result: Longint
... výsledok spracovania správy (niektoré správy očakávajú výsledok).
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:
- napĺňame údajové štruktúry,
- voláme funkcie,
- spracovávame a posielame správy,
- pri tom musíme správne pracovať s handlami.
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:
- 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.
- Po zaregistrovaní novej oknovej triedy môžeme vytvárať okná, čím získame
ich handle.
- Spustíme mechanizmus na čítanie správ z frontu a ich spracovávanie.
- 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:
- MainWindow
... handle hlavného okna aplikácie,
- PostQuitMessage(0)
... vygeneruje a pošle správu
WM_QUIT,
ktorá ukončí cyklus spracovania správ (0
je výsledok vykonávania programu),
- HInstance
... je handle aplikácie - premenná je zadeklarovaná
v systémovej knižnici a Delphi je nastavia pri spustení aplikácie
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:
- kým je front aplikácie prázdny, funkcia čaká,
- ak front obsahuje správu, funkcia vyberie správu z frontu a naplní ňou premennú
Msg (podobná, aj keď trochu zložitejšia údajová štruktúra, ako
TMessage),
- výsledkom funkcie je hodnota false,
ak sa frontu vybrala správu WM_QUIT,
inak funkcia vracia hodnotu true.
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):
- táto správa sa posiela predtým, ako sa okno zruší handle okna,
- funkcia PostQuitMessage vloží do
frontu správu WM_QUIT (jej vybratím
sa ukončí message loop),
- keďže handle okna sa ide rušiť a bude neplatné, priradíme
MainWindow:=0.
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