Grafikus C# – Windows Forms tanfolyam › 5.1 lecke

Windows Forms · 5.1 lecke · események

Események, delegáltak és eseménykezelő metódusok

Az első leckéktől fogva eseménykezelőket írtál: a btnMent_Click lefut, ha rákattintasz a gombra. De hogyan tudja a gomb, hogy melyik metódust hívja? Mi történik a háttérben, és hogyan hozhatsz létre saját eseményt? Ebben a leckében a „varázslat” mögé nézünk: az eseménykezelő metódus felépítésébe, a delegáltakba, a feliratkozásba (+=) és leiratkozásba (-=), a lambda kifejezésekbe, és megírod az első saját eseményedet.

  • kb. 110 perc
  • közepes szint
  • események és delegáltak
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • érted az esemény szerepét: valami történik, és a program erre reagál;
  • felismered az eseménykezelő metódus szerkezetét (object sender, EventArgs e);
  • tudod, mi a delegált, és hogyan hivatkozik metódusra;
  • az eseményt kódból is összekapcsolod (+=), és szükség esetén leválasztod (-=);
  • használsz lambda kifejezést rövid eseménykezelőhöz;
  • saját eseményt hozol létre és váltasz ki (event, ?.Invoke), és felismered a gyakori hibákat.

Mielőtt elkezded

Ehhez a leckéhez az 1. fejezet eseménykezelői, a 0.5 lecke (Form1.Designer.cs) és a 3.9 lecke (osztály, tulajdonság) szükséges. Új Windows Forms projektet készíts, és kísérletezz a példákkal.

4.8 Bemutatás és dokumentálás →

Mi az esemény?

A grafikus programok eseményvezéreltek (0.2 lecke): nem egy előre megírt sorrendben futnak végig, hanem várnak, és reagálnak arra, ami történik: egy kattintásra, egy billentyűre, egy beírt szövegre, egy lejáró időzítőre. Az esemény (event) egy jelzés: „ez történt”. Az eseménykezelő (event handler) pedig az a metódus, amely a jelzésre reagál.

Hasonlat: a csengő (a Button) nem tudja, ki van otthon és mit csináljon. Annyit tud, hogy megszólal, és akik előfizettek rá (a lakók), azok reagálnak: az egyik ajtót nyit, a másik nem figyel. A csengő a kibocsátó (publisher), a lakók a feliratkozók (subscribers). A kibocsátó nem ismeri a feliratkozókat, ezért egy csengőhöz bármennyi (akár nulla) lakó tartozhat.

KibocsátóAz objektum, amelynek az eseménye van: például a Button (Click), a TextBox (TextChanged), a Form (Load).
EseményA jelzés neve: Click, TextChanged, FormClosing.
EseménykezelőA metódus, amely lefut, ha az esemény bekövetkezik.
FeliratkozásAz összekötés: „ha ez az esemény bekövetkezik, hívd ezt a metódust”.

Jósolj!

Egy gombnak nincs eseménykezelője (senki nem iratkozott fel a Click eseményére). Mi történik, ha rákattintasz? Hibát kapsz?

Jóslat ellenőrzése

Nem történik semmi, hiba nincs. A gomb jelzi az eseményt, de ha senki nem figyel rá, a jelzés elveszik. A feliratkozók nélküli esemény teljesen normális helyzet.

Az eseménykezelő metódus felépítése

Az eseménykezelő metódusnak pontosan meghatározott szerkezete van. Ha duplán kattintasz egy gombra a tervezőben, a Visual Studio ezt generálja:

Form1.csC#
private void btnKoszon_Click(object sender, EventArgs e)
{
MessageBox.Show("Szia!");
}
voidAz eseménykezelő nem ad vissza értéket.
btnKoszon_ClickA név szabadon választható, de a megegyezés: vezérlő neve + _ + esemény neve. A név önmagában nem köti a metódust az eseményhez.
object senderA kibocsátó: az az objektum, amelyik az eseményt jelezte (például a megnyomott gomb). Részletesen az 5.2 leckében.
EventArgs eAz esemény részletei. A Click esetén nincs benne érdemi információ, a KeyPress-nél a lenyomott karakter is benne van.

Hol köti össze a Visual Studio az eseményt és a metódust?

A kapcsolatot nem a metódus neve hozza létre, hanem egy sor a Form1.Designer.cs fájlban, az InitializeComponent metódusban (0.5 lecke). Ezt a Designer kezeli, ezért ne szerkeszd kézzel, de érdemes megnézni:

Form1.Designer.cs (részlet)C#
// btnKoszon
this.btnKoszon.Name = "btnKoszon";
this.btnKoszon.Text = "Köszön";
this.btnKoszon.Click += new System.EventHandler(this.btnKoszon_Click);

Az utolsó sor a lényeg: a btnKoszon Click eseményére feliratkozik a btnKoszon_Click metódus. A += a feliratkozás jele, az EventHandler pedig a delegált típusa, amelyet a következő szakaszban nézünk meg.

Jósolj!

Átírod a btnKoszon_Click metódus nevét btnKoszon_Kattint-ra a Form1.cs fájlban (a Designer.cs-t nem módosítod). Lefordul-e a program? Hogy hívják ezt a hibát?

Jóslat ellenőrzése

Nem fordul le. A Designer.cs továbbra is a btnKoszon_Click nevű metódushoz akar kötni, de az már nincs: CS1061: A „Form1” nem tartalmaz „btnKoszon_Click” nevű definíciót. A javítás: a metódus átnevezése a Visual Studio Rename funkciójával (jobb gomb a néven → Rename), amely a Designer.cs-t is átírja, vagy a Properties ablak Events nézetében a kapcsolat átállítása.

A delegált: metódusra mutató típus

A delegált (delegate) egy típus, amelynek értéke egy metódus. Úgy működik, mint egy mutató vagy névjegykártya: nem a metódus kódját tartalmazza, hanem hivatkozik rá, és a delegáltat hívva a hivatkozott metódus fut le. A delegált típus megadja, milyen szerkezetű metódusra hivatkozhat (paraméterek és visszatérési típus):

Form1.cs (kísérlet)C#
// delegált típus: két egész számot kap, egész számot ad vissza
delegate int Muvelet(int a, int b);

private int Osszead(int a, int b) { return a + b; }
private int Szoroz(int a, int b) { return a * b; }

private void btnProba_Click(object sender, EventArgs e)
{
Muvelet m = Osszead; // a delegált az Osszead metódusra hivatkozik
MessageBox.Show(m(3, 4).ToString()); // 7

m = Szoroz; // most a Szoroz metódusra
MessageBox.Show(m(3, 4).ToString()); // 12
}
delegate int Muvelet(int a, int b)Új típust hoz létre: olyan metódusokra hivatkozhat, amelyek két int paramétert kapnak, és int értéket adnak vissza.
Muvelet m = OsszeadA változó az Osszead metódusra hivatkozik (a metódus nevét zárójel nélkül írjuk: nem hívjuk, csak hivatkozunk rá).
m(3, 4)A delegált hívása: a hivatkozott metódus fut le.
ÁtállíthatóA változó értéke bármikor másik, azonos szerkezetű metódusra állítható. Ez a delegált lényege: a hívandó metódus változó lehet.

Az EventHandler egy delegált

A Windows Forms események típusa is delegált. Az EventHandler definíciója a .NET-ben:

System névtérC#
public delegate void EventHandler(object sender, EventArgs e);

Ebből következik az eseménykezelő szerkezete: olyan metódus, amely object és EventArgs paramétert kap, és nem ad vissza értéket (void). A Click esemény tehát EventHandler típusú delegáltak gyűjteménye: az eseményre feliratkozó metódusokat tárolja, és az esemény bekövetkeztekor mindet meghívja.

Beépített delegált típusok: Action és Func

Nem kell mindig saját delegált típust írnod. Az Action olyan metódusra hivatkozik, amely nem ad vissza értéket (Action<int>: egy int paraméter), a Func<int, int, int> két int paramétert kap, és int értéket ad vissza (az utolsó típus a visszatérési érték). A fenti Muvelet helyett Func<int, int, int> is használható.

Jósolj!

A Muvelet m = Osszead; sort átírod Muvelet m = Osszead(3, 4); alakra. Lefordul-e? Miért nem ugyanaz a két sor?

Jóslat ellenőrzése

Nem fordul le. Az Osszead(3, 4) a metódus hívása, amely egy int értéket (7) ad, nem egy Muvelet típusú delegáltat. A zárójel nélküli Osszead a metódusra hivatkozik. A különbség ugyanaz, mint az eseményeknél: a += btnKoszon_Click nem hívja a metódust, csak feliratkoztatja az eseményre.

Feliratkozás és leiratkozás kódból

A tervezőn kívül kódból is feliratkozhatsz egy eseményre, a += operátorral, és leiratkozhatsz a -= operátorral. Erre akkor van szükség, ha a vezérlőt kódból hozod létre, vagy az eseménykezelő csak bizonyos ideig kell:

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
btnProba.Click += Proba_Click; // feliratkozás
}

private void Proba_Click(object sender, EventArgs e)
{
MessageBox.Show("Proba");
}

private void btnLeiratkozik_Click(object sender, EventArgs e)
{
btnProba.Click -= Proba_Click; // leiratkozás: a Proba_Click többé nem fut le
}

Több feliratkozó: a „multicast”

Egy eseményre több metódus is feliratkozhat. Ezek a feliratkozás sorrendjében futnak le:

Form1.csC#
btnProba.Click += Elso;
btnProba.Click += Masodik;
btnProba.Click += Elso; // ugyanaz a metódus kétszer is feliratkozhat!

// egy kattintás: Elso, Masodik, Elso

Lambda kifejezés: névtelen eseménykezelő

Rövid, egyszeri eseménykezelőhöz nem kell külön metódust írni: a lambda kifejezés (=> jellel) névtelen metódust ad:

Form1.csC#
// (paraméterek) => { utasítások }
btnProba.Click += (s, ev) =>
{
MessageBox.Show("Lambda kezelő");
};

// ha egyetlen utasítás, rövidebben:
btnProba.MouseEnter += (s, ev) => btnProba.BackColor = Color.LightYellow;
btnProba.MouseLeave += (s, ev) => btnProba.BackColor = SystemColors.Control;
(s, ev) => ...A lambda paraméterei (itt sender és EventArgs), a => jel után jön a kód. A paraméterek nevét te adod.
Mikor jó?Rövid, egyszerű reakcióhoz (színváltás, egy felirat átírása).
Mikor nem?Ha később le akarsz iratkozni: a névtelen lambdára nem hivatkozhatsz a -= operátorban (ugyanazt a lambdát nem tudod még egyszer leírni). Hosszabb, fontos kódhoz külön, névvel ellátott metódus kell.
Hol látja a külső változókat?A lambda a körülötte lévő változókat is eléri (capture), például az btnProba vezérlőt vagy egy helyi változót.

Jósolj!

Egy gomb Click eseményére kétszer is feliratkozol ugyanazzal a metódussal: egyszer a tervezőben (Events nézet), egyszer a Form1_Load-ban (+=). Hányszor fut le a metódus egy kattintásra?

Jóslat ellenőrzése

Kétszer. Minden feliratkozás külön számít, ezért az üzenet kétszer jelenik meg. Ez az egyik leggyakoribb eseménykezelési hiba: a metódust a tervező és a kód is hozzákötötte. Megoldás: csak az egyik helyen iratkozz fel (kódból feliratkozva a tervezőben ne válaszd ki a metódust).

Saját esemény létrehozása

Az események nemcsak a vezérlőknek vannak: a saját osztályaidnak is lehet eseménye. Ez azt jelenti, hogy az osztály „jelez” valamit, és nem kell ismernie, ki figyel rá. Készítsünk egy számlálót, amely jelez, ha eléri a határértéket. Először az osztály:

Szamlalo.csC#
public class Szamlalo
{
private int ertek = 0;

public int Hatar { get; set; } = 5;

public int Ertek
{
get { return ertek; }
}

// az esemény deklarációja
public event EventHandler ElerteAHatart;

public void Novel()
{
ertek = ertek + 1;

if (ertek >= Hatar)
{
// az esemény kiváltása: ha van feliratkozó, meghívja
ElerteAHatart?.Invoke(this, EventArgs.Empty);
}
}

public void Nulloz()
{
ertek = 0;
}
}

És a használata az ablakban:

Form1.csC#
private Szamlalo szamlalo = new Szamlalo();

private void Form1_Load(object sender, EventArgs e)
{
szamlalo.ElerteAHatart += Szamlalo_ElerteAHatart; // feliratkozás a saját eseményre
}

private void Szamlalo_ElerteAHatart(object sender, EventArgs e)
{
lblUzenet.Text = "Elérted a határt!";
}

private void btnNovel_Click(object sender, EventArgs e)
{
szamlalo.Novel();
lblErtek.Text = szamlalo.Ertek.ToString();
}
public event EventHandler ElerteAHatart;Az esemény deklarációja. Típusa az EventHandler delegált: olyan metódusok iratkozhatnak fel, amelyeknek a szerkezete (object, EventArgs).
ElerteAHatart?.Invoke(this, EventArgs.Empty)Az esemény kiváltása. A ?. (null-feltételes operátor) csak akkor hívja az Invoke metódust, ha van legalább egy feliratkozó. A this a kibocsátó (sender), az EventArgs.Empty az üres adat.
A kibocsátó nem ismeri a feliratkozótA Szamlalo osztály nem tud a Form1-ről, a feliratkozás kívülről történik. Ezért újrahasznosítható: egy másik program más módon reagálhat ugyanarra a jelre.
Csak az osztály váltja kiAz event kulcsszó miatt az osztályon kívülről az eseményt nem lehet kiváltani, csak += és -= műveletet lehet végezni rajta. Ez védi az osztályt a külső beavatkozástól.

Jósolj!

Az Hatar értéke 5. Hányadik Novel() hívásnál jelenik meg az üzenet, és megjelenik-e újra a 6. hívásnál? Mit tennél, hogy az üzenet csak egyszer jelenjen meg?

Jóslat ellenőrzése

Az 5. hívásnál jelenik meg (ertek >= Hatar először akkor igaz). A 6. és minden további hívásnál újra kiváltódik, mert a feltétel továbbra is igaz (6 >= 5). Ha csak egyszer kell, a feltételt ertek == Hatar alakra írhatod át (csak pontosan a határnál jelez), vagy a kiváltás után egy jelzővel megjegyezheted, hogy már jelzett.

📷 Képernyőkép helye

Mit tegyél ide: a futó program: egy Növel gomb, az érték felirata (5), és alatta a piros „Elérted a határt!” üzenet.Javasolt fájlnév: winforms-5-1-szamlalo.webp · Alt szöveg: Saját esemény a számláló osztályon

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. A metódus nem fordul le átnevezés után

// CS1061: A „Form1” nem tartalmaz „btnMent_Click” nevű definíciót
Hiba és javítás

A Form1.Designer.cs még a régi nevű metódust keresi. Állítsd vissza a metódus nevét, vagy használd a Rename funkciót, vagy a Properties → Events nézetben válaszd ki az új metódust.

2. A metódus kétszer fut le

Egy kattintásra az üzenet kétszer jelenik meg.

Ok és javítás

A metódusra kétszer iratkoztál fel: a tervezőben (Events nézet) és kódból (+=). Töröld az egyiket. A Designer.cs fájlban kereshetsz is a Click += sorra.

3. Nem tudsz leiratkozni a lambdáról

btnProba.Click += (s, e) => MessageBox.Show("Hello");
btnProba.Click -= (s, e) => MessageBox.Show("Hello"); // nem működik!
Hiba és javítás

A második lambda egy új névtelen metódus, amelyre senki nem iratkozott fel, ezért a -= nem csinál semmit (hiba sincs). Ha le akarsz iratkozni, használj névvel ellátott metódust, vagy tárold a lambdát egy változóban: EventHandler kezelo = (s, e) => ...; btnProba.Click += kezelo; ... btnProba.Click -= kezelo;

4. NullReferenceException az esemény kiváltásakor

ElerteAHatart(this, EventArgs.Empty);   // hiba, ha nincs feliratkozó
Hiba és javítás

Ha az eseményre senki nem iratkozott fel, az érték null, és a hívása NullReferenceException hibát dob. Használd a ?.Invoke(...) alakot: ElerteAHatart?.Invoke(this, EventArgs.Empty); A ?. csak akkor hív, ha van feliratkozó.

5. Rossz szerkezetű metódus

private void Kezelo()   // hiányoznak a paraméterek
{
}

btnProba.Click += Kezelo;
// CS0123: A „Kezelo” egyetlen túlterhelése sem felel meg a delegált „EventHandler” típusnak
Hiba és javítás

A Click esemény EventHandler típusú, tehát a metódusnak (object sender, EventArgs e) paraméterekkel kell rendelkeznie. Ha nincs szükséged a paraméterekre, akkor is meg kell adni őket (vagy lambda: (s, ev) => Kezelo()).

6. Az esemény nem fut le

A metódus megvan, de a gombnyomásra semmi sem történik.

Ok és javítás

Valószínűleg nincs feliratkozva a metódus az eseményre. Ellenőrizd a Properties ablak Events nézetét (villám ikon): a Click sorban a metódus neve áll-e. Ha a metódust másolással hoztad létre, a Designer nem tud róla. Kódból a += sor hiányozhat.

Mini projekt: eseménynapló

Készíts programot, amely egy saját esemény és a vezérlők eseményeinek működését napló formájában mutatja meg:

  1. Az ablakon: btnNovel, btnNulloz, btnKetszeres, lblErtek, lstNaplo (ListBox).
  2. Egy Szamlalo osztály (a leckéből), amelynek két eseménye van: ElerteAHatart és Nullazodott.
  3. Az ablak mindkét eseményre feliratkozik, és mindegyik reakció egy sort ír a naplóba (HH:mm:ss – szöveg, a legújabb felül).
  4. A btnKetszeres egy kattintásra kétszer növeli a számlálót úgy, hogy a btnKetszeres.Click eseményre két különböző metódus iratkozik fel kódból.
  5. Egy jelölőnégyzet (chkFigyel) be- és kikapcsolja a feliratkozást a ElerteAHatart eseményre (+= és -=), és minden váltást a napló is rögzít.
  6. Teszteld: a határ elérése, nullázás, feliratkozás ki- és bekapcsolása, a gombok többszöri megnyomása.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A napló egy közös metódus (Naplo), amelyet minden kezelő hív. A chkFigyel a feliratkozást a CheckedChanged eseményben kapcsolja. Fontos, hogy a feliratkozás egyszer történjen meg: a Form1_Load csak akkor iratkozik fel, ha a jelölőnégyzet bejelölt.

Szamlalo.cs és Form1.csC#
// Szamlalo.cs
public class Szamlalo
{
private int ertek = 0;

public int Hatar { get; set; } = 5;
public int Ertek { get { return ertek; } }

public event EventHandler ElerteAHatart;
public event EventHandler Nullazodott;

public void Novel()
{
ertek = ertek + 1;

if (ertek == Hatar)
{
ElerteAHatart?.Invoke(this, EventArgs.Empty);
}
}

public void Nulloz()
{
ertek = 0;
Nullazodott?.Invoke(this, EventArgs.Empty);
}
}

// Form1.cs
private Szamlalo szamlalo = new Szamlalo();

private void Naplo(string szoveg)
{
lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " – " + szoveg);
}

private void Form1_Load(object sender, EventArgs e)
{
szamlalo.Nullazodott += (s, ev) => Naplo("A számláló nullázódott.");

btnKetszeres.Click += Egyszer;
btnKetszeres.Click += Masodszor;

chkFigyel.Checked = true; // ez váltja ki a CheckedChanged eseményt, és ott iratkozunk fel
}

private void Elerte_Kezelo(object sender, EventArgs e)
{
Naplo("A számláló elérte a határt!");
}

private void chkFigyel_CheckedChanged(object sender, EventArgs e)
{
if (chkFigyel.Checked)
{
szamlalo.ElerteAHatart += Elerte_Kezelo;
Naplo("Feliratkoztunk a határ eseményére.");
}
else
{
szamlalo.ElerteAHatart -= Elerte_Kezelo;
Naplo("Leiratkoztunk a határ eseményéről.");
}
}

private void Frissit()
{
lblErtek.Text = szamlalo.Ertek.ToString();
}

private void btnNovel_Click(object sender, EventArgs e)
{
szamlalo.Novel();
Frissit();
}

private void btnNulloz_Click(object sender, EventArgs e)
{
szamlalo.Nulloz();
Frissit();
}

private void Egyszer(object sender, EventArgs e)
{
szamlalo.Novel();
}

private void Masodszor(object sender, EventArgs e)
{
szamlalo.Novel();
Frissit();
}
📷 Képernyőkép helye

Mit tegyél ide: a kész program: a három gomb, az érték, a jelölőnégyzet és a napló néhány sorral (köztük a „Feliratkoztunk” és a „határt elérte” sorral).Javasolt fájlnév: winforms-5-1-kesz.webp · Alt szöveg: Az eseménynapló program

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. A kezelők sorrendje

Egy gomb Click eseményére iratkoztass fel három metódust (A, B, C), amelyek egy ListBox-ba írják a nevüket. Milyen sorrendben jelennek meg? Mi történik, ha az A kezelőre kétszer iratkozol fel, és a B kezelőről leiratkozol a kattintás közben?

Egy lehetséges megoldás

A kezelők a feliratkozás sorrendjében futnak (A, B, C). Kétszeri feliratkozásnál az A kétszer fut le. A leiratkozás hatása a következő kattintásra érvényes, de az éppen futó eseményláncra nem mindig: a .NET az esemény kiváltásakor egy pillanatképpel dolgozik, ezért a B az éppen futó kattintásnál még lefuthat. A pontos viselkedést érdemes kipróbálni és megfigyelni.

Form1.csC#
private void A(object s, EventArgs e) { lstSor.Items.Add("A"); }
private void B(object s, EventArgs e) { lstSor.Items.Add("B"); }
private void C(object s, EventArgs e) { lstSor.Items.Add("C"); }

private void Form1_Load(object sender, EventArgs e)
{
btnProba.Click += A;
btnProba.Click += B;
btnProba.Click += C;
}

2. Egyszer lefutó kezelő

Egy btnBeallit gomb első megnyomásakor jelenjen meg egy üzenet (Az első kattintás!), és ezután a gomb ne váltson ki több ilyen üzenetet. Oldd meg leiratkozással.

Egy lehetséges megoldás

A kezelő a végén leiratkozik magáról a -= operátorral. Névvel ellátott metódus kell, mert a lambdáról nem lehetne leiratkozni.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
btnBeallit.Click += ElsoKattintas;
}

private void ElsoKattintas(object sender, EventArgs e)
{
MessageBox.Show("Az első kattintás!");
btnBeallit.Click -= ElsoKattintas;
}

3. Saját esemény adatokkal

Bővítsd a Szamlalo osztályt úgy, hogy az ElerteAHatart esemény átadja az elért értéket is (Elérted a határt: 5). Hogyan oldod meg az EventArgs segítségével?

Egy lehetséges megoldás

Saját EventArgs-ból származó osztály (SzamlaloEventArgs) viszi az adatot, az esemény típusa pedig az általános EventHandler<SzamlaloEventArgs>. A kezelő e.Ertek alakban éri el az adatot. Az öröklődés (: EventArgs) részletesebb tárgyalása a 12. fejezetben következik.

Szamlalo.cs és Form1.csC#
// Szamlalo.cs
public class SzamlaloEventArgs : EventArgs
{
public int Ertek { get; set; }
}

public event EventHandler<SzamlaloEventArgs> ElerteAHatart;

// a kiváltás:
ElerteAHatart?.Invoke(this, new SzamlaloEventArgs { Ertek = ertek });

// Form1.cs
private void Szamlalo_ElerteAHatart(object sender, SzamlaloEventArgs e)
{
lblUzenet.Text = "Elérted a határt: " + e.Ertek;
}

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Delegált és esemény: a különbség

Egy mondatban magyarázd el: mi a delegált, és mi az esemény? Miért nem iratkozhat fel (+=) egy egyszerű delegált-változóra az osztály kívülről?

Megoldás megnyitása

A delegált egy típus, amelynek értéke egy (vagy több) metódusra mutató hivatkozás. Az esemény egy delegáltra épülő, védett szerkezet: kívülről csak feliratkozni (+=) és leiratkozni (-=) lehet rajta, kiváltani csak a tulajdonos osztály tudja. Egy sima nyilvános delegált-mezőt bárki felülírhatna (= ...) vagy meghívhatna, ezért az event kulcsszó védi a kibocsátó osztályt.

2. Az eseménykezelő szerkezete

Írd meg egy lblFelirat MouseEnter eseményének kezelőjét úgy, hogy a felirat színe pirosra váltson. Mi a metódus pontos szerkezete?

Megoldás megnyitása

A MouseEnter is EventHandler típusú: void visszatérés, object sender és EventArgs e paraméterek. A feliratkozás a tervezőben (Events nézet) vagy kódból történik.

Form1.csC#
private void lblFelirat_MouseEnter(object sender, EventArgs e)
{
lblFelirat.ForeColor = Color.Red;
}

// kódból: lblFelirat.MouseEnter += lblFelirat_MouseEnter;

Közepes szint

3. Gombok kódból

Egy Form1_Load esemény kódból hozzon létre egy Button-t (Kattints rám), tegye az ablakra, és iratkozzon fel a Click eseményére, amely üzenetet ír ki. Miért kell a gombot a Controls gyűjteményhez adni?

Megoldás megnyitása

A kódból létrehozott vezérlő addig nem látszik, amíg nincs az ablak (vagy egy panel) Controls gyűjteményében: a gyűjtemény mondja meg, mely vezérlők tartoznak az ablakhoz, és ezeket rajzolja ki. A tervező is ezt teszi az InitializeComponent metódusban.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
Button gomb = new Button();
gomb.Text = "Kattints rám";
gomb.Location = new Point(20, 20);
gomb.Size = new Size(150, 40);

gomb.Click += (s, ev) => MessageBox.Show("Megnyomtál!");

this.Controls.Add(gomb);
}

4. Lambda és külső változó

Hozz létre kódból öt gombot (1 … 5) egy ciklusban, amelyek mindegyike a saját számát írja ki egy üzenetben. Mi a buktató a ciklusváltozóval és a lambdával?

Megoldás megnyitása

A lambda a külső változót eléri, de a ciklusváltozó (i) egyetlen változó, amely a ciklus végén 6 értékű. A for ciklusnál ezért a lambda mind az 5 gombnál a ciklus végén lévő értéket látná. A megoldás: a ciklusban egy helyi másolat (int szam = i;), amelyet a lambda megjegyez. A foreach ciklusban ez a probléma a modern C#-ban nem áll fenn, de a for-nál igen.

Form1.csC#
for (int i = 1; i <= 5; i++)
{
int szam = i; // helyi másolat minden körben

Button gomb = new Button();
gomb.Text = szam.ToString();
gomb.Location = new Point(20 + (szam - 1) * 60, 20);
gomb.Size = new Size(50, 40);

gomb.Click += (s, ev) => MessageBox.Show("A " + szam + ". gomb");

this.Controls.Add(gomb);
}

Kihívás szint

5. Az esemény kiváltója: a Termék osztály

Írj egy Termek osztályt Nev és Ar tulajdonsággal, amelynek van egy ArValtozott eseménye. Ha az ár megváltozik, az esemény kiváltódik (a régi és az új árral). Az ablak egy feliraton mutassa a változást.

Megoldás megnyitása

A tulajdonság set része váltja ki az eseményt, ha az érték ténylegesen változott. Az esemény adatai (régi és új ár) saját EventArgs-ban utaznak.

Termek.cs és Form1.csC#
public class ArEventArgs : EventArgs
{
public decimal Regi { get; set; }
public decimal Uj { get; set; }
}

public class Termek
{
private decimal ar;

public string Nev { get; set; } = "";

public decimal Ar
{
get { return ar; }
set
{
if (value == ar)
{
return;
}

decimal regi = ar;
ar = value;
ArValtozott?.Invoke(this, new ArEventArgs { Regi = regi, Uj = ar });
}
}

public event EventHandler<ArEventArgs> ArValtozott;
}

// Form1.cs
private Termek termek = new Termek { Nev = "Kenyér" };

private void Form1_Load(object sender, EventArgs e)
{
termek.ArValtozott += (s, ev) => lblValtozas.Text = $"{termek.Nev}: {ev.Regi} Ft → {ev.Uj} Ft";
}

private void btnBeallit_Click(object sender, EventArgs e)
{
termek.Ar = nudAr.Value;
}

6. Esemény vagy közvetlen hívás?

A Szamlalo osztály kódjában a Novel() metódus közvetlenül is hívhatná a Form1.UzenetetKiir() metódust az esemény helyett. Miért rossz ötlet? Mit nyersz az eseménnyel?

Megoldás megnyitása

Ha a Szamlalo közvetlenül hívná a Form1 metódusát, az osztály ismerné az ablakot: nem lehetne újra felhasználni egy másik programban, és a Form1 módosítása a Szamlalo-t is érintené. Az eseménynél a Szamlalo csak jelez, a feliratkozó dönti el, mit tesz. Így az osztály független, tesztelhető és újrahasznosítható. A rétegek szétválasztása (3.9 lecke) ugyanezt a célt szolgálja.

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Mi az esemény, és mi az eseménykezelő?

Az esemény egy jelzés arról, hogy valami történt. Az eseménykezelő az a metódus, amely a jelzésre reagál.

2. Milyen szerkezetű egy eseménykezelő metódus?

void visszatérés, két paraméter: object sender és EventArgs e (az EventHandler delegált szerkezete).

3. Mi a delegált?

Egy típus, amelynek értéke egy metódusra mutató hivatkozás. Megadja a metódus szerkezetét, és hívható.

4. Mit jelent a += és a -= egy eseménynél?

A += feliratkoztat egy metódust az eseményre, a -= leiratkoztatja.

5. Mi történik, ha egy metódusra kétszer iratkozol fel?

Kétszer fut le egy eseménynél. Tervező és kód együtt gyakran okozza ezt a hibát.

6. Miért kell a ?.Invoke az esemény kiváltásakor?

Mert ha nincs feliratkozó, az esemény null, és a közvetlen hívás NullReferenceException hibát dobna. A ?. csak akkor hív, ha van feliratkozó.

7. Miért jobb esemény, mint egy közvetlen metódushívás a kibocsátóból?

Mert a kibocsátó nem ismeri a feliratkozót: az osztály független és újrahasznosítható marad.

Mit tudsz már?

  • Érted az esemény, a kibocsátó, a feliratkozó és az eseménykezelő fogalmát.
  • Tudod, mi a delegált, és hogy az EventHandler egy delegált.
  • Kódból feliratkozol és leiratkozol (+=, -=), és használsz lambdát.
  • Saját eseményt hozol létre és váltasz ki (event, ?.Invoke).
  • Felismered a gyakori hibákat: duplázott feliratkozás, null kiváltás, hibás szerkezet.

Következő lecke: sender, EventArgs és eseményparaméterek

Az eseménykezelő két paraméterét mostanáig alig használtuk. A következő leckében megtudod, mire jó a sender, és milyen információt hordoz az EventArgs.