Grafikus C# – Windows Forms tanfolyam › 5.6 lecke

Windows Forms · 5.6 lecke · az ablak életciklusa

Load, Shown, FormClosing és FormClosed – az ablak életciklusa

Egy ablaknak élete van: létrejön, betöltődik, megjelenik, aktívvá válik, majd bezárul. Minden lépéshez tartozik egy esemény, és mindegyik máshoz való: az egyikben az adatokat töltöd be, a másikban a bezárást engeded vagy tiltod, a harmadikban rendet raksz magad után. Ebben a leckében megtanulod az események sorrendjét, és azt, hogy mit hova érdemes tenni: mikor használd a Load, a Shown, a FormClosing és a FormClosed eseményt, hogyan jegyezd meg az ablak méretét és helyét, és hogyan reagálj egy gyermekablak bezárására.

  • kb. 110 perc
  • közepes szint
  • ablak-életciklus
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • ismered az ablak eseményeinek sorrendjét (konstruktor, Load, Activated, Shown, FormClosing, FormClosed);
  • eldöntöd, melyik inicializálás való a konstruktorba, a Load és a Shown eseménybe;
  • a FormClosing eseményben megszakíthatod a bezárást, a FormClosed eseményben rendet raksz;
  • az ablak méretét, helyét és az utolsó adatokat megjegyzed a Settings segítségével;
  • gyermekablak bezárására reagálsz (FormClosed), és az összes nyitott ablakot kezeled (Application.OpenForms);
  • felismered a gyakori életciklus-hibákat.

Mielőtt elkezded

Ehhez a leckéhez az 5.1 és 5.2 lecke (események, FormClosingEventArgs), a 2.14 lecke (mentetlen dokumentum) és a 3.9 lecke (párbeszédablak) szükséges. Új projektben próbáld ki a példákat.

5.5 Változás-események →

Az ablak életének eseményei

LépésMikor?Hányszor?
Konstruktor (InitializeComponent)Az ablak objektum létrejön, a vezérlők felépülnek.egyszer
LoadAz ablak megjelenése előtt, egyszer.egyszer
ActivatedAz ablak megkapja a fókuszt (aktívvá válik).többször (minden aktiváláskor)
ShownAz ablak első megjelenése után.egyszer
DeactivateAz ablak elveszíti a fókuszt (másik ablakra kattintasz).többször
FormClosingA bezárás előtt: megszakítható.egyszer (vagy többször, ha megszakítják)
FormClosedAz ablak már bezárult.egyszer
Konstruktor és LoadA konstruktorban a vezérlők már léteznek (az InitializeComponent után), de az ablak még nem jelent meg. A Load ugyanezt a pillanatot adja, kényelmesebben, eseményként.
Load és ShownA Load még a megjelenés előtt fut: ha lassú, az ablak késve jelenik meg. A Shown a megjelenés után: az ablak már látszik, a lassabb munka mögötte futhat.
FormClosing és FormClosedA FormClosing még megállíthatja a bezárást (e.Cancel), a FormClosed már csak értesít: az ablak nem menthető meg.
Régi eseményekA Closing és a Closed régi, elavult változat: használd a FormClosing és FormClosed párost.

Kísérlet: az események sorrendje

Az események sorrendjét kísérlettel a legbiztosabb megismerni. Az ablak megjelenése előtt még nem lehet ListBox-ba írni, ezért egy listába gyűjtjük a sorokat, és a Shown eseményben jelenítjük meg:

Form1.cs (lstNaplo: ListBox; using System.Collections.Generic;)C#
private List<string> sorok = new List<string>();

private void Jegyez(string szoveg)
{
sorok.Add(DateTime.Now.ToString("HH:mm:ss.fff") + " – " + szoveg);

if (lstNaplo != null && lstNaplo.IsHandleCreated)
{
lstNaplo.Items.Add(sorok[sorok.Count - 1]);
}
}

public Form1()
{
InitializeComponent();
Jegyez("Konstruktor");
}

private void Form1_Load(object sender, EventArgs e) { Jegyez("Load"); }
private void Form1_Activated(object sender, EventArgs e) { Jegyez("Activated"); }
private void Form1_Deactivate(object sender, EventArgs e) { Jegyez("Deactivate"); }

private void Form1_Shown(object sender, EventArgs e)
{
// a korábbi sorok (konstruktor, Load, Activated) most kerülnek a listába
lstNaplo.Items.Clear();

foreach (string sor in sorok)
{
lstNaplo.Items.Add(sor);
}

Jegyez("Shown");
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e) { Jegyez("FormClosing (ok: " + e.CloseReason + ")"); }
private void Form1_FormClosed(object sender, FormClosedEventArgs e) { Jegyez("FormClosed"); }

Jósolj!

Indítod a programot, rákattintasz az ablak címsorára, majd egy másik programra, aztán vissza. Milyen sorrendben jelennek meg az események a listában? Hányszor szerepel az Activated, és hányszor a Shown?

Jóslat ellenőrzése

Az induláskor: Konstruktor, Load, Activated, Shown (az Activated és a Shown sorrendje a környezettől is függhet, ezt érdemes a saját gépeden megnézni). Ha másik programra váltasz: Deactivate, visszaváltáskor újra Activated. Az Activated tehát többször szerepel, a Load és a Shown pedig csak egyszer. Ezért az Activated eseménybe nem való az egyszeri inicializálás.

Mit hova tegyünk?

FeladatMelyik esemény?Miért?
Mezők kezdőértékei, listák feltöltése, a program állapotának előkészítéseLoadMinden vezérlő létezik, az ablak még nem látszik, így nincs villogás.
Beállítások betöltése (ablak mérete és helye)LoadAz ablak a megjegyzett helyen jelenik meg, és nem ugrik át.
Lassú munka (nagy fájl, adatbázis, hálózat)ShownAz ablak előbb megjelenik (például „Betöltés…” felirattal), nem áll fehéren.
Fókusz egy mezőre (txtNev.Focus())ShownA megjelenés után a fókusz beállítható.
Újrafrissítés, amikor az ablakra visszatér a felhasználóActivatedMinden aktiváláskor lefut.
Időzítő szüneteltetése, ha az ablak háttérbe kerülDeactivate / ActivatedNem fogyaszt feleslegesen erőforrást.
Mentetlen adat védelme, kilépés megerősítéseFormClosingMegszakíthatja a bezárást (e.Cancel).
Beállítások mentése, időzítők leállítása, erőforrások felszabadításaFormClosedA bezárás már biztos, nem szakítható meg.
Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
// gyors előkészítés: a megjelenés előtt
cmbSzuro.Items.AddRange(new object[] { "Összes", "Nyitott", "Kész" });
cmbSzuro.SelectedIndex = 0;
lblAllapot.Text = "Betöltés...";
}

private void Form1_Shown(object sender, EventArgs e)
{
// az ablak már látszik: a lassú munka itt jöhet
this.Cursor = Cursors.WaitCursor;
lblAllapot.Refresh(); // azonnali újrarajzolás, hogy a felirat látszódjon

try
{
// ... adatok betöltése az adatbázisból ...
lblAllapot.Text = "Kész.";
}
catch (Exception ex)
{
DB.Naploz(ex);
lblAllapot.Text = "A betöltés nem sikerült.";
}
finally
{
this.Cursor = Cursors.Default;
}

txtKereso.Focus();
}

A lassú Shown is lefagyasztja az ablakot

A Shown eseményben futó hosszú munka is a felületi szálon fut, ezért az ablak megjelenik, de a munka végéig nem reagál (nem lehet mozgatni, bezárni). A Refresh() csak a feliratot rajzolja újra. Hosszabb munkát (néhány másodperc fölött) a 11. fejezetben tanult háttérszálra (async/await, BackgroundWorker) kell bízni.

Jósolj!

A Form1_Load eseménybe beírsz egy System.Threading.Thread.Sleep(3000); sort (három másodperc várakozás). Mikor jelenik meg az ablak? És ha ugyanezt a Form1_Shown eseménybe írod?

Jóslat ellenőrzése

A Load esetén az ablak három másodperc késéssel jelenik meg (addig nem látszik semmi, a program „nem indul”). A Shown esetén az ablak azonnal megjelenik, de a három másodperc alatt kifagyottnak látszik (nem mozgatható). A két helyzet közül a második jobb (a felhasználó látja, hogy a program él), de a helyes megoldás a lassú munkát háttérszálra tenni.

Bezárás: FormClosing és FormClosed

Az 5.2 leckében láttad a FormClosing eseményt (e.Cancel, e.CloseReason). Itt a két esemény munkamegosztása a lényeg:

FormClosing: a döntés

Itt kérdezhetsz rá a mentésre, és itt szakíthatod meg a bezárást. Itt ne szabadíts fel semmit és ne menj végleges változtatásokat: a bezárás még meghiúsulhat (a felhasználó a Mégse gombot nyomja, vagy egy másik kezelő is megszakítja).

FormClosed: a takarítás

Itt már biztos a bezárás. Ide kerül a beállítások mentése, az időzítők leállítása, a fájlok és kapcsolatok lezárása, a naplózás. Itt már nem lehet megszakítani a bezárást.

Form1.csC#
private bool modosult = false;

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// csak döntés: szabad-e bezárni?
if (!modosult || e.CloseReason != CloseReason.UserClosing)
{
return;
}

DialogResult valasz = MessageBox.Show("Mented a változtatásokat?", "Kilépés", MessageBoxButtons.YesNoCancel);

if (valasz == DialogResult.Cancel)
{
e.Cancel = true;
}
else if (valasz == DialogResult.Yes)
{
if (!Mentes())
{
e.Cancel = true; // a mentés nem sikerült: az ablak marad
}
}
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// takarítás: a bezárás már biztos
tmrFrissit.Stop();
// itt írhatsz naplósort is: az ablak bezárásának oka az e.CloseReason
}
A Close() és az alkalmazás végeA főablak (a Program.cs-ben az Application.Run(new Form1()) által indított ablak) bezárásakor az egész program leáll. Egy másik ablak bezárása csak őt zárja.
Application.Exit()Az összes ablakot bezárja, és leállítja az üzenetfeldolgozást. A FormClosing ilyenkor is lefut, és megszakíthatja a kilépést.
Hide()Az ablakot elrejti, nem zárja be: a FormClosing és a FormClosed nem fut, az ablak él tovább, és később újra mutatható (Show()).
e.CloseReasonA bezárás oka (UserClosing, WindowsShutDown, ApplicationExitCall stb.). Érdemes a kérdéseket csak a UserClosing esetén feltenni.

Jósolj!

A FormClosing kezelőben a végleges mentés és a kapcsolat lezárása is megtörténik, és a felhasználó utána a kérdésnél a Mégse gombot választja (e.Cancel = true). Mi a baj ezzel a sorrenddel?

Jóslat ellenőrzése

Az ablak nyitva marad, de a program már lezárta az erőforrásait: a kapcsolat zárva van, a fájl lezárt, az időzítő leállt. A folytatott használat hibákat okoz. A szabály: a FormClosing csak dönt, a FormClosed takarít. Ezért a szétszedés a FormClosed eseménybe való.

Az ablak megjegyzése: a Settings

A programoknál megszokott, hogy a következő indításkor ugyanott és akkora ablakban nyílnak meg, ahol bezártad őket. Ehhez a felhasználói beállítások (Settings) használhatók: a .NET a felhasználó profiljában tárolja az értékeket, és a program újraindításakor visszaadja. Beállításuk:

  1. A Solution Explorerben: Properties → Settings.settings (dupla kattintás).
  2. Add hozzá a beállításokat a táblázatban: Szelesseg (int, User hatókör, érték: 0), Magassag (int), BalX (int), FelsoY (int), UtolsoSzoveg (string).
  3. A kódban a Properties.Settings.Default.Szelesseg alakkal éred el, a mentést a Save() végzi.
Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
var beall = Properties.Settings.Default;

if (beall.Szelesseg > 0 && beall.Magassag > 0)
{
Rectangle mentett = new Rectangle(beall.BalX, beall.FelsoY, beall.Szelesseg, beall.Magassag);

// csak akkor állítjuk vissza, ha az ablak látszik valamelyik képernyőn
foreach (Screen kepernyo in Screen.AllScreens)
{
if (kepernyo.WorkingArea.IntersectsWith(mentett))
{
this.StartPosition = FormStartPosition.Manual;
this.Bounds = mentett;
break;
}
}
}

txtJegyzet.Text = beall.UtolsoSzoveg;
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
var beall = Properties.Settings.Default;

// kicsinyített vagy maximalizált ablaknál a RestoreBounds adja az eredeti méretet és helyet
Rectangle hatar = this.WindowState == FormWindowState.Normal ? this.Bounds : this.RestoreBounds;

beall.BalX = hatar.X;
beall.FelsoY = hatar.Y;
beall.Szelesseg = hatar.Width;
beall.Magassag = hatar.Height;
beall.UtolsoSzoveg = txtJegyzet.Text;
beall.Save(); // enélkül a beállítások elvesznek
}
Properties.Settings.DefaultA beállítások gyűjteménye: minden sor egy tulajdonság (Szelesseg, UtolsoSzoveg), a típusa a táblázatban megadott.
Save()A mentés a felhasználó profiljába (user.config fájl). A mentés nélküli módosítás a program leállásakor elvész.
Screen.AllScreensA csatlakoztatott képernyők. Az ellenőrzés megakadályozza, hogy egy lecsatlakoztatott második monitoron mentett hely miatt az ablak a képernyőn kívül nyíljon meg.
RestoreBoundsKicsinyített (Minimized) vagy maximalizált ablaknál a Bounds a képernyő szélét vagy hibás értéket adna (például -32000); a RestoreBounds a normál méretű ablak helyét és méretét adja.

Jósolj!

Az ablakot lekicsinyített állapotban zárod be (a tálcán), a kód viszont mindig a this.Bounds értékét menti. Mi történik a következő indításkor?

Jóslat ellenőrzése

Lekicsinyített ablaknál a Bounds a Windowsban egy képernyőn kívüli, jellemzően -32000, -32000 pont körüli helyet ad, kis mérettel. A következő indításkor az ablak a képernyőn kívül nyílna meg, és a felhasználó azt hinné, hogy a program nem indul. A javítás: a RestoreBounds használata nem normál állapotban, és a képernyőn való látszás ellenőrzése betöltéskor (a Screen.AllScreens ciklussal).

Több ablak: gyermekablak bezárása és az összes ablak

A 3.9 leckében a párbeszédablakot ShowDialog() nyitotta meg, ezért a hívó kód addig várt, amíg bezárták. Ha az ablakot a Show() nyitja (nem modális), a főablak tovább használható, és a FormClosed esemény értesít, ha a gyermekablak bezárult:

Form1.csC#
private ReszletekAblak reszletek = null;

private void btnReszletek_Click(object sender, EventArgs e)
{
// ha már nyitva van, ne nyissunk másodikat, csak hozzuk előre
if (reszletek != null && !reszletek.IsDisposed)
{
reszletek.Activate();
return;
}

reszletek = new ReszletekAblak();

// ha bezárták, frissítjük a főablak listáját
reszletek.FormClosed += (s, ev) =>
{
Frissit();
reszletek = null;
};

reszletek.Show(this); // a főablak a gazdája (owner)
}

// az összes nyitott ablak bezárása a főablakon kívül
private void MindetBezar()
{
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
Form urlap = Application.OpenForms[i];

if (urlap != this)
{
urlap.Close();
}
}
}
Show() és Show(this)A Show() nem modális: a hívó kód azonnal folytatódik. A Show(this) megadja a gazdát (owner): a gyermekablak mindig a főablak felett marad, és vele együtt kicsinyedik, illetve záródik.
Egy példányHa a gomb többször új ablakot nyitna, a felhasználó tucatnyi ablakot nyit. A mezőben tárolt hivatkozás és az IsDisposed ellenőrzés egyetlen példányra korlátoz.
FormClosed a gazdábanA gazda a gyermek FormClosed eseményére feliratkozik (kódból, +=): így a bezárás után frissítheti saját adatait. Modális ablaknál (ShowDialog) erre nincs szükség, mert a hívás visszatér.
Application.OpenFormsAz összes jelenleg nyitott ablak gyűjteménye. A bezáráskor a lista változik, ezért a ciklust visszafelé járjuk.

Jósolj!

A MindetBezar metódus ciklusát előre haladóra írod át (for (int i = 0; i < Application.OpenForms.Count; i++)), és három gyermekablak van nyitva. Mind a hármat bezárja?

Jóslat ellenőrzése

Valószínűleg nem: egy ablak bezárásakor kikerül az OpenForms gyűjteményből, a többi elem pedig egyet előreugrik. A ciklusváltozó (i) átugrik egy elemet, így például a harmadik gyermek megmarad. Gyűjtemény módosítása közben ezért mindig visszafelé haladj (i--), vagy másolatot járj végig.

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 program lassan indul, és az ablak sokáig nem látszik

Az ablak csak több másodperc késéssel jelenik meg.

Ok és javítás

A lassú munka (adatbázis, fájl) a Load eseményben fut, amely a megjelenés előtt van. Vidd át a Shown eseménybe, és jelezd egy „Betöltés...” felirattal. Hosszú munkát háttérszálra tegyél (11. fejezet).

2. A Load fele nem fut le, és nincs hibaüzenet

Egy Load kezelőben lévő kód egy része sosem hajtódik végre, hiba nélkül.

Ok és javítás

Ha a Load kezelő kivételt dob, a kezelő futása megszakad, és bizonyos futtatási környezetekben (például 64 bites Windowson, hibakereső nélkül) a hiba csendben elnyelődhet, így a program „furán” működik. Tedd a Load kódját try–catch blokkba, és naplózd a hibát (DB.Naploz(ex)), így a hiba nem rejtőzik el.

3. A Mégse után használhatatlan a program

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
tmrFrissit.Stop();
kapcsolat.Close(); // lezárjuk, aztán rákérdezünk

if (MessageBox.Show("Biztosan kilépsz?", "", MessageBoxButtons.YesNo) == DialogResult.No)
{
e.Cancel = true;
}
}
Hiba és javítás

A takarítás (időzítő, kapcsolat) a FormClosing eseményben megtörtént, de a felhasználó a bezárást megszakította. Vidd át a takarítást a FormClosed eseménybe: a FormClosing csak döntsön.

4. A beállítások nem maradnak meg

Properties.Settings.Default.Szelesseg = this.Width;
// a Save() hiányzik
Hiba és javítás

A beállítás értéke csak a memóriában változott. Hívd a Properties.Settings.Default.Save() metódust (jellemzően a FormClosed eseményben). Ellenőrizd azt is, hogy a beállítás hatóköre User (az Application hatókörű beállítás csak olvasható).

5. Az ablak a képernyőn kívül nyílik meg

Lekicsinyített állapotban bezárva a program legközelebb látszólag nem indul.

Ok és javítás

A Bounds kicsinyített ablaknál képernyőn kívüli értéket ad. Normál állapotban a Bounds, egyébként a RestoreBounds kell, és betöltéskor ellenőrizni kell, hogy a hely valamelyik képernyőn látszik-e.

6. A gyermekablak többször is megnyílik

A gombra többször kattintva egyre több azonos ablak nyílik meg.

Ok és javítás

Minden kattintás új példányt hoz létre. Tárold az ablak hivatkozását mezőben, nyitás előtt ellenőrizd (reszletek != null && !reszletek.IsDisposed), és ha már nyitva van, csak aktiváld (Activate()). A mezőt a FormClosed eseményben nullázd.

Mini projekt: emlékező jegyzet

Készíts jegyzetprogramot, amely emlékszik az ablakára és a szövegére, és az életciklust is megmutatja:

  1. Az ablakon: txtJegyzet (többsoros), lstNaplo (az életciklus-napló), lblAllapot, btnReszletek és egy második ablak (StatAblak, amely a jegyzet karaktereinek számát mutatja).
  2. Beállítások (Settings): Szelesseg, Magassag, BalX, FelsoY (int), UtolsoSzoveg (string).
  3. Load: az ablak a megjegyzett helyen és méretben nyílik meg (csak ha látszik valamelyik képernyőn), a szöveg visszatöltődik. A Shown egy „Kész.” feliratot ír, és a fókuszt a jegyzetre teszi.
  4. FormClosing: ha a szöveg módosult a betöltés óta, és a bezárás oka a felhasználó, kérdés jön (Igen/Nem/Mégse); a Mégse megszakítja a bezárást.
  5. FormClosed: a beállítások mentése (a RestoreBounds alkalmazásával).
  6. A btnReszletek egyetlen StatAblak példányt nyit (Show(this)), a főablak pedig a gyermek FormClosed eseményére iratkozik fel, és a bezárás után kiír egy üzenetet a lblAllapot feliratra.
  7. Az életciklus eseményei (Load, Shown, Activated, Deactivate, FormClosing) az lstNaplo-ban látszanak.
  8. Teszteld: méret- és helyváltoztatás újraindítás után, lekicsinyített bezárás, a Mégse, a gyermekablak kétszeri nyitása, a második monitor hiánya (ha van rá lehetőséged).
Megoldás megnyitása (próbáld ki előbb önállóan!)

A napló az Jegyez metódussal készül (a konstruktor és a Load sorai a sorok listába gyűlnek, és a Shown jeleníti meg őket, ahogyan a leckében). A modul a modosult jelzőt a TextChanged eseményben állítja, és a betöltött szöveg beírása után nullázza.

Form1.csC#
private bool modosult = false;
private StatAblak stat = null;

private void Form1_Load(object sender, EventArgs e)
{
Jegyez("Load");

var beall = Properties.Settings.Default;

if (beall.Szelesseg > 0 && beall.Magassag > 0)
{
Rectangle mentett = new Rectangle(beall.BalX, beall.FelsoY, beall.Szelesseg, beall.Magassag);

foreach (Screen kepernyo in Screen.AllScreens)
{
if (kepernyo.WorkingArea.IntersectsWith(mentett))
{
this.StartPosition = FormStartPosition.Manual;
this.Bounds = mentett;
break;
}
}
}

txtJegyzet.Text = beall.UtolsoSzoveg;
modosult = false; // a betöltött szöveg nem számít módosításnak
}

private void Form1_Shown(object sender, EventArgs e)
{
lstNaplo.Items.Clear();

foreach (string sor in sorok)
{
lstNaplo.Items.Add(sor);
}

Jegyez("Shown");
lblAllapot.Text = "Kész.";
txtJegyzet.Focus();
}

private void txtJegyzet_TextChanged(object sender, EventArgs e)
{
modosult = true;
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Jegyez("FormClosing (" + e.CloseReason + ")");

if (!modosult || e.CloseReason != CloseReason.UserClosing)
{
return;
}

DialogResult valasz = MessageBox.Show("Mented a változtatásokat?", "Kilépés", MessageBoxButtons.YesNoCancel);

if (valasz == DialogResult.Cancel)
{
e.Cancel = true;
return;
}

if (valasz == DialogResult.No)
{
txtJegyzet.Text = Properties.Settings.Default.UtolsoSzoveg; // a mentetlen változtatás elvetése
}
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
var beall = Properties.Settings.Default;
Rectangle hatar = this.WindowState == FormWindowState.Normal ? this.Bounds : this.RestoreBounds;

beall.BalX = hatar.X;
beall.FelsoY = hatar.Y;
beall.Szelesseg = hatar.Width;
beall.Magassag = hatar.Height;
beall.UtolsoSzoveg = txtJegyzet.Text;
beall.Save();
}

private void btnReszletek_Click(object sender, EventArgs e)
{
if (stat != null && !stat.IsDisposed)
{
stat.Activate();
return;
}

stat = new StatAblak(txtJegyzet.TextLength);
stat.FormClosed += (s, ev) =>
{
lblAllapot.Text = "A statisztika ablak bezárult: " + DateTime.Now.ToString("HH:mm:ss");
stat = null;
};
stat.Show(this);
}
📷 Képernyőkép helye

Mit tegyél ide: a kész program: a jegyzet, mellette az életciklus-napló (Konstruktor, Load, Activated, Shown sorokkal), és a nyitott statisztika ablak.Javasolt fájlnév: winforms-5-6-kesz.webp · Alt szöveg: Az emlékező jegyzet program és az életciklus-napló

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. Load vagy Shown: a különbség kipróbálása

Készíts két gombot: az egyik az ablakot a Load, a másik a Shown eseményben késlelteti 2 másodpercre (Thread.Sleep), egy CheckBox választja ki, melyik legyen aktív. Mit látsz?

Egy lehetséges megoldás

Egy bool típusú LoadKesleltet beállítás (Settings) tárolja, hogy melyik esemény késleltessen. A Load változatnál az ablak két másodperc múlva jelenik meg, a Shown változatnál azonnal, de utána két másodpercig nem reagál. A kísérlet jól mutatja, miért kell a lassú munkát háttérszálra tenni.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
if (Properties.Settings.Default.LoadKesleltet)
{
System.Threading.Thread.Sleep(2000);
}
}

private void Form1_Shown(object sender, EventArgs e)
{
if (!Properties.Settings.Default.LoadKesleltet)
{
System.Threading.Thread.Sleep(2000);
}
}

2. Szünet, ha az ablak háttérbe kerül

Egy tmrOra Timer másodpercenként frissít egy órát (lblOra). Az óra álljon le, ha az ablak elveszíti a fókuszt, és induljon újra, ha visszatér. Miért érdemes?

Egy lehetséges megoldás

A Deactivate és az Activated esemény pontosan ezt a váltást jelzi. A háttérben felesleges frissíteni (energiát és processzoridőt spórol, és a felhasználó nem is látja).

Form1.csC#
private void Form1_Activated(object sender, EventArgs e)
{
tmrOra.Start();
lblOra.Text = DateTime.Now.ToString("HH:mm:ss");
}

private void Form1_Deactivate(object sender, EventArgs e)
{
tmrOra.Stop();
}

private void tmrOra_Tick(object sender, EventArgs e)
{
lblOra.Text = DateTime.Now.ToString("HH:mm:ss");
}

3. Gyermekablak eredményének átvétele

A főablak megnyit egy NevAblak nem modális ablakot, amelyben a felhasználó beír egy nevet, és bezárja. A főablak a bezáráskor a lblNev feliratra kiírja a nevet. Hogyan adja át az adatot a gyermekablak?

Egy lehetséges megoldás

A gyermekablak egy nyilvános tulajdonsága (Nev) tárolja az eredményt. A főablak a FormClosed eseményben a gyermek (sender) tulajdonságát olvassa ki. Ha a felhasználó nem írt be semmit, ne írjon felül semmit.

NevAblak.cs és Form1.csC#
// NevAblak.cs
public string Nev { get; private set; } = "";

private void NevAblak_FormClosing(object sender, FormClosingEventArgs e)
{
Nev = txtNev.Text.Trim();
}

// Form1.cs
private void btnNev_Click(object sender, EventArgs e)
{
NevAblak ablak = new NevAblak();

ablak.FormClosed += (s, ev) =>
{
NevAblak bezart = (NevAblak)s;

if (bezart.Nev.Length > 0)
{
lblNev.Text = "Üdv, " + bezart.Nev + "!";
}
};

ablak.Show(this);
}

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 hova való?

Sorold be: (a) a lista feltöltése; (b) a beállítások mentése; (c) kilépés megerősítése; (d) a fókusz a keresőmezőre; (e) időzítő leállítása.

Megoldás megnyitása

(a) Load; (b) FormClosed; (c) FormClosing; (d) Shown; (e) FormClosed. Az elv: a Load előkészít, a Shown a látható ablakra tesz, a FormClosing dönt, a FormClosed takarít.

2. Dátum a címsorban

Az ablak címsorában induláskor jelenjen meg: Jegyzet – 2026. október 27.. Melyik eseményben állítod be?

Megoldás megnyitása

A Load eseményben (kezdőérték-beállítás). A formátum a DateTime.Today.ToString("yyyy. MMMM d.") alak.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
this.Text = "Jegyzet – " + DateTime.Today.ToString("yyyy. MMMM d.");
}

Közepes szint

3. A színválasztás megjegyzése

Az ablak háttérszínét egy cmbSzin lista választja. Az utolsó választott szín maradjon meg a következő indításkor is (Settings). Melyik két eseményt használod?

Megoldás megnyitása

A Load visszaállítja, a FormClosed menti. A beállítás típusa lehet string (a szín neve) vagy int (a lista sorszáma). A sorszám egyszerűbb, de a lista sorrendjének változására érzékeny.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
cmbSzin.Items.AddRange(new object[] { "White", "LightYellow", "LightBlue" });
cmbSzin.Text = Properties.Settings.Default.UtolsoSzin;

if (cmbSzin.SelectedIndex < 0)
{
cmbSzin.SelectedIndex = 0;
}

this.BackColor = Color.FromName(cmbSzin.Text);
}

private void cmbSzin_SelectionChangeCommitted(object sender, EventArgs e)
{
this.BackColor = Color.FromName(cmbSzin.Text);
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
Properties.Settings.Default.UtolsoSzin = cmbSzin.Text;
Properties.Settings.Default.Save();
}

4. Kilépés közben dolgozó program

Ha a program éppen hosszú műveletet végez (bool dolgozik), a bezárás előtt jelenjen meg: A művelet még tart. Biztosan kilépsz?, és csak Igen válasznál zárjon be. Mikor ne kérdezzen?

Megoldás megnyitása

A FormClosing csak felhasználói bezárásnál kérdez (UserClosing), a Windows leállításakor nem. Így a program nem akadályozza meg a rendszer leállítását.

Form1.csC#
private bool dolgozik = false;

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!dolgozik || e.CloseReason != CloseReason.UserClosing)
{
return;
}

if (MessageBox.Show("A művelet még tart. Biztosan kilépsz?", "Kilépés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
{
e.Cancel = true;
}
}

Kihívás szint

5. Az összes gyermekablak bezárása a főablakkal

A főablak FormClosing eseményében zárd be az összes nyitott gyermekablakot, de ha valamelyik megszakítja a bezárást (mentetlen adat), a főablak se záródjon be. Hogyan ellenőrzöd?

Megoldás megnyitása

Egy gyermekablak Close() hívása után ellenőrizzük, hogy az ablak még nyitva van-e (!urlap.IsDisposed). Ha igen, a gyermek megszakította a bezárást, ezért a főablak bezárását is megszakítjuk.

Form1.csC#
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
Form urlap = Application.OpenForms[i];

if (urlap == this)
{
continue;
}

urlap.Close();

if (!urlap.IsDisposed)
{
e.Cancel = true; // a gyermek megszakította a bezárást: a főablak marad
return;
}
}
}

6. Egyetlen példány indítása

Hogyan akadályozod meg, hogy a programot kétszer lehessen elindítani egyszerre? Írd le az elvet, és egy lehetséges megoldás vázát.

Megoldás megnyitása

A program indításakor egy nevesített Mutex (rendszerszintű zár) létrehozását próbáljuk. Ha a zárat már egy másik példány birtokolja, az új példány üzenet nélkül (vagy üzenettel) kilép. A kód a Program.cs Main metódusában van, az Application.Run előtt. A nevet egyedivé tedd (például a program neve és egy egyedi azonosító).

Program.csC#
static void Main()
{
bool elso;

using (System.Threading.Mutex zar = new System.Threading.Mutex(true, "Jegyzet_EgyediAzonosito_2026", out elso))
{
if (!elso)
{
MessageBox.Show("A program már fut.");
return;
}

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

Gyors önellenőrzés

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

1. Mi az ablak eseményeinek sorrendje indításkor?

Konstruktor, Load, Activated, Shown (az utolsó kettő sorrendje a környezettől függhet). Bezáráskor: FormClosing, FormClosed.

2. Mi a különbség a Load és a Shown között?

A Load az ablak megjelenése előtt fut (kezdőértékek), a Shown az első megjelenés után (az ablak már látszik).

3. Mi a különbség a FormClosing és a FormClosed között?

A FormClosing a bezárás előtt fut, megszakítható (e.Cancel). A FormClosed a bezárás után fut: ide a takarítás való.

4. Miért veszélyes a takarítást a FormClosing eseményben végezni?

Mert a bezárás megszakítható: ha a felhasználó vagy egy másik kezelő megszakítja, az ablak nyitva marad, de az erőforrásai már lezártak.

5. Hogyan jegyzed meg az ablak helyét és méretét?

A Settings felhasználói beállításaival: a FormClosed-ben mented (RestoreBounds nem normál állapotban, Save()), a Load-ban visszaállítod, ha a hely látszik valamelyik képernyőn.

6. Hogyan értesül a főablak a gyermekablak bezárásáról?

Feliratkozik a gyermek FormClosed eseményére (+=). Modális ablaknál (ShowDialog) a hívás visszatérése jelzi.

7. Miért kell visszafelé járni az Application.OpenForms gyűjteményt bezáráskor?

Mert a bezárt ablak kikerül a gyűjteményből, és az előre haladó ciklus átugrana elemeket.

Mit tudsz már?

  • Ismered az ablak életciklusának eseményeit és sorrendjét.
  • Eldöntöd, mi való a Load, a Shown, a FormClosing és a FormClosed eseménybe.
  • Megjegyzed az ablak helyét és méretét a Settings segítségével.
  • Gyermekablak bezárására reagálsz, és kezeled az összes nyitott ablakot.
  • Felismered a gyakori életciklus-hibákat.

Következő lecke: események összekapcsolása, feliratkozás, leiratkozás

Az események életciklusát a feliratkozások életciklusa egészíti ki: mikor érdemes leiratkozni, és hogyan kerülheted el a memóriaszivárgást és a kettős feliratkozást.