Tvorba komponent pro C++ Builder - Komponenta HyperLink – 2. část

V dnešním díle budeme tuto komponentu dále rozšiřovat a především se naučíme zachytávat libovolné zprávy Windows, což je při tvorbě komponent jednou z klíčových záležitostí.
Úvodem

V minulém díle jsme vytvořili „minimální verzi“ komponenty realizující hypertextový odkaz. Máme výchozí nastavení písma, kurzoru a zobrazení hintu. Hint se automaticky aktualizuje podle nastavené vlastnosti „Hyperlink“. V dnešním díle budeme tuto komponentu dále rozšiřovat a především se naučíme zachytávat libovolné zprávy Windows, což je při tvorbě komponent jednou z klíčových záležitostí.

Přidáme „rollover efekt“

Nejprve trochu vylepšíme vzhled a chování komponenty při pohybu myši. Umožníme nastavit určitý „rollover efekt“ (terminologie FrontPage), tedy nějakou vizuální změnu hypertextového odkazu při najetí kurzorem na tento text. Přidáme proto vlastnost typu TFont, která bude určovat písmo hypertextu v době, kdy se na něm pohybuje kurzor myši. Navíc umožníme zadat zvuk, který se má při najetí myší na text přehrát. Přidáme tedy property typu AnsiString, která bude určovat jméno zvuku ve zdrojích (resources) programu nebo jméno .wav souboru. Přidejme tedy (z minulého dílu již známým způsobem) tyto dvě property (s tím, že si necháme vygenerovat příslušné funkce „Set“):

private:
TFont* FRolloverFont;
AnsiString FRolloverSound;
…..
__published:
__property TFont* RolloverFont =
{ read=FRolloverFont, write=SetRolloverFont };
__property AnsiString RolloverSound =
{ read=FRolloverSound, write=SetRolloverSound };

Nyní začínáme narážet na problémy s ClassWizardem, resp. s takto „vizuálně“ vygenerovanými propertry. Když necháte kód tak, jak je, přeložíte balíček a v testovacím projektu se pokusíte nastavit property RolloverFont (která se nám objeví v ObjectInspectoru), dostanete chybovou zprávu „Cannot assign a nil to a TFont“. Je to důsledek toho, že proměnná FRolloverFont, sloužící k uložení hodnoty property RolloverFont, je ukazatel na třídu, takže musíme nejprve vytvořit instanci třídy (TFont>. Přidáme proto do konstruktoru následující řádek:

FRolloverFont = new TFont();

Přeložte balíček a v testovací aplikaci nastavte RolloverFont. Když se nyní pokusíte nastavit tuto hodnotu znovu, nebo uložit project, dostanete pravděpodobně chybovou hlášku „Access violation….“. A budete muset ukončit C++ Builder bez uložení… Proč tomu tak je? Podívejme se na funkci „Set“ vygenerovanou ClassWizardem:

void __fastcall TRPJHyperlink::SetRolloverFont(TFont* value)
{
if(FRolloverFont != value) {
FRolloverFont = value;
}
}

Někteří z vás možná už správně hádají, že problém je v rovnítku při přiřazení hodnot. Takto prostě změníte hodnotu ukazatele místo přiřazení hodnot jedné instance třídy do druhé. Naštěstí je zde metoda

virtual void __fastcall Assign(Classes::TPersistent* Source);

kterou má implementovánu velké množství komponent a která slouží právě k přiřazení hodnot. Správná „verze“ funkce „Set“ je tedy třeba tato:

void __fastcall TRPJHyperlink::SetRolloverFont(TFont* value)
{
if ( FRolloverFont != value )
{
FRolloverFont->Assign(value);
}
}

Tento příklad ukazuje, že nelze vždy spoléhat na „wizarda“.

Zachytávání zpráv v komponentě

Nyní ale musíme tuto vlastnost také aplikovat. Co potřebujeme? Budeme muset odchytit okamžik, kdy kurzor myši vstoupí do oblasti komponenty a naopak kdy ji opustí, a v těchto okamžicích vhodně nastavit písmo textu komponenty.

V okamžiku vstupu myši do oblasti komponenty dostane tato zprávu CM_MOUSEENTER. Toto není zpráva Windows, ale uživatelsky definovaná zpráva v systému knihovny VCL. Tuto zprávu tedy komponentě posílá „sama VCL“, a tím v tomto případě trochu zjednoduší život, neboť ve Windows SDK není zatím implementována zpráva. V „čistém API“ se to řeší s pomocí zprávy WM_MOUSEHOVER, popř. WM_MOUSEMOVE a použitím funkce _TrackMouseEvent. Ve VCL vás mohu výkladu tohoto postupu ušetřit, a použít tedy funkci CM_MOUSEENTER. Pro případ opuštění oblasti komponenty již máme i ve Windows SDK zprávu WM_MOUSELEAVE, která však je zjednodušeně řečeno „někde uvnitř VCL“ zadržena a komponenta ji neobdrží. Můžeme však opět použít uživatelskou funkci VCL CM_MOUSELEAVE.

Nyní si musíme říci něco obecně o zachytávání zpráv v systému VCL. Nejjednodušší jsou dva následující způsoby: přepsání virtuální funkce WndProc, což je zapouzdřená procedura okna, nebo použití maker pro mapy zpráv. Ukažme si nejprve první způsob:

Přepsání metody WndProc Všechny komponenty odvozené od TControl mají virtuální metodu

virtual void __fastcall WndProc(Messages::TMessage &Message);

která v podstatě zapouzdřuje proceduru okna, jak je zřejmé z popisu struktury

struct TMessage
{
unsigned Msg;
union
{
struct
{
Word WParamLo;
Word WParamHi;
Word LParamLo;
Word LParamHi;
Word ResultLo;
Word ResultHi;
};
struct
{
int WParam;
int LParam;
int Result;
};
};
};
Přepíšeme proto tuto metodu s tím, že si „připravíme místo“ pro obsluhu dvou uvedených zpráv:

void __fastcall TRPJHyperlink::WndProc(Messages::TMessage &Message)
{
switch ( Message.Msg )
{
case CM_MOUSEENTER: // zde bude obsluha vstupu myši
break;
case CM_MOUSELEAVE: // obsluha opuštění komponenty myší
break;
}
TWinControl::WndProc(Message);
}

Při vstupu myši můžeme prostě přiřadit do property Font hodnotu property FRolloverFont. Musíme si však „někde pamatovat“ původní nastavenou hodnotu property Font, na kterou se po opuštění komponenty myší musíme vrátit. Řešením může být vytvořit si prostě privátní pomocnou proměnnou typu TFont, do které si uložíme nastavenou hodnotu. Vytvořme tedy členskou proměnnou

…..
private:
TFont* m_OrigFont;

jejíž instanci si vytvoříme v konstruktoru jako

FRolloverFont = new TFont();

Nejjednodušší implementace funkčnosti pak vypadá takto:

void __fastcall TRPJHyperlink::WndProc(Messages::TMessage &Message)
{
switch ( Message.Msg )
{
case CM_MOUSEENTER:
m_OrigFont->Assign(Font);
Font->Assign(FRolloverFont);
break;
case CM_MOUSELEAVE:
Font->Assign(m_OrigFont);
break;
}
TWinControl::WndProc(Message);
}

Když si nyní přeložíte balíček, nastavíte různé písmo do property Font a RolloverFont, vše by mělo fungovat správně. Ještě by možná bylo užitečné pro budoucího programátora-návrháře na začátku, tedy v konstruktoru nastavit property RolloverFont na stejnou „hodnotu“ jako Font, aby v případě, že nechce rollover efekt vizualizovat, nemusel hodnotu RolloverFont vůbec v návrhu měnit. V opačném případě by totiž byl RolloverFont po vytvoření instance nastaven na výchozí písmo formuláře, což by jistě nebylo žádoucí. Přidáme proto ještě do konstruktoru následující řádek (po vytvoření instance FRolloverFont):

…..
FRolloverFont = new TFont();
FRolloverFont->Assign(Font);
…..

Zvukový efekt

Nyní přistoupíme k realizaci zvukového efektu při najetí myší na text. Již máme vytvořenu property

__property AnsiString RolloverSound = { read=FRolloverSound, write=SetRolloverSound };

Řekli jsme si, že bude představovat buď název zvuku ve zdrojích programu, nebo název zvukového (.wav) souboru na disku. Více si o použití zdrojů v C++ Builderu řekneme na závěr tohoto dílu, nyní si řekněme, že k přehrání zvuku slouží funkce

BOOL PlaySound(
LPCSTR pszSound, // název zvuku
HMODULE hmod, // handle modulu
DWORD fdwSound // volby
);

kterou lze použít pro obě uvedené varianty. Pokud totiž je parametr hmod roven NULL, pszSound je jméno zvukového souboru a fdwSound obsahuje flag SND_FILENAME, přehraje se zadaný zvukový soubor. Chceme-li přehrát zvuk ze zdrojů, HMODULE bude handle naší instance (tj. ve VCL globální proměnné HInstance) a fdwSound musí obsahovat SND_RESOURCE. Podrobný popis funkce naleznete v dokumentaci. Zde se zmíním ještě o dvou dalších hodnotách, které může obsahovat parametr fdwSound: SND_SYNC – funkce čeká na ukončení přehrávání zvuku a pak teprve vrátí výstupní hodnotu a program může pokračovat, tedy synchronní přehrávání. SND_ASYNC – spustí se přehrávání zvuku a program bezprostředně pokračuje ve svém běhu za současného pokračování přehrávání zvuku, které může být zastaveno předčasně, třeba následným voláním této funkce s jiným zvukem nebo s parametrem pszSound rovným NULL, kterým zastavíme jakékoli předchozí přehrávání. Je tedy na úvaze programátora, jaký způsob přehrávání zvolit, je však třeba si uvědomit, že použití synchronního přehrávání nese sebou pochopitelné „riziko“ při použití delších zvukových nahrávek. Program celou dobu prostě „stojí“. Použijme tedy tuto funkci tak, že její volání přidáme do obsluhy zprávy CM_MOUSEENTER:

…..
case CM_MOUSEENTER:
m_OrigFont->Assign(Font);
Font->Assign(FRolloverFont);
if ( !PlaySound(FRolloverSound.c_str(), HInstance,
SND_RESOURCE | SND_ASYNC) )
PlaySound(FRolloverSound.c_str(), NULL, SND_FILENAME | SND_ASYNC);
break;
…..

Jak vidíte, použil jsem možná poněkud zjednodušené řešení pro určení, zda v property FRolloverSound je zadané jméno zdroje nebo jméno diskového .wav souboru.

Jako „domácí cvičení“ si můžete třeba vyřešit problém tím způsobem, že ve vhodném místě (otázka 1. - kde je to vhodné místo?) zjistíte, zda jde o název zdroje, jméno souboru nebo zda je hodnota nezadaná anebo jméno neexistuje. Výsledek zjištění pak můžete „někam“ uložit a ve výše uvedeném místě zavolat příslušnou verzi funkce, nebo ji nevolat vůbec.

P.S. Pokud to chcete brát vážně, očekávám výsledky „domácího úkolu“ na e-mailu radek@rplusj.cz.

Pro tento díl tedy opustíme komponentu a na závěr si řekneme – jak jsem slíbil – pár slov o zdrojích (resources) v C++ Builderu.

Zdroje (resources) v C++ Builderu

Bohužel Borlandu se ve svých nástrojích C++ Builder a Delphi podařilo docela úspěšně zamlžit rozsáhlé komunitě programátorů problematiku zdrojů a principů programování ve Windows SDK (tedy Win API) vůbec. Velké části programátorů, kteří se naučili céčko a „programování“ ve Windows v prostředí C++ Builder či Delphi, pojmy jako zdroje programu, procedura okna, zprávy Windows apod. moc nebo vůbec nic neříkají. Jediným nástrojem, který umožňuje vizuálně pracovat se zdroji, je ImageEditor. Ten je – upřímně řečeno – spíše vzpomínkou na doby Windows 3.x. Kurzory „umí“ pouze černobílé, ikony pouze ve 2 velikostech a maximálně 16 barvách. A především o nějakém vkládání ostatních standardních typů zdrojů, jako jsou právě zvuky WAV, animace AVI, menu apod. si můžeme nechat jenom zdát. Jak si tedy poradit s „vizuálním“ vkládáním a editací? Možností máme více. Uveďme tu jednodušší: Můžete použít (pokud jej máte k dispozici) Microsoft Visual C++ jako editor zdrojů, přesněji řečeno souborů kompilovaných zdrojů <.res), které C++ Builder standardně používá a které lze otevřít a upravovat například právě ve Visual C++. Pokud víte o jiném dobrém editoru .res souborů, můžete jej rovněž použít. Stručný a spolehlivý postup je tento: Vytvořte si v ImageEditoru prázdný soubor .res („File -> New -> Resource File (.res)). Ten uložte nejlépe do složky projektu. Tento soubor pak otevřete ve Visual C++, vložte do něj zdroje podle libosti a přilinkujte ho do projektu. Například tak, že v hlavním zdrojovém souboru, tedy tam, kde je funkce WinMain, doplníte řádek (tučně je přidaný řádek):

…..
#pragma hdrstop
USERES("Vyvoj.res");
USERES("mojezdroje.res");
USEFORM("main.cpp", Form1);
…..

Jak si můžete všimnout, již je zde jeden takový soubor .res, nazvaný shodně s názvem aplikace. Do něj si totiž C++ Builder ukládá ikonu aplikace, kterou lze nastavit v „Project Option -> Application- > Icon“. Je sice možné otevřít a upravovat přímo tento soubor, ale z vlastní zkušenosti silně doporučuji: raději C++ Builder příliš nedráždit, on si totiž tento soubor obsluhuje svým způsobem právě při tomto vizuálním nastavení ikony programu.

Další možností je naspat si zdrojový skritpt (resource script – soubory .rc), což je textový soubor, který se odkazuje na data uložená v externích souborech (wav, bmp, ico apod), na rozdíl od souborů .res, které už obsahují data zdrojů, a nejsou to tedy textové soubory. Jako příklad zdrojového skriptu si ukažme zápis vložení zvuku:

JMENO_ZDROJE  WAVE  DISCARDABLE  "JMENO_SOUBORU.WAV"

Pro úplnost doplním, že JMENO_ZDROJE bude právě tím 1. parametrem při volání výše uvedené funkce PlaySound.

Podobně lze do zdrojů přidat třeba bitmapu v true-color, ikony, animované kurzory apod. Samozřejmě, vytvářet takto „ručně“ skripty pro nějaký složitější dialog není zrovna „příjemná práce“ a je velkou výhodou použít třeba zmíněný editor zdrojů z Visual C++, v jehož případě je slovo „visual“ opravdu na místě. Když máme takovýto soubor skriptu zdrojů, můžeme jej přidat do projektu podobně jako soubor typu .res:

USERES("Vyvoj.res");
USERC("zvuky.rc");

Omezením uvedených maker (USERES a USERC) je to, že se dají použít pouze v projektu založeném na VCL. Pokud nepoužijete v projektu VCL, můžete vkládat pouze kompilované zdroje .res pomocí pragmy:

#pragma resource "další_zdroje.res“

Skripty .rc musíme zkompilovat pomocí kompilátoru zdrojů z příkazové řádky BRCC32.EXE. Tento kompilátor vytvoří ze vstupního .rc souboru kompilovaný soubor .res, který už můžeme použít v jakémkoli typu projektu.

Pokud někdo zná způsob, jak ve Win32 aplikaci v C++ Builderu použít přímo .rc skripty, sdělte mi to, já o něm nevím.

K tomu však ještě poznámku: Používat C++ Builder pro psaní Win32 aplikací (myslím tím „čisté API“, bez dalších knihoven jako MFC, VCL) není zrovna dobrý nápad. Síla C++ Builderu spočívá v možnosti rychlého vizuálního návrhu. Nic více, nic méně. Psaní kódu již není tak rychlé a efektivní. Když si porovnáte třeba funkci „Code Insight“ s podobnou funkcí „Statement completion“ v Microsoft Visual C++ (verze 6), bude vše jasné. A to zmíněná verze Visual C++ 6 je minimálně o 1-2 roky starší než C++ Builder 5. Dodnes jsem nepochopil smysl nastavení „Delay“ ve volbách funkce „Code Insight“, kde si můžeme prodlevu zvolit z rozsahu 0,5 – 1,5 sekundy. Myslím, že většina programátorů by požadovala, aby tato pomůcka byla k dispozici co nejdříve, nejlépe prakticky ihned, jako je tomu v případě Visual C++. Ve skutečnosti spíše za té půl sekundy začne IDE teprve „přemýšlet“. Dále již bez komentáře….

A nakonec ještě zdrojový kód komponenty ve stavu, do něhož jsme ji dnes „dotáhli“: Hyperlink2.zip.

Váš názor Další článek: Společnost RealNetwork uvedla na trh „Real One“

Témata článku: Software, Windows, Programování, TV +, Příjemné zjištění, Icon, Hype, Case, Read, Code, Dobrá obsluha, Tvorba, Delay, Zdroje, Komp, Tvor, Následující řádek, Message, Rychlé vkládání, Zvuk, Komponenta, True Color, Resource, Flag


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

Microsoft Defender je jeden z nejlepších antivirových programů, tvrdí výsledky AV-TESTu
Karel Kilián
Windows DefenderAntivirusWindows 10
10 míst na mapách Googlu, která nesmíte vidět. Nahradily je čtverečky

10 míst na mapách Googlu, která nesmíte vidět. Nahradily je čtverečky

** Deset míst, které nesmíte vidět ve webových mapách ** Jsou to letiště, základny i elektrárny ** Nejvíce míst tají Francie

Jakub Čížek | 21

Jakub Čížek
Mapy GoogleMapy
Google chystá funkci, která z chytrého Gmailu udělá hloupý Gmail
Lukáš Václavík
SoukromíGmailGoogle
Vybrali jsme 12 programovatelných hraček a stavebnic pro děti a jejich rodiče

Vybrali jsme 12 programovatelných hraček a stavebnic pro děti a jejich rodiče

** Získejte děti pro matematiku a základy techniky ** Kupte jim hračku nebo stavebnici, které vdechnou vlastní život ** Vybrali jsme stavebnice pro malé caparty i budoucí experty

Jakub Čížek | 10

Jakub Čížek
Stavebnice
AMD uvádí grafické karty Radeon RX 6800, 6800 XT a 6900 XT. Útočí přímo na modely od Nvidie

AMD uvádí grafické karty Radeon RX 6800, 6800 XT a 6900 XT. Útočí přímo na modely od Nvidie

** AMD představilo tři nové grafické karty ** Všechny s architekturou RDNA2, kterou používají i PS5 a Xbox Series ** Karty útočí přímo na GeForce RTX 3000

Karel Javůrek | 77

Karel Javůrek
Radeon RX 6000Grafické kartyAMD

Aktuální číslo časopisu Computer

Jak prodloužit výdrž notebooku

Velké testy: gamepady a inkoustové tiskárny

Důkladný test Sony Playstation 5