Grafikus C# – Windows Forms tanfolyam › 5.8 lecke Windows Forms · 5.8 lecke · közös kezelő Egy számológép tíz számjegy-gombja, egy játéktábla kilenc mezője, egy űrlap öt szövegmezője: ha mindegyiknek külön eseménykezelőt írnál, a kódod tele lenne ismétlődéssel. A megoldás a közös eseménykezelő: egyetlen metódus kezeli az összes hasonló vezérlő eseményét, és a Egy eseménykezelő több vezérlőelemhez
sender alapján tudja, melyik váltotta ki. Ebben a leckében megtanulod, hogyan rendeld hozzá a kezelőt sok vezérlőhöz (a tervezőben és kódból), hogyan azonosítsd a küldőt (Name, Tag, index), hogyan használd a switch utasítást, és hogyan generálj kódból egész vezérlőtáblát egyetlen kezelővel.
A lecke végére
- egyetlen kezelőt rendelsz sok vezérlőhöz a tervezőben (több vezérlő kijelölésével) és kódból is;
- a küldőt többféleképp azonosítod, és tudod, melyik mód mikor megbízható;
- a
Tagtulajdonságban adatot tárolsz a vezérlőhöz; - használod a
switchutasítást; - rekurzívan bejárod a beágyazott vezérlőket (
GroupBox,Panel); - kódból generálsz vezérlőtáblát egy közös kezelővel, és elkerülöd a ciklusváltozó csapdáját.
Mielőtt elkezded
Ehhez a leckéhez az 5.1–5.3 lecke (események, sender, egéresemények), az 5.7 lecke (feliratkozás, leiratkozás) és az 1.4 lecke (a közös kezelő első bemutatása) szükséges. Új projektben próbáld ki a példákat.
A közös kezelő hozzárendelése
Ugyanazt a metódust több vezérlőhöz háromféleképp kötheted:
1. A tervezőben, egyenként
Jelölj ki egy vezérlőt, a Properties ablakban nyisd meg az Events nézetet (villám ikon), és a Click sor melletti legördülő listából válaszd ki a már meglévő metódust (ne kattints duplán, mert az újat hozna létre).
2. A tervezőben, egyszerre
Tartsd lenyomva a Ctrl billentyűt, és kattints sorra az összes vezérlőre (vagy húzz kijelölő keretet körülöttük). Az Events nézetben a Click sorban megadott metódus mindegyikhez hozzárendelődik. Tíz gombnál ez egyetlen lépés.
3. Kódból
A += operátorral, egyenként vagy ciklusban (lásd lent). Kódból generált vezérlőknél ez az egyetlen mód.
// egyetlen metódus a tíz számgombnak (btn0 ... btn9)
private void SzamGomb_Click(object sender, EventArgs e)
{
Button gomb = (Button)sender;
txtKijelzo.Text = txtKijelzo.Text + gomb.Text;
}
// ugyanez kódból, egy tömbbel és ciklussal
private void Form1_Load(object sender, EventArgs e)
{
Button[] szamGombok = { btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9 };
foreach (Button gomb in szamGombok)
{
gomb.Click += SzamGomb_Click;
}
}Click sora ugyanarra a névre mutat: a SzamGomb_Click névre. A név nem tartalmazza egyik vezérlő nevét sem (nincs btn5_Click), mert mindenkié.Button.Click és egy Label.Click is EventHandler típusú, ezért egy metódus kezelheti mindkettőt. A Button.Click és a TextBox.KeyPress viszont más típusú, azokat nem lehet egy metódusba tenni.btn5_Click), a közös használatra nevezd át (Rename parancs, 5.1 lecke), hogy a név ne félrevezessen.Jósolj!
A kilenc gombból nyolchoz hozzárendeled a Gomb_Click kezelőt a tervezőben, a kilencediket (btn9) elfelejted. A Form1_Load eseményben pedig mind a kilencre kódból is feliratkozol (+=). Hányszor fut le a kezelő az egyes gombok megnyomására?
Jóslat ellenőrzése
A nyolc gombnál kétszer (a tervező és a kód is feliratkoztatta), a btn9-nél egyszer (csak a kód). A hiba azért nehezen vehető észre, mert minden gomb „működik”, csak néhány kétszer. A két hozzárendelési mód együttes használata kerülendő: vagy a tervezőben, vagy kódból iratkozz fel.
A küldő azonosítása: Name, Tag vagy index?
A közös kezelőnek tudnia kell, melyik vezérlő váltotta ki az eseményt. Négy mód van, mindegyiknek más az erőssége:
| Mód | Példa | Előnye | Hátránya |
|---|---|---|---|
Név (Name) | gomb.Name == "btnPlusz" | Egyszerű, a tervezőben már megvan. | Átnevezéskor a szövegben lévő név nem frissül, és elírást a fordító nem vesz észre. |
Felirat (Text) | gomb.Text == "+" | Rövid, olvasható. | A felirat változhat (nyelv, átírás), és nem azonosító. Számjegyeknél jó (a felirat az érték). |
| Tag | (string)gomb.Tag == "szorzas" | Megbízható: független a névtől és a felirattól, tetszőleges adatot hordozhat. | Be kell állítani (tervezőben vagy kódból), és null lehet. |
| Index / hivatkozás | Array.IndexOf(gombok, sender), sender == btnA | Tömbből a sorszám közvetlenül adódik; a hivatkozás-összehasonlítás pontos. | Csak akkor, ha a vezérlők tömbben vannak (vagy ismert nevű változók). |
A Tag a leghasznosabb: minden vezérlőnek van egy Tag tulajdonsága (típusa object), amelybe bármit tehetsz: szöveget, számot, színt, egy Point értéket, akár egy saját osztály példányát. A tervezőben csak szöveget adhatsz meg, kódból bármit:
private void Form1_Load(object sender, EventArgs e)
{
// kódból bármit eltárolhatunk a Tag-ben
btnPiros.Tag = Color.Firebrick;
btnKek.Tag = Color.RoyalBlue;
btnZold.Tag = Color.SeaGreen;
}
private void SzinGomb_Click(object sender, EventArgs e)
{
Button gomb = (Button)sender;
if (gomb.Tag is Color szin)
{
pnlElonezet.BackColor = szin;
}
}Tag object típusú, ezért vissza kell alakítani. Az is minta ellenőriz és átalakít egyszerre, és null esetén egyszerűen nem teljesül (nincs NullReferenceException).Tag tartalmában térnek el: a kezelő kódja ugyanaz, az adat vezérlőnként más. Ez a „adatvezérelt” megközelítés lényege.szorzas, 5). Számként használni így a int.Parse((string)gomb.Tag) szükséges.Tag-be tegyél olyan adatot, amelynek saját tulajdonsága van (a felirat a Text, a név a Name). A Tag a „plusz” adatnak való.Jósolj!
A btnPiros Tag tulajdonságát elfelejted beállítani, és a SzinGomb_Click kezelőben Color szin = (Color)gomb.Tag; áll. Mi történik a gomb megnyomására?
Jóslat ellenőrzése
A Tag értéke null, és a (Color)null átalakítás NullReferenceException (kicsomagolási hiba) kivételt dob. Az is Color szin minta ezt megelőzi: null esetén a feltétel hamis, és a kód egyszerűen nem fut. Ezért a Tag kiolvasásakor mindig az is vagy az as a biztonságos.
A switch utasítás: sok eset, egy döntés
Ha a kezelőnek tucatnyi esetet kell megkülönböztetnie (például a számológép műveletei), az if–else if lánc hosszú és nehezen olvasható. A switch utasítás egyetlen értéket hasonlít össze több lehetséges esettel (case):
private void MuveletGomb_Click(object sender, EventArgs e)
{
Button gomb = (Button)sender;
double a = (double)nudA.Value;
double b = (double)nudB.Value;
double eredmeny = 0;
switch (gomb.Tag.ToString()) // a Tag: osszead, kivon, szoroz, oszt
{
case "osszead":
eredmeny = a + b;
break;
case "kivon":
eredmeny = a - b;
break;
case "szoroz":
eredmeny = a * b;
break;
case "oszt":
if (b == 0)
{
lblEredmeny.Text = "Nullával nem lehet osztani";
return;
}
eredmeny = a / b;
break;
default:
lblEredmeny.Text = "Ismeretlen művelet";
return;
}
lblEredmeny.Text = eredmeny.ToString();
}case ágat hajtja végre.break (vagy return) zár le. Enélkül a C# fordítási hibát jelez (nem „esik át” a következő ágra, mint más nyelvekben).case sem egyezik. Érdemes mindig megadni: a váratlan értéket így kezeled.case címkék ugyanazt az ágat adják: case "a": case "b": ... break;Jósolj!
A switch egyik ágából (case "kivon":) kitörlöd a break; sort. Lefordul-e a program?
Jóslat ellenőrzése
Nem fordul le. A C# nem engedi, hogy egy ág (amely tartalmaz utasítást) a break, return vagy más kilépés nélkül véget érjen: CS0163: Az irányítás nem eshet át az egyik case címkéről a másikra. Így a más nyelvekben gyakori „átesés” hiba itt nem fordulhat elő.
Vezérlők bejárása: ciklus és rekurzió
Gyakori feladat, hogy minden adott típusú vezérlőhöz ugyanazt a kezelőt kell kötni (például az összes szövegmező kiemelése, ha fókuszba kerül). A this.Controls gyűjtemény viszont csak az ablak közvetlen gyermekeit tartalmazza: a GroupBox vagy Panel belsejében lévő vezérlők nem szerepelnek benne. Ezeket rekurzióval (a metódus önmagát hívja) lehet bejárni:
// minden TextBox kiemelése fókuszba kerüléskor, a beágyazottaké is
private void Form1_Load(object sender, EventArgs e)
{
Feliratkozas(this);
}
private void Feliratkozas(Control szulo)
{
foreach (Control gyermek in szulo.Controls)
{
if (gyermek is TextBox mezo)
{
mezo.Enter += Mezo_Enter;
mezo.Leave += Mezo_Leave;
}
if (gyermek.Controls.Count > 0)
{
Feliratkozas(gyermek); // rekurzió: a gyermek gyermekei
}
}
}
private void Mezo_Enter(object sender, EventArgs e)
{
((TextBox)sender).BackColor = Color.LightYellow;
}
private void Mezo_Leave(object sender, EventArgs e)
{
((TextBox)sender).BackColor = SystemColors.Window;
}Feliratkozas a vezérlő gyermekeit járja be, és ha egy gyermeknek is vannak gyermekei, önmagát hívja rájuk. Így a tetszőleges mélységű beágyazás is bejárható.Click eseménnyel: akkor futnak, amikor a vezérlő fókuszt kap, illetve elveszíti, billentyűzettel is.Jósolj!
Az ablakon három TextBox van, kettő egy GroupBox-ban. A Form1_Load-ban ezt írod: foreach (Control c in this.Controls) { if (c is TextBox) { ... } } (rekurzió nélkül). Hány szövegmezőre köt kezelőt?
Jóslat ellenőrzése
Csak egyre: az ablak közvetlen gyermeke. A GroupBox belsejében lévő két szövegmező a GroupBox.Controls gyűjteményben van, és az ablak Controls gyűjteménye nem tartalmazza őket. A GroupBox maga egy Control, így a ciklus látja, de a tartalmába csak a rekurzió lép be.
Kódból generált tábla egyetlen kezelővel
Ha a vezérlők száma sok vagy a kiosztásuk szabályos (játéktábla, billentyűzet, naptár), kódból hozzuk létre őket egy ciklusban, és mindegyikhez ugyanazt a kezelőt kötjük. A hely adatát a Tag tárolja, így a kezelő tudja, melyik mezőről van szó:
private Button[,] mezok = new Button[3, 3];
private void Form1_Load(object sender, EventArgs e)
{
for (int sor = 0; sor < 3; sor++)
{
for (int oszlop = 0; oszlop < 3; oszlop++)
{
Button mezo = new Button();
mezo.Size = new Size(80, 80);
mezo.Location = new Point(oszlop * 84, sor * 84);
mezo.Font = new Font("Segoe UI", 28, FontStyle.Bold);
mezo.Tag = new Point(oszlop, sor); // a mező helye a táblán
mezo.Click += Mezo_Click; // ugyanaz a kezelő mind a kilencnek
mezok[sor, oszlop] = mezo;
pnlTabla.Controls.Add(mezo);
}
}
}
private void Mezo_Click(object sender, EventArgs e)
{
Button mezo = (Button)sender;
if (mezo.Tag is Point hely)
{
lblInfo.Text = $"Sor: {hely.Y}, oszlop: {hely.X}";
}
}A ciklusváltozó és a lambda
Az 5.1 lecke Gyakorlótárában láttad: ha lambdával kötöd a kezelőt, és a lambda a ciklusváltozót használja, minden vezérlő ugyanazt az értéket látja. A közös kezelő és a Tag ezt a problémát elkerüli (nincs lambda). Ha mégis lambdát használsz, helyi másolat kell:
for (int i = 0; i < 5; i++)
{
Button gomb = new Button();
// HIBÁS: gomb.Click += (s, ev) => MessageBox.Show(i.ToString()); // mindegyik 5-öt mutat!
int szam = i; // helyi másolat minden körben
gomb.Click += (s, ev) => MessageBox.Show(szam.ToString());
pnlGombok.Controls.Add(gomb);
}Jósolj!
A Mezo_Click kezelőben a Point hely = (Point)mezo.Tag; sor áll, de a ciklusban elfelejted beállítani a Tag tulajdonságot. Mi történik az első kattintásra?
Jóslat ellenőrzése
A Tag null, és a (Point)null átalakítás NullReferenceException kivételt dob. Az is Point hely minta ilyenkor hamis, és a kód nem fut le: nincs hiba, de nem is történik semmi, ami hibakeresésnél szintén félrevezető lehet. Ezért a Tag beállítása a létrehozás ciklusban kötelező lépés.
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. NullReferenceException a Tag használatakor
string muvelet = gomb.Tag.ToString(); // a Tag nullHiba és javítás
A Tag nincs beállítva, így null. Állítsd be a tervezőben vagy kódból, és olvasd ki biztonságosan: string muvelet = gomb.Tag as string; majd if (muvelet != null), vagy gomb.Tag?.ToString().
2. InvalidCastException a kezelőben
A közös kezelőt egy A Button és egy Label is használja, és a (Button)sender hibát dob a címkénél.Ok és javítás
sender nem minden esetben Button. Használj is mintát (if (sender is Button gomb)) vagy a közös Control típust (5.2 lecke).
3. A switch nem talál egyezést átnevezés után
switch (gomb.Name)
{
case "btnOsszead": /* ... */ break; // a gombot átnevezted btnPlusz-ra
}Hiba és javítás
A szövegben szereplő név nem frissül az átnevezéskor, és a fordító nem jelez hibát: a default ág fut. Használj Tag értéket (amely független a névtől), vagy a vezérlőt tömbben tárold, és az indexet vizsgáld.
4. Minden gomb ugyanazt az értéket adja
for (int i = 0; i < 5; i++)
{
Button g = new Button();
g.Click += (s, e) => MessageBox.Show(i.ToString()); // mind 5-öt mutat
}Hiba és javítás
A lambda a for ciklus egyetlen i változóját látja, amelynek értéke a ciklus végén 5. Készíts helyi másolatot a ciklustörzsben (int szam = i;), vagy használj közös kezelőt és Tag értéket.
5. A kezelő nem kötődik a panelen lévő mezőkhöz
Az összes szövegmezőre ráírtad a ciklust, de a A GroupBox belsejében lévők kimaradnak.Ok és javítás
this.Controls csak a közvetlen gyermekeket tartalmazza. Járd be rekurzívan a beágyazott vezérlőket (Feliratkozas(Control szulo)), ahogy a leckében látod.
6. A kezelő kétszer fut a számgombokra
A számgombok eseménykezelője minden megnyomásra kétszer ad hozzá számjegyet. A gombokat a tervezőben is és a Ok és javítás
Form1_Load ciklusban is feliratkoztattad. Válassz egy módot, és töröld a másikat (a Designer.cs fájlban a Click += sorokat az Events nézetben törölheted a metódus nevének kitörlésével).
Mini projekt: amőba
Készíts kétszemélyes amőba (tic-tac-toe) játékot, amelynek mezői kódból jönnek létre egyetlen közös kezeléssel:
- Az ablakon: egy
pnlTablapanel (260 × 260),lblSoron(ki következik),lblEredmeny(pontállás: X: 2 | O: 1 | Döntetlen: 0),btnUj(új játék). - A
Form1_Loadegy 3 × 3-as tömbbe (Button[,]) hozza létre a kilenc mezőt, mindegyikTagtulajdonságába a helyét (Point) teszi, és mindegyikhez aMezo_Clickközös kezelőt köti. - A
Mezo_Clickaz üres mezőre X vagy O jelet tesz (felváltva), a foglalt mezőre kattintást figyelmen kívül hagyja. - Minden lépés után ellenőrzi a nyerést (sorok, oszlopok, átlók) és a döntetlent (9 lépés). Nyerésnél kiemeli a nyerő mezőket, letiltja a táblát, és növeli a pontszámot.
- Az Új játék gomb törli a táblát (a pontszám marad), és az X kezd.
- Teszteld: minden nyerő vonal, döntetlen, foglalt mezőre kattintás, új játék nyerés után, a pontállás.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A nyerés ellenőrzése egy Egyezik segédmetódusra épül, amely három mezőről megállapítja, hogy azonos, nem üres jelet hordoznak-e. A játék állapotát három mező őrzi: a soron következő jel, a lépések száma és a vége-jelző.
private Button[,] mezok = new Button[3, 3];
private bool xKovetkezik = true;
private int lepesek = 0;
private bool vege = false;
private int xPont = 0;
private int oPont = 0;
private int dontetlen = 0;
private void Form1_Load(object sender, EventArgs e)
{
for (int sor = 0; sor < 3; sor++)
{
for (int oszlop = 0; oszlop < 3; oszlop++)
{
Button mezo = new Button();
mezo.Size = new Size(80, 80);
mezo.Location = new Point(oszlop * 84, sor * 84);
mezo.Font = new Font("Segoe UI", 28, FontStyle.Bold);
mezo.Tag = new Point(oszlop, sor);
mezo.Click += Mezo_Click;
mezok[sor, oszlop] = mezo;
pnlTabla.Controls.Add(mezo);
}
}
Frissit();
}
private void Mezo_Click(object sender, EventArgs e)
{
Button mezo = (Button)sender;
if (vege || mezo.Text != "")
{
return; // vége a játéknak, vagy foglalt mező
}
mezo.Text = xKovetkezik ? "X" : "O";
mezo.ForeColor = xKovetkezik ? Color.RoyalBlue : Color.Firebrick;
lepesek = lepesek + 1;
if (Nyert())
{
vege = true;
if (xKovetkezik)
{
xPont = xPont + 1;
}
else
{
oPont = oPont + 1;
}
lblSoron.Text = (xKovetkezik ? "X" : "O") + " nyert!";
}
else if (lepesek == 9)
{
vege = true;
dontetlen = dontetlen + 1;
lblSoron.Text = "Döntetlen.";
}
else
{
xKovetkezik = !xKovetkezik;
lblSoron.Text = "Soron: " + (xKovetkezik ? "X" : "O");
}
Frissit();
}
private bool Egyezik(Button a, Button b, Button c)
{
if (a.Text != "" && a.Text == b.Text && b.Text == c.Text)
{
a.BackColor = Color.LightGreen;
b.BackColor = Color.LightGreen;
c.BackColor = Color.LightGreen;
return true;
}
return false;
}
private bool Nyert()
{
for (int i = 0; i < 3; i++)
{
if (Egyezik(mezok[i, 0], mezok[i, 1], mezok[i, 2]))
{
return true;
}
if (Egyezik(mezok[0, i], mezok[1, i], mezok[2, i]))
{
return true;
}
}
return Egyezik(mezok[0, 0], mezok[1, 1], mezok[2, 2]) || Egyezik(mezok[0, 2], mezok[1, 1], mezok[2, 0]);
}
private void Frissit()
{
lblEredmeny.Text = $"X: {xPont} | O: {oPont} | Döntetlen: {dontetlen}";
}
private void btnUj_Click(object sender, EventArgs e)
{
foreach (Button mezo in mezok)
{
mezo.Text = "";
mezo.BackColor = SystemColors.Control;
}
xKovetkezik = true;
lepesek = 0;
vege = false;
lblSoron.Text = "Soron: X";
}Mit tegyél ide: egy befejezett játék: a három nyerő mező zölden, a „X nyert!” felirat és a pontállás.Javasolt fájlnév: winforms-5-8-kesz.webp · Alt szöveg: Az amőba játék egy nyerő állással
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. Színpaletta egy kezelővel
Hat színes A színt a panel saját Panel (vagy gomb) palettát alkot. Egy közös kezelő a kattintott szín értékét a pnlKijelolt panel háttérszínére állítja. Hol tárolod a színt, és hogyan állítod be?Egy lehetséges megoldás
BackColor tulajdonsága tárolja, ezért a kezelőnek nem is kell külön adat. A közös kezelő a sender hátterét másolja. Ha a paletta elemei színtelenek lennének (például képek), a Tag tárolná a színt.private void Paletta_Click(object sender, EventArgs e)
{
Control elem = (Control)sender;
pnlKijelolt.BackColor = elem.BackColor;
lblSzin.Text = "Kiválasztott szín: " + elem.BackColor.Name;
}
2. Hőmérséklet-átváltó választógombokkal
Három A RadioButton (Celsius, Fahrenheit, Kelvin) a kimeneti mértékegységet választja. A bemenet mindig Celsius (nudCelsius). Egyetlen CheckedChanged kezelő és a Tag használatával számold át az értéket, és az lblEredmeny-ben jelenítsd meg.Egy lehetséges megoldás
RadioButton.CheckedChanged kétszer fut (az új és a régi gombra), ezért csak a bejelölt gombbal foglalkozunk (rb.Checked). A Tag a mértékegység kódját tartalmazza (C, F, K). Az érték változásakor is újra kell számolni, ezért a számolás külön metódus.private void Szamol()
{
double c = (double)nudCelsius.Value;
string egyseg = "C";
foreach (Control vezerlo in grpEgyseg.Controls)
{
RadioButton rb = vezerlo as RadioButton;
if (rb != null && rb.Checked)
{
egyseg = rb.Tag.ToString();
}
}
switch (egyseg)
{
case "F":
lblEredmeny.Text = (c * 9 / 5 + 32).ToString("0.##") + " °F";
break;
case "K":
lblEredmeny.Text = (c + 273.15).ToString("0.##") + " K";
break;
default:
lblEredmeny.Text = c.ToString("0.##") + " °C";
break;
}
}
private void Egyseg_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
if (rb.Checked)
{
Szamol();
}
}
private void nudCelsius_ValueChanged(object sender, EventArgs e)
{
Szamol();
}
3. Mentés gomb engedélyezése több mezőből
Az űrlapon öt szövegmező van (két A rekurzív bejárás a mezőkhöz köti a kezelőt (egyszer, a GroupBox-ban elosztva). A Mentés gomb csak akkor aktív, ha mindegyik nem üres. Egy közös TextChanged kezelőt kell használnod, a beágyazott mezőket is bejárva.Egy lehetséges megoldás
Load-ban). Az ellenőrzés egy másik rekurzív metódus (VanUres), amely igazat ad, ha bármelyik szövegmező üres. A gomb állapotát a közös kezelő frissíti.private void Kot(Control szulo)
{
foreach (Control gyermek in szulo.Controls)
{
if (gyermek is TextBox mezo)
{
mezo.TextChanged += Mezo_TextChanged;
}
Kot(gyermek);
}
}
private bool VanUres(Control szulo)
{
foreach (Control gyermek in szulo.Controls)
{
if (gyermek is TextBox mezo && mezo.Text.Trim().Length == 0)
{
return true;
}
if (VanUres(gyermek))
{
return true;
}
}
return false;
}
private void Mezo_TextChanged(object sender, EventArgs e)
{
btnMent.Enabled = !VanUres(this);
}
private void Form1_Load(object sender, EventArgs e)
{
Kot(this);
btnMent.Enabled = !VanUres(this);
}
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. A Tag használata szövegként
Három gombot (Kicsi, Közepes, Nagy) egy közös kezelő köt. A tervezőben a A tervezőben megadott Tag értékük 10, 16, 24. A kezelő a lblPelda betűméretét ennek megfelelően állítsa. Hogyan alakítod számmá a Tag értékét?Megoldás megnyitása
Tag szöveg (string), ezért int.Parse (vagy TryParse) kell. A TryParse megvédi a programot egy elgépelt értéktől.private void Meret_Click(object sender, EventArgs e)
{
Button gomb = (Button)sender;
int meret;
if (gomb.Tag != null && int.TryParse(gomb.Tag.ToString(), out meret))
{
lblPelda.Font = new Font(lblPelda.Font.FontFamily, meret);
}
}
2. Tervezői kijelölés
Magyarázd el lépésről lépésre, hogyan rendelsz hozzá egy meglévő (1) Kattints az első gombra, tartsd lenyomva a Gomb_Click metódust öt gombhoz a tervezőben anélkül, hogy öt külön alkalommal állítanád.Megoldás megnyitása
Ctrl billentyűt, és kattints a másik négyre is (vagy húzz kijelölő keretet körülöttük). (2) A Properties ablakban válts az Events (villám) nézetre. (3) A Click sor melletti legördülő listából válaszd ki a Gomb_Click metódust (a meglévő metódust válaszd ki, ne duplán kattints). (4) A hozzárendelés mind az öt gombra érvényes lesz.
Közepes szint
3. Az összes Label átszínezése
Egy gomb az ablak összes Rekurzív metódus, amely a gyermekeket bejárja, a Label vezérlőjének (a GroupBox-ban lévőknek is) a betűszínét pirosra állítja. Hogyan járod be?Megoldás megnyitása
Label típusúaknál átállítja a színt, és minden gyermekre meghívja önmagát.private void Szinez(Control szulo, Color szin)
{
foreach (Control gyermek in szulo.Controls)
{
if (gyermek is Label cimke)
{
cimke.ForeColor = szin;
}
Szinez(gyermek, szin);
}
}
private void btnSzinez_Click(object sender, EventArgs e)
{
Szinez(this, Color.Firebrick);
}
4. Számológép műveletei switch-csel
Négy műveletgomb ( A felirat (+, -, ×, ÷) közös kezelője a gombok felirata alapján számol két NumericUpDown értékével. Mi a különbség a felirat és a Tag használata között?Megoldás megnyitása
Text) rövid és olvasható, de ha a felirat megváltozik (például × helyett *), a switch elromlik. A Tag független a megjelenéstől. Kis programban a felirat is elfogadható, nagyobb programban a Tag a biztosabb.private void Muvelet_Click(object sender, EventArgs e)
{
double a = (double)nudA.Value;
double b = (double)nudB.Value;
double eredmeny;
switch (((Button)sender).Text)
{
case "+": eredmeny = a + b; break;
case "-": eredmeny = a - b; break;
case "×": eredmeny = a * b; break;
case "÷":
if (b == 0) { lblEredmeny.Text = "Nullával nem lehet osztani"; return; }
eredmeny = a / b;
break;
default: return;
}
lblEredmeny.Text = eredmeny.ToString();
}
Kihívás szint
5. Menüpontok közös kezelővel
A Nézet → Betűméret menü három pontja ( A menüpont (miKicsi, miKozepes, miNagy) közös kezelővel állítja a txtSzoveg betűméretét. Miért nem alakíthatod a sender paramétert Control típusra?Megoldás megnyitása
ToolStripMenuItem) nem Control (5.2 lecke). A közös őse a ToolStripItem, amelynek van Tag tulajdonsága, és a sender erre (vagy ToolStripMenuItem-re) alakítható.private void Betumeret_Click(object sender, EventArgs e)
{
ToolStripMenuItem pont = (ToolStripMenuItem)sender;
int meret;
if (pont.Tag != null && int.TryParse(pont.Tag.ToString(), out meret))
{
txtSzoveg.Font = new Font(txtSzoveg.Font.FontFamily, meret);
// a jelölés: csak az aktuális pont kapja a pipát
miKicsi.Checked = pont == miKicsi;
miKozepes.Checked = pont == miKozepes;
miNagy.Checked = pont == miNagy;
}
}
6. Billentyűzet kódból
Hozz létre kódból egy telefonos billentyűzetet (4 sor × 3 oszlop: 1 … 9, *, 0, #) egyetlen közös kezelővel, amely a nyomott gombot egy A feliratokat egy szöveg tömb tartalmazza, és a gomb txtSzam mezőhöz fűzi. Hol tárolod a gombok feliratát?Megoldás megnyitása
Text tulajdonságába kerülnek. A közös kezelő a Text értéket fűzi hozzá, így nincs szükség külön Tag-re. A sor és oszlop az index osztásából és maradékából adódik.private void Form1_Load(object sender, EventArgs e)
{
string[] feliratok = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#" };
for (int i = 0; i < feliratok.Length; i++)
{
Button gomb = new Button();
gomb.Text = feliratok[i];
gomb.Size = new Size(60, 50);
gomb.Location = new Point((i % 3) * 64, (i / 3) * 54); // oszlop: maradék, sor: egész osztás
gomb.Click += Billentyu_Click;
pnlBillentyuk.Controls.Add(gomb);
}
}
private void Billentyu_Click(object sender, EventArgs e)
{
txtSzam.Text = txtSzam.Text + ((Button)sender).Text;
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Kijelölöd a vezérlőket ( Mert a kezelő kétszer iratkozik fel, és kétszer fut le. Ha az azonosítás nem függhet a névtől vagy a felirattól (átnevezés, nyelv). A A C# nem engedi az átesést az egyik ágból a másikba ( Csak a közvetlen gyermekeket tartalmazza. A A lambda egyetlen ciklusváltozót lát, amelynek végső értéke minden vezérlőnél azonos. Helyi másolat ( Bármilyen objektumot: szöveget, számot, színt, 1. Hogyan rendelsz egy kezelőt több vezérlőhöz a tervezőben?
Ctrl + kattintás), és az Events nézetben a meglévő metódust választod ki az eseményhez.2. Miért ne használd a tervezőt és a kódot egyszerre ugyanarra az eseményre?
3. Mikor a legmegbízhatóbb a Tag a Name vagy a Text helyett?
Tag bármilyen adatot hordozhat.4. Miért kell a switch ágait break vagy return zárja?
CS0163), így a hiba nem fordulhat elő.5. Miért nem elég a this.Controls ciklus a beágyazott vezérlőkhöz?
GroupBox vagy Panel belsejét rekurzióval kell bejárni.6. Mi a ciklusváltozó és a lambda csapdája?
int szam = i;) vagy közös kezelő + Tag oldja meg.7. Mit tárolhatsz a Tag-ben?
Point értéket, saját osztály példányát. A tervezőben csak szöveget.
Mit tudsz már?
- Egyetlen kezelőt rendelsz sok vezérlőhöz (tervezőben, kódból), és a küldőt azonosítod.
- A
Tagtulajdonságban adatot tárolsz, és biztonságosan olvasod ki. - Használod a
switchutasítást és a rekurzív bejárást. - Kódból generálsz vezérlőtáblát közös kezelővel, és kerülöd a ciklusváltozó csapdáját.
- Elkészítettél egy kész játékot (amőba) a közös kezelő mintájával.
Következő lecke: mini projekt – billentyűzettel vezérelt program
Az 5. fejezet záró projektjében az eseményekről tanultak mind egy programban találkoznak: billentyűzet, egér, változás-események és életciklus.



