8. Okná vo Windows
Domovská stránka Programovanie v C++ OOP a Windows Ročníkový projekt Princípy počítačov Princípy databáz Všeličo Kontakt

 

Na prednáške:


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:

procedure TForm1.WMChangeSize(var Msg: TWMChangeSize);
begin
  ...
 
Broadcast(Msg);
end;

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

  1. 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;

  2. Po zaregistrovaní oknovej triedy môžeme vytvárať okná - tak získame ich handle;

  3. Pomocou oknovej procedúry spracovávame správy;

  4. 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): LongBool

TWndClass má položky:

LoadCursor(HandleAplikácie, MenoKurzora): HCURSOR

GetStockObject(ČísloObjektu): HBRUSH

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:

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:

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