Grafikus C# – Windows Forms tanfolyam › 12.1 lecke Windows Forms · 12.1 lecke · OOP Idáig a programjaid nagy része egyetlen Osztályok, objektumok és Formok
Form1 osztályban élt, amelybe minden bekerült. Most egy lépést hátralépünk, és megnézzük, mi az a Form valójában: egy osztály, amelyből objektumokat (példányokat) hozol létre. Ebből következik minden, amit a 12. fejezetben tanulsz: ugyanabból az osztályból tetszőleges számú, egymástól független ablak nyitható; az objektumok referenciákon át érhetők el; a saját osztályaid ugyanúgy működnek, mint a Form. Ez a lecke az alap a rendezett, karbantartható program felé.
A lecke végére
- érted, hogy a
Form1osztály, és aForm1 : Formöröklést jelent; - megkülönbözteted az osztályt (terv) és az objektumot (a terv alapján létrehozott példány);
- ugyanabból a Form osztályból több, független állapotú ablakot nyitasz;
- érted a referenciákat: két változó ugyanarra az objektumra mutathat; tudod, mi a
null; - használod a
thiskulcsszót, aShowésShowDialogmetódust, és kezeled az ablak életciklusát; - saját osztályt írsz, és a példány- és
statictagok különbségét ismered.
Mielőtt elkezded
Ehhez a leckéhez a 4.x leckék (metódusok), az 5.1 lecke (események) és a 9.8 lecke (Dispose) szükséges. Az osztályokkal már találkoztál (például Alakzat a 9.7 leckében), most rendszerbe foglaljuk.
A Form is osztály
// Form1.cs: az osztály (a TERV)
public partial class Form1 : Form // a Form1 a Form leszármazottja: mindent tud, amit a Form, és még többet
{
private int kattintasok = 0; // MEZŐ: minden Form1 objektum saját példánya
public Form1() // KONSTRUKTOR: az objektum létrehozásakor fut
{
InitializeComponent(); // a Designer által generált vezérlők (12.2 lecke)
}
private void btnKatt_Click(object sender, EventArgs e) // METÓDUS
{
kattintasok++;
Text = "Kattintások: " + kattintasok; // a Text a Form tulajdonsága (örökölt)
}
}
// Program.cs: az objektum létrehozása (PÉLDÁNYOSÍTÁS)
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1()); // new: egy Form1 objektum jön létre, az Application.Run megjeleníti
}Form1 osztály egy ablak terve: gombokkal, címkékkel, eseménykezelőkkel.new Form1() a tervből egy tényleges ablakot épít: saját mezőkkel és vezérlőkkel a memóriában. Ugyanabból a tervből több ház is építhető.Form1 a Form leszármazottja: megörökli a Text, Width, Show(), Close() és az összes többi tagot, és újakat ad hozzá. A partial osztályról a 12.2 lecke szól.InitializeComponent() hívás hozza létre a vezérlőket.Jósolj!
A program kétszer hívja a new Form1() kifejezést, és mindkét ablakot megjeleníti. Az egyikben háromszor a Katt gombra kattintasz. Mi lesz a másik ablak címsorában?
Jóslat ellenőrzése
A másik ablak nem változik: minden objektumnak saját kattintasok mezője van (példány-szintű tag). Az osztály a terv, az állapot az objektumban él.
Referenciák, null és a this
// a változó egy HIVATKOZÁS (referencia) az objektumra, nem maga az objektum
Form2 a = new Form2(); // egy objektum jön létre; az "a" hivatkozik rá
Form2 b = a; // NEM másolat: a "b" ugyanarra az objektumra mutat
b.Text = "Közös ablak";
// az a.Text is "Közös ablak": egyetlen objektum, két név
bool ugyanaz = object.ReferenceEquals(a, b); // igaz
// NULL: a változó nem hivatkozik semmire
Form2 c = null;
if (c != null) { c.Show(); } // ellenőrzés nélkül: NullReferenceException
c?.Show(); // rövidítés: csak akkor hívja, ha nem null
// this: az aktuális objektum (maga a Form, amelyben a kód fut)
private void btnMasik_Click(object sender, EventArgs e)
{
Form2 uj = new Form2(this); // átadjuk a SAJÁT objektumunkat az új ablaknak
uj.Show(this); // tulajdonos: a Form1 (az új ablak vele együtt zárul/minimalizálódik)
}
// a this a névütközést is feloldja: a konstruktor paramétere és a mező neve azonos lehet
public Form2(Form1 szulo)
{
InitializeComponent();
this.szulo = szulo; // bal oldal: a mező, jobb oldal: a paraméter
}b = a) a címet másolja, nem az objektumot. Két változó így ugyanazt az objektumot módosítja.null azt jelenti: nincs objektum. Egy null változón metódust hívni NullReferenceException kivételt dob, a leggyakoribb kezdő hiba. Ellenőrizd (!= null), vagy használd a ?. operátort.int, double, bool) érték-típusok: a hozzárendelés másol. Az osztályok referencia-típusok: a hozzárendelés a hivatkozást másolja. Ez a különbség okozza a legtöbb „miért változott meg?” meglepetést.this az aktuális objektum: az a példány, amelyen a kód épp fut. Átadhatod más objektumnak (new Form2(this)), és megkülönbözteti a mezőt a hasonló nevű paramétertől.Jósolj!
A Form2 a = new Form2(); Form2 b = a; b.Close(); sorok után az a.Visible értéke mi?
Jóslat ellenőrzése
Az ablak bezáródik (és az a is bezártat mutat): az a és a b ugyanarra az objektumra hivatkozik. Bezárás után a Visible hamis, és az objektum felszabadított (IsDisposed igaz): az a.Show() ObjectDisposedException kivételt dobna.
Több ablak: példányok és életciklus
// több független ablak ugyanabból az osztályból; a példányokat lista tartja számon
private readonly List<JegyzetForm> nyitottak = new List<JegyzetForm>();
private void btnUj_Click(object sender, EventArgs e)
{
JegyzetForm ablak = new JegyzetForm(nyitottak.Count + 1);
ablak.FormClosed += (s, ev) => nyitottak.Remove(ablak); // bezáráskor kivesszük a listából
nyitottak.Add(ablak);
ablak.Show(this); // NEM modális: a főablak közben használható
}
// ShowDialog: MODÁLIS, a hívó kód megáll, amíg az ablak be nem zárul; az eredmény DialogResult
private void btnBeallitas_Click(object sender, EventArgs e)
{
using (BeallitasForm f = new BeallitasForm()) // ShowDialog után a Form NEM szabadul fel magától: using vagy Dispose kell
{
if (f.ShowDialog(this) == DialogResult.OK)
{
szin = f.ValasztottSzin; // az eredményt a bezárt ablak tulajdonságából olvassuk
}
}
}
// EGYETLEN PÉLDÁNY: ha már nyitva van, azt hozzuk előre, nem nyitunk újat
private SugoForm sugo;
private void btnSugo_Click(object sender, EventArgs e)
{
if (sugo == null || sugo.IsDisposed)
{
sugo = new SugoForm();
sugo.Show(this);
}
else
{
sugo.Activate(); // előre hozza; ha minimalizált: sugo.WindowState = FormWindowState.Normal;
}
}Show() megnyitja az ablakot, és azonnal visszatér: mindkét ablak használható. A ShowDialog() modális: a hívó ablak le van tiltva, a kód addig áll, amíg a párbeszédablak be nem zárul, az eredmény a DialogResult.Show() alapú ablak bezáráskor automatikusan megsemmisül (Dispose). A ShowDialog() ablak nem: csak elrejtőzik, hogy a hívó kiolvashassa az eredményt, ezért a using blokk (vagy Dispose()) a te dolgod.Load (első megjelenés előtt), Shown (megjelenés után), FormClosing (bezárás előtt, megakadályozható), FormClosed (bezárás után). A kezelőket mindig a példány bezárásakor kell takarítani (nyilvántartásból kivenni).FormClosed esemény kiveszi a bezártat. Ugyanez a minta egy mezőben egyetlen ablakra (sugo == null || sugo.IsDisposed).Jósolj!
A sugo ablak már be van zárva. A btnSugo_Click csak a sugo == null feltételt ellenőrzi. Mi történik a következő kattintáskor?
Jóslat ellenőrzése
A sugo nem null (a referencia megmaradt a bezárt objektumra), a kód az else ágra lép, és a sugo.Activate() ObjectDisposedException kivételt dob. Kell az IsDisposed ellenőrzés, vagy a FormClosed-ben a mező nullázása.
Saját osztály és a static tagok
public class Jegyzet
{
// STATIC: az egész osztályé, nem a példányé (egyetlen közös érték)
private static int kovetkezoAzonosito = 1;
public static int Letrehozott { get { return kovetkezoAzonosito - 1; } }
// PÉLDÁNY-mezők: minden Jegyzet objektumnak saját
public int Azonosito;
public string Cim;
public string Szoveg = "";
public DateTime Letrehozva;
public Jegyzet(string cim)
{
Azonosito = kovetkezoAzonosito++; // minden új objektum következő számot kap
Cim = cim;
Letrehozva = DateTime.Now;
}
public int SzavakSzama() // példány-metódus: a SAJÁT mezőkkel dolgozik
{
return Szoveg.Split(new char[] { ' ', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
public static string Biztonsagos(string cim) // static metódus: nincs példány, nincs this
{
return string.IsNullOrWhiteSpace(cim) ? "Névtelen" : cim.Trim();
}
}
// használat
Jegyzet j1 = new Jegyzet(Jegyzet.Biztonsagos(" Teendők "));
Jegyzet j2 = new Jegyzet("Ötletek");
j1.Szoveg = "tejet venni kenyeret";
MessageBox.Show(j1.SzavakSzama() + " szó, azonosító: " + j1.Azonosito + ", létrehozva: " + Jegyzet.Letrehozott); // 3 szó, 1, 2j1.Cim és a j2.Cim független. A metódus a this objektum mezőivel dolgozik.static mező az osztályé: egyetlen közös érték, bármelyik objektum nélkül elérhető az osztály nevén át (Jegyzet.Letrehozott). Számlálóra, segédmetódusra jó; közös, módosítható állapotra óvatosan (szálbiztonság, tesztelhetőség).Jegyzet(string cim) kötelezi: nincs cim nélküli objektum. A konstruktor biztosítja, hogy az objektum létrehozás után érvényes állapotú legyen (a 12.4 lecke részletezi).SzavakSzama) összetartoznak: egy helyen vannak, nem szétszórt változók és metódusok a Form kódjában. A Form csak megjeleníti. Ez a 12. fejezet vezérgondolata.Jósolj!
A fenti kódban három new Jegyzet(...) hívás után mi a Jegyzet.Letrehozott értéke, és mi a harmadik objektum Azonosito mezője?
Jóslat ellenőrzése
A Letrehozott 3 (a közös számláló), a harmadik objektum Azonosito értéke 3 (a létrehozáskori számláló), az elsőé 1, a másodiké 2: minden objektum saját mezője más, a static érték közös.
Mit tegyél ide: az ablakkezelő-labor: a főablak és több független jegyzetablak, az állapotukkal.Javasolt fájlnév: winforms-12-1-ablakkezelo.webp · Alt szöveg: Az ablakkezelő-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
Jegyzet j;
lblCim.Text = j.Cim; // a j értéke nullHiba és javítás
A változó nem hivatkozik objektumra. Hozz létre példányt (new), vagy ellenőrizd a null értéket (if (j != null), j?.Cim).
2. A két ablak ugyanazt mutatja
JegyzetForm a = new JegyzetForm();
JegyzetForm b = a; // „második” ablak?
b.Show();Hiba és javítás
Nincs második objektum: b ugyanarra mutat. Új ablakhoz új new JegyzetForm() kell.
3. A bezárt ablak előhozása kivételt dob
if (sugo == null) { sugo = new SugoForm(); sugo.Show(); }
else { sugo.Activate(); }Hiba és javítás
A bezárt ablak referenciája nem lett null. Ellenőrizd az IsDisposed értéket is: sugo == null || sugo.IsDisposed.
4. A párbeszédablak memóriát szivárogtat
BeallitasForm f = new BeallitasForm();
f.ShowDialog(); // nincs DisposeHiba és javítás
A ShowDialog után a Form nem szabadul fel magától. Használj using blokkot.
5. A mező nem látható a másik osztályból
A A CS0122 hiba: a mező védett szintje miatt nem érhető el.Ok és javítás
private mező csak a saját osztályban látszik. Tegyél hozzá public tulajdonságot (12.3, 12.5 lecke), ne a mezőt tedd nyilvánossá.
6. A static számláló újraindításkor nullázódik
A program újraindításakor az azonosítók újra 1-től indulnak. A Ok és javítás
static mező csak a program futása alatt él. A tartós értékhez fájlba (10. fejezet) vagy adatbázisba mentés kell.
Mini projekt: ablakkezelő-labor
Készíts programot, amely több független jegyzetablakot kezel, és az osztály–objektum–referencia fogalmakat gyakorolja:
- Jegyzet osztály:
Azonosito(statikus számlálóból),Cim,Szoveg,Letrehozva,Modositva; konstruktor a címmel;SzavakSzama();static Biztonsagos(cim);static Letrehozott. - JegyzetForm: konstruktora egy
Jegyzetobjektumot kap; aTexta Jegyzet #3: Teendők;txtSzoveg(többsoros),lblSzavak(szavak száma, élőben),chkKiemel(háttérszín); amodositottjelző; aFormClosingmódosított tartalomnál rákérdez (Mented?). - Főablak: Új jegyzet (címet kér, új
JegyzetFormnyílik,Show(this)), alstAblakokfelsorolja a nyitott ablakokat (#3 Teendők, 4 szó), dupla kattintás előre hozza az ablakot (Activate). - Nyilvántartás:
List<JegyzetForm>; aFormClosedkiveszi; a lista és az állapotsor (Nyitva: 3, létrehozott összesen: 5) frissül. - Egyetlen példány: Súgó gomb: egyszer nyílik, újabb kattintásra előre hozza (
IsDisposedvédelem). - Modális ablak: Beállítás...:
BeallitasForm(ShowDialog+using), amely az alapértelmezett kiemelő színt adja vissza; OK / Mégse aDialogResultszerint. - Referencia-kísérlet: Két név, egy objektum gomb:
JegyzetForm a = nyitottak[0]; JegyzetForm b = a;, ab.Textmódosítása látszik aza-n is, és aReferenceEqualseredménye a naplóban; anulleset (nincs nyitott ablak) érthető üzenettel. - Összes bezárása: a Mind bezár gomb végigmegy a lista másolatán (
ToList()), mert a bezárás módosítja az eredeti listát. - Teszteld: több ablak független állapota, a bezárás sorrendje, a súgó kétszeri kattintása bezárás után, a módosított jegyzet bezárása (Igen/Nem/Mégse), a lista frissülése, üres lista.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A Jegyzet osztályt a 4. lépés kódja adja; itt a két Form kulcsrésze.
// ---- JegyzetForm.cs ----
public partial class JegyzetForm : Form
{
private readonly Jegyzet jegyzet;
private bool modositott = false;
public Jegyzet Adat { get { return jegyzet; } }
public JegyzetForm(Jegyzet j)
{
InitializeComponent();
jegyzet = j; // a hivatkozás: ugyanaz az objektum, amit a főablak is ismer
Text = "Jegyzet #" + j.Azonosito + ": " + j.Cim;
txtSzoveg.Text = j.Szoveg;
Frissit();
modositott = false;
}
private void Frissit()
{
jegyzet.Szoveg = txtSzoveg.Text;
lblSzavak.Text = jegyzet.SzavakSzama() + " szó";
modositott = true;
}
private void txtSzoveg_TextChanged(object sender, EventArgs e) { Frissit(); }
private void chkKiemel_CheckedChanged(object sender, EventArgs e)
{
BackColor = chkKiemel.Checked ? Color.LemonChiffon : SystemColors.Control;
}
private void JegyzetForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (modositott && e.CloseReason == CloseReason.UserClosing)
{
DialogResult v = MessageBox.Show(this, "Mented a módosításokat?", Text, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (v == DialogResult.Cancel) { e.Cancel = true; return; }
if (v == DialogResult.Yes) { jegyzet.Modositva = DateTime.Now; }
}
}
}
// ---- Form1.cs (főablak) ----
private readonly List<JegyzetForm> nyitottak = new List<JegyzetForm>();
private SugoForm sugo;
private Color kiemeloSzin = Color.LemonChiffon;
private void btnUj_Click(object sender, EventArgs e)
{
string cim = Jegyzet.Biztonsagos(Microsoft.VisualBasic.Interaction.InputBox("A jegyzet címe:", "Új jegyzet", ""));
JegyzetForm ablak = new JegyzetForm(new Jegyzet(cim));
ablak.FormClosed += (s, ev) => { nyitottak.Remove(ablak); ListaFrissit(); };
nyitottak.Add(ablak);
ablak.Show(this);
ListaFrissit();
}
private void ListaFrissit()
{
lstAblakok.Items.Clear();
foreach (JegyzetForm f in nyitottak)
{
lstAblakok.Items.Add("#" + f.Adat.Azonosito + " " + f.Adat.Cim + ", " + f.Adat.SzavakSzama() + " szó");
}
tsslUzenet.Text = "Nyitva: " + nyitottak.Count + ", létrehozott összesen: " + Jegyzet.Letrehozott;
}
private void lstAblakok_DoubleClick(object sender, EventArgs e)
{
int i = lstAblakok.SelectedIndex;
if (i >= 0 && i < nyitottak.Count)
{
if (nyitottak[i].WindowState == FormWindowState.Minimized) { nyitottak[i].WindowState = FormWindowState.Normal; }
nyitottak[i].Activate();
}
}
private void btnSugo_Click(object sender, EventArgs e)
{
if (sugo == null || sugo.IsDisposed) { sugo = new SugoForm(); sugo.Show(this); }
else { sugo.Activate(); }
}
private void btnBeallitas_Click(object sender, EventArgs e)
{
using (BeallitasForm f = new BeallitasForm(kiemeloSzin))
{
if (f.ShowDialog(this) == DialogResult.OK) { kiemeloSzin = f.ValasztottSzin; }
}
}
private void btnReferencia_Click(object sender, EventArgs e)
{
if (nyitottak.Count == 0) { tsslUzenet.Text = "Nincs nyitott jegyzet."; return; }
JegyzetForm a = nyitottak[0];
JegyzetForm b = a; // nem másolat
b.Text = "(átnevezve b-n keresztül)";
lstNaplo.Items.Add("a.Text = " + a.Text + "; ReferenceEquals: " + object.ReferenceEquals(a, b));
}
private void btnMindBezar_Click(object sender, EventArgs e)
{
foreach (JegyzetForm f in nyitottak.ToList()) { f.Close(); } // másolaton megyünk végig: a bezárás módosítja a listát
}Mit tegyél ide: a kész ablakkezelő-labor több nyitott jegyzetablakkal.Javasolt fájlnév: winforms-12-1-kesz.webp · Alt szöveg: A kész ablakkezelő-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. Számláló osztály
Írj Két Szamlalo osztályt Novel(), Nulláz() metódussal és Ertek mezővel, majd hozz létre kettőt.Egy lehetséges megoldás
new Szamlalo() két független Ertek mezőt ad: az egyik növelése nem hat a másikra. Ha az értéket static mezőbe teszed, közös lesz: ez a különbség lényege.
2. Modális választó
Egy Az ablak nyilvános tulajdonsága (ShowDialog ablakban a felhasználó kiválaszt egy színt. Hogyan jut az eredmény a hívóhoz?Egy lehetséges megoldás
ValasztottSzin), amelyet az OK gomb a DialogResult.OK beállítása előtt kitölt; a hívó using blokkban olvassa ki.
3. Másolat
Hogyan készítesz valódi másolatot egy Új objektumot hozol létre, és átmásolod a mezőket (Jegyzet objektumról?Egy lehetséges megoldás
Jegyzet masolat = new Jegyzet(eredeti.Cim) { Szoveg = eredeti.Szoveg };), esetleg egy Masol() metódusba téve. A b = a nem másol.
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. Osztály vagy objektum?
Melyik az osztály, melyik az objektum: A Form1; new Form1()?Megoldás megnyitása
Form1 az osztály (terv); a new Form1() egy objektumot (példányt) hoz létre.
2. Mi a this?
Fogalmazd meg egy mondatban. Az aktuális objektum: az a példány, amelyen a kód éppen fut.Megoldás megnyitása
Közepes szint
3. Mit ír ki?
y: egyetlen objektum két névvel.Jegyzet a = new Jegyzet("x"); Jegyzet b = a; b.Cim = "y"; MessageBox.Show(a.Cim);Megoldás megnyitása
4. Show vagy ShowDialog?
Melyiket választod: (a) súgó; (b) mentés előtti megerősítés? (a) Megoldás megnyitása
Show: a főablak közben használható; (b) ShowDialog: választ kell adni, mielőtt továbblépsz.
Kihívás szint
5. Szivárgás
Egy program 1000-szer megnyit és bezár egy Az ablak nem szabadul fel (a ShowDialog ablakot using nélkül. Mi a gond?Megoldás megnyitása
ShowDialog után csak elrejtőzik): a fogantyúk és a memória nőnek (9.8 lecke). using vagy Dispose() kell.
6. Lista bejárása bezáráskor
Miért hibás a A foreach (var f in nyitottak) f.Close();, ha a FormClosed kiveszi az elemet?Megoldás megnyitása
foreach közben módosul a lista: InvalidOperationException. A lista másolatán (nyitottak.ToList()) kell végigmenni.
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Az osztály a terv, az objektum a belőle létrehozott példány saját állapottal. A A hivatkozást, nem az objektumot: két név, egy objektum. Nincs objektum; a használata A Az osztályé, egyetlen közös érték, példány nélkül elérhető.1. Mi a különbség az osztály és az objektum között?
2. Mit jelent a Form1 : Form?
Form1 a Form leszármazottja: örökli a tagjait.3. Mit másol a b = a?
4. Mi a null?
NullReferenceException.5. Mi a különbség a Show és a ShowDialog között?
Show azonnal visszatér; a ShowDialog modális, megvárja a bezárást, és DialogResult-ot ad.6. Mikor kell felszabadítani a Formot?
ShowDialog után (using/Dispose); a Show ablak bezáráskor magától.7. Mi a static tag?
Mit tudsz már?
- Érted, hogy a Form osztály, és példányokból több független ablak nyitható.
- Kezeled a referenciákat, a
nullértéket és athiskulcsszót. - Helyesen használod a
ShowésShowDialogmetódust, és kezeled az ablakok életciklusát. - Saját osztályt írsz példány- és
statictagokkal.
Következő lecke: partial class és a Designer generált kódja
Mit rejt a Form1.Designer.cs, hogyan kapcsolódik a két fájl, és mit szabad (és mit nem) szerkeszteni.



