Grafikus C# – Windows Forms tanfolyam › 8.2 lecke Windows Forms · 8.2 lecke · listaelemek A listavezérlők szíve az Items, SelectedItem és SelectedIndex
Items gyűjtemény és a kijelölés (SelectedItem, SelectedIndex). Ebben a leckében pontosan megtanulod, mit tehetsz az elemekkel (hozzáadás, beszúrás, csere, törlés, mozgatás), hogyan tárolsz a listában saját objektumokat (nem csak szöveget), hogyan olvasod vissza őket típushelyesen, és hogyan követed a kijelölést úgy, hogy a SelectedIndexChanged esemény ne okozzon váratlan hibát.
A lecke végére
- az
Itemsgyűjtemény összes fontos műveletét használod; - saját objektumokat teszel a listába, és típushelyesen olvasod vissza (
as,is); - követed a kijelölést, és a törlés vagy csere után helyreállítod;
- elemet mozgatsz fel és le (csere), és a kijelölés vele marad;
- a
SelectedIndexChangedeseményt védetten kezeled (-1, jelző); - felismered a gyakori hibákat (
null, elcsúszó kijelölés, bejárás közbeni módosítás).
Mielőtt elkezded
Ehhez a leckéhez a 8.1 lecke (listavezérlők), a 7.2 lecke (ToString, Items[i] = elem), az 5.2 lecke (as, is) és az 5.5 lecke (jelző) szükséges.
Az Items gyűjtemény
| Tag | Mit tesz? |
|---|---|
Add(elem) | Az elemet a lista végére teszi, a sorszámát adja vissza. |
AddRange(tömb) | Egyszerre több elem. |
Insert(index, elem) | Adott helyre szúr (rendezetlen listán). |
Remove(elem), RemoveAt(index) | Törlés elem vagy sorszám alapján. |
Clear() | Az összes elem törlése (a kijelölés -1 lesz). |
Contains(elem), IndexOf(elem) | Szerepel-e az elem, hanyadik. |
Count, Items[i] | Elemszám, elem sorszám szerint (felülírható is). |
int hely = lst.Items.Add("Alma"); // hely: az új elem sorszáma
lst.SelectedIndex = hely; // az újonnan hozzáadott elem kijelölve
lst.Items.Insert(0, "Első"); // az elejére (nem rendezett listán)
lst.Items[2] = "Felülírt"; // csere
if (lst.Items.Contains("Alma")) { lst.Items.Remove("Alma"); }
lst.Items.RemoveAt(0);
for (int i = lst.Items.Count - 1; i >= 0; i--) // bejárás törléshez: visszafelé
{
if (lst.Items[i].ToString().StartsWith("X"))
{
lst.Items.RemoveAt(i);
}
}Add a hozzáadott elem sorszámát adja: lst.SelectedIndex = lst.Items.Add(x); egy sorban hozzáad és kijelöl.Remove(elem) és a Contains(elem) az elem Equals metódusát használja: saját objektumnál ez alapból a hivatkozás egyezése (ugyanaz a példány), nem a tartalomé.Jósolj!
Egy ötelemű lista elemeit előrefelé haladó for ciklussal törölöd (for (int i = 0; i < Count; i++) if (feltétel) RemoveAt(i);), és két szomszédos elem is megfelel a feltételnek. Mi történik?
Jóslat ellenőrzése
Az egyik megfelelő elem megmarad: a törlés után a következő elem a törölt helyére csúszik, a ciklus pedig már a következő sorszámra lép, így kihagyja. A visszafelé haladó ciklus ezt kiküszöböli.
Saját objektumok a listában
Az Items elemei object típusúak: bármit tárolhat, a megjelenített szöveget a ToString() adja. Szöveg helyett így valódi adatobjektumokat teszel a listába, és a kijelölésnél az egész objektumot megkapod vissza:
public class Szam
{
public string Cim = "";
public int Masodperc = 0;
public override string ToString()
{
return Cim + " (" + Masodperc / 60 + ":" + (Masodperc % 60).ToString("00") + ")";
}
}
lst.Items.Add(new Szam { Cim = "Esti dal", Masodperc = 215 });
// visszaolvasás: típuskényszerítéssel; az 'as' null-t ad, ha nincs kijelölés
Szam kijelolt = lst.SelectedItem as Szam;
if (kijelolt != null)
{
txtCim.Text = kijelolt.Cim;
nudHossz.Value = kijelolt.Masodperc;
}
// az összes elem feldolgozása típushelyesen
int ossz = 0;
foreach (object o in lst.Items)
{
ossz += ((Szam)o).Masodperc;
}lst.SelectedItem as Szam null értéket ad, ha nincs kijelölés (vagy az elem más típusú): a null ellenőrzés védi a kódot.as Szam ugyanazt a példányt adja, a módosítása közvetlenül a listaelemet módosítja. A lista szövege viszont nem frissül (Items[i] = elem kell).ToString() adja. A DisplayMember (8.5 lecke) ennek rugalmasabb alternatívája.foreach (Szam s in lst.Items) alakban a ciklusváltozó típusa is megadható: a C# típuskényszerítést végez, és hibát dob, ha valamelyik elem más típusú.Jósolj!
A lista egy Szam objektumot tartalmaz, a kijelölt elem módosítása után (kijelolt.Cim = "Új cím";) a lista szövege nem változik. Miért, és mi a javítás?
Jóslat ellenőrzése
Az objektum módosult, de a lista a szöveget a hozzáadáskor (vagy a legutóbbi rajzoláskor) kérte le, és nem tud a változásról. A javítás: lst.Items[lst.SelectedIndex] = kijelolt; (ugyanaz az objektum visszaírva újrarajzoltatja), vagy az adatkötés (BindingList, 8.4 lecke), amely magától frissül.
A kijelölés követése
private bool betoltes = false;
private void lst_SelectedIndexChanged(object sender, EventArgs e)
{
if (betoltes)
{
return; // programozott változás közben nem reagálunk
}
Szam sz = lst.SelectedItem as Szam;
// a -1 (nincs kijelölés) is érvényes eset: a gombokat tiltjuk, a mezőket ürítjük
btnTorol.Enabled = sz != null;
btnFel.Enabled = lst.SelectedIndex > 0;
btnLe.Enabled = lst.SelectedIndex >= 0 && lst.SelectedIndex < lst.Items.Count - 1;
txtCim.Text = sz == null ? "" : sz.Cim;
}
// törlés után a szomszéd elem jelölése, hogy a felhasználó folytathassa
private void btnTorol_Click(object sender, EventArgs e)
{
int i = lst.SelectedIndex;
if (i < 0) { return; }
lst.Items.RemoveAt(i);
if (lst.Items.Count > 0)
{
lst.SelectedIndex = Math.Min(i, lst.Items.Count - 1);
}
}SelectedIndexChanged lefut, ha a kijelölés megszűnik (Clear, törlés): a kezelőnek a -1 és a null esetet is kezelnie kell, különben NullReferenceException keletkezik.betoltes jelzővel kihagyhatod, és a művelet végén egyszer frissítesz (5.5 lecke).Math.Min védi a listavégi esetet.SelectedIndexChanged eseményben frissül, és minden módosítás után is meg kell hívni.Jósolj!
A SelectedIndexChanged kezelő a ((Szam)lst.SelectedItem).Cim sort tartalmazza ellenőrzés nélkül, és a Törlés gomb az utolsó elemet törli. Mi történik?
Jóslat ellenőrzése
Az utolsó elem törlése után a kijelölés megszűnik (-1), az esemény lefut, és a SelectedItem null: NullReferenceException. A kezelőnek mindig kezelnie kell a nincs-kijelölés esetét.
Elem mozgatása és cseréje
A felhasználók gyakran szeretnének egy elemet feljebb vagy lejjebb tenni (lejátszási lista, prioritások). A ListBoxnak nincs „csere” művelete: az elemet kivesszük, és az új helyre beszúrjuk, a kijelölést pedig mi állítjuk vissza:
private void Mozgat(int irany) // -1: fel, +1: le
{
int i = lst.SelectedIndex;
int uj = i + irany;
if (i < 0 || uj < 0 || uj >= lst.Items.Count)
{
return;
}
object elem = lst.Items[i];
betoltes = true; // a közbülső kijelölésváltozások ne fussanak
lst.Items.RemoveAt(i);
lst.Items.Insert(uj, elem);
lst.SelectedIndex = uj; // a kijelölés követi az elemet
betoltes = false;
Frissit(); // gombok állapota, összesítő
}
private void btnFel_Click(object sender, EventArgs e) { Mozgat(-1); }
private void btnLe_Click(object sender, EventArgs e) { Mozgat(1); }Sorted) listán a mozgatás értelmetlen, mert a sorrendet a rendezés adja.object t = Items[i]; Items[i] = Items[i2]; Items[i2] = t;, de a kijelölés ilyenkor az eredeti helyen marad, ezért itt is be kell állítani.BindingList), a mozgatás az adatgyűjteményben történik, és a lista követi (8.4 lecke).Jósolj!
A Mozgat metódusból kihagyod a lst.SelectedIndex = uj; sort. Mit tapasztal a felhasználó a Fel gomb megnyomásakor?
Jóslat ellenőrzése
Az elem feljebb kerül, de a kijelölés elvész (vagy egy másik elemen marad): a felhasználó nem tudja a Fel gombot ismételten nyomni az elem követésével. A kijelölés visszaállítása a mozgatás része.
Mit tegyél ide: a lejátszási lista: elemek, mellettük a Fel, Le, Törlés gombok és az összes hossz felirat.Javasolt fájlnév: winforms-8-2-lista.webp · Alt szöveg: Átrendezhető lejátszási lista
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. InvalidCastException a listaelemnél
Szam sz = (Szam)lst.SelectedItem; // az elem szöveg, vagy nullHiba és javítás
A kijelölt elem nem Szam típusú (például szöveg került a listába), vagy nincs kijelölés. Használj as és null ellenőrzést, és csak egy típust tegyél a listába.
2. A törlés kihagy egy elemet
A feltételnek megfelelő szomszédos elemek közül az egyik megmarad. Előrefelé haladó Ok és javítás
for ciklussal töröltél. Járj visszafelé (for (int i = Count - 1; i >= 0; i--)).
3. A módosított adat nem látszik a listában
Az objektum mezőjét átírtad, a lista szövege régi. A lista nem értesül a változásról. Írd vissza az elemet a helyére (Ok és javítás
Items[i] = elem), vagy használj adatkötést (8.4 lecke).
4. NullReferenceException az utolsó elem törlése után
A A kezelő a -1 (nincs kijelölés) esetet nem kezeli. Ellenőrizd a SelectedIndexChanged kezelő összeomlik.Ok és javítás
SelectedItem != null feltételt.
5. A Remove nem talál egy elemet
lst.Items.Remove(new Szam { Cim = "Esti dal", Masodperc = 215 }); // új példányHiba és javítás
A Remove hivatkozás szerint egyeztet: az új példány nem azonos a listában lévővel. Add meg a listában lévő példányt (SelectedItem), vagy keresd meg tartalom alapján, és úgy töröld.
6. A kijelölés ugrál mozgatás közben
A felhasználó nem tudja követni az elemet. A kivétel és beszúrás közben a kijelölés megváltozik, és a kezelő közben lefut. Használj Ok és javítás
betoltes jelzőt, és a végén állítsd be az új sorszámot.
Mini projekt: lejátszási lista
Készíts átrendezhető lejátszási listát:
- Az ablak:
lst(ListBox,Szamobjektumok),txtCim,nudPercésnudMp(hossz), gombok: Hozzáad, Módosít, Töröl, Fel, Le, Duplikál;lblOssz(5 szám, 18:42). - Kijelölésre a mezők feltöltődnek a szám adataival; kijelölés nélkül ürülnek, és a kijelölést igénylő gombok le vannak tiltva.
- A Módosít a kijelölt objektumot módosítja, és visszaírja a helyére; a Duplikál másolatot (új példány) tesz közvetlenül az eredeti alá.
- A Töröl megerősítést kér (7.4 lecke), a törlés után a szomszéd elem jelölt.
- A Fel és Le mozgatja az elemet, a kijelölés vele marad, a határoknál a gomb tiltott.
- A
lblOsszminden változás után frissül. - Teszteld: hozzáadás, módosítás (a lista szövege frissül), törlés az utolsó elemre, mozgatás a határokon, duplikálás, üres lista.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A Frissit() metódus számolja az összesítést és az engedélyezéseket; minden módosítás után meghívjuk. A betoltes jelző a mozgatás és a mezők feltöltése közben védi a kezelőt.
public class Szam
{
public string Cim = "";
public int Masodperc = 0;
public Szam Masol()
{
return (Szam)this.MemberwiseClone();
}
public override string ToString()
{
return Cim + " (" + Masodperc / 60 + ":" + (Masodperc % 60).ToString("00") + ")";
}
}
private bool betoltes = false;
private void Form1_Load(object sender, EventArgs e)
{
Frissit();
}
private int Hossz()
{
return (int)nudPerc.Value * 60 + (int)nudMp.Value;
}
private void Frissit()
{
int i = lst.SelectedIndex;
bool van = i >= 0;
btnModosit.Enabled = van;
btnTorol.Enabled = van;
btnDuplikal.Enabled = van;
btnFel.Enabled = i > 0;
btnLe.Enabled = van && i < lst.Items.Count - 1;
int ossz = 0;
foreach (Szam s in lst.Items)
{
ossz += s.Masodperc;
}
lblOssz.Text = lst.Items.Count + " szám, " + ossz / 60 + ":" + (ossz % 60).ToString("00");
}
private void lst_SelectedIndexChanged(object sender, EventArgs e)
{
if (betoltes) { return; }
Szam sz = lst.SelectedItem as Szam;
betoltes = true;
txtCim.Text = sz == null ? "" : sz.Cim;
nudPerc.Value = sz == null ? 0 : Math.Min(nudPerc.Maximum, sz.Masodperc / 60);
nudMp.Value = sz == null ? 0 : sz.Masodperc % 60;
betoltes = false;
Frissit();
}
private void btnHozzaad_Click(object sender, EventArgs e)
{
if (txtCim.Text.Trim().Length == 0) { return; }
Szam sz = new Szam { Cim = txtCim.Text.Trim(), Masodperc = Hossz() };
lst.SelectedIndex = lst.Items.Add(sz);
}
private void btnModosit_Click(object sender, EventArgs e)
{
Szam sz = lst.SelectedItem as Szam;
if (sz == null) { return; }
sz.Cim = txtCim.Text.Trim();
sz.Masodperc = Hossz();
betoltes = true;
lst.Items[lst.SelectedIndex] = sz; // a lista szövegének frissítése
betoltes = false;
Frissit();
}
private void btnDuplikal_Click(object sender, EventArgs e)
{
Szam sz = lst.SelectedItem as Szam;
if (sz != null)
{
lst.Items.Insert(lst.SelectedIndex + 1, sz.Masol());
Frissit();
}
}
private void btnTorol_Click(object sender, EventArgs e)
{
int i = lst.SelectedIndex;
if (i < 0 || MessageBox.Show(this, "Törlöd a kijelölt számot?", "Törlés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes)
{
return;
}
lst.Items.RemoveAt(i);
if (lst.Items.Count > 0)
{
lst.SelectedIndex = Math.Min(i, lst.Items.Count - 1);
}
Frissit();
}
private void Mozgat(int irany)
{
int i = lst.SelectedIndex;
int uj = i + irany;
if (i < 0 || uj < 0 || uj >= lst.Items.Count) { return; }
object elem = lst.Items[i];
betoltes = true;
lst.Items.RemoveAt(i);
lst.Items.Insert(uj, elem);
lst.SelectedIndex = uj;
betoltes = false;
Frissit();
}
private void btnFel_Click(object sender, EventArgs e) { Mozgat(-1); }
private void btnLe_Click(object sender, EventArgs e) { Mozgat(1); }Három önálló gyakorlófeladat
Egyszerre csak egyet csinálj, és csak utána nyisd ki a megoldást.
1. Első és utolsó
Két gomb (Az elejére, A végére) a kijelölt elemet a lista elejére vagy végére viszi. Hogyan? Kivétel, majd beszúrás a Egy lehetséges megoldás
0 vagy Count helyre, kijelölés beállítása.private void Szel(bool elejere)
{
int i = lst.SelectedIndex;
if (i < 0) { return; }
object elem = lst.Items[i];
lst.Items.RemoveAt(i);
int uj = elejere ? 0 : lst.Items.Count;
lst.Items.Insert(uj, elem);
lst.SelectedIndex = uj;
}
2. Keresés tartalom szerint
A Keres gomb kijelöli azt a számot, amelynek a címe megegyezik a beírt szöveggel. Miért nem jó a Az Items.IndexOf(new Szam{...})?Egy lehetséges megoldás
IndexOf hivatkozás szerint keres, az új példány nem egyezik. Ciklus kell a tartalom összehasonlítására.for (int i = 0; i < lst.Items.Count; i++)
{
if (((Szam)lst.Items[i]).Cim.Equals(txtKeres.Text.Trim(), StringComparison.CurrentCultureIgnoreCase))
{
lst.SelectedIndex = i;
return;
}
}
lblUzenet.Text = "Nincs ilyen című szám.";
3. Több kijelölt elem mozgatása
MultiExtended listán a Fel gomb az összes kijelölt elemet egy hellyel feljebb viszi. Mire kell vigyáznod? A sorszámokat előbb egy listába másold, és felülről lefelé dolgozz; ha az első elem már a lista elején van, nem mozgatható semmi.Egy lehetséges megoldás
List<int> indexek = new List<int>();
foreach (int i in lst.SelectedIndices) { indexek.Add(i); }
if (indexek.Count == 0 || indexek[0] == 0) { return; }
lst.BeginUpdate();
foreach (int i in indexek)
{
object elem = lst.Items[i];
lst.Items.RemoveAt(i);
lst.Items.Insert(i - 1, elem);
}
lst.ClearSelected();
foreach (int i in indexek) { lst.SetSelected(i - 1, true); }
lst.EndUpdate();
Gyakorlótár: 6 további feladat
Alap, közepes és kihívás szinten gyakorolhatsz. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Add visszatérési értéke
Mire használod az Az új elem sorszáma: egy sorban hozzáadhatod és kijelölheted (Add által visszaadott számot?Megoldás megnyitása
lst.SelectedIndex = lst.Items.Add(x);).
2. as vagy kényszerítés?
Mikor jobb az Az as, mikor a (Szam) kényszerítés?Megoldás megnyitása
as null-t ad (nincs kijelölés, más típus): védett kódhoz. A (Szam) kivételt dob, ha nem megfelelő: ott használd, ahol a típus biztosan helyes (például a listát te töltöd, és minden elem Szam).
Közepes szint
3. Összes elem szövegben
Készíts szöveget, amely a lista elemeit vesszővel elválasztva sorolja fel.Megoldás megnyitása
string.Join és a ToString(): a Items elemei object típusúak, ezért tömbbe kell másolni.List<string> szovegek = new List<string>();
foreach (object o in lst.Items)
{
szovegek.Add(o.ToString());
}
string vege = string.Join(", ", szovegek);
4. A lista rendezése tartalom szerint
A Rendezés hossz szerint gomb a listát a számok hossza szerint rendezi. A A Sorted nem jó, miért?Megoldás megnyitása
Sorted a ToString() szövege szerint rendez (ábécé), nem a hossz szerint. A számokat egy List<Szam> listába másolod, rendezed (Sort lambdával), és újratöltöd.List<Szam> tomb = new List<Szam>();
foreach (Szam s in lst.Items) { tomb.Add(s); }
tomb.Sort((a, b) => a.Masodperc.CompareTo(b.Masodperc));
lst.BeginUpdate();
lst.Items.Clear();
foreach (Szam s in tomb) { lst.Items.Add(s); }
lst.EndUpdate();
Kihívás szint
5. Visszavonás
Az utolsó törlés visszavonható. Hogyan őrzöd az elemet és a helyét? Egy Megoldás megnyitása
Stack veremre teszed a törölt elemet és a sorszámát; a visszavonás kiveszi, és visszaszúrja.private readonly Stack<KeyValuePair<int, object>> torolt = new Stack<KeyValuePair<int, object>>();
private void TorolVisszavonhatoan(int i)
{
torolt.Push(new KeyValuePair<int, object>(i, lst.Items[i]));
lst.Items.RemoveAt(i);
}
private void btnVisszavon_Click(object sender, EventArgs e)
{
if (torolt.Count > 0)
{
KeyValuePair<int, object> t = torolt.Pop();
lst.Items.Insert(Math.Min(t.Key, lst.Items.Count), t.Value);
lst.SelectedIndex = Math.Min(t.Key, lst.Items.Count - 1);
}
}
6. Egyenlőség tartalom szerint
A Ezek innentől tartalom szerint egyeztetnek, a Szam osztály Equals metódusa a címet és a hosszt hasonlítja. Mi változik a Contains és Remove viselkedésében?Megoldás megnyitása
Contains(new Szam{...}) megtalálja az azonos adatú elemet. Az Equals felülírásakor a GetHashCode felülírása is kötelező szokás.public override bool Equals(object obj)
{
Szam masik = obj as Szam;
return masik != null && masik.Cim == Cim && masik.Masodperc == Masodperc;
}
public override int GetHashCode()
{
return Cim.GetHashCode() ^ Masodperc;
}
Gyors önellenőrzés
1. Mit ad vissza az Items.Add?
Az új elem sorszámát.
2. Hogyan törölsz bejárás közben?
Visszafelé haladó for ciklussal.
3. Mit ad a SelectedItem kijelölés nélkül?
null (a SelectedIndex -1).
4. Miért nem frissül a lista szövege az objektum módosítása után?
A lista nem tud a változásról: írd vissza az elemet (Items[i] = elem), vagy használj adatkötést.
5. Hogyan mozgatsz elemet?
Kivétel (RemoveAt), beszúrás az új helyre (Insert), a kijelölés beállítása.
6. Miért kell a -1 esetet kezelni?
A SelectedIndexChanged törlésnél és Clear-nél is lefut, ilyenkor nincs kijelölt elem.
7. Hogyan egyeztet a Remove és a Contains?
Az Equals szerint: saját objektumnál alapból hivatkozás szerint.
Mit tudsz már?
- Az
Itemsgyűjtemény összes műveletét használod. - Saját objektumokat tárolsz a listában, és típushelyesen olvasod vissza.
- Követed a kijelölést, és védetten kezeled a
SelectedIndexChangedeseményt. - Elemet mozgatsz és cserélsz úgy, hogy a kijelölés vele marad.
Következő lecke: DataGridView – oszlopok, sorok és cellák
A táblázat: oszlopok, sorok és cellák, szerkesztés, kijelölés és a leggyakoribb beállítások.



