Grafikus C# – Windows Forms tanfolyam › 12.4 lecke Windows Forms · 12.4 lecke · OOP 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.Konstruktorok és adatátadás

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.
Konstruktor, túlterhelés, láncolás
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önnew hívja meg. Feladata az objektum érvényes kezdőállapotba hozása.Szemely. Így az osztály többi kódja biztos lehet az adatok helyességében.: this(...) a másik konstruktort hívja: az ellenőrzés egy helyen van, nincs kétszer ugyanaz a kód.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
// 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;
}
}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ó).InitializeComponent() hívás legyen az első: addig a vezérlők nem léteznek, és a txtNev.Text NullReferenceException lenne.?? 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ód | Irány | Mikor jó? |
|---|---|---|
| 1. Konstruktor | hívó → új ablak | Kötelező kezdőadat (a szerkesztendő objektum). |
| 2. Tulajdonság | mindkét irány | Választható beállítás, vagy az eredmény kiolvasása a bezárt ablakból. |
| 3. Esemény | ablak → 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 ----
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); */ }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).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ő.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.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: 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
}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.
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. Az ablak az eredeti objektumot módosítja. Adj másolatot (másoló konstruktor), és csak OK után cseréld.Ok és javítás
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 felHiba é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ű A konstruktor nem ellenőriz, vagy a tulajdonság nyilvánosan írható. Ellenőrizd a konstruktorban, és a Szemely objektumot tárol.Ok és javítás
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:
- 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(). - Főablak:
lstSzemelyek(a lista), Új, Szerkeszt, Törlés;lstNaplo. - 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. - Eredmény: a
ShowDialogablaknál azEredmenytulajdonság (OKesetén), a főablak cseréli az eredetit. - Esemény: a
Mentveesemény (EventHandler<SzemelyMentveEventArgs>); a főablak naplózza. - 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 aSzemelyhivatkozásával. - Visszahívás: az
Action<Szemely>paraméterűMegnyitmetódus ugyanazt, eseménynélkül mutatja. - Ellenőrzés: hibás adatnál az ablak nyitva marad, a
lblHibamutatja aArgumentExceptionüzenetét; Mégse után az eredeti változatlan. - 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 ----
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(); }
}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 (1) Color értéket. Három megoldás?Egy lehetséges megoldás
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? Másolaton dolgozik az ablak, és csak az OK adja vissza az új objektumot; a hívó csak akkor cseréli az eredetit.Egy lehetséges megoldás
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? Az objektumot érvényes kezdőállapotba hozza a létrehozáskor.Megoldás megnyitása
2. this(...)
Mire jó a konstruktor-láncolás? Egy konstruktor egy másikat hív: az ellenőrzés és az inicializálás egy helyen van.Megoldás megnyitása
Közepes szint
3. Inicializálás sorrendje
Hol hívod az A Form konstruktorának első sorában, a vezérlők használata előtt.InitializeComponent() metódust?Megoldás megnyitása
4. Kötelező és választható
Hová teszed a kötelező, hová a választható adatot? Kötelező: konstruktor; választható: írható tulajdonság (objektum-inicializáló).Megoldás megnyitása
Kihívás szint
5. Eseménybiztonság
Miért Feliratkozó nélkül az esemény Mentve?.Invoke(...)?Megoldás megnyitása
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 Szoros csatolás: csak onnan nyitható, (Form1)Owner típusra számít?Megoldás megnyitása
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. Csak ha megírod: a fordító nem ad automatikusat. Hogy érvénytelen adatból ne jöjjön létre objektum. Tulajdonság a Esemény vagy visszahívás. Az osztály ismeri a másik pontos típusát és belső részeit; nem választható szét. Hogy a Mégse ne módosítsa az eredetit.1. Mikor van saját konstruktor mellett paraméter nélküli?
2. Miért dobhat kivételt a konstruktor?
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?
ShowDialog után, DialogResult döntéssel.5. És nem modálishoz?
6. Mi a szoros csatolás?
7. Miért másolattal dolgozik a szerkesztő ablak?
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.



