Grafikus C# – Windows Forms tanfolyam › 12.6 lecke

Windows Forms · 12.6 lecke · OOP

Adatmodellek és felelősségek

Mi tartozik egy osztályba, és mi nem? A kezdő programokban a Form1 mindent tud: tárolja az adatot, ellenőrzi, számolja, menti, és kirajzolja. Ez működik, amíg kicsi a program, de a változtatás egyre nehezebb: egy új szabály a Form tucatnyi pontját érinti. A megoldás a felelősségek szétválasztása: az adatot és annak szabályait egy modellosztály tartja, a Form csak megjeleníti azt. Ebben a leckében megtanulod, hogyan tervezz modellosztályt, mi az egy felelősség elve, hogyan tedd a modellt kényelmesen használhatóvá (ToString, Equals, rendezés, record), hol a helye az ellenőrzésnek, és hogyan jelenik meg a modell a ListBoxban.

  • kb. 100 perc
  • közepes–haladó szint
  • modell
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • modellosztályt tervezel: az adat és a rá vonatkozó szabályok egy helyen;
  • felismered a Form túl sok felelősségét, és szétosztod;
  • a modellt kényelmessé teszed (ToString, Equals/GetHashCode, IComparable);
  • ismered a record típust (új .NET), és tudod, mikor jó;
  • a szabályokat a modellben ellenőrzöd, a felületen csak megjeleníted a hibát;
  • a modellt ListBoxban, ComboBoxban és BindingList<T>-vel jeleníted meg.

Mielőtt elkezded

Ehhez a leckéhez a 12.1–12.5 lecke (osztály, konstruktor, egységbezárás) és a 7.x lecke (gyűjtemények) szükséges.

12.5 Hozzáférési szintek →

Mi az adatmodell?

A modell a program tárgyának leírása a program saját nyelvén: egy Feladat, egy Névjegy, egy Könyv. Tárolja az adatot, és ismeri a rá vonatkozó szabályokat (mi érvényes, mi számolható belőle). Nem tud a felületről: nincs benne TextBox, MessageBox, vezérlő.

Feladat.csC#
public enum Fontossag { Alacsony, Normal, Magas }

public class Feladat
{
public string Cim { get; private set; }
public DateTime? Hatarido { get; set; }
public Fontossag Fontos { get; set; } = Fontossag.Normal;
public bool Kesz { get; private set; }

public Feladat(string cim)
{
Atnevez(cim);
}

public void Atnevez(string cim)
{
if (string.IsNullOrWhiteSpace(cim)) { throw new ArgumentException("A cím kötelező.", "cim"); }

Cim = cim.Trim();
}

public void Elvegez() { Kesz = true; }

// a modell saját szabálya: ő tudja, mikor lejárt
public bool Lejart(DateTime most)
{
return !Kesz && Hatarido.HasValue && Hatarido.Value < most;
}
}
Adat + szabályA modell nem csak tároló (adatzsák): a rá vonatkozó szabályokat is ismeri (Lejart, Atnevez ellenőrzéssel). Ha a „lejárt” fogalma változik (hétvége nem számít), egyetlen helyen kell átírni.
Nincs felületi kódA modellben nincs MessageBox, TextBox. Ezért a modell önmagában kipróbálható, és több felületen (WinForms, konzol, web) is használható.
Nullable: DateTime?A DateTime? (Nullable<DateTime>) null is lehet: „nincs határidő”. A HasValue és Value adja az ellenőrzést és az értéket.
EnumA Fontossag felsorolás a hibás értékeket kizárja (nincs „7. fontosság”), és olvashatóbb a számnál.

Jósolj!

A Feladat osztály Lejart metódusa a MessageBox.Show("Lejárt!") hívást is tartalmazza. Miért gond ez?

Jóslat ellenőrzése

A modell a felülethez kötődik: nem hívható háttérszálból vagy tesztből (felugró ablak!), nem használható más felületen, és a megjelenítés módja (ablak, színezés, hang) a modellben dől el, nem a felületen. A modell értéket ad vissza (true), a megjelenítés a Form dolga.

Az egy felelősség elve

Az egy felelősség elve (Single Responsibility Principle): egy osztálynak egyetlen oka legyen a változásra. Ha egy osztály változik, valahányszor (1) a felület kinézete, (2) az üzleti szabály, (3) a fájlformátum is módosul, túl sokat vállal.

Mindent tudó Form (rossz)

  • tárolja a feladatokat egy List-ben,
  • ellenőrzi a bemenetet,
  • számolja a lejártakat,
  • menti és tölti a fájlt,
  • kirajzolja a listát.

Öt változási ok: egy módosítás sok helyen kell, és semmi sem tesztelhető külön.

Szétválasztva (jó)

  • Feladat: adat és szabály (modell),
  • FeladatLista: a gyűjtemény műveletei (12.7 lecke),
  • FeladatTarolo: mentés és betöltés (12.8 lecke),
  • Form1: csak megjelenítés és eseménykezelés.

Minden osztály egy dologért felel.

Hogyan ismered fel?Kérdezd meg: miért kell ezt az osztályt módosítani? Ha több, egymástól független okot találsz (kinézet, szabály, tárolás), szét kell bontani.
A név árulkodikHa egy osztály neve „...Kezelő” vagy „...Manager”, és minden benne van, valószínűleg túl sokat vállal. A jó név egy felelősséget fejez ki.
Nem „minél több osztály”Egy három soros programot nem kell tíz osztályra bontani. A szétválasztás ott éri meg, ahol a változások különböző okokból jönnek.
A Form feladataA Form: eseményt fogad, a modellt hívja, az eredményt megjeleníti. Ha a Form döntéseket hoz az adatról (érvényes-e, lejárt-e), az a modellbe való.

Jósolj!

A „lejárt” szabályt hárman is használják: a lista színezése, az emlékeztető és az export. A szabály háromszor, a Formban van megírva, és most a hétvégét nem számolják. Hány helyen kell módosítani?

Jóslat ellenőrzése

Három helyen (és könnyű egyet kihagyni: ellentmondó viselkedés). Ha a szabály a Feladat.Lejart metódusban él, egy helyen módosítod, és mindhárom használat követi.

Kényelmes modell: ToString, Equals, rendezés, record

Nevjegy.csC#
public class Nevjegy : IComparable<Nevjegy>, IEquatable<Nevjegy>
{
public string Nev { get; private set; }
public string Email { get; private set; }

public Nevjegy(string nev, string email) { Nev = nev.Trim(); Email = email.Trim().ToLowerInvariant(); }

// 1. ToString: a ListBox ezt jeleníti meg; a hibakereső is ezt mutatja
public override string ToString() { return Nev + " <" + Email + ">"; }

// 2. egyenlőség ÉRTÉK szerint: két névjegy ugyanaz, ha az e-mail egyezik
public bool Equals(Nevjegy mas) { return mas != null && Email == mas.Email; }
public override bool Equals(object o) { return Equals(o as Nevjegy); }
public override int GetHashCode() { return Email.GetHashCode(); } // az Equals-szal összhangban!

// 3. természetes rendezés: név szerint, magyar ábécé szerint
public int CompareTo(Nevjegy mas)
{
return string.Compare(Nev, mas.Nev, new System.Globalization.CultureInfo("hu-HU"), System.Globalization.CompareOptions.IgnoreCase);
}
}

List<Nevjegy> lista = new List<Nevjegy>();

lista.Add(new Nevjegy("Zita", "zita@pelda.hu"));
lista.Add(new Nevjegy("Ádám", "adam@pelda.hu"));

lista.Sort(); // a CompareTo alapján: Ádám, Zita
bool van = lista.Contains(new Nevjegy("Más Név", "ZITA@pelda.hu")); // igaz: az Equals az e-mailt nézi

// record (C# 9, .NET 5 és újabb): az egész automatikus
public record Konyv(string Cim, string Szerzo, int Ev);

Konyv k = new Konyv("Abigél", "Szabó Magda", 1970);
Konyv masolt = k with { Ev = 1972 }; // új példány, egy mező módosítva; az eredeti változatlan
ToStringA ListBox az elemek ToString() eredményét jeleníti meg. Alapból az osztály nevét (WindowsFormsApp.Nevjegy): saját ToString kell.
Equals és GetHashCodeAlapból az Equals a hivatkozást hasonlítja (12.1 lecke). Ha „két névjegy ugyanaz, ha az e-mail egyezik”, felülírod az Equals-t, és mindig együtt a GetHashCode-ot (különben a Dictionary és HashSet hibásan működik).
IComparableA CompareTo a természetes sorrendet adja: a Sort() ezt használja. Más sorrendhez: lista.Sort((a, b) => ...) vagy LINQ OrderBy.
recordA record adat-osztály: az Equals, GetHashCode, ToString automatikus (érték szerinti), a tulajdonságok alapból csak init (nem módosíthatók létrehozás után); a with módosított másolatot ad. Csak újabb .NET-ben (5+, C# 9); .NET Frameworkben nem.

Jósolj!

A Nevjegy osztály felülírja az Equals metódust, de a GetHashCode-ot nem. Egy HashSet<Nevjegy>-be kétszer ugyanazt az e-mail-t tartalmazó (két külön) névjegyet teszel. Hány elem lesz?

Jóslat ellenőrzése

Valószínűleg kettő: az alapértelmezett GetHashCode a hivatkozásból számol, így a két objektum különböző „fiókba” kerül, és az Equals-ot meg sem hívja a halmaz. Az Equals és a GetHashCode mindig együtt, azonos mezőkre épüljön.

Validáció és megjelenítés: ki mit tesz?

Form1.csC#
// A MODELL: eldönti, érvényes-e, és megmondja, MIÉRT nem (szöveg, nem MessageBox)
public static string Hiba(string nev, string email)
{
if (string.IsNullOrWhiteSpace(nev)) { return "A név kötelező."; }
if (email == null || !email.Contains("@")) { return "Az e-mail cím hibás."; }

return null; // nincs hiba
}

// A FORM: beolvassa a mezőket, megkérdezi a modellt, megjeleníti a választ
private void btnHozzaad_Click(object sender, EventArgs e)
{
string hiba = Nevjegy.Hiba(txtNev.Text, txtEmail.Text);

if (hiba != null) { errorProvider1.SetError(txtNev, hiba); return; } // megjelenítés: a Form dolga

nevjegyek.Add(new Nevjegy(txtNev.Text, txtEmail.Text));
}

// modell megjelenítése: BindingList + DataSource: a lista változása automatikusan frissíti a ListBoxot
private BindingList<Nevjegy> nevjegyek = new BindingList<Nevjegy>();

private void Form1_Load(object sender, EventArgs e)
{
lstNevjegyek.DataSource = nevjegyek; // a ListBox ezt a listát tükrözi
lstNevjegyek.DisplayMember = "Nev"; // melyik tulajdonságot mutassa (ToString helyett)
}

private void lstNevjegyek_SelectedIndexChanged(object sender, EventArgs e)
{
Nevjegy n = lstNevjegyek.SelectedItem as Nevjegy; // a kiválasztott: maga a MODELL-objektum, nem szöveg

if (n != null) { lblReszlet.Text = n.ToString(); }
}
Szabály a modellbenA modell mondja meg, érvényes-e az adat és miért nem (hibaüzenet-szöveg). Így ugyanaz a szabály él a felületen, a fájlbetöltésnél és a tesztben.
Megjelenítés a FormbanHogy a hibát hogyan jelzi (ErrorProvider, szín, MessageBox), a Form dolga. A modell csak szöveget ad.
DataSourceA DataSource a vezérlőt a listához köti: a ListBox elemei a lista elemei (objektumok, nem szövegek). A SelectedItem az eredeti modell-objektumot adja: nincs szöveg-elemzés.
BindingList<T>A sima List<T> változásáról a vezérlő nem értesül (újra kell kötni). A BindingList<T> jelez, ezért a Add/Remove után a lista magától frissül. (System.ComponentModel.) DisplayMember a tulajdonság neve szövegként.

Jósolj!

A ListBox.DataSource egy sima List<Nevjegy>. Hozzáadsz egy elemet a listához (lista.Add(...)). Mit mutat a ListBox?

Jóslat ellenőrzése

Nem változik: a List<T> nem értesíti a vezérlőt. Vagy BindingList<T> kell, vagy újra be kell állítani a DataSource-ot (= null, majd = lista).

📷 Képernyőkép helye

Mit tegyél ide: a kontaktok programja: a lista, a részletek és a modell hibaüzenete az ErrorProviderben.Javasolt fájlnév: winforms-12-6-kontaktok.webp · Alt szöveg: A kontaktok programja

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 ListBox az osztály nevét mutatja

Az elemek helyén MiProjekt.Nevjegy áll.

Ok és javítás

Nincs felülírt ToString(). Írd meg, vagy állítsd be a DisplayMember értékét.

2. A Contains nem találja meg

lista.Contains(new Nevjegy("Zita", "zita@pelda.hu"));   // hamis, pedig van ilyen
Hiba és javítás

Az alap Equals a hivatkozást hasonlítja. Írd felül az Equals és GetHashCode metódust az azonosító mezőre.

3. A szótár hibásan működik

Az Equals felülírva, a Dictionary mégis duplán tárolja ugyanazt.

Ok és javítás

Hiányzik a GetHashCode felülírása (vagy más mezőkre épül, mint az Equals). Mindkettő ugyanazokat a mezőket használja.

4. A modell MessageBoxot hív

if (nev == "") { MessageBox.Show("Hibás név"); }   // a Nevjegy osztályban
Hiba és javítás

A modell a felületre támaszkodik. Dobj kivételt, vagy adj vissza hibaszöveget; a Form jelenítse meg.

5. A lista nem frissül a hozzáadás után

List<Nevjegy> lista = new List<Nevjegy>();
lst.DataSource = lista;
lista.Add(n); // a ListBox nem változik
Hiba és javítás

BindingList<Nevjegy>-et használj, vagy kösd újra a DataSource-ot.

6. A modell minden mezője nyilvánosan írható

Egy hibás nev.Nev = "" sor érvénytelenné teszi az objektumot.

Ok és javítás

private set, és a módosítás ellenőrző metóduson (Atnevez) át (12.5 lecke).

Mini projekt: kontaktok modellje

Készíts kontaktlistát, ahol a modell és a felület szigorúan külön él:

  1. Nevjegy modell: Nev, Email, Telefon, Kategoria (enum: Barát, Család, Iskola, Munka); private set; fő konstruktor ellenőrzéssel; static string Hiba(...) az érvényesség ellenőrzéséhez (a Form is ezt használja előzetes ellenőrzésre).
  2. Érték szerinti egyenlőség: az e-mail alapján (Equals, GetHashCode, IEquatable<Nevjegy>); az e-mail kisbetűre normalizálva.
  3. Rendezés: IComparable<Nevjegy> magyar ábécé szerint; a Form Rendez gombja és egy ComboBox (név / kategória / e-mail szerint).
  4. Felület: lstNevjegyek (DataSource egy BindingList<Nevjegy>), txtNev, txtEmail, txtTelefon, cmbKategoria, Hozzáad, Törlés, txtKeres (szűrés); lblReszlet; ErrorProvider.
  5. Hozzáadás: a Form előbb a Nevjegy.Hiba metódust hívja (a hibát az ErrorProvider mutatja); ismétlődő e-mail: Ez az e-mail már szerepel (a Contains segítségével).
  6. Szűrés: a keresőmező szerint a név vagy az e-mail része (kis-nagybetű nélkül); a szűrt lista külön BindingList (a modell lista változatlan); üres keresésnél mindenki.
  7. Statisztika a modell oldaláról: kategóriánkénti darabszám (LINQ GroupBy) a lblStatban; a számolás a modell oldalán (static metódus egy listára), a Form csak kiírja.
  8. Szétválasztás ellenőrzése: a Nevjegy fájlban nincs using System.Windows.Forms;.
  9. Teszteld: hibás adatok, ismétlődő e-mail (kis-nagybetű eltéréssel), rendezés ékezetes nevekkel (Ádám, Zita, Éva), szűrés, törlés, üres lista, kijelölés nélküli törlés.
Megoldás megnyitása (próbáld ki előbb önállóan!)
Nevjegy.cs és Form1.cs (using System.ComponentModel; using System.Globalization; using System.Linq;)C#
// ---- Nevjegy.cs: NINCS using System.Windows.Forms ----
public enum Kategoria { Barat, Csalad, Iskola, Munka }

public class Nevjegy : IComparable<Nevjegy>, IEquatable<Nevjegy>
{
private static readonly CultureInfo hu = new CultureInfo("hu-HU");

public string Nev { get; private set; }
public string Email { get; private set; }
public string Telefon { get; private set; }
public Kategoria Kat { get; private set; }

public Nevjegy(string nev, string email, string telefon, Kategoria kat)
{
string hiba = Hiba(nev, email);

if (hiba != null) { throw new ArgumentException(hiba); }

Nev = nev.Trim();
Email = email.Trim().ToLowerInvariant();
Telefon = (telefon ?? "").Trim();
Kat = kat;
}

public static string Hiba(string nev, string email)
{
if (string.IsNullOrWhiteSpace(nev)) { return "A név kötelező."; }
if (string.IsNullOrWhiteSpace(email)) { return "Az e-mail kötelező."; }

int at = email.IndexOf('@');

if (at <= 0 || email.IndexOf('.', at) < at + 2 || email.EndsWith(".")) { return "Az e-mail cím hibás."; }

return null;
}

public static Dictionary<Kategoria, int> Statisztika(IEnumerable<Nevjegy> lista)
{
return lista.GroupBy(n => n.Kat).ToDictionary(g => g.Key, g => g.Count());
}

public bool Egyezik(string keres)
{
return keres.Length == 0 || Nev.IndexOf(keres, StringComparison.CurrentCultureIgnoreCase) >= 0 || Email.IndexOf(keres, StringComparison.OrdinalIgnoreCase) >= 0;
}

public override string ToString() { return Nev + " <" + Email + ">"; }

public bool Equals(Nevjegy mas) { return mas != null && Email == mas.Email; }
public override bool Equals(object o) { return Equals(o as Nevjegy); }
public override int GetHashCode() { return Email.GetHashCode(); }

public int CompareTo(Nevjegy mas) { return string.Compare(Nev, mas.Nev, hu, CompareOptions.IgnoreCase); }
}

// ---- Form1.cs ----
private readonly List<Nevjegy> mind = new List<Nevjegy>();
private readonly BindingList<Nevjegy> latszik = new BindingList<Nevjegy>();

private void Form1_Load(object sender, EventArgs e)
{
cmbKategoria.DataSource = Enum.GetValues(typeof(Kategoria));
cmbRendez.Items.AddRange(new object[] { "Név", "Kategória", "E-mail" });
cmbRendez.SelectedIndex = 0;
lstNevjegyek.DataSource = latszik;
}

private void Szur()
{
string k = txtKeres.Text.Trim();

latszik.RaiseListChangedEvents = false; // egyszerre frissítünk
latszik.Clear();
foreach (Nevjegy n in mind.Where(x => x.Egyezik(k))) { latszik.Add(n); }
latszik.RaiseListChangedEvents = true;
latszik.ResetBindings();

Statisztika();
}

private void Statisztika()
{
lblStat.Text = string.Join(", ", Nevjegy.Statisztika(mind).Select(p => p.Key + ": " + p.Value));
}

private void btnHozzaad_Click(object sender, EventArgs e)
{
errorProvider1.Clear();

string hiba = Nevjegy.Hiba(txtNev.Text, txtEmail.Text); // a szabály a MODELLBEN él

if (hiba != null) { errorProvider1.SetError(hiba.Contains("név") || hiba.Contains("Név") ? txtNev : txtEmail, hiba); return; }

Nevjegy uj = new Nevjegy(txtNev.Text, txtEmail.Text, txtTelefon.Text, (Kategoria)cmbKategoria.SelectedItem);

if (mind.Contains(uj)) { errorProvider1.SetError(txtEmail, "Ez az e-mail már szerepel."); return; }

mind.Add(uj);
Szur();
}

private void btnTorles_Click(object sender, EventArgs e)
{
Nevjegy n = lstNevjegyek.SelectedItem as Nevjegy;

if (n == null) { tsslUzenet.Text = "Válassz ki egy névjegyet."; return; }

mind.Remove(n);
Szur();
}

private void btnRendez_Click(object sender, EventArgs e)
{
switch (cmbRendez.SelectedIndex)
{
case 0: mind.Sort(); break; // IComparable
case 1: mind.Sort((a, b) => a.Kat.CompareTo(b.Kat)); break;
default: mind.Sort((a, b) => string.Compare(a.Email, b.Email, StringComparison.Ordinal)); break;
}

Szur();
}

private void txtKeres_TextChanged(object sender, EventArgs e) { Szur(); }

private void lstNevjegyek_SelectedIndexChanged(object sender, EventArgs e)
{
Nevjegy n = lstNevjegyek.SelectedItem as Nevjegy;

lblReszlet.Text = n == null ? "" : n.Nev + "\n" + n.Email + "\n" + n.Telefon + "\n" + n.Kat;
}
📷 Képernyőkép helye

Mit tegyél ide: a kész kontaktlista szűréssel és statisztikával.Javasolt fájlnév: winforms-12-6-kesz.webp · Alt szöveg: A kész kontaktlista

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. Könyv record

Írj record Konyv(string Cim, string Szerzo, int Ev) típust, és hozz létre egy módosított másolatot.

Egy lehetséges megoldás

var b = a with { Ev = 2000 }; Az eredeti változatlan, a a == b hamis (más Ev), az a == new Konyv(...azonos...) igaz (érték szerinti egyenlőség).

2. Felelősségek

Egy Form1 tárolja a tanulókat, számolja az átlagot, fájlba ment és megjeleníti őket. Hány osztályra bontanád?

Egy lehetséges megoldás

Legalább négyre: Tanulo (modell), Osztaly (gyűjtemény, átlag), TanuloTarolo (fájl), Form1 (megjelenítés).

3. Rendezés több szempont szerint

Rendezz Tanulo objektumokat átlag szerint csökkenően, azonos átlagnál név szerint.

Egy lehetséges megoldás

lista.OrderByDescending(t => t.Atlag).ThenBy(t => t.Nev).ToList(), vagy Sort összetett összehasonlítóval.

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. Mi a modell?

Fogalmazd meg egy mondatban.

Megoldás megnyitása

A program tárgyának adatai és szabályai egy osztályban, felületi kód nélkül.

2. Hová való?

Modellbe vagy Formba: (a) „lejárt-e a határidő”; (b) a lejárt feladat piros színe?

Megoldás megnyitása

(a) modell; (b) Form (megjelenítés).

Közepes szint

3. ToString vagy DisplayMember?

Mikor melyiket?

Megoldás megnyitása

A ToString általános szöveges alak (naplóhoz, hibakeresőhöz is jó); a DisplayMember az adott vezérlő egyetlen tulajdonságát mutatja.

4. Equals és GetHashCode

Miért kell együtt felülírni?

Megoldás megnyitása

A hash-alapú gyűjtemények (Dictionary, HashSet) előbb a hash-et nézik: az egyenlő objektumoknak azonos hash kell.

Kihívás szint

5. Módosíthatatlan modell

Hogyan készítesz olyan modellt, amely létrehozás után nem módosítható?

Megoldás megnyitása

Csak get (vagy init) tulajdonságok és konstruktor; vagy record. A módosítás új példány (with).

6. Modell és hibák

Kivétel vagy Hiba() szöveg: melyiket mikor?

Megoldás megnyitása

A Hiba() a felületi előzetes ellenőrzéshez (a felhasználó gépelése hibás lehet: ez nem kivételes); a konstruktor kivétele a programozói hibára (érvénytelen objektum létrehozása).

Gyors önellenőrzés

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

1. Mi nincs a modellben?

Felületi kód (vezérlők, MessageBox).

2. Mi az egy felelősség elve?

Egy osztálynak egyetlen oka legyen a változásra.

3. Mit mutat a ListBox?

Az elemek ToString() eredményét (vagy a DisplayMember tulajdonságot).

4. Mi a record?

Adat-osztály automatikus érték szerinti egyenlőséggel, with másolattal (C# 9, .NET 5+).

5. Miért BindingList?

Változáskor értesíti a kötött vezérlőt, a List<T> nem.

6. Ki ellenőriz, ki jelenít meg?

A modell ellenőriz és hibaszöveget ad; a Form megjeleníti.

7. Mi a SelectedItem előnye?

Az eredeti modell-objektumot adja, nem szöveget.

Mit tudsz már?

  • Modellosztályt tervezel adattal és szabályokkal, felületi kód nélkül.
  • Felismered és szétosztod a Form túl sok felelősségét.
  • A modellt kényelmessé teszed (ToString, Equals, rendezés, record).
  • A modellt DataSource és BindingList segítségével jeleníted meg.

Következő lecke: Programlogika külön osztályban

A számítás és a műveletek kiemelése a Formból egy külön, tesztelhető osztályba.