Grafikus C# – Windows Forms tanfolyam › 7.5 lecke Windows Forms · 7.5 lecke · több ablak Egy valódi program nem egyetlen ablakból áll: van beállítóablak, névjegy, keresőablak, szerkesztő, eszköztár-ablak. Az ablakok megnyitására két metódus szolgál: a Form.Show és Form.ShowDialog
Show() nem modális ablakot nyit (a felhasználó közben a főablakban is dolgozhat), a ShowDialog() modálisat (addig nem lehet másik ablakot használni, amíg be nem zárul, és a kód megvárja az eredményt). Ebben a leckében megtanulod a különbséget, a tulajdonos (Owner) kapcsolatot, az ablak élettartamát (Close, Hide, Dispose) és az egypéldányos ablak mintáját.
A lecke végére
- megkülönbözteted a
Showés aShowDialogviselkedését és használatát; - a tulajdonost (
Owner) helyesen adod meg, és érted a hatását; - tudod, mikor semmisül meg az ablak (
Close), és mikor kell kézzel felszabadítani (ShowDialog); - egypéldányos nem modális ablakot készítesz (
IsDisposed,FormClosed); - az
Application.OpenFormssegítségével kezeled a nyitott ablakokat; - felismered a gyakori hibákat (
ObjectDisposedException,usingaShowmellett, ablakok halmozódása).
Mielőtt elkezded
Ehhez a leckéhez a 6.7 lecke (ShowDialog és DialogResult első bemutatása), az 5.6 lecke (FormClosed, Application.OpenForms) és az 5.7 lecke (feliratkozás, leiratkozás) szükséges.
Nem modális és modális ablak
Show() (nem modális) | ShowDialog() (modális) | |
|---|---|---|
| A hívó kód | Azonnal továbbfut (nem vár). | Megáll, amíg az ablak be nem záródik. |
| A hívó ablak | Használható marad. | Le van tiltva, amíg a dialógus nyitva van. |
| Visszatérési érték | Nincs (void). | DialogResult (6.7 lecke). |
| Mire való? | Önálló munkaablak, eszköztár, kereső, súgó, megfigyelő. | Döntés vagy adat bekérése: beállítás, mentés kérdés, bejelentkezés. |
| Bezáráskor | Megsemmisül (Dispose). | Csak elrejtődik: kézzel kell felszabadítani (using). |
// nem modális: a tulajdonos a főablak, a kód azonnal folytatódik
private void btnKereso_Click(object sender, EventArgs e)
{
KeresoForm ablak = new KeresoForm();
ablak.Show(this); // az Owner = this
lblInfo.Text = "A kereső megnyílt."; // ez azonnal lefut
}
// modális: a kód megvárja az ablak bezárását
private void btnBeallitas_Click(object sender, EventArgs e)
{
using (BeallitasForm ablak = new BeallitasForm())
{
if (ablak.ShowDialog(this) == DialogResult.OK)
{
lblInfo.Text = "A beállítások elfogadva."; // csak a bezárás után fut le
}
}
}Show(this) és a ShowDialog(this) beállítja a tulajdonost. A tulajdonos ablak fölött marad az ablak, vele együtt kicsinyítődik és záródik be, és a CenterParent értékű StartPosition ennek közepére helyezi. Tulajdonos nélkül az ablak független, és a főablak mögé kerülhet.ShowDialog alatt a hívó kód „áll”, de az üzenetek feldolgozása (Timer, újrarajzolás) működik: az időzítők a modális ablak alatt is futnak.Jósolj!
A kereső ablakot Show() hívással nyitod meg (tulajdonos nélkül), majd a főablakra kattintasz. Mi történhet a kereső ablakkal?
Jóslat ellenőrzése
A kereső ablak a főablak mögé kerülhet (nincs tulajdonos, ami fölötte tartaná), és a tálcán külön gombként látszik. A Show(this) megadja a tulajdonost, így az ablak a főablak fölött marad, és vele együtt kicsinyedik.
Az ablak élettartama: Close, Hide, Dispose
Close()
Bezárja az ablakot. A Show() ablaknál ez megsemmisíti (Dispose): az objektum nem használható újra, a Show() második hívása ObjectDisposedException kivételt dob. A ShowDialog() ablaknál a Close csak elrejti, az objektum él tovább, és a Dispose a hívó dolga (using).
Hide()
Elrejti az ablakot (Visible = false), de megőrzi az állapotát (a beírt szöveget, a vezérlők értékét). A Show() újra megjeleníti ugyanazt a példányt. Ez az „eltűnik és visszatér” ablak módja.
KeresoForm kereso = new KeresoForm();
kereso.Show(this);
kereso.Close();
kereso.Show(this); // ObjectDisposedException: a bezárt ablak megsemmisült
// helyesen: elrejtés és újramegjelenítés ugyanazzal a példánnyal
kereso.Hide();
kereso.Show(this); // működik, az állapot megmaradt
// vagy: új példány a bezárás után
if (kereso == null || kereso.IsDisposed)
{
kereso = new KeresoForm();
}
kereso.Show(this);A klasszikus csapda: using a Show() mellett
A using (var ablak = new Form2()) { ablak.Show(); } a blokk végén azonnal felszabadítja az ablakot, ezért az megjelenik, majd azonnal eltűnik. A using csak a ShowDialog mellett helyes, mert az megvárja a bezárást. A Show() ablak magától megsemmisül, amikor bezárják.
Jósolj!
Egy beállítóablakot ShowDialog() hívással nyitsz meg using nélkül, és minden gombnyomásra új példányt hozol létre. Mit tapasztalsz a program memóriahasználatánál sok megnyitás után?
Jóslat ellenőrzése
A modális ablak bezáráskor csak elrejtődik, és Dispose nélkül erőforrásai (vezérlők, kezelők) csak a szemétgyűjtésig élnek tovább: sok megnyitás után a memória- és GDI-objektumok száma észrevehetően nőhet. A using (vagy a Dispose() hívás) a bezárás után azonnal felszabadít.
Egypéldányos nem modális ablak
Gyakori igény: az eszköztár- vagy keresőablak egyetlen példányban létezzen. Ha a felhasználó másodszor is megnyomja a gombot, ne nyíljon újabb ablak, hanem a meglévő kerüljön előre:
private PaletteForm paletta = null;
private void btnPaletta_Click(object sender, EventArgs e)
{
if (paletta == null || paletta.IsDisposed)
{
paletta = new PaletteForm();
paletta.FormClosed += Paletta_FormClosed; // ha bezárják, a mezőt nullázzuk
paletta.Show(this);
}
else
{
if (paletta.WindowState == FormWindowState.Minimized)
{
paletta.WindowState = FormWindowState.Normal;
}
paletta.Activate(); // előre hozza, a fókuszt is megkapja
}
}
private void Paletta_FormClosed(object sender, FormClosedEventArgs e)
{
paletta.FormClosed -= Paletta_FormClosed; // 5.7 lecke: leiratkozás
paletta = null;
}IsDisposed értéke igaz. A mező null vagy IsDisposed vizsgálata megmondja, kell-e új példány.Activate() előrehozza az ablakot, és a fókuszt is neki adja. A BringToFront() csak a sorrendet változtatja.FormClosing eseményben e.Cancel = true; this.Hide(); (csak UserClosing esetén, a program kilépésekor engedd a bezárást).Jósolj!
A Paletta_FormClosed kezelőben nem nullázod a paletta mezőt, de az IsDisposed vizsgálat megvan. Működik-e a második megnyitás?
Jóslat ellenőrzése
Igen: az IsDisposed igaz a bezárt ablakra, ezért új példány készül. A null beállítás kényelmes, és a régi, bezárt ablakra mutató hivatkozást is eltávolítja (a szemétgyűjtő felszabadíthatja), de a működéshez az IsDisposed elég.
A nyitott ablakok kezelése
// az összes nyitott ablak listája (a főablakot is tartalmazza)
private void ListaFrissit()
{
lstAblakok.Items.Clear();
foreach (Form f in Application.OpenForms)
{
lstAblakok.Items.Add(f.Text + " (" + f.GetType().Name + ")");
}
}
// az összes tulajdonolt ablak bezárása: másolat a bejáráshoz, mert a gyűjtemény módosul
private void btnMindBezar_Click(object sender, EventArgs e)
{
Form[] tulajdonolt = this.OwnedForms; // már másolat (tömb)
for (int i = tulajdonolt.Length - 1; i >= 0; i--)
{
tulajdonolt[i].Close();
}
}
// az aktív ablak
Form aktiv = Form.ActiveForm; // null, ha a program ablaka nem aktívfor ciklust használni.Owner kapcsolat alapján). A főablak bezárásakor ezek is bezáródnak.Application.Run által indított főablak bezárásakor a program kilép, a többi ablak is bezáródik. Ha a főablak helyett másik ablaknak kell tovább élnie, a program szerkezetét kell átgondolni (a bejelentkező ablak mintája, 6.7 lecke).null érték azt jelzi, hogy a program egyik ablaka sem aktív (például másik programra váltottak).Jósolj!
A foreach (Form f in Application.OpenForms) { if (f != this) f.Close(); } ciklus bezár minden más ablakot. Mi a hiba veszélye?
Jóslat ellenőrzése
A bezárás módosítja a bejárt gyűjteményt, ezért a foreach InvalidOperationException kivételt dobhat (a gyűjtemény módosult), vagy kihagyhat ablakokat. Biztonságos megoldás: előbb másolat egy listába, vagy visszafelé haladó for ciklus (a Application.OpenForms[i] indexeléssel).
Mit tegyél ide: a főablak a nyitott ablakok listájával, mellette két dokumentumablak és az eszköztár-ablak.Javasolt fájlnév: winforms-7-5-ablakok.webp · Alt szöveg: A főablak és a tulajdonolt ablakok
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. ObjectDisposedException a második megnyitáskor
private KeresoForm kereso = new KeresoForm();
private void btnKereso_Click(object sender, EventArgs e)
{
kereso.Show(this); // az első bezárás után megsemmisült
}Hiba és javítás
A bezárt ablak megsemmisült. Készíts új példányt, ha a mező null vagy IsDisposed, vagy a bezárás helyett Hide() hívással rejtsd el.
2. Az ablak megjelenik, és azonnal eltűnik
using (RiportForm ablak = new RiportForm())
{
ablak.Show(); // a blokk végén felszabadul
}Hiba és javítás
A using a Show azonnali visszatérése után felszabadítja az ablakot. Nem modális ablaknál ne használj using-ot (az ablak magától megsemmisül bezáráskor), modálisnál (ShowDialog) a using helyes.
3. Minden gombnyomásra új ablak nyílik
A kereső ablakból tíz példány jelenik meg. Nem őrzöl hivatkozást a meglévőre. Használd az egypéldányos mintát: mező, Ok és javítás
IsDisposed vizsgálat, Activate().
4. A dialógus eredményét hibásan veszi a kód
ablak.ShowDialog(this);
lblNev.Text = ablak.Nev; // akkor is, ha a felhasználó a Mégse gombot nyomtaHiba és javítás
Az eredményt vizsgálni kell: if (ablak.ShowDialog(this) == DialogResult.OK) { ... }. Mégse esetén az adatot nem szabad átvenni.
5. A főablak mögé kerül az ablak
A segédablak elvész a főablak mögött. Nincs tulajdonos. Használd a Ok és javítás
Show(this) hívást (vagy az Owner tulajdonságot).
6. InvalidOperationException az ablakok bezárásakor
foreach (Form f in Application.OpenForms)
{
if (f != this) { f.Close(); } // a gyűjtemény módosul
}Hiba és javítás
Készíts másolatot egy listába, és azon haladj, vagy használj visszafelé haladó for ciklust az OpenForms[i] indexeléssel.
Mini projekt: ablakkezelő
Készíts programot, amely a három ablaktípust és az élettartamot szemlélteti:
- Főablak (
Form1): gombok: Új dokumentumablak, Paletta (egy példány), Paletta elrejtése / megjelenítése, Modális kérdés, Mind bezár; egylstAblakoklista (a nyitott ablakok), egylblInfocímke és egy 1 másodperces időzítő a lista frissítéséhez. - DokumentumForm: egy
RichTextBox, a cím Dokumentum N (a főablak számlálója adja). A főablakShow(this)hívással nyitja, és nem kellusing. - PaletteForm: egypéldányos, nem modális; a bezárásakor a főablak mezője nullázódik; az elrejtés gomb
Hide()hívással elrejti (az állapot megmarad), a megjelenítés ugyanazt a példányt hozza vissza. A palettán egyTextBoxvan, amelynek tartalma az elrejtés után is megmarad (a bizonyíték). - KerdesForm: modális ablak Igen és Nem gombbal (
DialogResult),usingblokkban; az eredmény alblInfocímkében. - A Mind bezár gomb visszafelé haladva bezárja a
OwnedFormsösszes ablakát. - Teszteld: több dokumentumablak, a palette második megnyitása (előrehozás), elrejtés és megjelenítés (a szöveg megmarad), bezárás utáni új palette (üres szöveg), a modális kérdés alatt a főablak letiltása, a főablak kicsinyítése (a tulajdonolt ablakok vele mennek), a lista pontossága.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A palette elrejtése és újra megjelenítése ugyanazt a példányt használja, a bezárás viszont megsemmisíti. A lista frissítését az időzítő végzi, mert az ablakok maguktól záródhatnak.
private PaletteForm paletta = null;
private int szamlalo = 0;
private void Form1_Load(object sender, EventArgs e)
{
tmrLista.Interval = 1000;
tmrLista.Start();
ListaFrissit();
}
private void tmrLista_Tick(object sender, EventArgs e)
{
ListaFrissit();
}
private void ListaFrissit()
{
lstAblakok.Items.Clear();
foreach (Form f in Application.OpenForms)
{
string allapot = f.Visible ? "" : " (rejtett)";
string tulajdonos = f.Owner == null ? "" : " ← " + f.Owner.Text;
lstAblakok.Items.Add(f.Text + " [" + f.GetType().Name + "]" + allapot + tulajdonos);
}
}
private void btnUjDok_Click(object sender, EventArgs e)
{
szamlalo = szamlalo + 1;
DokumentumForm dok = new DokumentumForm(szamlalo);
dok.Show(this); // nem modális, tulajdonos a főablak; using nélkül
ListaFrissit();
}
private void btnPaletta_Click(object sender, EventArgs e)
{
if (paletta == null || paletta.IsDisposed)
{
paletta = new PaletteForm();
paletta.FormClosed += Paletta_FormClosed;
paletta.Show(this);
}
else
{
if (paletta.WindowState == FormWindowState.Minimized)
{
paletta.WindowState = FormWindowState.Normal;
}
if (!paletta.Visible)
{
paletta.Show(this);
}
paletta.Activate();
}
ListaFrissit();
}
private void Paletta_FormClosed(object sender, FormClosedEventArgs e)
{
paletta.FormClosed -= Paletta_FormClosed;
paletta = null;
}
private void btnPalettaRejt_Click(object sender, EventArgs e)
{
if (paletta != null && !paletta.IsDisposed)
{
if (paletta.Visible)
{
paletta.Hide(); // az állapot (szöveg) megmarad
}
else
{
paletta.Show(this);
}
ListaFrissit();
}
}
private void btnKerdes_Click(object sender, EventArgs e)
{
using (KerdesForm kerdes = new KerdesForm("Folytatod?"))
{
DialogResult v = kerdes.ShowDialog(this); // a hívó kód itt vár
lblInfo.Text = v == DialogResult.Yes ? "Igen" : "Nem";
}
}
private void btnMindBezar_Click(object sender, EventArgs e)
{
Form[] tulajdonolt = this.OwnedForms;
for (int i = tulajdonolt.Length - 1; i >= 0; i--)
{
tulajdonolt[i].Close();
}
ListaFrissit();
}
// PaletteForm.cs: a FormClosing-ban nincs külön kód; a btnRejt: this.Hide();
// DokumentumForm.cs: public DokumentumForm(int szam) { InitializeComponent(); this.Text = "Dokumentum " + szam; }
// KerdesForm.cs: btnIgen.DialogResult = Yes; btnNem.DialogResult = No; AcceptButton = btnIgen; CancelButton = btnNem;Mit tegyél ide: a főablak az ablaklistával (két dokumentum, egy rejtett paletta) és a nyitott ablakok.Javasolt fájlnév: winforms-7-5-kesz.webp · Alt szöveg: Az ablakkezelő program
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. Egypéldányos súgóablak
A F1 billentyű vagy a Súgó menüpont egyetlen súgóablakot nyit. Ha már nyitva van, kerüljön előre. Hol tárolod a példányt? A főablak mezőjében, az Egy lehetséges megoldás
IsDisposed vizsgálattal és az Activate() hívással. A menüpont gyorsbillentyűje (F1, 7.1 lecke) hívja ugyanazt a kezelőt.private SugoForm sugo = null;
private void miSugo_Click(object sender, EventArgs e)
{
if (sugo == null || sugo.IsDisposed)
{
sugo = new SugoForm();
sugo.Show(this);
}
else
{
sugo.Activate();
}
}
2. Modális dialógus eredmény nélkül
A felhasználó a Beállítások ablakot a bezáró X gombbal zárja be. Mit ad vissza a ShowDialog, és hogyan kezeled?Egy lehetséges megoldás
DialogResult.Cancel értéket (az X gomb így zár). Ezért csak az OK eredménynél veszed át az adatokat.
3. Dokumentumablakok száma
A főablak címsora mutassa a nyitott dokumentumablakok számát (Főablak – 3 dokumentum). Hogyan számolod, és mikor frissíted? A Egy lehetséges megoldás
OwnedForms tömb elemeit típus szerint számolod (is DokumentumForm). A frissítés a nyitáskor és az ablakok FormClosed eseményén történik (a bezáráskor a tulajdonolt ablakok száma már csökkent).private void CimFrissit()
{
int db = 0;
foreach (Form f in this.OwnedForms)
{
if (f is DokumentumForm)
{
db++;
}
}
this.Text = "Főablak – " + db + " dokumentum";
}
private void Dok_FormClosed(object sender, FormClosedEventArgs e)
{
((Form)sender).FormClosed -= Dok_FormClosed;
CimFrissit();
}
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. Melyik metódus?
Show vagy ShowDialog: (a) beállítóablak OK/Mégse gombbal; (b) kereső, amely közben a szerkesztőt is használni kell; (c) bejelentkezés indulás előtt; (d) nyomtatási előnézet. (a) Megoldás megnyitása
ShowDialog; (b) Show; (c) ShowDialog; (d) általában ShowDialog (a felhasználó azonnal döntést vár), de külön ablakként is lehet Show.
2. Mi a különbség Close és Hide között?
Magyarázd el két mondatban. A Megoldás megnyitása
Close a Show ablakot megsemmisíti, a példány nem használható újra. A Hide csak elrejti, az állapota megmarad, és a Show újra megjeleníti.
Közepes szint
3. Hide bezáráskor
A keresőablak X gombja ne zárja be, hanem rejtse el (az állapota megmarad), de a program kilépésekor engedje a bezárást. Hol kezeled? A Megoldás megnyitása
FormClosing eseményben, a CloseReason vizsgálatával.private void KeresoForm_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true; // nem zárjuk be
this.Hide(); // csak elrejtjük
}
}
4. Ablak a tálca nélkül
A segédablak ne kapjon tálcagombot. Mely tulajdonságot állítod, és mi a következménye, ha nincs tulajdonos? A Megoldás megnyitása
ShowInTaskbar = false. Tulajdonos nélkül az ablak a főablak mögé kerülhet, és a felhasználó nem találja meg: ilyenkor a Show(this) (Owner) és/vagy TopMost szükséges.
Kihívás szint
5. Mindig a tulajdonos közepén
Írj segédmetódust ( A Megnyit(Form ablak)), amely egy ablakot a főablak közepére helyez és megnyit. Mit állítasz?Megoldás megnyitása
StartPosition = CenterParent (tulajdonossal együtt működik), és a Show(this).private void Megnyit(Form ablak){
ablak.StartPosition = FormStartPosition.CenterParent;
ablak.ShowInTaskbar = false;
ablak.Show(this);
}
6. Az utolsó ablak bezárása után kilépés
A program több független ablakból áll (nincs főablak): az utolsó ablak bezárásakor lépjen ki. Hogyan oldod meg az Az Application.Run segítségével?Megoldás megnyitása
Application.Run(new ApplicationContext()) saját környezettel, amelynek ExitThread() hívása az utolsó ablak bezárásakor történik. Haladó téma: egyszerűbb, ha van egy főablak, amely a többi tulajdonosa.public class TobbAblakKornyezet : ApplicationContext{
private int nyitott = 0;
public void Hozzaad(Form ablak)
{
nyitott = nyitott + 1;
ablak.FormClosed += (s, e) =>
{
nyitott = nyitott - 1;
if (nyitott == 0)
{
ExitThread();
}
};
ablak.Show();
}
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A A tulajdonos ablak fölött marad az ablak, vele kicsinyedik és záródik, és a A A blokk végén azonnal felszabadítja az ablakot, ezért az megjelenik és azonnal eltűnik. Mezőben tárolod, Másolatból vagy visszafelé haladó 1. Mi a különbség a Show és a ShowDialog között?
Show nem modális, azonnal visszatér. A ShowDialog modális, megvárja a bezárást, és DialogResult értéket ad.2. Mire való az Owner?
CenterParent annak közepére helyezi.3. Mikor semmisül meg az ablak?
Show() ablak a Close() hívásakor. A ShowDialog() ablak csak elrejtődik: a Dispose a hívó dolga (using).4. Miért hibás a using a Show mellett?
5. Hogyan készítesz egypéldányos ablakot?
null vagy IsDisposed esetén új példányt készítesz, egyébként Activate().6. Hogyan zárod be az összes ablakot biztonságosan?
for ciklussal (OwnedForms tömb, OpenForms[i]).7. Mit ad vissza a ShowDialog az X gomb után?
DialogResult.Cancel értéket (ha a DialogResult nincs más módon beállítva).
Mit tudsz már?
- Megkülönbözteted a modális és a nem modális ablakot, és tudod, mikor melyiket válaszd.
- Helyesen kezeled az ablak élettartamát (
Close,Hide,Dispose). - Egypéldányos ablakot készítesz, és az
Ownerkapcsolatot használod. - Kezeled a nyitott ablakokat (
OpenForms,OwnedForms) biztonságosan. - Felismered a gyakori hibákat (
ObjectDisposedException,usingaShowmellett).
Következő lecke: Új Form hozzáadása és modalitás
Hogyan készíts saját párbeszédablakot: a tervezés szabályai, a modalitás fajtái és egy nem modális keresőablak a főablakkal összekötve.




