Grafikus C# – Windows Forms tanfolyam › 5.6 lecke Windows Forms · 5.6 lecke · 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, Shown, FormClosing és FormClosed – az ablak életciklusa
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.
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 aShowneseménybe; - a
FormClosingeseményben megszakíthatod a bezárást, aFormClosedeseményben rendet raksz; - az ablak méretét, helyét és az utolsó adatokat megjegyzed a
Settingssegí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.
Az ablak életének eseményei
| Lépés | Mikor? | Hányszor? |
|---|---|---|
Konstruktor (InitializeComponent) | Az ablak objektum létrejön, a vezérlők felépülnek. | egyszer |
Load | Az ablak megjelenése előtt, egyszer. | egyszer |
Activated | Az ablak megkapja a fókuszt (aktívvá válik). | többször (minden aktiváláskor) |
Shown | Az ablak első megjelenése után. | egyszer |
Deactivate | Az ablak elveszíti a fókuszt (másik ablakra kattintasz). | többször |
FormClosing | A bezárás előtt: megszakítható. | egyszer (vagy többször, ha megszakítják) |
FormClosed | Az ablak már bezárult. | egyszer |
InitializeComponent után), de az ablak még nem jelent meg. A Load ugyanezt a pillanatot adja, kényelmesebben, eseményként.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 még megállíthatja a bezárást (e.Cancel), a FormClosed már csak értesít: az ablak nem menthető meg.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:
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?
| Feladat | Melyik esemény? | Miért? |
|---|---|---|
| Mezők kezdőértékei, listák feltöltése, a program állapotának előkészítése | Load | Minden 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) | Load | Az ablak a megjegyzett helyen jelenik meg, és nem ugrik át. |
| Lassú munka (nagy fájl, adatbázis, hálózat) | Shown | Az ablak előbb megjelenik (például „Betöltés…” felirattal), nem áll fehéren. |
Fókusz egy mezőre (txtNev.Focus()) | Shown | A megjelenés után a fókusz beállítható. |
| Újrafrissítés, amikor az ablakra visszatér a felhasználó | Activated | Minden aktiváláskor lefut. |
| Időzítő szüneteltetése, ha az ablak háttérbe kerül | Deactivate / Activated | Nem fogyaszt feleslegesen erőforrást. |
| Mentetlen adat védelme, kilépés megerősítése | FormClosing | Megszakí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ása | FormClosed | A bezárás már biztos, nem szakítható meg. |
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.
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
}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.FormClosing ilyenkor is lefut, és megszakíthatja a kilépést.FormClosing és a FormClosed nem fut, az ablak él tovább, és később újra mutatható (Show()).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:
- A Solution Explorerben: Properties → Settings.settings (dupla kattintás).
- 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). - A kódban a
Properties.Settings.Default.Szelessegalakkal éred el, a mentést aSave()végzi.
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
}Szelesseg, UtolsoSzoveg), a típusa a táblázatban megadott.user.config fájl). A mentés nélküli módosítás a program leállásakor elvész.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:
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() 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.IsDisposed ellenőrzés egyetlen példányra korlátoz.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.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. A lassú munka (adatbázis, fájl) a Ok és javítás
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 Ha a Load kezelőben lévő kód egy része sosem hajtódik végre, hiba nélkül.Ok és javítás
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ányzikHiba é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. A Ok és javítás
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. 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 (Ok és javítás
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:
- 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). - Beállítások (Settings):
Szelesseg,Magassag,BalX,FelsoY(int),UtolsoSzoveg(string). 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. AShownegy „Kész.” feliratot ír, és a fókuszt a jegyzetre teszi.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.FormClosed: a beállítások mentése (aRestoreBoundsalkalmazásával).- A
btnReszletekegyetlenStatAblakpéldányt nyit (Show(this)), a főablak pedig a gyermekFormClosedeseményére iratkozik fel, és a bezárás után kiír egy üzenetet alblAllapotfeliratra. - Az életciklus eseményei (
Load,Shown,Activated,Deactivate,FormClosing) azlstNaplo-ban látszanak. - 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.
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);
}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 Egy 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
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.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 A 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
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).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 A gyermekablak egy nyilvános tulajdonsága (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
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
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. (a) Megoldás megnyitása
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? A Megoldás megnyitása
Load eseményben (kezdőérték-beállítás). A formátum a DateTime.Today.ToString("yyyy. MMMM d.") alak.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 A 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
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.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 ( A 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
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.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 Egy gyermekablak 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
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.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. 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 Megoldás megnyitása
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ó).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. Konstruktor, A A 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. A Feliratkozik a gyermek Mert a bezárt ablak kikerül a gyűjteményből, és az előre haladó ciklus átugrana elemeket.1. Mi az ablak eseményeinek sorrendje indításkor?
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?
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?
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?
5. Hogyan jegyzed meg az ablak helyét és méretét?
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?
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?
Mit tudsz már?
- Ismered az ablak életciklusának eseményeit és sorrendjét.
- Eldöntöd, mi való a
Load, aShown, aFormClosingés aFormClosedeseménybe. - Megjegyzed az ablak helyét és méretét a
Settingssegí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.




