4. Vytváření událostí

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.
 
4. Vytváření událostí