Grafikus C# – Windows Forms tanfolyam › 12.4 lecke

Windows Forms · 12.4 lecke · OOP

Konstruktorok és adatátadás

Az objektum életének első pillanata a konstruktor: itt dől el, hogy az új objektum érvényes állapotban jön-e létre. Ebben a leckében megtanulod a konstruktor túlterhelését és láncolását, az objektum-inicializálót, és azt, hogyan jut át az adat egyik ablakból a másikba: konstruktorral, tulajdonsággal, eseménnyel és visszahívással. A 8. fejezetben már átadtál adatot két Form között; most megismered a módszerek előnyeit és buktatóit, és tudatosan választasz közülük.

  • kb. 100 perc
  • közepes szint
  • adatátadás
Diáklány C# programot készít laptopon

A lecke végére

  • írsz konstruktort, túlterhelést és this(...) láncolást;
  • az objektumot érvényes kezdőállapotba hozod, és az érvénytelen létrehozást megakadályozod;
  • használod az objektum-inicializálót (new T { X = 1 });
  • négyféleképpen adsz át adatot Formok között, és tudod az előnyeiket, hátrányaikat;
  • elkerülöd a szoros csatolást (a gyerek ablak ne ismerje a szülő összes tagját);
  • eseménnyel és Action<T> visszahívással értesíted a hívót.

Mielőtt elkezded

Ehhez a leckéhez a 12.1–12.3 lecke és a 8. fejezet (több Form, adatátadás) szükséges.

12.3 Mezők, propertyk →

Konstruktor, túlterhelés, láncolás

Szemely.csC#
public class Szemely
{
public string Nev { get; private set; }
public int Eletkor { get; private set; }
public string Email { get; set; } = "";

// 1. fő konstruktor: ide fut össze minden, itt az ellenőrzés
public Szemely(string nev, int eletkor)
{
if (string.IsNullOrWhiteSpace(nev)) { throw new ArgumentException("A név kötelező.", "nev"); }
if (eletkor < 0 || eletkor > 150) { throw new ArgumentOutOfRangeException("eletkor", "Az életkor 0 és 150 között lehet."); }

Nev = nev.Trim();
Eletkor = eletkor;
}

// 2. túlterhelés: más paraméterlista; a this(...) a fő konstruktort hívja (láncolás)
public Szemely(string nev) : this(nev, 18) { }

// 3. másoló konstruktor
public Szemely(Szemely mas) : this(mas.Nev, mas.Eletkor) { Email = mas.Email; }
}

Szemely a = new Szemely("Anna", 16);
Szemely b = new Szemely("Béla"); // életkor: 18
Szemely c = new Szemely(a); // valódi másolat (nem b = a)
Szemely d = new Szemely("", 16); // ArgumentException: az objektum létre sem jön
Mi a konstruktor?Az osztály nevével azonos, visszatérési típus nélküli metódus: a new hívja meg. Feladata az objektum érvényes kezdőállapotba hozása.
Érvényes állapotHa a konstruktor kivételt dob érvénytelen adatnál, az objektum létre sem jön: nem létezik „félig kész” Szemely. Így az osztály többi kódja biztos lehet az adatok helyességében.
Túlterhelés és láncolásTöbb konstruktor lehet, ha a paraméterlistájuk eltér. A : this(...) a másik konstruktort hívja: az ellenőrzés egy helyen van, nincs kétszer ugyanaz a kód.
Alapértelmezett konstruktorHa nem írsz konstruktort, a fordító ad egy paraméter nélkülit. Amint írsz egyet, az eltűnik: a new Szemely() már nem fordul, hacsak nem írod meg külön.

Jósolj!

A Szemely osztálynak csak a Szemely(string nev, int eletkor) konstruktora van. A kód new Szemely()-t hív. Mi történik?

Jóslat ellenőrzése

Fordítási hiba (CS7036: nincs a híváshoz megfelelő paraméter): mivel saját konstruktort írtál, a fordító nem ad paraméter nélkülit. Ez a kívánt védelem: a Szemely nem hozható létre név nélkül.

Inicializálás: objektum-inicializáló és kötelező adat

Kod.csC#
// objektum-inicializáló: a konstruktor UTÁN állítja a nyilvános tulajdonságokat
Szemely p = new Szemely("Cili", 17) { Email = "cili@pelda.hu" };

// kötelező adat konstruktoron át, választható adat tulajdonságon át
// (a kötelezőt NEM lehet kihagyni, a választhatót igen)

// a Form konstruktora is paramétert kaphat (12.1 lecke)
public partial class SzerkesztoForm : Form
{
private readonly Szemely eredeti;

public SzerkesztoForm(Szemely szemely)
{
InitializeComponent(); // ELŐSZÖR: különben a vezérlők még null értékűek!

eredeti = szemely ?? throw new ArgumentNullException("szemely");
txtNev.Text = szemely.Nev;
}
}
InicializálóA new T(...) { Tul = ertek } a konstruktor után állítja a nyilvános, írható tulajdonságokat. Rövid, olvasható; de az érvényességet nem biztosítja (a tulajdonság bármikor módosítható).
Kötelező vs választhatóAmi nélkül az objektum értelmetlen, kerüljön konstruktorba; a választható beállítás maradhat tulajdonságnak. Így az objektum soha nem jön létre hiányos állapotban.
Form konstruktorA Form konstruktorában az InitializeComponent() hívás legyen az első: addig a vezérlők nem léteznek, és a txtNev.Text NullReferenceException lenne.
Null ellenőrzésA paraméter ellenőrzése (?? throw vagy if (x == null) throw ...) azonnal jelzi a hibás hívást, nem csak később, egy távoli helyen.

Jósolj!

A SzerkesztoForm konstruktorában az InitializeComponent() hívás után áll a txtNev.Text = szemely.Nev; sor. Mi történik, ha a hívást a sor mögé teszed?

Jóslat ellenőrzése

NullReferenceException: a txtNev mező addig null, amíg az InitializeComponent() létre nem hozza. A vezérlők használata előtt mindig az inicializálás.

Négy mód az adatátadásra

MódIrányMikor jó?
1. Konstruktorhívó → új ablakKötelező kezdőadat (a szerkesztendő objektum).
2. Tulajdonságmindkét irányVálasztható beállítás, vagy az eredmény kiolvasása a bezárt ablakból.
3. Eseményablak → hívóNem modális ablak jelez a hívónak (Mentve), akár többször is.
4. Visszahívás (Action)ablak → hívóUgyanaz, egyszerűbben: a hívó egy metódust ad át.
SzerkesztoForm.cs és Form1.csC#
// ---- SzerkesztoForm.cs ----
public partial class SzerkesztoForm : Form
{
// 3. ESEMÉNY: a hívó feliratkozik, az ablak kiváltja
public event EventHandler<SzemelyMentveEventArgs> Mentve;

// 2. TULAJDONSÁG: az eredmény kiolvasható
public Szemely Eredmeny { get; private set; }

// 1. KONSTRUKTOR: kötelező kezdőadat
public SzerkesztoForm(Szemely kiindulas)
{
InitializeComponent();
txtNev.Text = kiindulas.Nev;
nudKor.Value = kiindulas.Eletkor;
}

private void btnMent_Click(object sender, EventArgs e)
{
try { Eredmeny = new Szemely(txtNev.Text, (int)nudKor.Value); }
catch (ArgumentException ex) { lblHiba.Text = ex.Message; return; }

Mentve?.Invoke(this, new SzemelyMentveEventArgs(Eredmeny)); // ?. : ha senki sem figyeli, nem hiba
DialogResult = DialogResult.OK;
}
}

public class SzemelyMentveEventArgs : EventArgs
{
public Szemely Szemely { get; private set; }
public SzemelyMentveEventArgs(Szemely s) { Szemely = s; }
}

// ---- Form1.cs ----
private void btnSzerkeszt_Click(object sender, EventArgs e)
{
using (SzerkesztoForm f = new SzerkesztoForm(kivalasztott)) // 1. konstruktor
{
f.Mentve += (s, ev) => tsslUzenet.Text = "Mentve: " + ev.Szemely.Nev; // 3. esemény

if (f.ShowDialog(this) == DialogResult.OK)
{
kivalasztott = f.Eredmeny; // 2. tulajdonság
}
}
}

// 4. VISSZAHÍVÁS: a hívó egy metódust ad át, az ablak meghívja
public void Nyit(Szemely sz, Action<Szemely> kesz) { /* a mentésnél: kesz(eredmeny); */ }
Modális: eredmény a tulajdonságbanA ShowDialog ablaknál a hívó megvárja a bezárást, és az Eredmeny tulajdonságból olvassa ki az adatot (a DialogResult adja az OK/Mégse döntést).
Nem modális: eseményA Show ablaknál a hívó nem vár: az esemény értesíti, ha az ablak mentett. Több ablak is ugyanarra az eseményre köthető.
Mit ne tegyél?Ne adj át a gyerek ablaknak nyilvános vezérlőket (Modifiers = Public), és ne írd a hívó vezérlőit az ablak kódjából (((Form1)Owner).lblEredmeny.Text = ...). Ez a szoros csatolás: a két osztály nem választható szét.
Eredeti objektum módosításaHa a konstruktorba az eredeti objektumot adod, és a Form közvetlenül módosítja, a Mégse után is megváltozik (egy objektum, két név, 12.1 lecke). Szerkesztéshez adj másolatot, és csak mentéskor cseréld az eredetit.

Jósolj!

A SzerkesztoForm a kapott Szemely objektum Email tulajdonságát gépelés közben közvetlenül módosítja, a felhasználó a Mégse gombbal zárja be. Mi lesz az eredeti objektum e-mailjével?

Jóslat ellenőrzése

Megváltozik: az ablak és a hívó ugyanazt az objektumot látja, a Mégse nem vonja vissza a módosítást. Megoldás: másolaton dolgozik az ablak (new Szemely(eredeti)), és csak az OK adja vissza.

Laza csatolás: ismerje a legkevesebbet

Szoros és laza csatolásC#
// SZOROS: a gyerek ismeri a szülő osztályt, és belenyúl a vezérlőibe
private void btnKesz_Click(object sender, EventArgs e)
{
Form1 szulo = (Form1)this.Owner; // csak Form1-ből nyitható!
szulo.lstEredmeny.Items.Add(txtNev.Text); // és a lstEredmeny publikus kell hogy legyen
}

// LAZA: a gyerek csak az adatot és egy értesítést ismer; nem tudja, ki hívta
public event Action<string> NevMegadva;

private void btnKesz_Click(object sender, EventArgs e)
{
NevMegadva?.Invoke(txtNev.Text); // a hívó dönti el, mi történik vele
}
Szoros csatolásA gyerek ablak ismeri a szülő pontos típusát és belső vezérlőit. Következmény: csak onnan nyitható, minden felületi módosítás (vezérlő átnevezése) a másikat is eltöri, nem tesztelhető külön.
Laza csatolásAz ablak az adatot és az eseményt (vagy visszahívást) ismeri. A hívó dönti el, mit kezd vele. Ugyanaz az ablak több helyről használható.
Gyakorlati szabályAz adat megy be (konstruktor), az eredmény jön ki (tulajdonság, esemény). A vezérlők mindig privátok maradnak.
Mi a baj a static mezővel?Adatátadás static mezővel (Globalis.Aktualis) könnyű, de bárki módosíthatja, nehéz követni, és egyszerre csak egy példány élhet. Kerüld, ha lehet.

Jósolj!

A szoros csatolású SzerkesztoForm kódja (Form1)this.Owner értéket olvas. A program egy Form3 ablakból nyitja meg. Mi történik?

Jóslat ellenőrzése

InvalidCastException: az Owner egy Form3, nem Form1. A szoros csatolás miatt az ablak csak egyetlen hívótól működik; az esemény vagy visszahívás ezt elkerüli.

📷 Képernyőkép helye

Mit tegyél ide: az adatátadó-labor: a főablak, a szerkesztő ablak és a négy mód naplója.Javasolt fájlnév: winforms-12-4-adatatado.webp · Alt szöveg: Az adatátadó-labor program

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 Form konstruktorában

public SzerkesztoForm(Szemely sz)
{
txtNev.Text = sz.Nev;
InitializeComponent();
}
Hiba és javítás

A vezérlők az InitializeComponent() után léteznek. Az első sor legyen az inicializálás.

2. A Mégse után is megváltozott az adat

A felhasználó mégsem mentett, az eredeti objektum mégis módosult.

Ok és javítás

Az ablak az eredeti objektumot módosítja. Adj másolatot (másoló konstruktor), és csak OK után cseréld.

3. CS7036: nincs paraméter nélküli konstruktor

Szemely s = new Szemely();
Hiba és javítás

Saját konstruktor mellett nincs automatikus paraméter nélküli. Add meg a kötelező adatot, vagy írj paraméter nélkülit (ha értelmes).

4. Az esemény NullReferenceException-t dob

Mentve(this, new SzemelyMentveEventArgs(sz));   // senki sem iratkozott fel
Hiba és javítás

Feliratkozó nélkül az esemény null. Használd az ?.Invoke(...) alakot.

5. Érvénytelen objektum jön létre

A program egy 0 karakteres nevű Szemely objektumot tárol.

Ok és javítás

A konstruktor nem ellenőriz, vagy a tulajdonság nyilvánosan írható. Ellenőrizd a konstruktorban, és a set legyen private.

6. InvalidCastException az Owner-nél

Form1 sz = (Form1)this.Owner;
Hiba és javítás

Az Owner típusát feltételezed. Használj eseményt vagy visszahívást: az ablak ne ismerje a hívót.

Mini projekt: adatátadó-labor

Készíts programot, amely egy személy adatait szerkeszti egy külön ablakban, és a négy átadási módot egymás mellett mutatja:

  1. Szemely osztály: Nev, Eletkor (private set), Email; fő konstruktor ellenőrzéssel (kivétel hibás adatnál); túlterhelés (Szemely(nev), láncolva), másoló konstruktor; ToString().
  2. Főablak: lstSzemelyek (a lista), Új, Szerkeszt, Törlés; lstNaplo.
  3. SzerkesztoForm: konstruktora kapja a személyt (új: üres mezők; szerkesztés: kitöltve); txtNev, nudKor, txtEmail, lblHiba, OK, Mégse; az ablak másolaton dolgozik.
  4. Eredmény: a ShowDialog ablaknál az Eredmeny tulajdonság (OK esetén), a főablak cseréli az eredetit.
  5. Esemény: a Mentve esemény (EventHandler<SzemelyMentveEventArgs>); a főablak naplózza.
  6. Nem modális változat: Szerkeszt (nem modális) gomb: Show, több ablak egyszerre; az esemény minden mentéskor frissíti a listát; a lista elem azonosítása a Szemely hivatkozásával.
  7. Visszahívás: az Action<Szemely> paraméterű Megnyit metódus ugyanazt, eseménynélkül mutatja.
  8. Ellenőrzés: hibás adatnál az ablak nyitva marad, a lblHiba mutatja a ArgumentException üzenetét; Mégse után az eredeti változatlan.
  9. Teszteld: új és szerkesztett személy, Mégse, üres név, 200 éves életkor, két nem modális ablak ugyanarra a személyre (a legutolsó mentés felülír: jelezd), bezárás, lista frissülése.
Megoldás megnyitása (próbáld ki előbb önállóan!)
Szemely.cs, SzerkesztoForm.cs, Form1.cs (using System.Linq;)C#
// ---- Szemely.cs ----
public class Szemely
{
public string Nev { get; private set; }
public int Eletkor { get; private set; }
public string Email { get; set; } = "";

public Szemely(string nev, int eletkor)
{
if (string.IsNullOrWhiteSpace(nev)) { throw new ArgumentException("A név kötelező.", "nev"); }
if (eletkor < 0 || eletkor > 150) { throw new ArgumentOutOfRangeException("eletkor", "Az életkor 0 és 150 között lehet."); }

Nev = nev.Trim();
Eletkor = eletkor;
}

public Szemely(string nev) : this(nev, 18) { }
public Szemely(Szemely mas) : this(mas.Nev, mas.Eletkor) { Email = mas.Email; }

public override string ToString() { return Nev + " (" + Eletkor + ")" + (Email.Length > 0 ? ", " + Email : ""); }
}

// ---- SzerkesztoForm.cs ----
public class SzemelyMentveEventArgs : EventArgs
{
public Szemely Eredeti { get; private set; }
public Szemely Uj { get; private set; }
public SzemelyMentveEventArgs(Szemely eredeti, Szemely uj) { Eredeti = eredeti; Uj = uj; }
}

public partial class SzerkesztoForm : Form
{
private readonly Szemely eredeti;

public event EventHandler<SzemelyMentveEventArgs> Mentve;
public Szemely Eredmeny { get; private set; }

public SzerkesztoForm(Szemely szerkesztendo) // null: új személy
{
InitializeComponent(); // ELŐSZÖR

eredeti = szerkesztendo;

if (szerkesztendo != null)
{
Szemely masolat = new Szemely(szerkesztendo); // a Form a másolattal dolgozik

txtNev.Text = masolat.Nev;
nudKor.Value = masolat.Eletkor;
txtEmail.Text = masolat.Email;
}
}

private void btnOk_Click(object sender, EventArgs e)
{
try
{
Szemely uj = new Szemely(txtNev.Text, (int)nudKor.Value) { Email = txtEmail.Text.Trim() };

Eredmeny = uj;
Mentve?.Invoke(this, new SzemelyMentveEventArgs(eredeti, uj));
DialogResult = DialogResult.OK; // modálisnál zár; nem modálisnál a Close kell
if (!Modal) { Close(); }
}
catch (ArgumentException ex) { lblHiba.Text = ex.Message; }
}
}

// ---- Form1.cs ----
private readonly List<Szemely> szemelyek = new List<Szemely>();

private void Frissit()
{
lstSzemelyek.Items.Clear();
foreach (Szemely s in szemelyek) { lstSzemelyek.Items.Add(s); } // a ToString() jelenik meg
}

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

private void btnUj_Click(object sender, EventArgs e)
{
using (SzerkesztoForm f = new SzerkesztoForm(null))
{
if (f.ShowDialog(this) == DialogResult.OK) { szemelyek.Add(f.Eredmeny); Frissit(); Naploz("Új (tulajdonság): " + f.Eredmeny); }
}
}

private void btnSzerkeszt_Click(object sender, EventArgs e)
{
Szemely kivalasztott = lstSzemelyek.SelectedItem as Szemely;

if (kivalasztott == null) { tsslUzenet.Text = "Válassz ki egy személyt."; return; }

using (SzerkesztoForm f = new SzerkesztoForm(kivalasztott))
{
f.Mentve += (s, ev) => Naploz("Esemény: " + ev.Eredeti + " → " + ev.Uj);

if (f.ShowDialog(this) == DialogResult.OK)
{
szemelyek[szemelyek.IndexOf(kivalasztott)] = f.Eredmeny; // csak OK után cserélünk
Frissit();
}
else { Naploz("Mégse: az eredeti változatlan (" + kivalasztott + ")"); }
}
}

private void btnNemModalis_Click(object sender, EventArgs e)
{
Szemely kivalasztott = lstSzemelyek.SelectedItem as Szemely;

if (kivalasztott == null) { return; }

SzerkesztoForm f = new SzerkesztoForm(kivalasztott); // Show: bezáráskor magától felszabadul

f.Mentve += (s, ev) =>
{
int i = szemelyek.IndexOf(ev.Eredeti);

if (i < 0) { Naploz("Az eredeti személyt közben módosították vagy törölték."); return; }

szemelyek[i] = ev.Uj;
Frissit();
Naploz("Nem modális mentés: " + ev.Uj);
};

f.Show(this);
}

private void btnTorles_Click(object sender, EventArgs e)
{
Szemely s = lstSzemelyek.SelectedItem as Szemely;

if (s != null) { szemelyek.Remove(s); Frissit(); }
}
📷 Képernyőkép helye

Mit tegyél ide: a kész adatátadó-labor.Javasolt fájlnév: winforms-12-4-kesz.webp · Alt szöveg: A kész adatátadó-labor

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. Téglalap osztály

Írj Teglalap osztályt Teglalap(a, b) konstruktorral (pozitív oldalak) és Teglalap(a) négyzet-konstruktorral láncolva.

Egy lehetséges megoldás

public Teglalap(double a) : this(a, a) { }; a fő konstruktor ArgumentOutOfRangeException kivételt dob, ha a <= 0 vagy b <= 0.

2. Választó ablak

Egy ablak színt választ, és visszaad egy Color értéket. Három megoldás?

Egy lehetséges megoldás

(1) ShowDialog + ValasztottSzin tulajdonság; (2) Szin esemény minden módosításnál (élő előnézet); (3) Action<Color> visszahívás a konstruktorban.

3. Mégse biztonság

Hogyan biztosítod, hogy a Mégse semmit sem módosít?

Egy lehetséges megoldás

Másolaton dolgozik az ablak, és csak az OK adja vissza az új objektumot; a hívó csak akkor cseréli az eredetit.

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. Konstruktor

Mi a konstruktor feladata?

Megoldás megnyitása

Az objektumot érvényes kezdőállapotba hozza a létrehozáskor.

2. this(...)

Mire jó a konstruktor-láncolás?

Megoldás megnyitása

Egy konstruktor egy másikat hív: az ellenőrzés és az inicializálás egy helyen van.

Közepes szint

3. Inicializálás sorrendje

Hol hívod az InitializeComponent() metódust?

Megoldás megnyitása

A Form konstruktorának első sorában, a vezérlők használata előtt.

4. Kötelező és választható

Hová teszed a kötelező, hová a választható adatot?

Megoldás megnyitása

Kötelező: konstruktor; választható: írható tulajdonság (objektum-inicializáló).

Kihívás szint

5. Eseménybiztonság

Miért Mentve?.Invoke(...)?

Megoldás megnyitása

Feliratkozó nélkül az esemény null; a ?. elkerüli a NullReferenceException kivételt, és a feliratkozók listájának másolatán dolgozik (szálbiztosabb).

6. Függőségek

Miért rossz, ha az ablak a (Form1)Owner típusra számít?

Megoldás megnyitása

Szoros csatolás: csak onnan nyitható, InvalidCastException más hívónál, a vezérlők nyilvánossá tételét kényszeríti, és nem tesztelhető külön.

Gyors önellenőrzés

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

1. Mikor van saját konstruktor mellett paraméter nélküli?

Csak ha megírod: a fordító nem ad automatikusat.

2. Miért dobhat kivételt a konstruktor?

Hogy érvénytelen adatból ne jöjjön létre objektum.

3. Mi az objektum-inicializáló?

new T(...) { Tul = ertek }: a konstruktor után állít tulajdonságokat.

4. Melyik módszer való modális ablak eredményéhez?

Tulajdonság a ShowDialog után, DialogResult döntéssel.

5. És nem modálishoz?

Esemény vagy visszahívás.

6. Mi a szoros csatolás?

Az osztály ismeri a másik pontos típusát és belső részeit; nem választható szét.

7. Miért másolattal dolgozik a szerkesztő ablak?

Hogy a Mégse ne módosítsa az eredetit.

Mit tudsz már?

  • Konstruktort írsz túlterheléssel és láncolással, érvényes kezdőállapottal.
  • Négyféleképpen adsz át adatot Formok között, és választasz közülük.
  • Elkerülöd a szoros csatolást és az eredeti objektum véletlen módosítását.
  • Eseményt és visszahívást használsz az értesítéshez.

Következő lecke: Hozzáférési szintek

Mi legyen public, mi private: az egységbezárás elve, és hogyan védd az osztály belső állapotát.