Grafikus C# – Windows Forms tanfolyam › 2.14 lecke

Windows Forms · 2.14 lecke

Mentetlen dokumentum és a bezárás megerősítése

Nincs bosszantóbb, mint az elveszett munka. Ha a felhasználó mentés nélkül zárja be a szövegszerkesztőt, a program kérdezzen rá: mented a változtatásokat? Ebben a leckében a MiniWord megjegyzi, hogy módosult-e a dokumentum, és a bezárás, a megnyitás és az új dokumentum előtt megvédi a nem mentett munkát.

  • kb. 75 perc
  • közepes szint
  • adatvédelem
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • jelzőváltozóval (mező) megjegyzed, hogy a dokumentum módosult;
  • a címsorban csillaggal jelzed a nem mentett állapotot;
  • a FormClosing esemény e.Cancel tulajdonságával megállítod a bezárást;
  • Igen–Nem–Mégse kérdést teszel fel (MessageBoxButtons.YesNoCancel);
  • a Mentes() metódust úgy alakítod át, hogy visszaadja, sikerült-e a mentés;
  • ugyanazt a védelmet használod a megnyitás és az Új dokumentum előtt is.

Mielőtt elkezded

Ehhez a leckéhez kell a MiniWord a 2.13 lecke után, a bool visszatérési értékű metódus (1.11 lecke), a mező fogalma (1.12), a DialogResult (1.7) és a Form életciklusa (0.8).

2.13 Clipboard →

0.8 Program.cs és a Form életciklusa →

Megjegyezzük, hogy módosult a dokumentum

A programnak tudnia kell, hogy az utolsó mentés óta történt-e változás. Ehhez egy bool típusú mezőt használunk (1.12 lecke), amelyet a módosításkor true, a mentéskor és a megnyitáskor false értékre állítunk. A címsorban egy csillag (*) jelzi a nem mentett állapotot, mint a legtöbb szerkesztőben:

Form1.csC#
private bool valtozott = false;

private void Modositva()
{
if (!valtozott)
{
valtozott = true;
this.Text = "MiniWord *";
}
}

private void Mentve()
{
valtozott = false;
this.Text = "MiniWord";
}

private void rtbDokumentum_TextChanged(object sender, EventArgs e)
{
lblKarakterszam.Text = $"Karakterek: {rtbDokumentum.Text.Length}";
Modositva();
}
valtozottMező: megjegyzi, van-e mentetlen módosítás. A metódusok között is megmarad.
Modositva()Beállítja a jelzőt és a címsor csillagát. Csak az első módosításkor dolgozik.
Mentve()Törli a jelzőt és a csillagot. A mentés és a megnyitás után hívjuk.
this.TextAz ablak címsora (nem a szövegdoboz szövege!).

A formázás nem biztos, hogy számít

Ha a felhasználó csak formáz (félkövér, igazítás), a szöveg nem változik, ezért a TextChanged esemény nem feltétlenül fut le. Ezért hívd meg a Modositva() metódust a formázó gombok eseménykezelőiből is (StilusValt, igazítás, betűtípus és méret). Ha a TextChanged amúgy is lefut, a duplázás nem baj, mert a metódus csak egyszer dolgozik.

Jósolj!

Megnyitsz egy RTF fájlt a LoadFile metódussal. A szöveg betöltése közben a TextChanged lefut. Milyen lesz a címsor a betöltés után, ha nem teszel semmit?

Jóslat ellenőrzése

A címsor MiniWord * lesz, mintha a dokumentum módosult volna, pedig csak megnyitottad. Ezért kell a LoadFile után meghívni a Mentve() metódust: az nullázza a jelzőt.

A Mentes() metódus válaszol: sikerült-e?

A bezárás előtt a program megkérdezi: mented? Ha a felhasználó az Igent választja, a program mentést kezd. De a mentés kudarcot is vallhat: a felhasználó a mentés ablakban a Mégse gombot nyomja, vagy a fájl nem írható. Ilyenkor a program nem zárhat be, mert elveszne a munka. Ezért a Mentes() metódusnak vissza kell adnia, hogy sikerült-e (az 1.11 leckében látott módon bool típussal):

Form1.csC#
private bool Mentes()
{
sfdMentes.Title = "Dokumentum mentése";
sfdMentes.Filter = "RTF dokumentum (*.rtf)|*.rtf";
sfdMentes.DefaultExt = "rtf";
sfdMentes.AddExtension = true;

if (sfdMentes.ShowDialog() != DialogResult.OK)
{
return false; // a felhasználó a Mégse gombot nyomta
}

try
{
rtbDokumentum.SaveFile(sfdMentes.FileName, RichTextBoxStreamType.RichText);
Mentve();
return true;
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült menteni a fájlt: " + ex.Message);
return false;
}
}
private bool Mentes()A void helyett bool: a metódus választ ad vissza.
ShowDialog() != DialogResult.OKA != azt jelenti: nem egyenlő. Ha nem az OK gombot nyomták meg, azonnal kilépünk false értékkel.
return true;Sikeres mentés: a fájl kiírva, a jelző törölve.
return false; a catch ágbanSikertelen mentés: a hibaüzenet után a metódus false értékkel tér vissza.

A gombok és menüpontok ugyanúgy hívják

A Mentes() metódus ugyanúgy hívható a gombról és a menüpontból: Mentes(); A visszatérési értéket nem kötelező felhasználni. A megerősítő kérdésnél viszont szükségünk lesz rá. (A korábbi, void típusú változatot ez a metódus váltja le.)

A FormClosing esemény és az e.Cancel

A Form bezárása előtt lefut a FormClosing esemény (0.8 lecke). Az eseménykezelő második paramétere (FormClosingEventArgs e) lehetővé teszi a bezárás megállítását: ha az e.Cancel értékét true-ra állítod, az ablak nyitva marad. Írjunk egy FolytathatE() metódust, amely eldönti, hogy mehet-e tovább a program:

Form1.csC#
private bool FolytathatE()
{
if (!valtozott)
{
return true;
}

DialogResult valasz = MessageBox.Show(
"A dokumentum módosult. Mented a változtatásokat?",
"MiniWord",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);

if (valasz == DialogResult.Yes)
{
return Mentes();
}

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

return false;
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!FolytathatE())
{
e.Cancel = true;
}
}
MessageBoxButtons.YesNoCancelHárom gomb: Igen, Nem, Mégse.
Igen (Yes)Mentés. A metódus a mentés sikerességét adja vissza: ha a mentés elmaradt, a bezárás is leáll.
Nem (No)A változtatásokat eldobja, a program mehet tovább (true).
Mégse (Cancel)A felhasználó meggondolta magát: nem megyünk tovább (false).
e.Cancel = trueMegállítja a bezárást, az ablak nyitva marad.
A Kilépés menüpontA this.Close() is lefuttatja a FormClosing eseményt, ezért a menüpontban nem kell külön kérdezni (lásd a hibavadászatot).

Jósolj!

A dokumentum módosult. Bezárod az ablakot, az Igen gombra kattintasz, a mentés ablakban viszont a Mégse gombot nyomod. Bezárul a program?

Jóslat ellenőrzése

Nem. A Mentes() false értéket ad vissza (a felhasználó megszakította), ezért a FolytathatE() is false, a FormClosing pedig e.Cancel = true értéket állít. Az ablak nyitva marad, a munka nem vész el.

Windows leállításakor ne kérdezz

Ha a számítógép leáll, a Windows bezárja a programokat. Ilyenkor a kérdés felesleges, és akadályozhatja a leállást. Az e.CloseReason megmondja az okot: a kérdést csak akkor tedd fel, ha a felhasználó zárja be az ablakot (e.CloseReason == CloseReason.UserClosing).

📷 Képernyőkép helye

Mit tegyél ide: a MiniWord „MiniWord *” címsorral, és előtte a „A dokumentum módosult. Mented a változtatásokat?” kérdés Igen, Nem és Mégse gombbal.Javasolt fájlnév: winforms-2-14-mentes-kerdes.webp · Alt szöveg: A mentetlen dokumentum bezárásakor megjelenő Igen–Nem–Mégse kérdés

Ugyanaz a védelem a megnyitásnál és az Új dokumentumnál

A nem mentett munka nem csak a bezáráskor veszhet el: egy új fájl megnyitása vagy egy üres dokumentum indítása is felülírná a szöveget. Mindkettő elé ugyanaz a FolytathatE() ellenőrzés kerül:

Form1.csC#
private void Megnyitas()
{
if (!FolytathatE())
{
return;
}

// ... a 2.2 leckében megírt megnyitás ...
// a LoadFile után:
rtbDokumentum.ClearUndo();
Mentve();
}

private void miUj_Click(object sender, EventArgs e)
{
if (FolytathatE())
{
rtbDokumentum.Clear();
rtbDokumentum.ClearUndo();
Mentve();
}
}
if (!FolytathatE()) { return; }Ha a felhasználó Mégsét választott vagy a mentés nem sikerült, a megnyitás meg sem kezdődik.
Mentve() a végénAz új vagy megnyitott dokumentum nem módosított állapotban kezdődik, ezért törli a csillagot.
rtbDokumentum.Clear()Kiüríti a szövegdobozt. Ez is kiváltja a TextChanged eseményt, ezért kell utána a Mentve().

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. Bezár, pedig a Mégse gombot választottam

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
FolytathatE();
}
Hiba és javítás

Rossz működés: a metódus visszaadott értéke nincs felhasználva, és az e.Cancel sincs beállítva, ezért az ablak mindenképpen bezárul. Helyesen: if (!FolytathatE()) { e.Cancel = true; }

2. Megnyitás után csillag a címsorban

Egy fájl megnyitása után a címsor „MiniWord *” lesz.

Ok és javítás

A LoadFile közben a TextChanged lefut, és a jelző igazra áll. A betöltés után hívd meg a Mentve() metódust.

3. Mentés nélkül is bezárul

A felhasználó az Igen gombot választja, a mentési ablakban a Mégse gombot, a program mégis bezár.

Ok és javítás

A Mentes() metódus mindig true értéket ad vissza, vagy void típusú. A mentés ablak Mégse gombjánál false kell visszatérnie: if (sfdMentes.ShowDialog() != DialogResult.OK) { return false; }

4. Kétszer kérdez

A Kilépés menüpont után a kérdés kétszer jelenik meg.

Ok és javítás

A miKilepes_Click maga is megerősítést kér, majd a this.Close() kiváltja a FormClosing eseményt, amely újra kérdez. Hagyd meg a FormClosing védelmet, és a menüpontban csak a this.Close(); maradjon.

5. A formázás után nincs csillag

Félkövérre formázol egy szót, de a címsor nem változik.

Ok és javítás

A formázás nem biztos, hogy kiváltja a TextChanged eseményt. Hívd meg a Modositva() metódust a formázó gombok eseménykezelőiből is.

Mini projekt: a MiniWord nem engedi elveszni a munkát

Építsd be a védelmet a MiniWordbe:

  1. Mező és két metódus: valtozott, Modositva(), Mentve(). A TextChanged és a formázó gombok hívják a Modositva() metódust.
  2. A Mentes() bool típusú, az 1. szakasz szerint.
  3. A FolytathatE() metódus és a Form1_FormClosing a 3. szakasz kódja szerint.
  4. A Megnyitas() és az Új menüpont a 4. szakasz szerint védett.
  5. A miKilepes_Click csak a this.Close(); sort tartalmazza.
Teszt 1Gépelj, majd zárd be az ablakot: megjelenik a kérdés. A Mégse gomb után az ablak nyitva marad.
Teszt 2Gépelj, válaszd az Igent, majd a mentés ablakban a Mégsét: az ablak nyitva marad.
Teszt 3Gépelj, válaszd a Nemet: az ablak bezárul, a változtatások elvesznek.
Teszt 4Mentsd el a dokumentumot, majd zárd be: nincs kérdés, mert nincs mentetlen változtatás.
Teszt 5Nyiss meg egy fájlt: nincs csillag a címsorban.
Teszt 6Formázz egy szót: megjelenik a csillag.
📷 Képernyőkép helye

Mit tegyél ide: két kép egymás mellett: a mentett állapotú MiniWord („MiniWord” címsor) és a módosított állapotú („MiniWord *” címsor).Javasolt fájlnév: winforms-2-14-cimsor.webp · Alt szöveg: A MiniWord címsora mentett és módosított állapotban

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. A fájl neve a címsorban

A címsor mutassa a megnyitott vagy elmentett fájl nevét: MiniWord – level.rtf, módosítva csillaggal.

Egy lehetséges megoldás
private string fajlNev = "Névtelen";

private void CimFrissit()
{
string cim = "MiniWord – " + fajlNev;

if (valtozott)
{
cim = cim + " *";
}

this.Text = cim;
}

A Modositva() és a Mentve() a this.Text beállítása helyett a CimFrissit() metódust hívja. A fájl nevét System.IO.Path.GetFileName(fajl) adja meg, amelyet a megnyitás és a mentés után tárolsz a fajlNev mezőben.

2. Állapot az állapotsorban

Az lblAllapot feliraton jelenjen meg: Módosítva vagy Mentve.

Egy lehetséges megoldás
// a Modositva() metódusban:
lblAllapot.Text = "Módosítva";

// a Mentve() metódusban:
lblAllapot.Text = "Mentve";

3. Windows leállítása

Úgy módosítsd a FormClosing eseménykezelőt, hogy a kérdést csak akkor tegye fel, ha a felhasználó zárja be az ablakot.

Egy lehetséges megoldás
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason != CloseReason.UserClosing)
{
return;
}

if (!FolytathatE())
{
e.Cancel = true;
}
}

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A feladatok a MiniWord mentetlen munka elleni védelmét gyakoroltatják. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. A módosítás jelzése

Készíts egy valtozott jelzőt, amely igaz lesz, ha a szöveg módosult. A címsorban jelenjen meg ilyenkor egy csillag (MiniWord *).

Megoldás megnyitása

A jelző mezőben él (a metódusok között is megmarad). A TextChanged állítja igazra, és a címsort is frissíti.

Form1.csC#
private bool valtozott = false;

private void rtbDokumentum_TextChanged(object sender, EventArgs e)
{
if (!valtozott)
{
valtozott = true;
this.Text = "MiniWord *";
}
}

2. A bezárás megállítása

Ha a dokumentum módosult, a bezárás előtt jelenjen meg egy kérdés: Biztosan kilépsz mentés nélkül? Nem esetén az ablak maradjon nyitva.

Megoldás megnyitása

A FormClosing esemény a bezárás előtt fut. Az e.Cancel = true megállítja a bezárást.

Form1.csC#
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!valtozott)
{
return;
}

DialogResult valasz = MessageBox.Show("Biztosan kilépsz mentés nélkül?", "MiniWord", MessageBoxButtons.YesNo, MessageBoxIcon.Question);

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

Közepes szint

3. Igen–Nem–Mégse

Alakítsd át a kérdést háromgombossá: Mented a változtatásokat? Igen: mentés, Nem: bezárás mentés nélkül, Mégse: maradás. A mentés sikertelensége esetén az ablak maradjon nyitva.

Megoldás megnyitása

A Mentes() bool értéket ad vissza: igaz, ha a mentés sikerült. Csak ekkor engedjük a bezárást. A Mégse és a Nem gomb ágai eltérnek: a Nem a változtatásokat eldobja, a Mégse megállítja a bezárást.

Form1.csC#
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!valtozott)
{
return;
}

DialogResult valasz = MessageBox.Show("Mented a változtatásokat?", "MiniWord", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

if (valasz == DialogResult.Cancel)
{
e.Cancel = true;
}
else if (valasz == DialogResult.Yes)
{
if (!Mentes())
{
e.Cancel = true;
}
}
}

4. Védett Új dokumentum

Az Új menüpont törölje a szöveget, de módosított dokumentumnál előbb kérdezzen rá a mentésre ugyanazzal a logikával, mint a bezárásnál.

Megoldás megnyitása

A kérdés logikáját külön metódusba tesszük (FolytathatE()), amelyet a bezárás és az Új menüpont is hív. Igaz értéket ad, ha folytathatjuk (nincs mit menteni, mentettünk, vagy eldobtuk a változtatásokat).

Form1.csC#
private bool FolytathatE()
{
if (!valtozott)
{
return true;
}

DialogResult valasz = MessageBox.Show("Mented a változtatásokat?", "MiniWord", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

if (valasz == DialogResult.Yes)
{
return Mentes();
}

return valasz == DialogResult.No;
}

private void miUj_Click(object sender, EventArgs e)
{
if (FolytathatE())
{
rtbDokumentum.Clear();
valtozott = false;
this.Text = "MiniWord";
}
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!FolytathatE())
{
e.Cancel = true;
}
}

Kihívás szint

5. Automatikus mentés

Egy tmrAutoMentes Timer 30 másodpercenként mentse a módosított dokumentumot egy ideiglenes fájlba (a felhasználó fájlját ne írja felül). Hogyan és hová?

Megoldás megnyitása

Az automatikus mentés biztonsági másolat: a felhasználó saját fájlját nem írja felül, hanem az ideiglenes mappába ment (Path.GetTempPath()). A Timer Interval értéke 30000 (ezredmásodperc). Hiba esetén csendben kihagyja a mentést, nem zavarja a felhasználót hibaüzenettel.

Form1.csC#
private void tmrAutoMentes_Tick(object sender, EventArgs e)
{
if (!valtozott)
{
return;
}

try
{
string utvonal = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "miniword_biztonsagi.rtf");
rtbDokumentum.SaveFile(utvonal, RichTextBoxStreamType.RichText);
}
catch (Exception)
{
// a biztonsági mentés sikertelensége nem zavarja a munkát
}
}

6. Üres dokumentumnál nincs kérdés

Ha a dokumentum üres (nincs benne szöveg), a bezáráskor ne kérdezzen a program akkor sem, ha a jelző szerint módosult (például beírt, majd mindent kitörölt). Hogyan módosítod a FolytathatE() metódust?

Megoldás megnyitása

A feltételt kiegészítjük egy második vizsgálattal: ha nincs módosítás, vagy a szöveg üres, nincs mit menteni. Ez a tervezői döntés a program viselkedését egyszerűsíti. Figyelem: formázott üres dokumentumnál ezzel elvész a beállított formázás, ezért vitatható döntés.

Form1.csC#
private bool FolytathatE()
{
if (!valtozott || rtbDokumentum.TextLength == 0)
{
return true;
}

DialogResult valasz = MessageBox.Show("Mented a változtatásokat?", "MiniWord", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

if (valasz == DialogResult.Yes)
{
return Mentes();
}

return valasz == DialogResult.No;
}

Gyors önellenőrzés

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

1. Mire való a valtozott mező?

Megjegyzi, hogy az utolsó mentés óta módosult-e a dokumentum.

2. Mit csinál az e.Cancel = true?

Megállítja a bezárást: az ablak nyitva marad.

3. Miért kell a Mentes() metódusnak bool értéket visszaadnia?

Hogy a hívó tudja, sikerült-e a mentés. Ha nem, a bezárás nem folytatódhat.

4. Mit jelent az Igen, a Nem és a Mégse a FolytathatE() metódusban?

Igen: mentés (a mentés eredményét adja vissza). Nem: a változtatások eldobása, mehet tovább. Mégse: ne menjen tovább.

5. Miért kell a Mentve() hívás a LoadFile után?

Mert a betöltés kiváltja a TextChanged eseményt, és enélkül a frissen megnyitott dokumentum is módosítottnak látszana.

6. Miért nem kell a Kilépés menüpontban külön megerősítés?

Mert a this.Close() kiváltja a FormClosing eseményt, amely már elvégzi az ellenőrzést.

Mit tudsz már?

  • Jelzővel követed a mentetlen módosítást.
  • A FormClosing eseményben megállítod a bezárást.
  • Igen–Nem–Mégse kérdést teszel fel és kezelsz.
  • A Mentes() metódus válaszol, hogy sikerült-e.
  • Ugyanazt a védelmet használod a megnyitásnál és az Új dokumentumnál.

Következő lecke: nyomtatás és nyomtatási előnézet

A munka védett. A következő leckében a MiniWord dokumentumát ki is tudjuk nyomtatni.