Události jsou velmi důležitou částí komponent. Jsou propojením
mezi výskytem v systému (jako je např. akce uživatele nebo změna zaostření),
na který komponenta může reagovat a částí kódu, který reaguje na tento
výskyt. Reagující kód je obsluha události a je většinou zapisována uživatelem
komponenty. Pomocí událostí, vývojář aplikace může přizpůsobit chování
komponenty a to bez nutnosti změny samotné třídy. Jako tvůrce komponenty,
použijeme události k povolení vývojáři aplikace přizpůsobit chování komponenty.
Události pro mnoho akcí uživatele (např. akce myši) jsou
zabudovány ve všech standardních komponentách C++ Builderu, ale můžeme
také definovat nové události. K vytváření událostí v komponentě, se musíme
seznámit s:
C++ Builder implementuje události jako vlastnosti.
Co jsou události?
Obecně řečeno, událost je mechanismus, který propojuje výskyt
s určitým kódem. Více specificky, událost je závěr (ukazatel), který ukazuje
na určitou metodu v určité instanci třídy.
Z perspektivy uživatele komponenty, událost je jméno
svázané s událostí systému, jako je např. OnClick, kterému uživatel
může přiřadit specifický kód. Např. stisknutí tlačítka nazvaného
Button1
má metodu OnClick. Implicitně C++ Builder generuje obsluhu události
nazvanou Button1Click na formuláři, který obsahuje tlačítko a přiřadí
ji k OnClick. Když se vyskytne událost kliknutí na tlačítku, tlačítko
volá metodu přiřazenou OnClick, v tomto případě
Button1Click.
K zápisu události musíme pochopit toto:
Události jsou závěry
C++ Builder používá k implementaci událostí závěry. Závěr
je speciální typ ukazatele, který ukazuje na určitou metodu v určité instanci
objektu. Jako tvůrce komponenty můžeme používat závěr jako adresu místa.
Náš kód detekuje výskyt události a je volána metoda (je-li) specifikovaná
uživatelem pro tuto událost.
Závěr obhospodařuje skrytý ukazatel na instanci třídy.
Když uživatel přiřadí obsluhu k události komponenty, nepřiřadí metodu jistého
jména, ale jistou metodu jisté instance objektu. Tato instance je obvykle
formulář obsahující komponentu, ale nemusí jim být.
Všechny ovladače např. dědí virtuální metodu nazvanou
Click
pro zpracování události kliknutí:
virtual void __fastcall Click(void);
Implementace Click volá uživatelovu obsluhu události
kliknutí, pokud existuje. Jestliže uživatel má přiřazenou obsluhu k události
OnClick
ovladače, pak výskytem kliknutí na ovladači je volání přiřazené obsluhy.
Jestliže obsluha není přiřazena, neprovádí se nic.
Události jsou vlastnosti
Komponenty používají k implementaci svých událostí vlastnosti.
Narozdíl od jiných vlastností, události nemohou použít metody k implementování
částí read a write. Je zde nutno použít soukromou položku
třídy a to stejného typu jako je vlastnost.
Podle konvencí, jméno položky je stejné jako jméno vlastnosti,
ale na začátek je přidáno písmeno F. Např. závěr (ukazatel) metody
OnClick
je uložen v položce nazvané FOnClick typu TNotifyEvent a
deklarace vlastnosti události OnClick je tato:
class TControl : public TComponent
{
private:
TNotifyEvent FOnClick;
...
protected:
__property TNotifyEvent OnClick =
{read=FOnClick, write=FOnClick};
...
};
Stejně jako u jiných vlastností, můžeme nastavovat nebo
měnit hodnotu události při běhu aplikace a pomocí Inspektora objektů může
uživatel komponent přiřazovat obsluhu při návrhu.
Typy událostí jsou typu závěr
Protože událost je ukazatel na obsluhu události, typ vlastnosti
události musí být závěrem. Podobně, libovolný kód použitý jako obsluha
události musí být odpovídajícím typem metody třídy. Všechny metody obsluh
událostí jsou funkce typu void. Jsou kompatibilní s událostí daného
typu, metoda obsluhy událostí musí mít stejný počet a typy parametrů a
musí být předány ve stejném pořadí.
C++ Builder definuje typy metod pro všechny své standardní
události. Když vytváříme svou vlastní událost, můžeme použít existující
typ (pokud vyhovuje) nebo definovat svůj vlastní.
Obsluhy událostí
mají návratový typ void
Přestože obsluha události je funkce, nesmíme hodnotu funkce
nikdy použít při zpracování události (funkce musí být typu void).
Prázdná funkce vrací nedefinovaný výsledek, prázdná obsluha události, která
by vracela hodnotu, by byla chybná. Jelikož obsluha události nemůže vracet
hodnotu, musíme získávat informace zpět z uživatelova kódu prostřednictvím
parametrů volaných odkazem.
Příkladem předávání parametrů volaných odkazem obsluze
události je událost stisku klávesy, která je typu TKeyPressEvent.
TKeyPressEvent
definuje dva parametry, první, indikující, který objekt generuje událost
a druhý indikující, která klávesa byla stisknuta:
typedef void __fastcall(__closure *TKeyPressEvent)(TObject
*Sender,Char &Key);
Normálně, parametr Key obsahuje znak stisknutý
uživatelem. V některých situacích může uživatel komponenty chtít tento
znak změnit. Např. může chtít převést znaky malých písmen na odpovídající
písmena velká. V tomto případě může uživatel definovat následující obsluhu
události pro editační ovladač:
void __fastcall TForm1::Edit1KeyPress(TObject
*Sender,Char &Key)
{
Key = UpCase(Key);
}
Můžeme také používat parametry předané odkazem k umožnění
uživateli přepsat implicitní zpracování.
Obsluhy události jsou nepovinné
Při vytváření událostí komponenty musíme pamatovat na to,
že uživatel našich komponent nemusí připojit obsluhu k události. To znamená,
že naše komponenta negeneruje chybu, pokud uživatel komponenty nepřipojí
obsluhu k jisté události.
Události ve Windowsovských aplikací vznikají stále. Při
přesunu myši nad viditelnou komponentou, Windows zasílá řadu zpráv pohybu
myši, které komponenta převádí na události OnMouseMove. Většinou
vývojář nechce zpracovávat pohyby myši a nebude tedy pro tuto událost vytvářet
obsluhu. Vytvořené komponenty nesmí vyžadovat obsluhu svých událostí.
Vývojář aplikace ale může zapsat libovolný kód do obsluhy
události. Komponenty ve VCL mají události zapsané způsobem minimalizujícím
možnost generování chyb obsluhou události. I když nelze zabránit před zápisem
logických chyb v kódu aplikace, lze zajistit inicializaci datových struktur
před voláním události a tak vývojář aplikace nemůže získat chybná data.
Implementování standardních
událostí
Všechny ovladače v C++ Builderu dědí události nejdůležitějších
událostí Windows. Tyto události nazýváme standardní události. Všechny tyto
události zabudované v abstraktních ovladačích, jsou implicitně chráněné,
což znamená, že koncový uživatel k nim nemůže připojit obsluhu. Když vytvoříme
ovladač, můžeme zvolit, zda bude událost viditelná pro uživatele našeho
ovladače.
Jsou tři věci, které je nutno provést, když používáme
standardní události v našich ovladačích:
Identifikace standardní
události
Jsou dvě kategorie standardních událostí: události definované
pro všechny ovladače a události definované pouze pro standardní okenní
ovladače. Nejzákladnější události jsou definovány v typu objektu TControl.
Všechny ovladače (okenní, grafické nebo uživatelské) dědí tyto události:
OnClick OnDragDrop
OnEndDrag OnMouseMove OnDblClick OnDragOver
OnMouseDown OnMouseUp
Všechny standardní události mají chráněné virtuální metody
deklarované v TControl, jejichž jméno odpovídá jménu události. Např.
událost
OnClick volá metodu jména Click a OnEndDrag
volá metodu nazvanou
DoEndDrag.
Mimo událostí společných pro všechny ovladače, mají ovladače
odvozené od TWinControl další události:
OnEnter OnKeyDown
OnKeyPress OnKeyUp OnExit
Podobně jako standardní události v TControl, i
události okenních ovladačů mají odpovídající metody.
Zviditelnění události
Deklarace standardních události jsou chráněné a chráněné
jsou i metody, které jim odpovídají. Jestliže chceme tyto vlastnosti zpřístupnit
uživateli při běhu programu nebo při návrhu, musíme opětovně deklarovat
vlastnost události jako veřejnou nebo zveřejňovanou.
Opětovná deklarace vlastnosti bez specifikace její implementace
zachovává implementovanou metodu, ale změní úroveň přístupu. Tím můžeme
zviditelnit standardní události.
Když vytváříme komponentu a chceme např. zpřístupnit
událost OnClick během návrhu, pak přidáme do deklarace typu komponenty:
class PACKAGE TMujOvladac : public TCustomControl
{
__published:
__property OnClick;
//zviditelní OnClick v Inspektoru objektů
};
Změna zpracování standardních
událostí
Jestliže chceme změnit způsob reakce komponenty na jistou
třídu událostí, zapíšeme příslušný kód a přiřadíme jej události. Pro uživatele
komponenty je to přesně to co chce. Nicméně, když vytváříme komponentu,
není to co chceme, protože musíme udržet událost přístupnou pro uživatele
komponenty.
Má to význam pro chráněné implementace metod přiřazených
ke každé standardní události. Předefinováním implementace metody, můžeme
modifikovat vnitřní obsluhu události a voláním zděděné metody můžeme obsloužit
standardní zpracování, včetně uživatelova kódu.
Je důležité že voláme zděděnou metodu. Obecné pravidlo
je volat zděděnou metodu nejdříve, a použít kód původní obsluhy události
dříve než svůj přizpůsobený. Nicméně, někdy chceme provést svůj kód před
voláním zděděné metody. Např. jestliže zděděný kód je nějak závislý na
stavu komponenty a náš kód mění tento stav, pak chceme změnit stav a nechat
uživatelův kód reagovat na změněný stav.
Předpokládejme např. že zapisujeme komponentu a chceme
modifikovat způsob reakce nové komponenty na kliknutí. Namísto přiřazení
obsluhy k události OnClick, jak by to provedl uživatel komponenty,
předefinujeme chráněnou metodu Click:
void __fastcall TMujOvladac::Click() {
TWinControl::Click();
// naše přizpůsobení vložíme sem
}
Definování svých vlastních
událostí
Definování nových událostí je relativně vzácné. Mnohem častěji
provádíme předefinování již existujících událostí. Nicméně někdy, když
chování komponenty je značně odlišné, pak je vhodné definovat pro ni událost.
Definování události probíhá ve čtyřech krocích:
Generování události
První co provedeme, když definujeme svou vlastní událost,
která neodpovídá žádné standardní události, je určení co událost spustí.
Pro některé události je odpověď zřejmá. Např. když uživatel stiskne levé
tlačítko myši, Windows zasílá zprávu WM_LBUTTONDOWN aplikaci. Po
příjmu této zprávy komponenta volá svou metodu MouseDown, která
dále volá nějaký kód, který uživatel má připojen k události OnMouseDown.
Ale u některých událostí je obtížnější určit, co specifikuje
externí výskyt. Např. posuvník má událost OnChange, spouštěnou několika
typy výskytů, včetně klávesnice, kliknutí myši nebo změnou v jiném ovladači.
Když definujeme svou událost, musíme zajistit, že všechny možné výskyty
spustí naši událost.
Jsou dva typy výskytů, pro které musíme události ošetřit:
změna stavu a akce uživatele. Mechanismus zpracování je stejný, ale liší
se sémanticky. Událost akce uživatele je téměř vždy spouštěna zprávou Windows,
indikující, že uživatel provedl něco na co naše komponenta má reagovat.
Událost změny stavu je také svázána se zprávou od Windows (např. změna
zaostření nebo povolení něčeho), ale může také vzniknou na základě změny
vlastnosti nebo jiného kódu. Musíme definovat, že to vše může spustit událost.
Definování typu obsluhy
Když určíme jak naše událost vznikne, musíme definovat jak
ji chceme obsloužit. To znamená určit typ obsluhy události. V mnoha případech,
obsluhy pro události definujeme sami jednoduchým oznámením nebo typem specifickým
události. To také umožňuje získat informace zpět z obsluhy.
Jednoduchá oznámení
Oznamovací událost je událost, která pouze říká, se jistá
událost nastala a nespecifikuje žádné informace o ní. Oznámení používá
typ TNotifyEvent, který má pouze jeden parametr (odesilatel události).
Tedy všechny obsluhy pro oznámení znají o události pouze to, o jakou třídu
události se jedná a která komponenta událost způsobila. Např. události
kliknutí jsou oznámení. Když zapisujeme obsluhu pro událost kliknutí, vše
co známe je to, že kliknutí nastalo a na které komponentě bylo kliknuto.
Oznámení jsou jednosměrný proces. Není mechanismus k
poskytnutí zpětné vazby nebo k zabránění budoucí obsluhy oznámení.
Obsluha specifická pro událost
V některých případech nám ale tyto informace nestačí.
Např. jestliže událost je událost stisku klávesy, je pravděpodobné, že
chceme také znát, která klávesa byla stisknuta. V těchto případech požadujeme
typ obsluhy, který obsahuje parametry s nějakými nezbytnými informacemi
o události. Jestliže naše událost je generována v reakci na zprávu, je
pravděpodobné, že parametry předávané obsluze události získáme přímo z
parametrů zprávy.
Návrat informací z obsluhy
Protože všechny obsluhy událostí jsou funkce vracející
void,
jediný způsob k předání informací zpět z obsluhy je pomocí parametru volaného
odkazem. Naše komponenta může použít tyto informace k určení jak a co událost
udělá po provedení obsluhy uživatele. Např. všechny události klávesnice
(OnKeyDown, OnKeyUp a OnKeyPress) předávají hodnotu
stisknuté klávesy v parametru volaném odkazem jména Key. Obsluha
události může změnit Key a tak aplikace vidí jinou klávesu než která
způsobila událost. To je např. způsob jak změnit zapsaný znak na velká
písmena.
Deklarování události
Když jsme určili typ naší obsluhy události, můžeme deklarovat
závěr a vlastnost pro událost. Události dáme smysluplné a popisné jméno
tak, aby uživatel pochopil, co událost dělá. Je vhodné, aby bylo konzistentní
s jmény podobných událostí v jiných komponentách.
Jména všech standardních událostí v C++ Builderu začínají
"On".
Je to pouze konvence, překladač nevyžaduje její dodržování. Inspektor objektů
určuje že vlastnost je událost podle typu vlastnosti: všechny vlastnosti
závěrů jsou považovány za události a jsou zobrazeny na stránce událostí.
Volání události
Obecně je vhodné centralizovat volání události, tj. vytvořit
virtuální metodu v naši komponentě, která volá uživatelskou obsluhu události
(pokud ji uživatel přiřadí) a provádí nějaké implicitní zpracování.
Umístění všech volání událostí na jednom místě zajistí,
že nějaká odvozená nová komponenta od naší komponenty může přizpůsobit
obsluhu události předefinováním jen jedné metody, namísto hledáním v našem
kódu místa, kde událost je volána.
Nesmíme nikdy vytvořit situaci ve které prázdná obsluha
události způsobí chybu, tj. vlastní funkčnost našich komponent nesmí záviset
na jisté reakci z kódu obsluhy události. Z tohoto důvodu, prázdná obsluha
musí produkovat stejný výsledek jako neobsloužená událost. Komponenty nikdy
nesmějí požadovat aby uživatel je použil jistým způsobem. Důležitým aspektem
je princip, že uživatel komponent nemá žádné omezení na to, co může s nimi
dělat v obsluze události.
Jelikož prázdná obsluha se má chovat stejně jako žádná
obsluha, kód pro volání uživatelské obsluhy má vypadat takto:
if (OnClick) OnClick(this);
// provedení implicitního zpracování
Nikdy nesmíme použít tento způsob:
if (OnClick) OnClick(this);
else
// provedení implicitního zpracování
Pro některé typy událostí, uživatel může chtít nahradit
implicitní zpracování. K umožnění aby to mohl udělat, musíme přidat parametr
volaný odkazem k obsluze a testovat jej na jistou hodnotu při návratu z
obsluhy. Když např. zpracováváme událost stisku klávesy, uživatel může
požadovat implicitní zpracování nastavením parametru Key na nulový
znak (viz následující příklad):
if (OnKeyPress) OnKeyPress(this, &Key);
if (Key != NULL) // provedení implicitního
zpracování
Skutečný kód se nepatrně liší od zde uvedeného (spolupracuje
se zprávou Windows), ale logika je stejná. Implicitně komponenta volá nějakou
uživatelem přiřazenou obsluhu a pak provede své standardní zpracování.
Jestliže uživatelova obsluha nastaví Key na nulový znak komponenta
přeskočí implicitní zpracování.
S praktickým používáním událostí se podrobněji seznámíme v následujících
kapitolách.