Grafikus C# – Windows Forms tanfolyam › 5.5 lecke Windows Forms · 5.5 lecke · változás-események Egy űrlapon a legtöbb program nem a gombnyomásra, hanem arra reagál, hogy megváltozott valami: a felhasználó beírt egy betűt, elhúzott egy csúszkát, kiválasztott egy elemet. A vezérlők erre változás-eseményeket adnak (TextChanged, ValueChanged és SelectedIndexChanged
TextChanged, ValueChanged, SelectedIndexChanged, CheckedChanged). Van azonban egy csapda: ezek az események akkor is lefutnak, ha a program változtatja meg az értéket, ezért könnyen visszacsatolási hurkok és furcsa mellékhatások keletkezhetnek. Ebben a leckében ezeket az eseményeket és a védekezést tanulod meg.
A lecke végére
- ismered a leggyakoribb változás-eseményeket, és tudod, melyik vezérlőhöz melyik való;
- érted, hogy a változás-események a program általi változtatásra is lefutnak;
- jelzővel és a „csak felhasználó” eseményekkel (
SelectionChangeCommitted,Scroll) kerülöd el a visszacsatolást; - összekapcsolt vezérlőket (csúszka, szám, szöveg, szín) hangolsz össze hurok nélkül;
- kaszkádolt listát építesz (az egyik lista tartalma a másik kiválasztásától függ);
- késleltetett keresést készítesz
Timer-rel, és ismered aItemCheckcsapdáját.
Mielőtt elkezded
Ehhez a leckéhez az 5.1 és az 5.2 lecke (események, sender), valamint az 1.8–1.9 lecke vezérlői (ComboBox, TrackBar, NumericUpDown, CheckedListBox) szükségesek. Új projektben próbáld ki a példákat.
A változás-események áttekintése
| Vezérlő | Esemény | Mikor fut? |
|---|---|---|
TextBox, RichTextBox, Label | TextChanged | A Text tulajdonság értéke megváltozott (minden beírt vagy törölt karakternél). |
NumericUpDown, TrackBar, DateTimePicker | ValueChanged | Az érték megváltozott (a TrackBar csúszkát húzva többször is). |
ComboBox, ListBox | SelectedIndexChanged, SelectedValueChanged | A kiválasztott elem sorszáma, illetve értéke megváltozott. |
ComboBox | SelectionChangeCommitted | A felhasználó választott elemet (a program általi beállításra nem fut). |
CheckBox | CheckedChanged, CheckStateChanged | A jelölés megváltozott (a második a háromállapotú négyzetnél hasznos). |
RadioButton | CheckedChanged | Egy választógomb be- és kikapcsol: a váltáskor kétszer fut. |
CheckedListBox | ItemCheck | Egy elem jelölése éppen változik (a változás előtt fut!). |
TrackBar | Scroll | A felhasználó mozgatta a csúszkát (a program általi beállításra nem fut). |
Jósolj!
Egy txtProba mező TextChanged eseménye egy számlálót növel. Mennyi lesz a számláló értéke, ha (a) beírod, hogy abc; (b) a kódból beállítod txtProba.Text = "xyz";; (c) a kódból újra beállítod txtProba.Text = "xyz";; (d) kitörlöd a mezőt a Clear() metódussal?
Jóslat ellenőrzése
(a) 3: minden karakter külön változás (a, ab, abc). (b) +1: a program általi beállítás is kiváltja az eseményt (a régi érték abc, az új xyz). (c) +0: ha az új érték megegyezik a régivel, a Text nem változik, ezért az esemény nem fut le. (d) +1: a Clear() üres szövegre állítja a mezőt, tehát változás (xyz → üres), de ha a mező már üres volt, nem fut le. A tanulság: a TextChanged csak tényleges változásra fut, de bárki változtatja, a program is.
Program vagy felhasználó változtatott?
A legtöbb váratlan viselkedés abból jön, hogy az eseménykezelő nem tudja, ki váltotta ki a változást. Az eseménykezelő ugyanúgy lefut, ha a felhasználó gépel, és ha a program írja be az értéket. Néhány eset, amikor ez gondot okoz:
- Az ablak betöltésekor a program feltölt egy listát, és a
SelectedIndexChanged(vagy aCheckedChanged) az inicializálás közben lefut, amikor még nincs minden kész. - Egy mező tartalmát a program átírja (például a másik mezőből számolva), és ez a mező eseménye újabb átírást kezd, amelyik visszahat az elsőre: visszacsatolási hurok.
- A
ComboBox.DataSourceújratöltésekor aSelectedIndexChangedtöbbször is lefut, és közben aSelectedItemértéknulllehet.
Három védekezés van:
1. „Csak felhasználó” események
Ha van ilyen, használd: a ComboBox.SelectionChangeCommitted és a TrackBar.Scroll csak a felhasználó választására fut, a program általi beállításra nem.
2. Jelző (flag)
Egy bool mező (frissites) megmondja, hogy a program éppen maga állít-e értéket. Ha igen, a kezelő azonnal kilép. Ezt a mintát a 2.11 és 3.6 leckében is használtuk.
3. Leiratkozás
Az eseménykezelőt átmenetileg leválasztod (-=), megváltoztatod az értéket, és visszakapcsolod (+=). Működik, de bonyolultabb, és ha a visszakapcsolás kimarad (kivétel), a kezelő végleg kikapcsolva marad.
// Csak akkor fut, ha a FELHASZNÁLÓ választott a listából:
private void cmbSzin_SelectionChangeCommitted(object sender, EventArgs e)
{
this.BackColor = Color.FromName(cmbSzin.SelectedItem.ToString());
}
// Ez lefut a program általi beállításra is (például SelectedIndex = 0):
private void cmbSzin_SelectedIndexChanged(object sender, EventArgs e)
{
lblInfo.Text = "Kiválasztott sorszám: " + cmbSzin.SelectedIndex;
}Jósolj!
A Form1_Load eseményben beállítod: cmbSzin.SelectedIndex = 0;. Lefut-e ettől a cmbSzin_SelectedIndexChanged és a cmbSzin_SelectionChangeCommitted kezelő?
Jóslat ellenőrzése
A SelectedIndexChanged lefut (a program is változtatta a kiválasztást), a SelectionChangeCommitted nem (nem a felhasználó választott). Ezért ha a kezelő nem szeretne a program általi beállításra reagálni (például egy kellemetlen mellékhatás miatt), a SelectionChangeCommitted a biztonságos választás. Ha ugyanakkor az induláskor beállított értékre is reagálni kell (például a háttérszínt is be kell állítani), akkor a SelectedIndexChanged a jó.
Visszacsatolás és a jelző
Képzeld el, hogy a trbMeret csúszka és a nudMeret szám egymást követik: ha az egyik változik, a másik is. A naiv megoldás:
private void trbMeret_ValueChanged(object sender, EventArgs e)
{
nudMeret.Value = trbMeret.Value; // ez kiváltja a nudMeret.ValueChanged eseményt...
}
private void nudMeret_ValueChanged(object sender, EventArgs e)
{
trbMeret.Value = (int)nudMeret.Value; // ...ami kiváltja a trbMeret.ValueChanged eseményt
}Szerencsére itt a hurok magától megáll: amikor a második vezérlő értéke már megegyezik, a beállítás nem változtat semmin, ezért nem fut le az esemény. Ez azonban csak azért működik, mert a két érték pontosan ugyanaz. Ha az átalakítás veszteséges (kerekítés, más tartomány, szöveg ↔ szám, szín ↔ hexa kód), a hurok megállhatatlanná vagy hibás állapotúvá válhat. A biztos védekezés a jelző:
private bool szinkronban = false; // igaz, amíg a program maga állítja az értékeket
private void trbMeret_ValueChanged(object sender, EventArgs e)
{
if (szinkronban)
{
return;
}
szinkronban = true;
try
{
nudMeret.Value = trbMeret.Value;
}
finally
{
szinkronban = false; // hiba esetén is visszaáll
}
}
private void nudMeret_ValueChanged(object sender, EventArgs e)
{
if (szinkronban)
{
return;
}
szinkronban = true;
try
{
trbMeret.Value = (int)nudMeret.Value;
}
finally
{
szinkronban = false;
}
}TrackBar tartományán kívüli szám), a finally akkor is visszaállítja a jelzőt, különben a vezérlők végleg elszakadnának egymástól.Az igazi végtelen ciklus
Ha két TextBox a másik átalakított tartalmát írja be (az egyik nagybetűsre, a másik kisbetűsre), a TextChanged eseményeik végtelen ciklust indítanak: az egyik átírása a másikat írja át, az pedig vissza. A program lefagy, vagy StackOverflowException jelenti a végtelen rekurziót (ez a kivétel nem is kapható el). A jelző a megoldás.
Jósolj!
A trbMeret tartománya 0–100, a nudMeret tartománya 0–200. A nudMeret értékét 150-re írod be. Mi történik a nudMeret_ValueChanged kezelőben a trbMeret.Value = (int)nudMeret.Value; sorral?
Jóslat ellenőrzése
A TrackBar.Value tartományon kívüli értéke ArgumentOutOfRangeException kivételt dob, így a kezelő hibával leáll. A try–finally miatt a jelző visszaáll, de a hiba továbbmegy. A helyes megoldás: az értéket a tartományra szorítani: trbMeret.Value = Math.Min(trbMeret.Maximum, (int)nudMeret.Value);, vagy a két vezérlő tartományát azonosra állítani.
Gyakorlati minták
Kaszkádolt lista: az egyik választás tölti a másikat
Gyakori űrlap: kiválasztod a megyét, és a város lista csak az adott megye városait tartalmazza. A cmbMegye kiválasztása tölti a cmbVaros elemeit:
private void Form1_Load(object sender, EventArgs e)
{
cmbMegye.Items.Add("Veszprém");
cmbMegye.Items.Add("Győr-Moson-Sopron");
cmbMegye.Items.Add("Baranya");
cmbVaros.Enabled = false; // amíg nincs megye, a város nem választható
}
private void cmbMegye_SelectedIndexChanged(object sender, EventArgs e)
{
cmbVaros.Items.Clear();
if (cmbMegye.SelectedItem == null)
{
cmbVaros.Enabled = false;
return;
}
string megye = cmbMegye.SelectedItem.ToString();
if (megye == "Veszprém")
{
cmbVaros.Items.AddRange(new object[] { "Veszprém", "Ajka", "Pápa", "Tapolca" });
}
else if (megye == "Győr-Moson-Sopron")
{
cmbVaros.Items.AddRange(new object[] { "Győr", "Sopron", "Mosonmagyaróvár" });
}
else if (megye == "Baranya")
{
cmbVaros.Items.AddRange(new object[] { "Pécs", "Komló", "Mohács" });
}
cmbVaros.Enabled = true;
cmbVaros.SelectedIndex = -1; // nincs kiválasztva város, amíg nem választ
}SelectedIndex = -1, Items.Clear()), ilyenkor a SelectedItem null. Mindig ellenőrizd, mielőtt használod.object típusúak.cmbVaros eseményét is kiváltja (ha van kezelő), ezért a város kezelője is legyen null-biztos.Késleltetett keresés: ne keress minden betűnél
A 3.5 leckében minden beírt betűnél lefutott a keresés. Kis táblánál ez jó, de nagy adatnál (vagy lassú lekérdezésnél) pazarló, és a program akadozik. A megoldás a késleltetés (debounce): a keresés csak akkor indul, ha a felhasználó egy rövid ideig (például 400 ezredmásodpercig) nem gépel. Egy Timer számolja az időt, és minden új betű újraindítja a számlálást:
private void txtKereso_TextChanged(object sender, EventArgs e)
{
tmrKeres.Stop(); // az előző várakozás megszakítása
tmrKeres.Start(); // újraindítás: 400 ms-ig vár
lblAllapot.Text = "Gépelés...";
}
private void tmrKeres_Tick(object sender, EventArgs e)
{
tmrKeres.Stop(); // csak egyszer fusson le
string keresett = txtKereso.Text.Trim();
lblAllapot.Text = "Keresés: " + keresett;
// ide jön a tényleges keresés (adatbázis, szűrés)
}Jósolj!
Gyorsan beírod: alma (négy betű, 100–150 ezredmásodperces szünetekkel), majd megállsz. Hányszor fut le a tmrKeres_Tick, és milyen szöveggel?
Jóslat ellenőrzése
Egyszer, az alma szöveggel. Minden új betű leállítja és újraindítja a Timer-t, így a 400 ezredmásodperces várakozás csak a gépelés vége után telik le. A keresés egyszer fut, nem négyszer. Ez a késleltetés lényege.
A CheckedListBox ItemCheck csapdája
A CheckedListBox ItemCheck eseménye azelőtt fut le, hogy az elem jelölése ténylegesen megváltozna. Ezért az eseménykezelőben a CheckedItems.Count még a régi állapotot mutatja, és a számlálás egy lépéssel lemarad. Az e.NewValue adja az új állapotot:
private void clbMeghivottak_ItemCheck(object sender, ItemCheckEventArgs e)
{
// ROSSZ: lblDb.Text = "Bejelölve: " + clbMeghivottak.CheckedItems.Count;
// JÓ: a kezelő futásakor az elem állapota még a régi, az új az e.NewValue
int db = clbMeghivottak.CheckedItems.Count;
if (e.NewValue == CheckState.Checked)
{
db = db + 1;
}
else if (e.CurrentValue == CheckState.Checked)
{
db = db - 1;
}
lblDb.Text = "Bejelölve: " + db;
}this.BeginInvoke(new Action(Szamol)); (a BeginInvoke a kezelő lefutása után hívja a Szamol metódust, amikor az állapot már friss). Ezt a 11. fejezetben tárgyaljuk.Jósolj!
A CheckedListBox még nincs bejelölve semmit. Bejelölöd az első elemet, és a (hibás) kezelő a CheckedItems.Count értékét írja ki. Mit látsz? És ha ezt követően törlöd a jelölést?
Jóslat ellenőrzése
Bejelöléskor 0 látszik (az esemény a változás előtt fut, még nincs bejelölt elem), a jelölés törlésekor pedig 1 (még bejelölt az elem). A felirat mindig egy lépéssel lemarad. A javítás az e.NewValue használata.
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 program lefagy a két szövegmezőnél
private void txtA_TextChanged(object sender, EventArgs e) { txtB.Text = txtA.Text.ToUpper(); }
private void txtB_TextChanged(object sender, EventArgs e) { txtA.Text = txtB.Text.ToLower(); }Hiba és javítás
A két átalakítás nem azonos értéket ad (nagy- és kisbetűs), ezért egyik változás sem „fejeződik be”: végtelen visszacsatolás. Használj jelzőt (if (szinkronban) return;) mindkét kezelőben, és állítsd be a másik mező értékének átírása előtt.
2. NullReferenceException a lista törlésekor
cmbMegye.Items.Clear(); // a SelectedIndexChanged lefut
private void cmbMegye_SelectedIndexChanged(object sender, EventArgs e)
{
string megye = cmbMegye.SelectedItem.ToString(); // SelectedItem null!
}Hiba és javítás
Az Items.Clear() megszünteti a kiválasztást, és az esemény SelectedItem == null mellett fut. Mindig ellenőrizd: if (cmbMegye.SelectedItem == null) { return; }
3. A kezelő a betöltés közben összeomlik
Az ablak indulásakor a A tervező és a CheckedChanged vagy SelectedIndexChanged kezelő hibát dob, mert egy másik vezérlő még null, vagy üres.Ok és javítás
Form1_Load beállítja az alapértékeket, és ezzel eseményeket vált ki, amikor a többi vezérlő még nincs kész. Használj betoltve jelzőt: a kezelők csak akkor dolgoznak, ha az igaz, és a Form1_Load legvégén állítsd igazra (ahogyan a 3.5 lecke keresőjében).
4. Eggyel lemarad a számláló
lblDb.Text = clbLista.CheckedItems.Count.ToString(); // az ItemCheck eseménybenHiba és javítás
Az ItemCheck a változás előtt fut. Számolj az e.NewValue és e.CurrentValue alapján (lásd a leckét), vagy szervezd a számlálást a változás utánra.
5. A választógombnál kétszer fut a kód
Egy választógomb-csoportnál egy váltásra az üzenet kétszer jelenik meg. A Ok és javítás
RadioButton.CheckedChanged kétszer fut: a régi gombnál (kikapcsol) és az újnál (bekapcsol). Vizsgáld a Checked értéket: if (rb.Checked) { ... }.
6. A ComboBox kezelője induláskor is lefut
A program indulásakor a A SelectedIndex = 0 beállítás is megjeleníti a „Kiválasztottad!” üzenetet.Ok és javítás
SelectedIndexChanged a program általi beállításra is fut. Ha csak a felhasználó választására kell reagálni, használd a SelectionChangeCommitted eseményt, vagy a betoltve jelzőt.
Mini projekt: színkeverő összekapcsolt vezérlőkkel
Készíts színkeverőt, amelyben három különböző vezérlő mutatja ugyanazt az értéket, hurok nélkül:
- Az ablakon: három
TrackBar(trbR,trbG,trbB, 0–255), mindegyikhez egyNumericUpDown(nudR,nudG,nudB, 0–255), egytxtHexszövegmező (RRGGBB, 6 karakter), egypnlSzinelőnézeti panel, és egycmbElorelista előre megadott színekkel (Piros, Zöld, Kék, Szürke, Narancs). - A csúszka, a szám és a hexa szöveg mindig ugyanazt a színt mutatja: bármelyik változtatására a másik kettő frissül.
- A hexa mezőbe csak hexadecimális karaktert (0–9, A–F, kis- és nagybetű) lehet beírni (
KeyPress), és csak teljes (6 karakteres) érvényes kódnál frissül a szín. - Az
cmbElorelista a felhasználó választására (SelectionChangeCommitted) beállítja a három értéket, de induláskor nem ír felül semmit. - Egy
lblInfofelirat mutatja az aktuális RGB(…, …, …) és hexa értéket. - Egyetlen
szinkronbanjelző védi a hurkot. - Teszteld: csúszka húzása, szám beírása, hexa beírása (érvénytelen és érvényes), előre megadott szín választása, a kezdő állapot.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A három érték (r, g, b) egyetlen metódusban (SzinBeallit) kerül az összes vezérlőbe, a jelző alatt. A vezérlők kezelői csak kiolvassák a saját értéküket, és meghívják ezt a metódust. A hexa mező kódját a NumberStyles.HexNumber alakítja számmá.
private bool szinkronban = false;
private void SzinBeallit(int r, int g, int b)
{
szinkronban = true;
try
{
trbR.Value = r;
trbG.Value = g;
trbB.Value = b;
nudR.Value = r;
nudG.Value = g;
nudB.Value = b;
txtHex.Text = r.ToString("X2") + g.ToString("X2") + b.ToString("X2");
pnlSzin.BackColor = Color.FromArgb(r, g, b);
lblInfo.Text = $"RGB({r}, {g}, {b}) #{txtHex.Text}";
}
finally
{
szinkronban = false;
}
}
private void Form1_Load(object sender, EventArgs e)
{
cmbElore.Items.AddRange(new object[] { "Piros", "Zöld", "Kék", "Szürke", "Narancs" });
SzinBeallit(128, 64, 200);
}
private void Csuszka_ValueChanged(object sender, EventArgs e) // a három TrackBar közös kezelője
{
if (szinkronban)
{
return;
}
SzinBeallit(trbR.Value, trbG.Value, trbB.Value);
}
private void Szam_ValueChanged(object sender, EventArgs e) // a három NumericUpDown közös kezelője
{
if (szinkronban)
{
return;
}
SzinBeallit((int)nudR.Value, (int)nudG.Value, (int)nudB.Value);
}
private void txtHex_KeyPress(object sender, KeyPressEventArgs e)
{
bool hexa = (e.KeyChar >= '0' && e.KeyChar <= '9') || (e.KeyChar >= 'a' && e.KeyChar <= 'f') || (e.KeyChar >= 'A' && e.KeyChar <= 'F');
if (!hexa && !char.IsControl(e.KeyChar))
{
e.Handled = true;
}
}
private void txtHex_TextChanged(object sender, EventArgs e)
{
if (szinkronban || txtHex.Text.Length != 6)
{
return;
}
int r = int.Parse(txtHex.Text.Substring(0, 2), NumberStyles.HexNumber);
int g = int.Parse(txtHex.Text.Substring(2, 2), NumberStyles.HexNumber);
int b = int.Parse(txtHex.Text.Substring(4, 2), NumberStyles.HexNumber);
SzinBeallit(r, g, b);
}
private void cmbElore_SelectionChangeCommitted(object sender, EventArgs e)
{
string nev = cmbElore.SelectedItem.ToString();
if (nev == "Piros")
{
SzinBeallit(255, 0, 0);
}
else if (nev == "Zöld")
{
SzinBeallit(0, 160, 0);
}
else if (nev == "Kék")
{
SzinBeallit(0, 0, 255);
}
else if (nev == "Szürke")
{
SzinBeallit(128, 128, 128);
}
else if (nev == "Narancs")
{
SzinBeallit(255, 140, 0);
}
}Mit tegyél ide: a kész színkeverő: három csúszka és három számmező, a hexa kód, az előnézeti panel és az előre megadott színek listája.Javasolt fájlnév: winforms-5-5-kesz.webp · Alt szöveg: A színkeverő összekapcsolt vezérlőkkel
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. Gomb engedélyezése a mező tartalmától
A Mindkét mező btnKuld gomb csak akkor legyen engedélyezett, ha a txtNev és a txtEmail mező sem üres (szóköz nem számít). Melyik eseményt használod, és hol állítod be a kezdő állapotot?Egy lehetséges megoldás
TextChanged eseménye ugyanazt a metódust hívja. Az ellenőrzést egy Frissit() metódus végzi, amelyet az ablak indulásakor is meg kell hívni, hogy a kezdő állapot helyes legyen.private void GombFrissit()
{
btnKuld.Enabled = txtNev.Text.Trim().Length > 0 && txtEmail.Text.Trim().Length > 0;
}
private void Form1_Load(object sender, EventArgs e)
{
GombFrissit();
}
private void Mezo_TextChanged(object sender, EventArgs e)
{
GombFrissit();
}
2. Kaszkádolt választás: ország, város
Készíts három szintű kaszkádot ( Minden szint kezelőjében előbb törölni kell az alatta lévő összes listát, és cmbKontinens, cmbOrszag, cmbVaros), ahol minden lista az előzőtől függ, és ha egy felsőbb választás megváltozik, az alatta lévők kiürülnek. Hogyan kezeled a null kiválasztást?Egy lehetséges megoldás
null kiválasztásnál kilépni. Az alsó szint kezelője a Clear() hatására is lefut, ezért az is null-biztos legyen.private void cmbKontinens_SelectedIndexChanged(object sender, EventArgs e)
{
cmbOrszag.Items.Clear();
cmbVaros.Items.Clear();
if (cmbKontinens.SelectedItem == null)
{
return;
}
if (cmbKontinens.SelectedItem.ToString() == "Európa")
{
cmbOrszag.Items.AddRange(new object[] { "Magyarország", "Ausztria" });
}
else
{
cmbOrszag.Items.AddRange(new object[] { "Japán", "India" });
}
}
private void cmbOrszag_SelectedIndexChanged(object sender, EventArgs e)
{
cmbVaros.Items.Clear();
if (cmbOrszag.SelectedItem == null)
{
return;
}
string orszag = cmbOrszag.SelectedItem.ToString();
if (orszag == "Magyarország")
{
cmbVaros.Items.AddRange(new object[] { "Budapest", "Veszprém" });
}
else if (orszag == "Ausztria")
{
cmbVaros.Items.AddRange(new object[] { "Bécs", "Salzburg" });
}
else if (orszag == "Japán")
{
cmbVaros.Items.AddRange(new object[] { "Tokió", "Oszaka" });
}
else if (orszag == "India")
{
cmbVaros.Items.AddRange(new object[] { "Delhi", "Mumbai" });
}
}
3. Mindent kijelöl és a számláló
A A tömeges változtatás előtt jelzőt (CheckedListBox fölött egy chkMind jelölőnégyzet kijelöli vagy törli az összes elemet, és a lblDb mindig a helyes bejelölt darabszámot mutatja. Hogyan kerülöd el, hogy a „Mind” kijelölése közben az ItemCheck eseménye minden elemnél újraszámoljon hibásan?Egy lehetséges megoldás
tomeges) állítunk be, hogy az ItemCheck kezelő ne számoljon, a végén pedig egyszer számolunk. A végső számot a CheckedItems.Count ilyenkor már helyesen adja (a változások megtörténtek).private bool tomeges = false;
private void chkMind_CheckedChanged(object sender, EventArgs e)
{
tomeges = true;
try
{
for (int i = 0; i < clbLista.Items.Count; i++)
{
clbLista.SetItemChecked(i, chkMind.Checked);
}
}
finally
{
tomeges = false;
}
lblDb.Text = "Bejelölve: " + clbLista.CheckedItems.Count;
}
private void clbLista_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (tomeges)
{
return;
}
int db = clbLista.CheckedItems.Count;
if (e.NewValue == CheckState.Checked)
{
db = db + 1;
}
else if (e.CurrentValue == CheckState.Checked)
{
db = db - 1;
}
lblDb.Text = "Bejelölve: " + db;
}
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. Karakterszámláló és szóhossz
Egy A txtSzoveg mező alatt az lblDb mutassa a karakterek számát, és az lblFigyel legyen piros, ha a szöveg hossza meghaladja a 100 karaktert. Melyik esemény a legegyszerűbb ehhez?Megoldás megnyitása
TextChanged, mert a beírás mellett a beillesztést és a kódból történő módosítást is kezeli, a KeyPress viszont nem.private void txtSzoveg_TextChanged(object sender, EventArgs e)
{
int hossz = txtSzoveg.TextLength;
lblDb.Text = hossz + " / 100";
lblDb.ForeColor = hossz > 100 ? Color.Firebrick : Color.Black;
}
2. Jelszó megjelenítése
Egy A chkMutat jelölőnégyzet a txtJelszo mezőben mutassa vagy rejtse a jelszót. Melyik eseményt és tulajdonságot használod?Megoldás megnyitása
CheckedChanged eseményben a UseSystemPasswordChar tulajdonság értékét az ellenkezőjére állítjuk.private void chkMutat_CheckedChanged(object sender, EventArgs e)
{
txtJelszo.UseSystemPasswordChar = !chkMutat.Checked;
}
Közepes szint
3. Jelszóerősség jelzése
A A txtJelszo írása közben egy pgbEro ProgressBar mutassa a jelszó erősségét (0–100): +25 a legalább 8 karakterért, +25 a nagybetűért, +25 a számjegyért, +25 a speciális karakterért. Az lblEro is írja ki: Gyenge, Közepes, Erős.Megoldás megnyitása
TextChanged eseményben végigmegyünk a karaktereken, és gyűjtjük a feltételeket. A szabályokat egy metódusba tesszük, amely a pontszámot adja vissza.private int Pontszam(string jelszo)
{
int pont = 0;
bool nagy = false;
bool szam = false;
bool spec = false;
foreach (char c in jelszo)
{
if (char.IsUpper(c)) { nagy = true; }
else if (char.IsDigit(c)) { szam = true; }
else if (!char.IsLetter(c)) { spec = true; }
}
if (jelszo.Length >= 8) { pont = pont + 25; }
if (nagy) { pont = pont + 25; }
if (szam) { pont = pont + 25; }
if (spec) { pont = pont + 25; }
return pont;
}
private void txtJelszo_TextChanged(object sender, EventArgs e)
{
int pont = Pontszam(txtJelszo.Text);
pgbEro.Value = pont;
lblEro.Text = pont <= 25 ? "Gyenge" : (pont <= 75 ? "Közepes" : "Erős");
}
4. Tól–ig dátumok
Két A DateTimePicker (dtpTol, dtpIg) úgy működjön, hogy a tól sosem lehet a ig utáni. Ha a felhasználó a tól értékét az ig utánra állítja, az ig is utána kerüljön, és fordítva. Hogyan kerülöd el a hurkot?Megoldás megnyitása
ValueChanged eseményben a másik dátumot igazítjuk, jelző védelmében. A jelző nélkül is megállna a folyamat (az igazítás után a feltétel már nem teljesül), de a jelző megvédi a további mellékhatásoktól (például egy lekérdezéstől).private bool igazitas = false;
private void dtpTol_ValueChanged(object sender, EventArgs e)
{
if (igazitas)
{
return;
}
igazitas = true;
if (dtpTol.Value > dtpIg.Value)
{
dtpIg.Value = dtpTol.Value;
}
igazitas = false;
}
private void dtpIg_ValueChanged(object sender, EventArgs e)
{
if (igazitas)
{
return;
}
igazitas = true;
if (dtpIg.Value < dtpTol.Value)
{
dtpTol.Value = dtpIg.Value;
}
igazitas = false;
}
Kihívás szint
5. Csak a felhasználó változtatása
Egy A program általi beállítások köré jelzőt teszünk, és a kezelő csak akkor naplóz, ha a jelző hamis. A jelző minden program általi módosítást elnyel.nudMennyiseg mező minden felhasználói változtatást naplóz az lstNaplo listába, de a program általi beállításokat (például a Visszaállít gomb) nem. Mit teszel, ha a NumericUpDown-nak nincs „csak felhasználó” eseménye?Megoldás megnyitása
private bool programBeallit = false;
private void nudMennyiseg_ValueChanged(object sender, EventArgs e)
{
if (programBeallit)
{
return;
}
lstNaplo.Items.Insert(0, "Felhasználó: " + nudMennyiseg.Value);
}
private void btnVisszaallit_Click(object sender, EventArgs e)
{
programBeallit = true;
nudMennyiseg.Value = 1;
programBeallit = false;
}
6. Szűrő késleltetéssel és a Mégse
A késleltetett kereső ( A Timer, 400 ms) mellé készíts egy Törlés gombot, amely a keresőmezőt azonnal kiüríti, és a lista azonnal, várakozás nélkül frissüljön. Mire kell vigyázni a Timer állapotával?Megoldás megnyitása
txtKereso.Clear() kiváltja a TextChanged eseményt, amely elindítja a Timer-t, így a lista késéssel frissülne. Ezért a törlés után a Timer-t le kell állítani, és a szűrést közvetlenül meg kell hívni. A közös Szur() metódus teszi lehetővé, hogy a Timer és a gomb is ugyanazt hívja.private void Szur()
{
lblAllapot.Text = "Keresés: " + txtKereso.Text.Trim();
// ide jön a tényleges szűrés
}
private void tmrKeres_Tick(object sender, EventArgs e)
{
tmrKeres.Stop();
Szur();
}
private void btnTorles_Click(object sender, EventArgs e)
{
txtKereso.Clear(); // ez elindítja a Timer-t...
tmrKeres.Stop(); // ...ezért leállítjuk...
Szur(); // ...és azonnal szűrünk
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Minden tényleges szövegváltozásnál, a felhasználó és a program általi változtatásnál is. Azonos érték beállításánál nem fut. A Ha két vezérlő eseménye egymást változtatja. Védekezés: jelző ( Hogy a jelző hiba esetén is visszaálljon hamisra, különben a vezérlők végleg elszakadnának egymástól. A változás előtt fut, ezért a A keresés csak a gépelés befejezése után fut (a Mert az 1. Mikor fut le a TextChanged?
2. Mi a különbség a SelectedIndexChanged és a SelectionChangeCommitted között?
SelectedIndexChanged a program általi változtatásra is lefut, a SelectionChangeCommitted csak a felhasználó választására.3. Mi a visszacsatolási hurok, és hogyan védekezel ellene?
szinkronban), „csak felhasználó” esemény (Scroll, SelectionChangeCommitted), vagy átmeneti leiratkozás.4. Miért kell try–finally a jelző beállításánál?
5. Mi a baj az ItemCheck eseménnyel?
CheckedItems még a régi állapotot mutatja. Az új állapotot az e.NewValue adja.6. Mire való a késleltetett keresés?
Timer minden új karakternél újraindul), így nem fut feleslegesen minden betűnél.7. Miért kell ellenőrizni a SelectedItem értékét null-ra?
Items.Clear() vagy a SelectedIndex = -1 megszünteti a kiválasztást, és az esemény ilyenkor null elemmel fut.
Mit tudsz már?
- Ismered a változás-eseményeket, és tudod, mely vezérlőhöz melyik való.
- Tudod, hogy a program általi változtatás is kiváltja az eseményt.
- Jelzővel és „csak felhasználó” eseménnyel kerülöd a visszacsatolást.
- Összekapcsolt vezérlőket hangolsz össze, kaszkádolt listát és késleltetett keresést építesz.
- Ismered az
ItemCheckcsapdáját.
Következő lecke: Load, Shown, FormClosing és FormClosed
A vezérlők után az ablak életciklusa következik: mikor indul, jelenik meg és záródik az ablak, és mit kell az egyes pillanatokban tenni.




