7. Správy 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:

Odporúčam pozerať sa do win32.hlp alebo navštíviť http://msdn.microsoft.com/library.

Vo Windows sú programy riadené udalosťami (pohyb myši, zmena veľkosti okna a pod.). Po vzniku udalosti sa vygeneruje správa pre určené okno a táto správa sa buď posiela priamo oknu alebo sa zaradí do frontu. Správa vo fronte má nasledujúce, pre nás zaujímavé, položky:
  Handle: HWND;       // číslo okna
  Message: Cardinal;  // číslo správy - určuje jej význam
  WParam: Longint;    // parameter W
  LParam: Longint;    // parameter L

Každá správa má svoj význam a podľa toho aj nastavené jednotlivé položky WParam a LParam:

Ako prebieha posielanie správ:

procedure TForm1.ZatvorOknoAClick(Sender: TObject);
begin
 
SendMessage(Handle, WM_CLOSE, 0, 0);
end;

procedure TForm1.ZatvorOknoBClick(Sender: TObject);
begin
 
Perform(WM_CLOSE, 0, 0);
end;

procedure TForm1.ZatvorOknoCClick(Sender: TObject);
begin
 
PostMessage(Handle, WM_CLOSE, 0, 0);
end;

Správy, ktoré boli zaradené do frontu, sa spracovávajú tak, že aplikácia kontroluje stav svojho frontu a postupne z neho vyberá jednotlivé správy:

Potom, ako sa správa z frontu vybrala, distribuuje sa oknu (čo môže byť formulár, tlačidlo alebo komponent odvodený od TWinControl), ktorému je správa určená tak, že pre príslušný objekt sa zavolá metóda:

  procedure TWinControl.MainWndProc(var Msg: TMessage);

TMessage je  nasledujúci typ:

  TMessage = packed record
  
  Msg: Cardinal; //
číslo správy
    case Integer of
     
0: (
        WParam: Longint; //
parameter
        LParam: Longint; // parameter
        Result: Longint); // výsledok
      1: (
        WParamLo: Word;
        WParamHi: Word;
        LParamLo: Word;
        LParamHi: Word;
        ResultLo: Word;
        ResultHi: Word);
  end;

Metóda MainWndProc zavolá WindowProc:

Pre rôzne správy sa zvykne parameter Msg pretypovať. Napríklad, pri pohybe myši:
  TWMMouse = packed record
   
Msg: Cardinal;
    Keys: Longint;        //
zodpovedá časti WParam
    case Integer of
     
0: (
        XPos: Smallint;   //
zodpovedá časti LParamLo
        YPos: Smallint);  //
zodpovedá časti LParamHi
      1: (
        Pos: TSmallPoint; //
zodpovedá časti LParam
        Result: Longint);
  end;

V nasledujúcom príklade vytvoríme vlastnú metódu, ktorá odfiltruje správu WM_MOUSEMOVE, ak je x-ová súradnica sa myši menšia ako 100:

type
 
TForm1=class(TForm)
    procedure FormMouseMove(Sender: TObject;
      Shift: TShiftState; X, Y: Integer);
  private
   
OldProc: TWndMethod;
    procedure NewProc(var Msg: TMessage);
  public
    constructor
Create(Owner: TComponent); override;
    destructor Destroy; override;
  end;

var
 
Form1: TForm1;

implementation

{$R *.DFM}

constructor TForm1.Create(Owner: TComponent);
begin
  inherited
;
  OldProc:=WindowProc;
  WindowProc:=NewProc;
end;

destructor TForm1.Destroy;
begin
 
WindowProc:=OldProc;
  inherited;
end;

procedure TForm1.NewProc(var Msg: TMessage);
begin
  if
(Msg.Msg=WM_MOUSEMOVE) and (TWMMouse(Msg).XPos<100) then Msg.Result:=0
  else OldProc(Msg);
end;

procedure TForm1.FormMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
 
Canvas.Pixels[X, Y]:=clRed;
end;

Za normálnych okolností je vlastnosť WindowProc nastavená tak, že sa volá virtuálna metóda:
  procedure WndProc(var Message: TMessage);
V programoch sa skoro nikdy nezvykne meniť vlastnosť WindowProc, ale radšej sa prepisuje metóda WndProc:

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

procedure TForm1.WndProc(var Msg: TMessage);
begin
  if
(Msg.Msg=WM_MOUSEMOVE) and (TWMMouse(Msg).XPos<100) then Msg.Result:=0
  else inherited;
end;

Ak by sme potrebovali spracovať viacero správ, pravdepodobne by WndProc vyzerala oveľa komplikovanejšie - obsahovala by niekoľko príkazov if, prípadne jeden obrovský príkaz case:
  case Msg.Message of
   
WM_MOUSEMOVE:
...
    WM_ LBUTTONDOWN: ...
    atď.
Príkaz case môže byť veľmi rozsiahly, preto sa ani WndProc nezvykne prepisovať. Pôvodná WndProc zabezpečí, že pre správu sa zavolá špeciálna dynamická metóda - tzv. message handler:

type
  TForm1 = class(TForm)
    procedure FormMouseMove(Sender: TObject;
      Shift: TShiftState; X, Y: Integer);
  protected
    procedure WMMouseMove(var Msg: TWMMouseMove); message WM_MOUSEMOVE;
 
end;

procedure TForm1.WMMouseMove(var Msg: TWMMouseMove);
begin
  if
Msg.XPos<100 then Msg.Result:=0
  else inherited;
end;

Viaceré message handlere sú naprogramované tak, že vyvolávajú rôzne udalosti - WM_MOUSEMOVE vyvoláva udalosť OnMouseMove a pod.

Príklad vlastných message handlerov, ktoré vyvolávajú vlastné udalosti:

unit CoolButton;

interface

uses
 
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  ExtCtrls;

type
 
TCoolButton=class(TShape)
  private
   
FOnMouseEnter: TNotifyEvent;
    FOnMouseLeave: TNotifyEvent;
  protected
    procedure
CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
    procedure CMColorChanged(var Msg: TMessage); message CM_COLORCHANGED;
    procedure DoMouseEnter; virtual;
    procedure DoMouseLeave; virtual;
  published
    property
OnMouseEnter: TNotifyEvent read FonMouseEnter write FOnMouseEnter;
    property OnMouseLeave: TNotifyEvent read FonMouseLeave write FOnMouseLeave;
    property Color;
  end;

procedure Register;

implementation

procedure
TCoolButton.CMMouseEnter(var Msg: TMessage);
begin
 
DoMouseEnter;
  inherited;
end;

procedure TCoolButton.CMMouseLeave(var Msg: TMessage);
begin
 
DoMouseLEave;
  inherited;
end;

procedure TCoolButton.CMColorChanged(var Msg: TMessage);
begin
  inherited
;
  Brush.Color:=Color;
end;

procedure TCoolButton.DoMouseEnter;
begin
  if
Assigned(OnMouseEnter) then OnMouseEnter(Self);
end;

procedure TCoolButton.DoMouseLeave;
begin
  if
Assigned(OnMouseLeave) then OnMouseLeave(Self);
end;

procedure Register;
begin
 
RegisterComponents('Test', [TCoolButton]);
end;

end.

Príklad použitia:

procedure TForm1.CoolButton1MouseEnter(Sender: TObject);
begin
 
TCoolButton(Sender).Brush.Color:=clRed;
end;

procedure TForm1.CoolButton1MouseLeave(Sender: TObject);
begin
 
TCoolButton(Sender).Brush.Color:=TCoolButton(Sender).Color;
end;

V prípade, že objekt nemá napísaný message handler pre určitú správu, zavolá sa metóda:
  procedure DefaultHandler(var Msg);
Ak objekt nedokáže spracovať správu ani v tejto metóde, zavolá sa funkcia Windows - DefWindowProc. Tá sa postará o spracovanie správy, prípadne neznámu správu ignoruje.


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;
    CoolButton1: TCoolButton;
    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.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));
  Font.Height:=MulDiv(Font.Height, Msg.M, Msg.D);
  for Index:=0 to ControlCount-1 do
    Controls[Index].Perform(WM_CHANGESIZE, Msg.M, Msg.D);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 
SendPerform(WM_CHANGESIZE, 2, 1);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 
Perform(WM_CHANGESIZE, 1, 2);
end;

Namiesto cyklu, ktorým rozposielame správy, môžeme použiť metódu Broadcast:

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

Iný komponent, ktorý reaguje na 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);
  Msg.Result:=1;
end;

Pre komponenty, ktoré na správu nereagovali, upravíme metódu TForm1.WMChangeSize:

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;

©  2003 Ľubomír SALANCI