Grafikus C# – Windows Forms tanfolyam › 7.10 lecke Windows Forms · 7.10 · záró mini projekt 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.Mini projekt: beállítóablak több Formmal

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).
Követelmények és pontozás
| Rész | Követelmény | Pont |
|---|---|---|
| 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 / ablak | Felelőssége | Hogyan kapcsolódik? |
|---|---|---|
Beallitasok | A 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. |
BeallitasokForm | A 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. |
NevjegyForm | Program 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
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
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)
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);
}
}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
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | Első indítás (nincs mentett beállítás). | Az alapértelmezett betű és színek, nincs hiba. |
| 2 | Szö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. |
| 3 | Ctrl+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. |
| 4 | Menté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. |
| 6 | Kilépés mentetlen szöveggel; sikertelen mentés. | Megerősítés; a sikertelen mentés megakadályozza a kilépést. |
| 7 | Megnyitás nem létező vagy zárolt fájllal. | Érthető hibaüzenet, a program tovább használható. |
| 8 | A 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. |
| 9 | Eszkö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. |
| 10 | Betű és szín választása, Alkalmaz. | A szerkesztő azonnal változik, az ablak nyitva marad. |
| 11 | Rossz kontraszt (világosszürke a fehéren). | ✖ jelzés, Javítás gomb; a javítás után ✔. |
| 12 | Hibás érték (alapmappa nem létező útvonal). | ✖ üzenet, nem zár, az Általános fülre ugrik. |
| 13 | Módosítás után Mégse (és az X). | Megerősítés; Nem esetén az ablak marad; Igen esetén elvet. |
| 14 | Módosítás nélkül Mégse, Esc. | Azonnal bezár, nincs kérdés, a főablak nem változik. |
| 15 | OK. | Érvényesít, alkalmaz, ment, bezár. |
| 16 | Automatikus 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. |
| 17 | A program újraindítása. | A beállítások megmaradtak; sérült BeBetu szövegnél az alapértelmezett betű. |
| 18 | Billentyű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. A dialógus az eredeti objektumot módosította. Használd a Ok és javítás
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ó. 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 Ok és javítás
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 A gomb értéke maradjon DialogResult tulajdonsága OK.Ok és javítás
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. A mentés vagy a Ok és javítás
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. Két külön kód: a helyi menü pontjai a menüpontot nyomják meg (Ok és javítás
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. A Ok és javítás
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:
Beallitasok), a felület (Form1, BeallitasokForm) és a tárolás külön van; nincs ismétlődő kód.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 (1) a Nyelv (magyar/angol) beállítást. Mely helyeken kell módosítanod a kódot?Megoldás megnyitása
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. A Megoldás megnyitása
ShortcutKeys tulajdonságban a vessző billentyű Keys.Oemcomma.miBeallitasok.ShortcutKeys = Keys.Control | Keys.Oemcomma;
Közepes szint
3. Előre elkészített témák
A Statikus 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
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.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? A Megoldás megnyitása
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 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.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
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 ( 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 kulcs=érték sorok), az Importálás... visszatölti. Mire kell vigyáznod az importálásnál?Megoldás megnyitása
Hiba() ellenőrizze, és hibánál semmi ne változzon (előbb másolatba töltöd, utána cseréled).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. Hogy a Mégse semmit ne módosítson: az eredetit a főablak csak 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). Az ablak Hogy a funkció és az engedélyezési szabályok egyetlen helyen (a főmenü pontjában) legyenek. Egyben adja át az adatot, másolható, önmagát ellenőrzi és tárolja, ablak nélkül tesztelhető, több ablak használhatja. Csak sikeres ellenőrzés után: a gomb 1. Miért dolgozik a beállítóablak másolaton?
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?
3. Hogyan értesül a főablak az Alkalmaz gombról?
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?
6. Mi az előnye a Beallitasok osztálynak?
7. Mikor zárhat az OK 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.



