Dneska se podíváme na vytváření vlastních událostí za použití standartních i vlastních delegátů.
Co je vlastně událost? Událost je základní komunikační nástroj mezi objekty, reprezentovaná metodou. Vyvolána je objektem v okamžiku, kdy je chtěné předat vnějšímu okolí informaci o tom, že se v objektu něco mění, nedaří, začíná apod. Záleží tedy na daném programátorovi zda o danou informaci má či nemá zájem, jestli si událost objedná tím, že vytvoří tělo metody události a přiřadí ho k této události. Tak bude moci v těle metody na danou událost reagovat.
Aby mohl programátor kvalifikovaně reagovat mnohdy nestačí pouze informace, že ta či ona událost proběhla, ale je nutné získat i nějaké doplňující informace. K tomu nám slouží vlastní delegát události, který může obsahovat třídu argumentů speciálně vytvořenou pro danou událost. Pomocí této třídy, jež je odvozena od třídy EventArgs, můžeme předávat informace (např. chybový kód či chybovou zprávu) v okamžiku, kdy se bude jednat o událost vyvolanou při nějakém chybovém stavu. Nyní se podíváme na oba případy podrobněji.
Celou problematiku si ukážeme na třídě, která reprezentuje automobil. Jako každý automobil bude mít naše třída palivovou nádrž a tzv. “Hladové oko” – kontrolku signalizující docházející palivo. V našem případě se kontrolka rozsvítí v okamžiku, kdy bude v nádrži méně než 5 litrů paliva.
Podívejme se nyní na třídu Auto:

Třída obsahuje dvě konstanty: VelikostNadrze nastavenou na 60 litrů a VelikostRezervy s hodnotou 5 litrů. Dále proměnou pro objekt časovače t, vlastnost ObsahNadrze, ve které budeme uchovávat informaci o aktuálním obsahu nádrže v litrech a logickou vlastnost SvitiRezerva indikující zda svítí “hladové oko”. Konstruktor obsahuje nastavení časovače na 1 vteřinu a objednání události Elapsed, tedy uplynutí intervalu časovače. Při vyvolání této události se zavolá metoda t_Elapsed, ve které se provedou tyto úkony:
- Zmenší se obsah nádrže o jeden litr, což simuluje jízdu.
- Pokud je nádrž prázdná, vyvolá událost “došlo palivo” a zavolá se metoda “zastav motor”, která simuluju zastavení motoru při nedostatku paliva.
- Pokud není nádrž prázdná, vyvolá událost obsahující informaci o aktuálním stavu paliva v nádrži.
- Pokud je obsah nádrže menší než velikost rezervy a kontrolka ještě nesvítí, rozsvítí “hladové oko” a vyvolá událost informující o tom, že dochází palivo.
Přehledně se na to můžeme podívat v následujícím vývojovém diagramu reprezentujícím metodu t_Elapsed volanou událostí časovače Elapsed.

Dále třída Auto obsahuje metodu pro spuštění motoru SpustMotor, což na naší simulaci představuje spuštění časovače a samozřejmě metodu pro zastavení motoru ZastavMotor. Nezbytná je metoda NatankujPalivo, která naplní nádrž až po okraj.
Samostatnou kapitolou jsou události třídy Auto. Jak jsme si již říkali na začátku článku, třída obsahuje dva druhy událostí. Všechny události, kromě události StavPaliva, využívají standartního delegáta. Událost StavPaliva má vlastního delegáta StavPalivaEventHandler, který obsahuje již zmiňovanou třídu argumentů AutoEventArgs. Třída AutoEventArgs obsahuje jedinou vlastnost ObsahNadrze, která uvádí množství paliva v nádrži v litrech.
Události se dají rozdělit na dvě části. První je vytvoření a vyvolání události na straně objektu, který událostmi disponuje, v našem případě třídy Auto. Druhou částí je obsluha události na straně vnějšího okolí objektu, v našem případě to bude testovací program, lépe řečeno konzole.
Události uvnitř objektu
Vytvoření události se standartním delegátem je poměrně jednoduché a stačí uvést následující deklaraci události obsahující klíčové slovo event a delegáta, ke kterému se vztahuje.
public event EventHandler DochaziPalivo;
Přičemž
EventHandler je již deklarovaný delegát v ASP.NETu a vypadá takto:
public delegate void EventHandler(object sender, EventArgs e)
Po vytvoření deklarace už můžeme naši událost vyvolávat:
if (this.DosloPalivo != null)
this.DosloPalivo(this, new EventArgs());
Před skutečným vyvoláním události je nutné zjistit, zda si ji někdo objednal, respektive vytvořil tělo této události. To zjistíme dotazem, jestli se vyvolávaná událost nerovná
null. Je-li událost objednaná, zavoláme ji jako jakoukoliv jinou metodu.
Pokud bychom chtěli vytvořit událost, která bude předávat i další informace, musíme si vytvořit vlastního delegáta a jako jeden z parametrů použít třídu argumentů odvozenou od třídy EventArgs. Postupně se na to podívejme. Naše třída argumentů může vypadat například takto:
public class AutoEventArgs : EventArgs
{
public int ObsahNadrze
{
get;
set;
}
public AutoEventArgs(int ObsahNadrze)
{
this.ObsahNadrze = ObsahNadrze;
}
}
Jak je vidět, v tomto případě třída
AutoEventArgs obsahuje pouze vlastnost
ObsahNadrze, ve které se předává aktuální množství paliva v nádrži, a samozřejmě konstruktor, který má jako parametr celé číslo, jež je přiřazeno do popisované vlastnosti. Pokud by bylo třeba, tato třída může obsahovat i další informace, případně metody.
Máme-li naši třídu argumentů připravenou, nic nám nebrání abychom si vytvořili onoho vlastního delegáta:
public delegate void StavPalivaEventHandler(object sender, AutoEventArgs e);
Je vidět, že se náš delegát příliš od standartního neliší. Zaměněna je pouze již zmiňovaná třída argumentů. Nyní můžeme přistoupit k poslednímu kroku a tím je vytvoření naší události:
public event StavPalivaEventHandler StavPaliva;
Událost je vytvořena a my s ní můžeme začít pracovat. Vyvolání takovéto události je analogické jako u předchozí ukázky, pouze při vytváření instance třídy argumentů je jako parametr předána informace o množství paliva v nádrži.
if (this.StavPaliva != null)
this.StavPaliva(this, new AutoEventArgs(55));
Jako v předchozí ukázce volání události, i zde je nutné testovat, zda si někdo událost objednal. Pokud bychom to neotestovali a událost nebyla objednána, skončili bychom v chybě “
Object reference not set to an instance of an object”.
Nyní se podívejme na třídu Auto jako na celek:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
public class Auto
{
public event EventHandler DochaziPalivo;
public event EventHandler DosloPalivo;
public event EventHandler SpustenyMotor;
public event EventHandler ZastavenyMotor;
public delegate void StavPalivaEventHandler(object sender, AutoEventArgs e);
public event StavPalivaEventHandler StavPaliva;
private const int VelikostRezervyPaliva = 5;
private const int VelikostPalivoveNadrze = 30;
private Timer t;
public int ObsahNadrze
{
get;
private set;
}
public bool SvitiRezerva
{
get;
private set;
}
public Auto()
{
t = new Timer(1000);
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
}
public void NatankujPalivo()
{
this.ObsahNadrze = VelikostPalivoveNadrze;
this.SvitiRezerva = false;
}
public void SpustMotor()
{
t.Start();
if (this.SpustenyMotor != null)
this.SpustenyMotor(this, new EventArgs());
}
public void ZastavMotor()
{
t.Stop();
if (this.ZastavenyMotor != null)
this.ZastavenyMotor(this, new EventArgs());
}
private void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (--ObsahNadrze == 0)
{
if (this.DosloPalivo != null)
this.DosloPalivo(this, new EventArgs());
this.ZastavMotor();
}
if (this.StavPaliva != null)
this.StavPaliva(this, new AutoEventArgs(this.ObsahNadrze));
if (ObsahNadrze <= VelikostRezervyPaliva && !this.SvitiRezerva)
{
this.SvitiRezerva = true;
if (this.DochaziPalivo != null)
this.DochaziPalivo(this, new EventArgs());
}
}
public class AutoEventArgs : EventArgs
{
public int ObsahNadrze
{
get;
set;
}
public AutoEventArgs(int ObsahNadrze)
{
this.ObsahNadrze = ObsahNadrze;
}
}
}
Tím jsme si popsali třídu
Auto a ještě se podívejme, jak s touto třídou pracuje testovací aplikace.
Události vně objektu
Máme-li třídu Auto, můžeme si s touto třídou hrát a vše řádně otestovat. Za tímto účelem si vytvoříme konzolovou aplikaci, která bude simulovat chování řidiče. Scénář bude vypadat takto:
- Vytvoříme instanci třídy Auto.
- Natankujeme palivo.
- Spustíme motor.
O spuštění motoru nás bude informovat událost, sami budeme sledovat informace o množství paliva v nádrži a zároveň budeme informováni o tom, že v nádrži je méně paliva než je rezerva – díky tomu se rozsvítí kontrolka “hladové oko”. O nulovém stavu paliva budeme opět informováni událostí. Nakonec dojde k ukončení běhu motoru, o čemž budeme opět informováni.
Dosti abstrakce, pojďme se na to podívat. Pro to, abychom mohli být informování událostí, musíme si pro každou z popisovaných událostí nastavit odběr, tedy si jí objednat. To se prování za pomoci operátoru +=. Pokud bychom chtěli událost z nějakého důvodu odregistrovat, můžeme tak učinit za pomoci operátoru –=.
Objekt.Udalost += new NazevDelegata(NazevMetody);
private void NazevMetody(object sender, EventArgs e)
{
//tělo obslužné události
}
Nyní se pojďme podívat na reálnou testovací aplikaci, jejíž chování jsme si popsali.
class Program
{
private static Auto shAuto;
static void Main(string[] args)
{
//nová instance automobilu
shAuto = new Auto();
//objednání událostí
shAuto.DochaziPalivo += new EventHandler(shAuto_DochaziPalivo);
shAuto.DosloPalivo += new EventHandler(shAuto_DosloPalivo);
shAuto.SpustenyMotor += new EventHandler(shAuto_SpustenyMotor);
shAuto.ZastavenyMotor += new EventHandler(shAuto_ZastavenyMotor);
shAuto.StavPaliva += new Auto.StavPalivaEventHandler(shAuto_StavPaliva);
//natankujeme nádrž
shAuto.NatankujPalivo();
//spustíme motor a budeme čekat než nám dojde palivo
shAuto.SpustMotor();
//zamezeni ukončení konzole
Console.Read();
}
//zpracování jednotlivých událostí
private static void shAuto_StavPaliva(object sender, Auto.AutoEventArgs e)
{
Console.WriteLine("V nádrži je {0} litrů paliva", e.ObsahNadrze);
}
private static void shAuto_DosloPalivo(object sender, EventArgs e)
{
Console.WriteLine("Došlo palivo!");
}
private static void shAuto_ZastavenyMotor(object sender, EventArgs e)
{
Console.WriteLine("Motor je zastaven...");
}
private static void shAuto_SpustenyMotor(object sender, EventArgs e)
{
Console.WriteLine("Motor je spuštěn...");
}
private static void shAuto_DochaziPalivo(object sender, EventArgs e)
{
Console.WriteLine("Dochazí palivo, natankuj!");
Console.WriteLine("Hladové oko: {0}", shAuto.SvitiRezerva ? "Svítí" : "Nesvítí");
}
}
Po spuštění se na konzoli vypíší informace z třídy
Auto dle popisovaného scénáře. Pro ukončení aplikace je třeba stisknout například klávesu enter.
Registrace událostí v prostření Visual Studia
Pokud pracujeme ve Visual Studiu, můžeme pro registraci událostí využívat vlastnost tohoto prostředí, která se nám snaží celý proces zjednodušit. Po zapsání operátoru += za název události objektu nám Visual Studio zobrazí volbu v podobě nabídky IntelliSence, ve které je možnost automatického vytvoření asociace s delegátem události při stisknutí klávesy TAB.

Při druhém stisknutí klávesy TAB vytvoří přímo kostru metody, která bude událost obsluhovat.

Tato kostra potom vypadá takto:
private static void shAuto_DochaziPalivo(object sender, EventArgs e)
{
//prostor pro zpracování události
}
Nyní bychom již měli být schopni vytvářet vlastní události objektů se standartním i vlastním delegátem události. Pro úplnost ještě přikládám zdrojový kód příkladu zde.
Tím je to pro dnešek vše.