Grafikus C# – Windows Forms tanfolyam › 7.10 lecke

Windows Forms · 7.10 · záró mini projekt

Mini projekt: beállítóablak több Formmal

A 7. fejezet záró projektjében egy valódi alkalmazás keretét építed ki: egy jegyzetkezelő, amelynek van menüje és eszköztára (7.1), helyi menüje (7.2), állapotsora (7.3), felugró üzenetei (7.4), több ablaka (7.5–7.6), beállítóablaka másolattal dolgozó adatátadással (7.7), fájl- és mappaválasztói (7.8) és betű-, színválasztói (7.9). A beállítások az újraindítás után is megmaradnak. Ez egy olyan szerkezet, amelyet a saját iskolai vagy hobbiprogramjaidnál is újra felhasználhatsz.

  • kb. 5–6 óra
  • haladó szint
  • záró projekt
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A projekt végére

  • több ablakból álló programot tervezel meg, és az ablakok felelősségét elkülönítöd;
  • a beállításokat adatosztályban (Beallitasok) kezeled, másolattal és érvényesítéssel;
  • a beállítóablakot OK, Alkalmaz és Mégse gombbal, megerősítéssel készíted el;
  • a menüt, az eszköztárat és a helyi menüt közös kódra kötöd;
  • a beállításokat szövegként tárolod, hibatűrően olvasod vissza;
  • az egész programot teszteled és hozzáférhetővé teszed.

Mielőtt elkezded

Ehhez a projekthez a 7.1–7.9 lecke szükséges (és a 6.9 lecke Hozzaferhetoseg.Kontraszt metódusa). Tarts nyitva mellé a korábbi leckéket: a feladat az összerakás. Új Windows Forms projektben vedd fel a Settings.settings fájlba a következő User hatókörű szöveges beállításokat: BeBetu, BeSzoveg, BeHatter, BeAlapMappa (mind string), BeSorTores (bool, alap: True), BeAutoMentes (int, alap: 0).

7.9 FontDialog és ColorDialog →

Követelmények és pontozás

RészKövetelményPont
Főablak (30)Menü (Fájl, Szerkesztés, Eszközök, Súgó) gyorsbillentyűkkel; a Fájl menüben Új, Megnyitás, Mentés, Mentés másként, Kilépés; a Szerkesztés menü állapota a kijelöléshez és a vágólaphoz igazodik (DropDownOpening).12
Helyi menü a szerkesztőben (Kivágás, Másolás, Beillesztés, Mindent kijelöl, Beállítások...), az Opening eseményben igazítva.8
Állapotsor: üzenet (ideiglenes), sor és oszlop, módosítás jelzés (● Módosítva / ✔ Mentve); az ablak címe a fájlnevet és a módosítást mutatja.10
Fájlkezelés (20)Megnyitás és mentés OpenFileDialog, SaveFileDialog segítségével (szűrők, DefaultExt, OverwritePrompt), az alapmappából indulva.8
Mentetlen változtatás kezelése (YesNoCancel) új dokumentumnál, megnyitásnál és kilépésnél; a sikertelen mentés megakadályozza a folytatást.8
Fájlhibák érthető üzenettel (MessageBox, Error ikon).4
Beállítások (35)Beallitasok adatosztály (Masol, Hiba, Betolt, Ment); az ablak másolaton dolgozik.10
BeallitasokForm: két fül (Általános: automatikus mentés percben, sortörés, alapmappa tallózással; Megjelenés: betű, szövegszín, háttérszín, minta, kontraszt-jelzés), OK, Alkalmaz, Mégse (megerősítéssel, ha van módosítás), Alaphelyzet.15
Az Alkalmaz esemény a főablakon azonnal érvényesíti; az automatikus mentés időzítővel működik; a beállítások tartósak (szövegként, hibatűrően betöltve).10
Névjegy és minőség (15)NevjegyForm (modális, párbeszédablak-szabályok); érvényesítés és ErrorProvider/üzenet; közös kezelők, nincs ismétlődő kód; hozzáférhetőség (AccessibleName, kontraszt jelzése jellel és szöveggel, billentyűzetes használat).15

Összesen: 100 pont.

A program szerkezete

Osztály / ablakFelelősségeHogyan kapcsolódik?
BeallitasokA beállítások adata, érvényesítés, szöveges tárolás (Settings).Nincs felülete; a főablak és a beállítóablak is használja.
Form1 (főablak)Szerkesztő, menü, eszköztár, helyi menü, állapotsor, fájlműveletek, a beállítások alkalmazása.Megnyitja a BeallitasokForm és a NevjegyForm ablakot.
BeallitasokFormA beállítások szerkesztése másolaton; ellenőrzés; Alkalmaz esemény.Kapja a Beallitasok másolatát (Adat tulajdonság); OK esetén visszaadja; az Alkalmaz eseményt kiváltja.
NevjegyFormProgram neve, verzió, szerző.Modális, paraméter nélkül.

Gondold végig!

Miért nem a főablak (Form1) tárolja a beállítások adatát külön mezőkben (betu, szin, sortores…), és miért jobb egy Beallitasok osztály?

Egy lehetséges válasz

Az osztály egyben adja át az adatot (nem kell tíz paraméter), másolható (Masol, a Mégse miatt), önmaga tudja ellenőrizni és tárolni magát, és több ablak is használhatja anélkül, hogy a főablak belső szerkezetét ismernék. Ez az első lépés a felület és az adat szétválasztása felé (12. fejezet).

Építés lépésenként

1. lépés: a főablak és a fájlműveletek

Feladat: MenuStrip, RichTextBox (Dock = Fill, leghátul), StatusStrip. A Fájl menü funkciói a 7.1 és 7.8 lecke szerint, a mentetlen változtatás kezelése a 7.4 szerint.

Támpont: BezarhatoE() metódus (YesNoCancel), a mentés bool eredménnyel; a FormClosing is ezt használja.

2. lépés: állapotsor és helyi menü

Feladat: Uzen(szöveg) ideiglenes üzenetekhez, sor és oszlop a SelectionChanged eseményben, módosítás jelzés. Helyi menü a szerkesztőhöz, az Opening eseményben igazítva.

Támpont: 7.2 és 7.3 lecke.

3. lépés: a Beallitasok osztály

Feladat: mezők (Betu, Szoveg, Hatter, SorTores, AutoMentesPerc, AlapMappa), Masol(), Hiba(), statikus Betolt() és Ment() (szöveges Settings, hibatűrő olvasás).

Támpont: 7.7 (másolat) és 7.9 (FontConverter, ColorTranslator) lecke.

4. lépés: a beállítóablak váza

Feladat: új BeallitasokForm (7.6 szabályai), TabControl két füllel, a vezérlők a mezőkhöz kötve (Betolt(), Mezokbol()), Adat tulajdonság másolattal.

Támpont: a betoltes jelző (5.5 lecke), TableLayoutPanel a füleken (6.4 lecke).

5. lépés: betű, színek, kontraszt, tallózás

Feladat: FontDialog, ColorDialog az előnézeti címkén, kontraszt-jelzés (✔/✖) és Javítás; az alapmappa tallózása FolderBrowserDialog-gal.

Támpont: 7.8 és 7.9 lecke, 6.9 lecke kontrasztszámolása.

6. lépés: OK, Alkalmaz, Mégse

Feladat: az Alkalmaz és OK ellenőriz (Hiba()), az Alkalmaz eseményt vált ki; a Mégse módosítás esetén megerősítést kér (FormClosing); az Alaphelyzet visszaállítja az alapértékeket.

Támpont: 6.7 és 7.4 lecke: a gomb DialogResult értéke None, a kód zár.

7. lépés: a főablak és a beállítóablak összekötése

Feladat: az Eszközök → Beállítások... (Ctrl+,) és a helyi menü megnyitja az ablakot; az Alkalmaz esemény és az OK eredmény érvényesíti és menti a beállításokat; az automatikus mentés időzítője.

Támpont: 7.5 és 7.7 lecke; PerformClick az automatikus mentéshez.

8. lépés: névjegy, hozzáférhetőség, utómunka

Feladat: NevjegyForm; AccessibleName a címke nélküli elemeken, logikus tabulátorsorrend, hibák jellel és szöveggel; a teljes tesztlista végigjátszása.

Támpont: 6.6 és 6.9 lecke.

Mintamegoldás

Előbb próbáld meg önállóan!

A mintamegoldás csak az ellenőrzésre való: a felület kialakítása a tiéd. Itt a logikát adom meg (a tervező által készített vezérlők a leírás szerint), a hosszabb, ismétlődő részeket (menüpont-kezelők) rövidítve.

Megoldás: Beallitasok.cs
Beallitasok.csC#
using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;

public class Beallitasok
{
public Font Betu = SystemFonts.MessageBoxFont;
public Color Szoveg = SystemColors.WindowText;
public Color Hatter = SystemColors.Window;
public bool SorTores = true;
public int AutoMentesPerc = 0; // 0: ki
public string AlapMappa = "";

public Beallitasok Masol()
{
Beallitasok m = (Beallitasok)this.MemberwiseClone();
m.Betu = (Font)this.Betu.Clone(); // a Font is hivatkozás: önálló példányt adunk
return m;
}

public string Hiba()
{
if (AutoMentesPerc < 0 || AutoMentesPerc > 60)
{
return "Az automatikus mentés 1 és 60 perc között állítható.";
}

if (AlapMappa.Length > 0 && !Directory.Exists(AlapMappa))
{
return "Az alapmappa nem létezik.";
}

return "";
}

public static Beallitasok Betolt()
{
Beallitasok b = new Beallitasok();
Properties.Settings s = Properties.Settings.Default;

try
{
if (!string.IsNullOrWhiteSpace(s.BeBetu))
{
Font f = TypeDescriptor.GetConverter(typeof(Font)).ConvertFromInvariantString(s.BeBetu) as Font;

if (f != null) { b.Betu = f; }
}

if (!string.IsNullOrWhiteSpace(s.BeSzoveg)) { b.Szoveg = ColorTranslator.FromHtml(s.BeSzoveg); }
if (!string.IsNullOrWhiteSpace(s.BeHatter)) { b.Hatter = ColorTranslator.FromHtml(s.BeHatter); }
}
catch (Exception)
{
// sérült szöveg: az alapértékek maradnak
}

b.SorTores = s.BeSorTores;
b.AutoMentesPerc = Math.Max(0, Math.Min(60, s.BeAutoMentes));
b.AlapMappa = s.BeAlapMappa ?? "";

if (b.Hiba().Length > 0)
{
b.AlapMappa = ""; // az eltűnt mappa nem akadályozza az indulást
}

return b;
}

public void Ment()
{
Properties.Settings s = Properties.Settings.Default;

s.BeBetu = TypeDescriptor.GetConverter(typeof(Font)).ConvertToInvariantString(Betu);
s.BeSzoveg = ColorTranslator.ToHtml(Szoveg);
s.BeHatter = ColorTranslator.ToHtml(Hatter);
s.BeSorTores = SorTores;
s.BeAutoMentes = AutoMentesPerc;
s.BeAlapMappa = AlapMappa;
s.Save();
}
}
Megoldás: BeallitasokForm.cs
BeallitasokForm.csC#
public class BeallitasEventArgs : EventArgs
{
public Beallitasok Adat { get; private set; }

public BeallitasEventArgs(Beallitasok adat) { Adat = adat; }
}

public partial class BeallitasokForm : Form
{
public event EventHandler<BeallitasEventArgs> Alkalmaz;

private Beallitasok adat = new Beallitasok();
private bool betoltes = false;
private bool valtozott = false;
private static int[] egyeniSzinek = new int[] { };

public BeallitasokForm()
{
InitializeComponent();
}

public Beallitasok Adat
{
get { return adat.Masol(); }
set { adat = value.Masol(); Betolt(); }
}

private void Betolt()
{
betoltes = true;

chkAuto.Checked = adat.AutoMentesPerc > 0;
nudPerc.Value = Math.Max(nudPerc.Minimum, Math.Min(nudPerc.Maximum, adat.AutoMentesPerc == 0 ? 5 : adat.AutoMentesPerc));
nudPerc.Enabled = chkAuto.Checked;
chkSortores.Checked = adat.SorTores;
txtMappa.Text = adat.AlapMappa;

MintaFrissit();
betoltes = false;
valtozott = false;
}

private Beallitasok Mezokbol()
{
Beallitasok b = adat.Masol();

b.AutoMentesPerc = chkAuto.Checked ? (int)nudPerc.Value : 0;
b.SorTores = chkSortores.Checked;
b.AlapMappa = txtMappa.Text.Trim();

return b; // a betűt és a színeket a megfelelő gombok már az adat mezőibe írták
}

private void MintaFrissit()
{
lblMinta.Font = adat.Betu;
lblMinta.ForeColor = adat.Szoveg;
lblMinta.BackColor = adat.Hatter;
lblBetuInfo.Text = adat.Betu.Name + ", " + adat.Betu.Size.ToString("0.#") + " pt";

double arany = Hozzaferhetoseg.Kontraszt(adat.Szoveg, adat.Hatter);

lblKontraszt.Text = arany >= 4.5 ? $"✔ Kontraszt: {arany:0.0} : 1" : $"✖ Kontraszt: {arany:0.0} : 1 (4,5 alatt)";
btnJavit.Visible = arany < 4.5;
}

private void Valtozas(object sender, EventArgs e)
{
if (betoltes) { return; }

nudPerc.Enabled = chkAuto.Checked;
valtozott = true;
}

private Color SzinValaszt(Color kezdo)
{
using (ColorDialog ablak = new ColorDialog())
{
ablak.Color = kezdo;
ablak.FullOpen = true;
ablak.CustomColors = egyeniSzinek;

Color eredmeny = ablak.ShowDialog(this) == DialogResult.OK ? ablak.Color : kezdo;
egyeniSzinek = ablak.CustomColors;

return eredmeny;
}
}

private void btnBetu_Click(object sender, EventArgs e)
{
using (FontDialog ablak = new FontDialog())
{
ablak.Font = adat.Betu;
ablak.ShowEffects = false;
ablak.MinSize = 8;
ablak.MaxSize = 24;

if (ablak.ShowDialog(this) == DialogResult.OK)
{
adat.Betu = ablak.Font;
valtozott = true;
MintaFrissit();
}
}
}

private void btnSzoveg_Click(object sender, EventArgs e)
{
adat.Szoveg = SzinValaszt(adat.Szoveg);
valtozott = true;
MintaFrissit();
}

private void btnHatter_Click(object sender, EventArgs e)
{
adat.Hatter = SzinValaszt(adat.Hatter);
valtozott = true;
MintaFrissit();
}

private void btnJavit_Click(object sender, EventArgs e)
{
double fekete = Hozzaferhetoseg.Kontraszt(Color.Black, adat.Hatter);
double feher = Hozzaferhetoseg.Kontraszt(Color.White, adat.Hatter);

adat.Szoveg = fekete >= feher ? Color.Black : Color.White;
valtozott = true;
MintaFrissit();
}

private void btnAlap_Click(object sender, EventArgs e)
{
adat = new Beallitasok();
Betolt();
valtozott = true;
}

private void btnTallozas_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog ablak = new FolderBrowserDialog())
{
ablak.Description = "Válaszd ki az alapmappát.";

if (Directory.Exists(txtMappa.Text))
{
ablak.SelectedPath = txtMappa.Text;
}

if (ablak.ShowDialog(this) == DialogResult.OK)
{
txtMappa.Text = ablak.SelectedPath;
}
}
}

private bool Ellenoriz()
{
Beallitasok uj = Mezokbol();
string hiba = uj.Hiba();

lblHiba.Text = hiba.Length == 0 ? "" : "✖ " + hiba;

if (hiba.Length > 0)
{
tc.SelectedTab = tabAltalanos;
txtMappa.Focus();
return false;
}

adat = uj;
return true;
}

private void btnAlkalmaz_Click(object sender, EventArgs e)
{
if (Ellenoriz())
{
Alkalmaz?.Invoke(this, new BeallitasEventArgs(adat.Masol()));
valtozott = false;
}
}

private void btnOk_Click(object sender, EventArgs e)
{
if (Ellenoriz())
{
valtozott = false;
this.DialogResult = DialogResult.OK;
}
}

private void BeallitasokForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (valtozott && this.DialogResult != DialogResult.OK && e.CloseReason == CloseReason.UserClosing)
{
if (MessageBox.Show(this, "Elveted a nem alkalmazott módosításokat?", "Beállítások", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No)
{
e.Cancel = true;
this.DialogResult = DialogResult.None;
}
}
}
}
Megoldás: Form1.cs (főablak)
Form1.cs (using System.IO;)C#
private Beallitasok beall;
private string fajl = "";
private bool modosult = false;
private const string Alap = "Kész";

private void Form1_Load(object sender, EventArgs e)
{
beall = Beallitasok.Betolt();
BeallitasAlkalmaz(beall);
ofd.Filter = sfd.Filter = "Szövegfájlok (*.txt)|*.txt|Minden fájl (*.*)|*.*";
sfd.DefaultExt = "txt";
tsslUzenet.Text = Alap;
CimFrissit();
PozFrissit();
}

private void BeallitasAlkalmaz(Beallitasok b)
{
rtbSzoveg.Font = b.Betu;
rtbSzoveg.ForeColor = b.Szoveg;
rtbSzoveg.BackColor = b.Hatter;
rtbSzoveg.WordWrap = b.SorTores;

tmrAutoMent.Stop();

if (b.AutoMentesPerc > 0)
{
tmrAutoMent.Interval = b.AutoMentesPerc * 60000;
tmrAutoMent.Start();
}

if (Directory.Exists(b.AlapMappa))
{
ofd.InitialDirectory = b.AlapMappa;
sfd.InitialDirectory = b.AlapMappa;
}
}

private void Uzen(string szoveg)
{
tsslUzenet.Text = szoveg;
tmrUzenet.Stop();
tmrUzenet.Start();
}

private void tmrUzenet_Tick(object sender, EventArgs e)
{
tmrUzenet.Stop();
tsslUzenet.Text = Alap;
}

private void CimFrissit()
{
string nev = fajl.Length == 0 ? "Névtelen" : Path.GetFileName(fajl);
this.Text = nev + (modosult ? "*" : "") + " – Jegyzetek";
tsslModosult.Text = modosult ? "● Módosítva" : "✔ Mentve";
}

private void PozFrissit()
{
int kezdet = rtbSzoveg.SelectionStart;
int sor = rtbSzoveg.GetLineFromCharIndex(kezdet) + 1;
int oszlop = kezdet - rtbSzoveg.GetFirstCharIndexOfCurrentLine() + 1;

tsslPoz.Text = $"Sor {sor}, oszlop {oszlop}";
}

private void rtbSzoveg_SelectionChanged(object sender, EventArgs e) { PozFrissit(); }

private void rtbSzoveg_TextChanged(object sender, EventArgs e)
{
if (!modosult)
{
modosult = true;
CimFrissit();
}
}

// ---- fájlok ----

private bool Ment(bool maskent)
{
string ut = fajl;

if (maskent || ut.Length == 0)
{
sfd.FileName = ut.Length == 0 ? "Névtelen" : Path.GetFileName(ut);

if (sfd.ShowDialog(this) != DialogResult.OK)
{
return false;
}

ut = sfd.FileName;
}

try
{
File.WriteAllText(ut, rtbSzoveg.Text);
fajl = ut;
modosult = false;
CimFrissit();
Uzen("Mentve: " + DateTime.Now.ToString("HH:mm:ss"));
return true;
}
catch (Exception ex)
{
MessageBox.Show(this, "A mentés nem sikerült." + Environment.NewLine + ex.Message, "Mentés", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}

private bool BezarhatoE()
{
if (!modosult)
{
return true;
}

switch (MessageBox.Show(this, "Mented a változtatásokat?", "Jegyzetek", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning))
{
case DialogResult.Yes: return Ment(false);
case DialogResult.No: return true;
default: return false;
}
}

private void miUj_Click(object sender, EventArgs e)
{
if (BezarhatoE())
{
rtbSzoveg.Clear();
fajl = "";
modosult = false;
CimFrissit();
}
}

private void miMegnyit_Click(object sender, EventArgs e)
{
if (!BezarhatoE() || ofd.ShowDialog(this) != DialogResult.OK)
{
return;
}

try
{
rtbSzoveg.Text = File.ReadAllText(ofd.FileName);
fajl = ofd.FileName;
modosult = false;
CimFrissit();
Uzen("Megnyitva: " + Path.GetFileName(fajl));
}
catch (Exception ex)
{
MessageBox.Show(this, "A fájl nem nyitható meg." + Environment.NewLine + ex.Message, "Megnyitás", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

private void miMent_Click(object sender, EventArgs e) { Ment(false); }
private void miMentMaskent_Click(object sender, EventArgs e) { Ment(true); }
private void miKilep_Click(object sender, EventArgs e) { this.Close(); }

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing && !BezarhatoE())
{
e.Cancel = true;
}
}

private void tmrAutoMent_Tick(object sender, EventArgs e)
{
if (fajl.Length > 0 && modosult)
{
Ment(false);
Uzen("Automatikus mentés: " + DateTime.Now.ToString("HH:mm:ss"));
}
}

// ---- szerkesztés, helyi menü ----

private void miSzerkeszt_DropDownOpening(object sender, EventArgs e)
{
bool kijeloles = rtbSzoveg.SelectionLength > 0;

miKivag.Enabled = kijeloles;
miMasol.Enabled = kijeloles;
miBeillesztes.Enabled = Clipboard.ContainsText();
}

private void cmsSzoveg_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
bool kijeloles = rtbSzoveg.SelectionLength > 0;

cmKivag.Enabled = kijeloles;
cmMasol.Enabled = kijeloles;
cmBeillesztes.Enabled = Clipboard.ContainsText();
}

private void miKivag_Click(object sender, EventArgs e) { rtbSzoveg.Cut(); }
private void miMasol_Click(object sender, EventArgs e) { rtbSzoveg.Copy(); }
private void miBeillesztes_Click(object sender, EventArgs e) { rtbSzoveg.Paste(); }
private void miMindent_Click(object sender, EventArgs e) { rtbSzoveg.SelectAll(); }
private void cmKivag_Click(object sender, EventArgs e) { miKivag.PerformClick(); }
private void cmMasol_Click(object sender, EventArgs e) { miMasol.PerformClick(); }
private void cmBeillesztes_Click(object sender, EventArgs e) { miBeillesztes.PerformClick(); }
private void cmMindent_Click(object sender, EventArgs e) { miMindent.PerformClick(); }
private void cmBeallitasok_Click(object sender, EventArgs e) { miBeallitasok.PerformClick(); }

// ---- ablakok ----

private void miBeallitasok_Click(object sender, EventArgs e)
{
using (BeallitasokForm ablak = new BeallitasokForm())
{
ablak.Adat = beall;

ablak.Alkalmaz += (s, ev) =>
{
beall = ev.Adat;
beall.Ment();
BeallitasAlkalmaz(beall);
Uzen("A beállítások alkalmazva.");
};

if (ablak.ShowDialog(this) == DialogResult.OK)
{
beall = ablak.Adat;
beall.Ment();
BeallitasAlkalmaz(beall);
Uzen("A beállítások elmentve.");
}
}
}

private void miNevjegy_Click(object sender, EventArgs e)
{
using (NevjegyForm ablak = new NevjegyForm())
{
ablak.ShowDialog(this);
}
}
📷 Képernyőkép helye

Mit tegyél ide: a főablak és előtte a Beállítások ablak a Megjelenés fülön, a kontraszt-jelzéssel és az Alkalmaz gombbal.Javasolt fájlnév: winforms-7-10-kesz.webp · Alt szöveg: A jegyzetkezelő és a beállítóablak

Tesztlista

#TesztVárt eredmény
1Első indítás (nincs mentett beállítás).Az alapértelmezett betű és színek, nincs hiba.
2Szöveg beírása, a cím és az állapotsor.A cím Névtelen* – Jegyzetek, az állapotsor ● Módosítva, a sor és az oszlop frissül.
3Ctrl+S új dokumentumnál.Mentés másként ablak, .txt kiterjesztés, felülírás kérdés létező fájlnál.
4Mentés után.✔ Mentve, a cím a fájlnévvel, ideiglenes „Mentve: ideje” üzenet, 4 másodperc múlva Kész.
5Új dokumentum mentetlen szöveggel: Igen, Nem, Mégse.Mentés + új, elvetés + új, maradás.
6Kilépés mentetlen szöveggel; sikertelen mentés.Megerősítés; a sikertelen mentés megakadályozza a kilépést.
7Megnyitás nem létező vagy zárolt fájllal.Érthető hibaüzenet, a program tovább használható.
8A Szerkesztés menü és a helyi menü kijelöléssel és anélkül; üres vágólap.A pontok engedélyezése az állapotot követi.
9Eszközök → Beállítások... és Ctrl+,, helyi menü Beállítások....A beállítóablak a főablak közepén nyílik, modális.
10Betű és szín választása, Alkalmaz.A szerkesztő azonnal változik, az ablak nyitva marad.
11Rossz kontraszt (világosszürke a fehéren).✖ jelzés, Javítás gomb; a javítás után ✔.
12Hibás érték (alapmappa nem létező útvonal).✖ üzenet, nem zár, az Általános fülre ugrik.
13Módosítás után Mégse (és az X).Megerősítés; Nem esetén az ablak marad; Igen esetén elvet.
14Módosítás nélkül Mégse, Esc.Azonnal bezár, nincs kérdés, a főablak nem változik.
15OK.Érvényesít, alkalmaz, ment, bezár.
16Automatikus mentés: 1 perc, módosítás, várakozás.A fájl mentődik, az állapotsor üzen; névtelen dokumentumnál nem ment.
17A program újraindítása.A beállítások megmaradtak; sérült BeBetu szövegnél az alapértelmezett betű.
18Billentyűzet- és 150%-os betűméret-teszt.Minden elérhető, logikus Tab sorrend, a beállítóablak rendezett.

Gyakori hibák és önértékelés

Ezeken szoktak elcsúszni

Gondold végig, nálad előfordul-e valamelyik.

1. A Mégse is módosítja a főablakot

A beállítóablakban átállítottad a betűt, a Mégse után az új betű marad.

Ok és javítás

A dialógus az eredeti objektumot módosította. Használd a Masol() másolatot a set-ben és a get-ben (7.7 lecke). A Font is hivatkozás típus: a Masol() metódus Clone() hívással önálló példányt ad.

2. Az Alkalmaz után a Mégse visszavonja a változtatást

A felhasználó azt várja, hogy az Alkalmaz maradandó.

Ok és javítás

Az Alkalmaz a főablakban érvényesíti (és menti) a beállítást: a későbbi Mégse csak a további változtatásokat veti el. A valtozott jelzőt az Alkalmaz nullázza, hogy a Mégse ne kérdezzen feleslegesen.

3. Az OK gomb hibás adatnál is bezár

Az OK gomb DialogResult tulajdonsága OK.

Ok és javítás

A gomb értéke maradjon None, a kód állítsa be this.DialogResult = DialogResult.OK; csak az ellenőrzés után (6.7 lecke).

4. Az újraindítás nem tölti vissza a betűt

Az indítás hibával áll le, vagy az alapbetű tér vissza.

Ok és javítás

A mentés vagy a Save() hiányzik, illetve a betöltés kivételt dob. A betöltés legyen try–catch-csel védett, az üres szöveg külön kezelve, tartalék értékkel (7.9 lecke).

5. A menü és a helyi menü kódja eltér

A menü Kivágás pontja letiltott, a helyi menüé nem.

Ok és javítás

Két külön kód: a helyi menü pontjai a menüpontot nyomják meg (PerformClick), az állapotot mindkét menü Opening eseménye frissíti.

6. A beállítóablak az automatikus mentés idejét tartomány nélkül fogadja

Nulla vagy 999 perc is elfogadott.

Ok és javítás

A NumericUpDown tartománya (1–60), és a Beallitasok.Hiba() is ellenőrizzen (az adat kézzel is sérülhet a Settings fájlban).

Az értékeléshez ellenőrizd az alábbi szempontokat is:

MűködésA tesztlista mind a 18 pontja teljesül.
SzerkezetAz adat (Beallitasok), a felület (Form1, BeallitasokForm) és a tárolás külön van; nincs ismétlődő kód.
Biztonságos viselkedésMégse, hibás beállítás, sérült fájl, hiányzó mappa: a program mindig használható marad.
HozzáférhetőségLogikus billentyűzetes használat, kontraszt jelzése jellel és szöveggel, AccessibleName, betűméret-változásnál rendezett felület.

Bővítési ötletek, ha kész vagy

Ha maradt időd, próbáld ki:

  • Keresés és csere: a 7.6 lecke ablaka beépítve (Ctrl+F).
  • Több dokumentum: füles szerkesztő (6.5 lecke) és az utolsó fájlok listája (7.1 lecke).
  • Téma: világos és sötét előre elkészített téma a beállítóablakban egy legördülő listából.
  • Nyomtatás: nyomtatási előnézet (2.15 lecke).
  • Ablakméret és -hely megjegyzése: a Settings-ben (5.6 lecke).
  • Exportálás: a beállítások mentése és betöltése fájlból (JSON, 10. fejezet).

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A kész programot bővíted. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Új beállítás felvétele

Vegyél fel egy Nyelv (magyar/angol) beállítást. Mely helyeken kell módosítanod a kódot?

Megoldás megnyitása

(1) a Settings.settings fájlban új beállítás; (2) a Beallitasok osztályban új mező, Betolt és Ment; (3) a BeallitasokFormban a vezérlő, a Betolt/Mezokbol metódus; (4) a főablakban az alkalmazás. A Masol a MemberwiseClone miatt változatlan.

2. Gyorsbillentyű a beállításokhoz

Állítsd be a Beállítások... menüponthoz a Ctrl+, billentyűt.

Megoldás megnyitása

A ShortcutKeys tulajdonságban a vessző billentyű Keys.Oemcomma.

Form1.csC#
miBeallitasok.ShortcutKeys = Keys.Control | Keys.Oemcomma;

Közepes szint

3. Előre elkészített témák

A cmbTema legördülő listából (Világos, Sötét, Sárga papír) a témák a betű nélküli színpárokat állítják be. Hol tárolod a színpárokat?

Megoldás megnyitása

Statikus Color[] tömbökben vagy egy kis Tema osztályban. A választás az adat mezőit írja, és az előnézetet frissíti. A kontraszt-ellenőrzés mindegyikre elvégezhető egy tesztben.

BeallitasokForm.csC#
private readonly Color[][] temak =
{
new Color[] { Color.Black, Color.White },
new Color[] { Color.FromArgb(230, 230, 230), Color.FromArgb(30, 30, 36) },
new Color[] { Color.FromArgb(50, 40, 20), Color.FromArgb(250, 240, 200) }
};

private void cmbTema_SelectionChangeCommitted(object sender, EventArgs e)
{
adat.Szoveg = temak[cmbTema.SelectedIndex][0];
adat.Hatter = temak[cmbTema.SelectedIndex][1];
valtozott = true;
MintaFrissit();
}

4. Az ablak helye és mérete

A főablak mérete és helye maradjon meg az újraindítás után (5.6 lecke). Mit kell figyelned kicsinyített és maximalizált állapotnál, több monitornál?

Megoldás megnyitása

A RestoreBounds értékét mentsd (az igazi méret, ha az ablak kicsinyített vagy maximalizált), a betöltésnél ellenőrizd, hogy a hely látható-e valamelyik monitoron (Screen.AllScreens), különben középre tedd.

Kihívás szint

5. Egység tesztek az érvényesítéshez

Írj tesztmetódust, amely a Beallitasok.Hiba() metódust öt esetre ellenőrzi (érvényes, 0 perc, 61 perc, nem létező mappa, üres mappa). Mi az előnye annak, hogy a Hiba() az adatosztályban van?

Megoldás megnyitása

Az adatosztály ablak nélkül tesztelhető (nincs felület), ezért a szabályok gyorsan és ismételhetően ellenőrizhetők. Ez a felület és az adat szétválasztásának egyik legnagyobb haszna.

Teszt.csC#
public static void HibaTesztek()
{
Beallitasok b = new Beallitasok();

b.AutoMentesPerc = 10;
System.Diagnostics.Debug.Assert(b.Hiba() == "", "érvényes");

b.AutoMentesPerc = 61;
System.Diagnostics.Debug.Assert(b.Hiba() != "", "61 perc");

b.AutoMentesPerc = 5;
b.AlapMappa = @"C:\nincs\ilyen\mappa";
System.Diagnostics.Debug.Assert(b.Hiba() != "", "nem létező mappa");

b.AlapMappa = "";
System.Diagnostics.Debug.Assert(b.Hiba() == "", "üres mappa elfogadott");
}

6. Beállítások exportálása

Az Exportálás... gomb a beállításokat szövegfájlba menti (kulcs=érték sorok), az Importálás... visszatölti. Mire kell vigyáznod az importálásnál?

Megoldás megnyitása

Az importált fájl bármi lehet: minden sort védetten dolgozz fel (hiányzó kulcs, hibás érték), az eredményt a Hiba() ellenőrizze, és hibánál semmi ne változzon (előbb másolatba töltöd, utána cseréled).

BeallitasokForm.csC#
private Beallitasok Importal(string utvonal)
{
Beallitasok b = adat.Masol();

foreach (string sor in File.ReadAllLines(utvonal))
{
string[] resz = sor.Split(new char[] { '=' }, 2);

if (resz.Length != 2) { continue; }

int szam;

if (resz[0] == "AutoMentes" && int.TryParse(resz[1], out szam)) { b.AutoMentesPerc = szam; }
else if (resz[0] == "SorTores") { b.SorTores = resz[1] == "1"; }
else if (resz[0] == "AlapMappa") { b.AlapMappa = resz[1]; }
}

return b.Hiba().Length == 0 ? b : null; // hibás fájl: nem változik semmi
}

Gyors önellenőrzés

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

1. Miért dolgozik a beállítóablak másolaton?

Hogy a Mégse semmit ne módosítson: az eredetit a főablak csak OK vagy Alkalmaz esetén cseréli le.

2. Mi a különbség az OK, az Alkalmaz és a Mégse között?

Az OK alkalmaz és zár, az Alkalmaz alkalmaz és nyitva marad, a Mégse a nem alkalmazott változtatást elveti (megerősítéssel).

3. Hogyan értesül a főablak az Alkalmaz gombról?

Az ablak Alkalmaz eseményt vált ki a beállítások másolatával; a főablak feliratkozik rá.

4. Mi a hibatűrő beállítás-betöltés lényege?

try–catch és tartalék érték: sérült vagy hiányzó szöveg sem akadályozhatja az indulást.

5. Miért PerformClick a helyi menü pontjaiban?

Hogy a funkció és az engedélyezési szabályok egyetlen helyen (a főmenü pontjában) legyenek.

6. Mi az előnye a Beallitasok osztálynak?

Egyben adja át az adatot, másolható, önmagát ellenőrzi és tárolja, ablak nélkül tesztelhető, több ablak használhatja.

7. Mikor zárhat az OK gomb?

Csak sikeres ellenőrzés után: a gomb DialogResult értéke None, a kód állítja be az OK értéket.

Mit tudsz már? (a 7. fejezet végén)

  • Menüt, eszköztárat, helyi menüt és állapotsort építesz közös kódra.
  • Felugró üzeneteket és saját párbeszédablakokat használsz, a modalitást ismered.
  • Ablakok között adatot adsz át, a másolat és hivatkozás különbségét helyesen kezeled.
  • Fájl-, mappa-, betű- és színválasztókat használsz, és a beállításokat tartósan, hibatűrően tárolod.
  • Elkészítettél egy többablakos programot beállítóablakkal, amely a felület és az adat szétválasztása felé mutat.

Következő fejezet: listák, táblázatok és adatkötés

A 7. fejezet kész. A 8. fejezetben az összetett adatok megjelenítése és szerkesztése kerül sorra: listák, DataGridView, adatkötés, rendezés és szűrés.