Umíme to s Delphi: 66. díl – Delphi a zprávy systému Windows, uživatelské zprávy

Dnešní článek je věnován vytváření vlastních zpráv, jejich vytváření, práci s nimi, zasílání a odchytávání. Ukážeme si také příklad aplikace, která používá vlastní zprávy k synchronizaci vícevláknového běhu, což je jedním z nejtypičtějších použití zpráv vůbec. V úvodu kapitoly také poznáme další typ systémových zpráv: zprávy informační.
Před týdnem jsme se stali znalci v oboru „systémové zprávy a jejich odchyt“. Ukázali jsme si řadu metod (v obecném slova smyslu), které umožňují zpracování systémových zpráv. Umíme vytvořit formulář, stejně jako odvodit libovolnou komponentu, která si poradí se systémovými zprávami. Dokážeme pracovat s událostí OnMessage a víme, jak si již v době návrhu poradit s událostmi celé aplikace (globálního objektu Application).

Víme, že systém Windows nabízí nespočet všemožných zpráv. Prozatím jsme se však zabývali výhradně normálními zprávami, tedy těmi, jejichž název začíná na WM_***. Kromě nich také existují tzv. informační zprávy.

Informační zprávy

Názvy informačních zpráv definovaných v systému začínají obvykle BN_*** (informační zprávy tlačítka Button), CBN_*** (informační zprávy rozbalovacího seznamu ComboBox), EN_*** (informační zprávy editačního pole) nebo LBN_*** (pro případ klasického seznamu ListBox). Kromě těchto typů zpráv je celá řada dalších definována přímo v knihovně VCL. V aplikacích se běžně nevyužívají, ke slovu přijdou obvykle až při vytváření nových komponent. Jejich názvy začínají na CM_*** (component message) nebo CN_*** (component notification).

Známým příkladem jejich použití je vytvoření komponenty, která změní svou barvu po najetí kurzoru myši. Pojďme tento jednoduchý příklad společně vytvořit.

1. Na formulář Form1 tentokráte nemusíme umisťovat žádnou komponentu, pouze si trocvhu „pohrajeme“ ze zdrojovým kódem.

2. Vytvoříme komponentu TSpecialPanel odděděním od třídy TPanel a přidáme dvě obslužné procedury pro zprávy CM_MOUSEENTER a CM_MOUSELEAVE:

  TSpecialPanel = class(TPanel)
  protected
    procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
  end;

3. Implementujeme těla obou obslužných procedur:

procedure TSpecialPanel.CMMouseEnter(var Msg: TMessage);
begin
  inherited;
  Color := clWhite;
end;

procedure TSpecialPanel.CMMouseLeave(var Msg: TMessage);
begin
  inherited;
  Color := clBtnFace;
end;

4. Do obsluhy události OnCreate hlavního formuláře přidáme kód zajišťující vytvoření a inicializaci instance speciálního panelu:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with TSpecialPanel.Create(self) do
  begin
    Width  := 100;
    Height := 100;
    Top    := 20;
    Left  := 20;
    Parent := self;
  end;
end;

5. Po spuštění programu bude panel bílý, bude-li se nad ním nalézat kurzor myši. V ostatních případech bude mít standardní barvu.

Uživatelsky definované zprávy

Nyní se dostáváme k hlavní náplni dnešního dílu. Ukážeme si, jak vytvářet uživatsky definované zprávy a vysvětlíme si, proč bychom to vlastně měli dělat.

Z podstaty systému zpráv, jak jsme ji dosud viděli, plyne, že zprávy jsou skvělou příležitostí pro předávání informací ze strany operačního systému jednotlivým aplikacím. Jakmile se v systému něco „šustne“, příslušná aplikace se o tom ihned dozví tím, že jí Windows pošlou zprávu. Tento princip lze zobecnit: proč by si nemohly vyměňovat informace i dvě aplikace mezi sebou? A půjdeme-li v úvahách ještě dále, můžeme se zeptat: existuje nějaký důvod, proč by nemohla jedna aplikace využívat zprávy k svému vlastnímu běhu, například mezi jedním a druhým oknem?

Samozřejmě, že tomu nic nebrání. Jedna aplikace může zasílat zprávy jiné, stejně tak jako mohou zprávy „běhat“ jen uvnitř jedné, téže aplikace (jinak řečeno – aplikace může zaslat zprávu sama sobě).

Může ale vyvstat otázka, proč by aplikace měla posílat zprávu sama sobě (např. o dokončení výpočtu), když může přímo sama vyvolat nějakou proceduru nebo funkci? To je sice pravda, nicméně použití zpráv přináší některé výhody oproti klasickému používání metod (byť třeba virtuálních):

  • zpráva může být zaslána, aniž by byl přesně znám typ jejího příjemce,
  • nic se nestane v případě, že příjemce na zprávu nereaguje,
  • zpráva může být zároveň zaslána více příjemcům (tzv. broadcasting, viz dále).

Nejprve si ukážeme, jak vytvořit uživatelskou zprávu.

Vytvoření uživatelské zprávy

Definovat vlastní zprávu je velmi jednoduché. Systém Windows poskytuje určitý rozsah zpráv (přesněji řečeno jejich čísel), které je možno využívat pro libovolné účely. Tento rozsah je od (WM_USER + 100) do $7FFF. WM_USER je systémová konstanta definující uživatelskou zprávu. K vytvoření zprávy nemusíme udělat nic jiného, než nadefinovat příslušnou konstantu. V následujícím příkladu definujeme zprávu AM_HOTOVO:

const
  AM_HOTOVO = WM_USER + 100;

Metody a funkce pro zasílání zpráv Máme-li zprávu definovánu, musíme se před jejím zasláním rozhodnout, kterou metodu nebo funkci pro tuto činnost použijeme. Máme v zásadě tři možnosti, přičemž každá má své pro i proti:

Metoda Perform

Metodou Perform je vybaven každý ovládací prvek, neboť je definována již ve třídě TControl (jedná se tedy o metodu VCL). Zavoláním této metody „obejdeme“ celý mechanismus zpráv systému Windows, včetně fronty zpráv a „pošleme“ zprávu přímo do obslužné procedury příslušného ovládacího prvku. Metoda Perform tedy funguje jen v případě, že známe konkrétní instanci ovládacího prvku, jemuž chceme zprávu zaslat. Má tři parametry:

function Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

  • Msg: zpráva
  • WParam, LParam: atributy zprávy, viz předchozí dva díly seriálu

Hodláme-li zprávu zaslat, použijeme následující zápis (dejme tomu, že editačnímu poli Edit1 hodláme poslat zprávu AM_HOTOVO:

  Edit1.Perform(AM_HOTOVO, 0, 0);

Návrat z metody Perform se provede až po dokončení obsluhy zprávy, metodu Perform tedy můžeme považovat za synchronní (volající je pozastaven po dobu činnosti volaného).

Funkce SendMessage

Zatímco metoda Perform je definována ve VCL, funkce SendMessage je systémová (jedná se o funkci WinAPI). Proč ji používat? V určitých situacích Perform ani použít nelze: třeba když neznáme název instance příjemce, případně když příjemce nebyl vůbec vytvořen v prostředí Delphi.

Abychom mohli použít SendMessage, musíme znát pouze manipulátor (handle) příjemce. SendMessage se při zasílání chová podobně jako Perform: předá zprávu přímo do obslužné procedury příjemce (nikoliv do fronty) a k návratu nedojde, dokud není dokončena obsluha zprávy.

SendMessage má čtyři parametry:

function SendMessage(hWnd: HWND, Msg: UINT Msg, wParam: WPARAM,
  lParam: LPARAM): LRESULT;

  • hWnd: manipulátor (handle) cílového okna
  • Msg: identifikátor zprávy
  • wParam, lParam: dodatečné parametry zprávy

Funkce PostMessage

Funkci PostMessage popíšeme velmi stručně, protože se chová a používá stejně jako SendMessage (opět se tedy jedná o funkci WinAPI) s jediným rozdílem: zatímco SendMessage pošle zprávu přímo do obslužné procedury a čeká na její dokončení, PostMessage ji zařadí na konec fronty zpráv příslušného okna a bezprostředně předává řízení (dojde okamžitě k návratu z PostMessage). Volání PostMessage je tedy asynchronní operací. Návratovou hodnotou je pouze logický údaj říkající, došlo-li k úspěšnému zařazení do fronty (zatímco Perform a SendMessage mohou vracet návratovou hodnotu zprávy, pokud ji obslužná procedura přiřadí).

Parametry fukce PostMessage jsou totožné s SendMessage.

Zasílání zpráv v rámci jedné aplikace prakticky

První (a možná nejčastější) činností, která nás v souvislosti s uživatelskými zprávami napadne, je jejich zasílání v rámci jedné aplikace. Začít samozřejmě musíme definicí zprávy a pokračujeme rozhodnutím, kterou metodu použít pro zaslání zprávy (viz předchozí odstavce). Máme-li toto rozhodnutí za sebou, můžeme se pustit do práce.

Pokusíme se nejprve vytvořit velmi jednoduchou aplikaci, která demonstruje zasílání zpráv v rámci jedné aplikace. Vytvoříme aplikaci, která po klepnutí na tlačítko (a dalším potvrzení) spustí výpočet spočívající v inkrementaci proměnné od 1 do 10000. Hodnota proměnné bude vždy vypisována do titulku formuláře. Po skončení výpočtu pošle „výpočetní“ procedura formuláři zprávu, že výpočet byl dokončen. Formulář na tuto zprávu zareaguje vypsáním informační hlášky.

1. Na formulář Form1 umístíme komponentu Button (Button1).

2. Dále definujeme zprávu AM_HOTOVO. Definice této konstanty musí předcházet deklaraci třídy TForm1, neboť v ní již konstantu používáme:

const
  AM_HOTOVO = WM_USER + 100;

3. Deklarujeme také obslužnou proceduru této zprávy:

procedure AMHotovo(var Msg: TMessage); message AM_HOTOVO;

4. Ošetříme událost OnClick tlačítka Button1:

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(`Klepnutím na OK spustíte výpoèet`);
  Spocti;
end;

5. Implementujeme metodu Spocti, která bude simulovat nějakou výpočetní aktivitu.Po skončení výpočtu pošle metoda aplikaci zprávu o skončení výpočtu:

procedure TForm1.Spocti;
var I, J: Integer;
begin
  for I:=1 to 10000 do
  begin
    Form1.Caption := IntToStr(I);
    J := I;
  end;
  SendMessage(Form1.Handle, AM_HOTOVO, J, 0);

end;

6. Posledním úkolem je implementace obslužné procedury zprávy AM_HOTOVO:

procedure TForm1.AMHotovo(var Msg: TMessage);
begin
  ShowMessage(`Výpoèet byl dokonèen. Výsledek = ` + IntToStr(Msg.WParam));
end;

Aplikace je dokončena. Po spuštění a doběhnutí výpočtu dojde k zaslání zprávy a zobrazení informační hlášky:

Klepněte pro větší obrázek

Poznámky k aplikaci:

  • Aplikace kromě jiného ukazuje, jak je možné použít parametrů zprávy k přenosu informací (v našem případě předáváme výsledek výpočtu).
  • Je zřejmé, že aplikace by si v tomto případě poradila i beze zpráv. Zprávy nalézají nejširší uplatnění při programování vícevláknových aplikací: kdyby naše aplikace prováděla onen „výpočet“ paralelně, mohl by každý „dělník“ poslat zprávu, že dokončil svou práci. Využití zprávy by v takovém případě bylo nanejvýš efektivní i elegantní.

Uvedeme si kompletní zdrojový kód aplikace:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

const
  AM_HOTOVO = WM_USER + 100;


type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure Spocti;
    procedure AMHotovo(var Msg: TMessage); message AM_HOTOVO;
  public
    { Public declarations }
  end;



var
  Form1: TForm1;


implementation
{$R *.dfm}


procedure TForm1.Spocti;
var I, J: Integer;
begin
  for I:=1 to 10000 do
  begin
    Form1.Caption := IntToStr(I);
    J := I;
  end;
  SendMessage(Form1.Handle, AM_HOTOVO, J, 0);

end;

procedure TForm1.AMHotovo(var Msg: TMessage);
begin
  ShowMessage(`Výpoèet byl dokonèen. Výsledek = ` + IntToStr(Msg.WParam));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(`Klepnutím na OK spustíte výpoèet`);
  Spocti;
end;

end.

Zasílání zpráv mezi různými aplikacemi

Druhou činností související se zprávami je jejich zasílání „napříč“ aplikacemi. Využíváme opět metodu Perform, případně funkce SendMessage či PostMessage. Nastává pouze několik odlišností od činnosti demonstrované v předchozím odstavci.

Rozhodneme-li se jiné aplikaci zaslat uživatelskou zprávu, musíme si především být jistí, že příjemce bude této zprávě „rozumět“. Je nutné si uvědomit, že definováním konstanty AM_HOTOVO vůbec nezajistíme, že jiná aplikace si se zprávou AM_HOTOVO poradí.

Při posílání zpráv do jiné aplikace obvykle řešíme problém spočívající v neznalosti příslušného handle. Jedno z možných řešení spočívá v rozeslání zprávy všem oknům (tzv. broadcast message). Používá se k tomu funkce Broadcast. Toto řešení se poměrně často využívá, jen je nutné počítat se dvěma možnými problémy:

  • Hodláme-li poslat zprávu jiné aplikaci, obvykle používáme funkci WinAPI RegisterWindowMessage(), s jejíž pomocí zajistíme, že každá zainteresovaná aplikace použije pro danou zprávu stejné identifikační číslo. Této funkci pouze předáme libovolný řetězec, na jehož základě je generováno číslo zprávy z intervalu $C000 do $FFFF. V obou (všech) aplikacích je tedy nutné předat této funkci stejný řetězec a máme zajištěno, že systém vrátí v každé aplikaci stejné identifikační číslo zprávy.
  • Obsluha těchto broadcast messages je nepatrně obtížnější, protože dopředu neznáme identifikátor zprávy a nemůžeme použít klasickou obslužnou proceduru zprávy. Obvykle tedy musíme předefinovat jednu z metod WndProc nebo DefaultHandler.

Všechny tyto činnosti by však vyžadovaly rozsáhlejší a delší popis, který myslím přesahuje rámec našeho seriálu.

Na závěr

Je vidět, že práce se zprávami je velmi zajímavá, nicméně v určitých momentech ne zcela triviální. Především komunikace mezi více různými aplikacemi již patří mezi pokročilé techniky a k její úspěšné realizaci je nutná celkem podrobná znalost systémových „hlubin“ (zde se již nejedná ani tak moc o Delphi jako takové). Častěji však využíváme zprávy pro komunikaci v rámci téže aplikace, jak jsme si to stručně ukázali na jednoduchém příkladu.
Diskuze (11) Další článek: Stručná historie operačního systému IBM OS/2

Témata článku: Software, Windows, Programování, Volaná zpráva, Předchozí odstavec, Stejný prvek, Informační panel, Self, Stejné parametry, Message, Určitý moment, Front, Dokončené dílo, Díl, Speciální panel, Libovolná aplikace, Ovládací aplikace, Manipulátor, Podrobná znalost, Zprávy, Stejná metoda, ETA, Příslušná aplikace, Editační funkce


Určitě si přečtěte

Inteligentní akvárium Bluenero se o rybičky postará samo

Inteligentní akvárium Bluenero se o rybičky postará samo

** Chcete chovat akvarijní rybičky, ale nemáte čas se o ně starat? ** Chytré akvárium je samo nakrmí a postará se o jejich komfort ** Projekt Bluenero zatím sbírá finance na Indiegogo

Karel Kilián | 20

Pojďme programovat elektroniku: Jak vlastně funguje akcelerometr a gyroskop nejen ve vašem telefonu

Pojďme programovat elektroniku: Jak vlastně funguje akcelerometr a gyroskop nejen ve vašem telefonu

** Každý současný vybavený mobil má akcelerometr a gyroskop ** Jenže co každé z těchto čidel vlastně dělá a jak vypadá? ** Dnes si to vysvětlíme a do hry zapojíme i Airbus A380 a Arduino

Jakub Čížek | 11

Vyzkoušeli jsme eObčanku a přihlásili se s ní na weby úřadů. Vážně to funguje!

Vyzkoušeli jsme eObčanku a přihlásili se s ní na weby úřadů. Vážně to funguje!

** Máme eObčanku, máme čtečku, vyzkoušeli jsme přihlášení na weby úřadů. ** Objevily se drobné problémy, podařilo se nám je vyřešit. ** Používání eObčanky pro online identifikaci je velmi pohodlné.

Marek Lutonský | 35

Astronomové objevili daleko za Plutem objekt s extrémní dráhou. Může ukazovat na existenci planety Devět

Astronomové objevili daleko za Plutem objekt s extrémní dráhou. Může ukazovat na existenci planety Devět

** Astronomové objevili daleko za Neptunem těleso 2015 TG387 ** Okolo Slunce se pohybuje po extrémně protáhlé dráze ** Jeho dráha může ukazovat na existenci planety Devět

Petr Kubala | 10


Aktuální číslo časopisu Computer

Jak vytvořit a spravovat vlastní web

Velký test herních klávesnic a DVB-T2 tunerů

Vše o formátu RAW

Vybíráme nejlepší základní desku