Umíme to s Delphi: 71. díl – ikona ve stavové oblasti hlavního panelu (tray ikona)

V minulém dílu jsme se dozvěděli, jak vylepšit aplikaci za pomoci systémových registrů nebo souborů *.ini. Dnes nás čeká další námět na zlepšovák: zamyslíme se nad tím, jak přidat do aplikace možnost zobrazit svou ikonu ve stavové (pravé) části hlavního panelu. Tato část hlavního panelu bývá nazývána tray.
Jsem si vědom toho, že tématem tray-ikon se již zabýval kolega Jan Šindelář ve 44. dílu svého seriálu Tipy a Triky v Delphi. Zmíněný článek se však zabýval postupem, jímž lze přidat do aplikace tray-ikonu pouze pomocí přidání určitých řádků programového kódu. Tento postup může často vyhovovat.

Přesto se podobným tématem budeme zabývat v našem seriálu, z trochu jiné strany. Existuje pro to několik důvodů:

  • jak je naším zvykem, popíšeme všechny kroky podrobně, tak, aby jim v ideálním případě porozuměl i čtenář – začátečník;
  • na problém se podíváme komplexněji a vytvoříme univerzálnější (zároveň však náročnější) řešení: naprogramujeme novou komponentu, kterou bude možné použít v libovolné aplikaci;
  • na procesu tvorby této komponenty budeme mít příležitost vysvětlit si celou řadu zajímavých problémů týkajících se programování systému Windows: nezastavíme se tedy u hranic Delphi, ale lehce zabrousíme i do Windows API.
  • zopakujeme si proces tvorby komponent, kterým jsme se zabývali v dílech 44 – 47 našeho seriálu.

Abychom si to shrnuli, zopakujeme, že cílem našich manévrů bude vytvoření nové komponenty TrayIkona, jejímž přidáním do libovolné aplikace zobrazíme ikonu příslušné aplikace ve stavové části hlavního panelu (v tzv. tray).

Námětem pro tuto tématiku pro mě byla 15. kapitola knihy Mistrovství v Delphi 6 (Programování jádra systému Windows), Computer Press 2002. Protože se mi zdál popis celého problému v této publikaci až příliš stručný, pokusím se jej vydatně rozšířit, „zlidštit“ a značně modifikovat pro jinou cílovou skupinu čtenářů.

Než se však pustíme do programování, musíme si vysvětlit několik velmi důležitých věcí. Začneme raději od úplného začátku a zodpovíme si následující otázku:

Co je tray a kde jej najít?

Vzhledem k zaměření seriálu na začátečníky je možné, že se najdou čtenáři netušící, co je tray. Abychom je hned zpočátku nediskriminovali, ukážeme tray na obrázku. Tray neboli stavová oblast hlavního panelu je oblast v pravé části tohoto panelu: úplně vpravo na něm nalezneme hodiny a vlevo od hodin ikony jednotlivých aplikací, které z toho či onoho důvodu považují za nutné v této části být zobrazeny. Podívejte se raději na následující obrázek:

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

Na tomto obrázku je zároveň hezky vidět, jak může stavová oblast hlavního panelu postupně „narůstat“, neboť řada aplikace touží být v této oblasti neustále vidět. Co je k tomu vede?

K čemu slouží tray?

Stavová oblast hlavního panelu je (ideálně) určena pro zobrazení ikon takových aplikací, jejichž povaha vyžaduje, aby byly stále „aktivní“, aby neustále běžely a aby vyžadovaly nějakou pozornost uživatele či interakci s ním, nebo přinejmenším aby bylo žádoucí indikování jejich běhu:

  • pokud aplikace nevyžaduje vůbec žádnou interakci s uživatelem, není nutné, aby uživatel viděl její ikonu, a to ani v trayi – to je princip nejrůznějších tzv. služeb;
  • pokud aplikace neběží skoro nepřetržitě, může být zobrazena „normálně“ v hlavním panelu a nikoliv v trayi.

V trayi bývají velmi často zobrazeny aplikace nabíhající automaticky hned při startu operačního systému.

Zcela oprávněně okupují stavovou oblast aplikace jako antivir, ovladač klávesnice nebo, hlasitosti, případně aplikace, u nichž si to uživatel výslovně přeje (například Windows Commander, který má uživatel stále zapnutý, ale často nechce, aby překážel na hlavním panelu). Za zbytečné považuji například zobrazování ikony pro infračervené spojení (to je přece navázáno automaticky při přiblížení vysílajícího zařízení a teprve potom by se měla zobrazit ikona tohoto připojení), nebo zobrazování aplikace pro mapování multimediálních kláves na klávesnici (přiřazení se provede jednou, od té doby již příslušnou aplikaci uživatel nepotřebuje). Ideálně je třeba vyřešena ikona Správce tisku, která se v trayi objeví po odeslání dokumentu k tisku a zmizí ihned po dokončení tiskové operace. To je přesná ukázka situace, v níž by stavová oblast hlavního panelu měla být využita.

Tyto informace zdůrazňuji proto, abych (v souladu se snahou tohoto seriálu vštípit začátečníkům dobré programátorské návyky) varoval před využíváním tray ikon jen proto, že to autor umí (tím opakuji stejné varování, které se objevilo ve zmíněném článku Jana Šindeláře). Přidávejte tray ikonu do své aplikace jen a pouze v případě, že charakter aplikace splňuje výše uvedená pravidla a umožněte uživateli, aby zobrazování této ikony v případě zájmu mohl zakázat.

V úvodu článku je uvedeno, že vytvoření požadované komponenty bude spočívat v několika činnostech, na nichž si lze ukázat celou řadu dalších zajímavostí. Proto nyní začneme popisem jednotlivých bodů, jejichž závěrečnou implementací získáme požadovaný výsledek.

Vytvoření komponenty

Přestože jsme se tvorbě komponent věnovali v dílech 44 – 47 tohoto seriálu, stručně si připomeneme základní kroky. V Delphi vybereme File – New... a na záložce New zvolíme Component. Otevře se dialog, v němž vyplníme první 4 řádky: jako Ancesor type (předek komponenty) zvolíme TComponent. Naše komponenta bude totiž natolik originální, že k ní žádného specializovanějšího předka nenalezneme :-) Do kolonky Class Name (jméno třídy nové komponenty) zapíšeme TTrayIkona. V Palette Page (stránka v paletě komponent) vyplníme třeba Ruzne. Cestu ponecháme, stejně jako seznam prohledávaných cest:

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

Klepneme na OK a otevře se Code Editor, v němž nám zbývá poslední maličkost: napsat programový kód naší komponenty. Ještě předtím však upravíme sekci Uses, protože budeme používat funkce z několika jednotek. Do sekce Uses tedy zapíšeme následující jednotky:

uses Windows, SysUtils, Messages, ShellAPI, Classes, Graphics, Forms, Menus, StdCtrls, ExtCtrls;

Kromě toho je nutné zdrojový soubor uložit.

Nyní se již můžeme krok za krokem pustit do implementace komponenty.

Základní funkce pro práci s tray ikonou: Shell_NotifyIcon

Při realizaci našeho dnešního úkolu si bohužel nevystačíme pouze s možnostmi Delphi. Budeme tedy používat také rozhraní Windows API, nicméně využijeme prakticky jen dvě základní funkce, přičemž nejdůležitější je Shell_NotifyIcon. Tato funkce je definována v jednotce ShellAPI, kterou je tedy nutné přidat do sekce Uses (už jsme to učinili o pár řádků výše). Pojďme si tuto funkci popsat.

Funkce Shell_NotifyIcon slouží k zaslání zprávy operačnímu systému za účelem přidání, modifikace nebo odstranění ikony do/na/ze stavové oblasti hlavního panelu. Podívejme se na deklaraci této funkce:

function Shell_NotifyIcon(
    dwMessage: DWORD,       // identifikátor zprávy
    pnid: PNOTIFYICONDATA // ukazatel na záznam s datovými údaji, viz níže
  ): BOOL; stdcall;

Jak je vidět, funkce má pouze dva parametry: dwMessage a pnid. Pomocí prvního z nich říkáme, jakou zprávu hodláme zaslat (neboli co s ikonou hodláme dělat). Možné hodnoty:

  • NIM_ADD: Chceme přidat ikonu do stavové oblasti hlavního panelu
  • NIM_MODIFY: Chceme modifikovat ikonu ve stavové oblasti hlavního panelu, např. změní ikonu nebo zobrazovaný text
  • NIM_DELETE: Odstraní ikonu ze stavové oblasti hlavního panelu

Druhý parametr je však poněkud rozsáhlejší, neboť se jedná o ukazatel na objekt typu TNotifyIconData. Tento datový typ je systémově deklarovanou datovou strukturou (záznamem), která obsahuje 7 položek. Protože první pohled na datový typ TNotifyIconData vypadá vskutku příšerně, rád bych vyzval případné zájemce o házení flinty do žita, aby vytrvali; vše si podrobně vysvětlíme.

type TNotifyIconData = record 
    cbSize: DWORD;
    hWnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon HICON;
    szTip: array [0..63] of AnsiChar;
end;

Následující tabulka obsahuje popis jednotlivých složek záznamu TNotifyIconData:

Složka záznamu Popis
cbSize Velikost záznamu TNotifyIconData. Tento údaj by měl být inicializován hodnotou SizeOf(objekt_typu_TNotifyIconData).
hWnd Handle (manipulátor) okna, jemuž budou odesílány zprávy (tzv. notification) týkající se ikony ve stavové oblasti. Vysvětlení: pomocí funkce Shell_NotifyIcon posíláme zprávu my, posíláme ji systému a očekáváme za to od něj vytvoření nebo změnu ikony ve stavové části. Jakmile ikona existuje, musíme se nějak dozvědět třeba o tom, že se na ni kleplo. Systém v takovém případě naopak pošle zprávu nám a pomocí hWnd určujeme, které okno bude příjemcem takové zprávy.
uID Identifikátor ikony ve stavové oblasti. Využívá se při následné práci se zprávami souvisejícími se ikonami ve stavové části hlavního panelu. Tento identifikátor si definujeme sami; pokud bychom měli aplikaci, která by obsahovala více tray ikon, museli bychom pro každou z nich definovat jiný identifikátor.
uFlags Jak je vidět v deklaraci typu TNotifyIconData, tento typ obsahuje (kromě jiného) datové složky uCallbackMessage, hIcon a szTip. Vzhledem k deklaraci typu TNotifyIconData jako záznamu obsahují vždy všechny položky nějaká data, byť nesmyslná. Pomocí uFlags říkáme, která z uvedených datových složek obsahuje platná (aktivní) data. Hodnota může být i kombinací (součtem) více konstant: NIF_MESSAGE (platná data jsou ve složce uCallBackMessage), NIF_ICON (platná data jsou v hIcon), NIF_TIP (platná data jsou v szTip).
uCallBackMessage Zde je uložen identifikátor zprávy týkající se ikony ve stavové části. Stručně řečeno: v okamžiku, kdy se s ikonou „něco stane“ (někdo na ni klepne myší), bude vytvořena zpráva, jejíž identifikátor je uložen v uCallBackMessage a bude odeslána oknu, jehož handle je uložen pro změnu v hWnd. Tento identifikátor si definujeme sami a obvykle k tomu využíváme funkci RegisterWindowMessage (viz 66. díl našeho seriálu), takže máme zajištěno, že vytvořený identifikátor zprávy bude v celém systému jednoznačný.
hIcon Handle ikony, kterou hodláme zobrazit/modifikovat/odstranit.
szTip Text (hint), který bude zobrazen, když uživatel nechá kurzor myši chvíli nad ikonou.

Ačkoliv se informace z předchozí tabulky mohou zdát poněkud nepochopitelné a obtížné, jistě se vše ujasní při praktických pokusech.

Nyní zbývá objasnit způsob, jakým z programového kódu naší komponenty budeme funkci Shell_NotifyIcon volat. Nejlepší asi bude vytvořit metodu, která by volání této funkce zapouzdřila a umožnila zároveň naplnit hodnotami všechny důležité datové složky. Vytvoříme tedy metodu SendTrayMessage třídy TTrayIkona (připomeňme, že TrayIkona je název nové, námi vytvářené komponenty). Metoda bude mít dva parametry, stejné jako má funkce Shell_NotifyIcon:

procedure TTrayIkona.SendTrayMessage(Msg: DWORD; Flags: UINT);
begin
  with Tnd do
  begin
    cbSize := SizeOf(Tnd);
    StrPLCopy(szTip, PChar(FHint), SizeOf(szTip));
    uFlags := Flags;
    uID := UINT(Self);
    Wnd := IconMgr.HWindow;
    uCallbackMessage := DDGM_TRAYICON;
    hIcon  := ActiveIconHandle;
  end;
  Shell_NotifyIcon(Msg, @Tnd);
end;

Poznámky k programovému kódu:

  • Tnd je soukromá (private) datová složka třídy komponenty (tedy třídy TTrayIkona). Datovým typem tohoto atributu je TNotifyIconData. Podrobný popis třídy TNotifyIconData je uveden v předchozí tabulce.
  • Naplníme jednotlivé složky atributu Tnd:
  • Složku cbSize naplníme velikostí atributu Tnd.
  • Složku szTip naplníme pomocí funkce StrPLCopy, která slouží ke kopírování řetězců. Naše nová komponenta bude obsahovat vlastnost Hint, v níž bude možné nastavovat plovoucí nápovědu pro tray ikonu. Hodnotu této vlastnosti (FHint) zkopírujeme do složky szTip.
  • Do složky uFlags pouze předáme parametr Flags, který byl do metody předán.
  • Složka uID bude obsahovat hodnotu Self přetypovanou na UINT. Znamená to, že v uID bude identifikátor ikony TrayIkona.
  • složka hIcon bude naplněna návratovou hodnotou funkce ActiveIconHandle, kterou si napíšeme později a která bude vracet handle ikony nastavené ve vlastnosti Icon naší vytvářené komponenty.
  • do hWnd předáme handle okna IconMgr.HWindow. Objekt IconMgr vytvoříme později a bude sloužit jako jakési rozhraní mezi stavovou částí a naší aplikací. Toto rozhraní bude přijímat zprávy z tray a předávat je do naší aplikace.
  • uCallbackMessage naplníme identifikátorem DDGM_TRAYICON. Tento identifikátor získáme (jak bylo výše uvedeno) pomocí funkce RegisterWindowMessage takto:

var
  DDGM_TRAYICON: Cardinal;

const
  TrayMsgStr = `DDG.TrayNotifyIconMsg`;

initialization
  DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);

Popis: nejprve definujeme konstantu TrayMsgStr, do níž uložíme řetězec. Ten pak použijeme jako parametr při volání RegisterWindowMessage (provedeme to v sekci inicialization, která se provádí při startu programu). Získáme jednoznačný identifikátor (uložíme jej do proměnné DDGM_TRAYICON), který můžeme bez obav použít pro práci se zprávami.

Vzhledem k tomu, že programujeme komponentu a nikoliv standardní aplikaci, je nutné kromě implementace metody SendTrayMessage zapsat i její deklaraci. Vzhledem k tomu, že tuto metodu si budeme volat interně, v rámci programového kódu a neexistuje žádný důvod, proč by ji měl přímo volat i uživatel komponenty, umístíme deklaraci do sekce private. Vypíšeme si nyní zdrojový kód komponenty, jak jsme jej dnes vytvořili:

unit TrayIkona;

interface

uses Windows, SysUtils, Messages, ShellAPI, Classes, Graphics, Forms, Menus,
  StdCtrls, ExtCtrls;

type
  TTrayIkona = class(TComponent)
  private
    Tnd: TNotifyIconData;
    procedure SendTrayMessage(Msg: DWORD; Flags: UINT);
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
  end;

procedure Register;

implementation
const
  TrayMsgStr = `DDG.TrayNotifyIconMsg`;

var
  DDGM_TRAYICON: Cardinal;

procedure Register;
begin
  RegisterComponents(`Ruzne`, [TTrayIkona]);
end;

procedure TTrayIkona.SendTrayMessage(Msg: DWORD; Flags: UINT);
begin
  with Tnd do
  begin
    cbSize := SizeOf(Tnd);
    StrPLCopy(szTip, PChar(FHint), SizeOf(szTip));
    uFlags := Flags;
    uID := UINT(Self);
    Wnd := IconMgr.HWindow;
    uCallbackMessage := DDGM_TRAYICON;
    hIcon  := ActiveIconHandle;
  end;
  Shell_NotifyIcon(Msg, @Tnd);
end;

initialization
  DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);

end.

Na závěr

Dnes jsme se prozatím zdánlivě nedostali daleko, nicméně opak je pravdou. Vysvětlili jsme si princip stavové oblasti hlavního panelu a stanovili jsme si úkol vytvořit univerzálně použitelnou komponentu, která bude umožňovat naším aplikacím zobrazit svou ikonu v této oblasti. Pomocí průvodce jsme v Delphi vytvořili kostru této komponenty. Pak jsme si ukázali, že hlavní použitou funkcí WinAPI bude Shell_NotifyIcon. Popsali jsme si podrobně její parametry a důležitý datový typ TNotifyIconData.

V závěru jsme implementovali metodu, která zapouzdřuje volání Shell_NotifyIcon a naplní všechny potřebné datové složky správnými hodnotami. Ukázali jsme si také, jak v praxi získat jednoznačný identifikátor použitelný pro zprávy systému Windows. Za týden budeme pokračovat.

Váš názor Další článek: K dispozici Mandrake Linux 9.1 Beta 2

Témata článku: Software, Windows, Programování, Hicon, Hlavní důvod, Pan, TNO, Jednotlivý díl, Deklarovaná data, Manipulátor, Nová komponenta, Následující aplikace, Palette, Následující řádek, Icon, Důležitý krok, Důležitá část, Hlavní funkce, Self, Dokončené dílo, Důležitá oblast, Důležitá složka, Hlavní panel, Ikona, Oblast

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


Aktuální číslo časopisu Computer

Test 6 odolných telefonů a 22 powerbank

Srovnání technologií QLED a OLED

Měřte své sportovní výkony

Sady pro chytrou domácnost