Grafikus C# – Windows Forms tanfolyam › 6.10 lecke Windows Forms · 6.10 · záró mini projekt A 6. fejezet záró projektjében a felülettervezés minden eszközét egyetlen programban használod: egy nyári tábor jelentkezési űrlapját készíted el. Az űrlap átméretezhető (Mini projekt: átméretezhető űrlap – táborjelentkezés
Anchor, Dock, SplitContainer), rendezett (TableLayoutPanel, FlowLayoutPanel, GroupBox), több lapra osztott (TabControl), egér nélkül is kitölthető (tabulátorsorrend, mnemonikus betűk, Enter-navigáció, érvényesítés), tisztességes alapértelmezett gombokkal (AcceptButton, CancelButton), nagyobb betűmérettel is rendezett, és hozzáférhető. Tanárként akár a saját iskolai programod űrlapját is ilyenre cserélheted.
A projekt végére
- egy összetett, több tárolóból álló űrlapot tervezel meg és építesz fel;
- az elrendezést úgy készíted el, hogy átméretezésnél és nagyobb betűnél is rendezett maradjon;
- a lapok közötti lépést ellenőrzéssel kötöd (
Selecting), a mezőket érvényesíted (Validating,ErrorProvider); - a fókuszt, a tabulátorsorrendet, az Enter-t és az alapértelmezett gombokat tudatosan állítod be;
- élő összegzést és árszámítást építesz a
Tagadatokra; - a hozzáférhetőségi szempontokat (kontraszt, címkék,
AccessibleName, nem csak színnel jelzés) érvényesíted.
Mielőtt elkezded
Ehhez a projekthez a 6.1–6.9 lecke és az 5.5–5.8 lecke eszközei szükségesek. Tarts nyitva mellé egy-két korábbi leckét, itt az összerakás a feladat. Új Windows Forms projektet készíts, és vedd fel a BetuMeret (float, User, alapérték 9) beállítást a Properties → Settings.settings fájlban.
Követelmények és pontozás
A Táborjelentkezés program egy ablak, amelyen a szülő vagy a diák kitölti a jelentkezési adatokat, kiválasztja a turnust, az étkezést és a tevékenységeket, és látja az előzetes díjat.
| Rész | Követelmény | Pont |
|---|---|---|
| Elrendezés (25) | Az ablak átméretezhető: bal oldalon a TabControl (három lap: Adatok, Választások, Összegzés), jobb oldalon egy SplitContainer panel az élő kártyával és a díjjal; alul gombsor, állapotsor. | 10 |
Az Adatok lap TableLayoutPanel (címke AutoSize, mező Percent 100), a Választások lapon GroupBox-ok és egy FlowLayoutPanel. | 8 | |
A gombsor FlowLayoutPanel (jobbra igazított), a jobb oldali panel összecsukható (Panel2Collapsed). | 7 | |
| Működés (30) | Mezők: név (kötelező, legalább 3 karakter), e-mail (kötelező, érvényes alak), telefon (nem kötelező, csak számjegy, +, szóköz, -), születési dátum, osztály (kötelező, ComboBox). | 10 |
| Választások: turnus és étkezés (egy-egy kötelező választógomb-csoport), tevékenységek (jelölőnégyzetek), megjegyzés (többsoros). | 10 | |
Az előzetes díj és az élő kártya minden változásra frissül (az ár a Tag értékekből). | 10 | |
| Navigáció és ellenőrzés (25) | Logikus tabulátorsorrend, mnemonikus betűk (Alt+betű), induló fókusz a névmezőn, Enter a következő mezőre (egysoros mezőben). | 8 |
Érvényesítés Validating és ErrorProvider segítségével; az Adatok lapról csak helyes adatokkal lehet továbblépni (Selecting); AutoValidate = EnableAllowFocusChange. | 9 | |
AcceptButton (a Tovább, az utolsó lapon a Küldés), CancelButton (Mégse, CausesValidation = false); kilépés megerősítése kitöltött adatnál. | 8 | |
| Betűméret és hozzáférhetőség (20) | Betűméret-választó (normál 9 pt, nagy 12 pt, nagyon nagy 15 pt): az űrlap minden méretnél rendezett, a minimális ablakméret a tartalomhoz igazodik, a választás a Settings-ben megmarad. | 10 |
Hibajelzés színnel és jellel/szöveggel, AccessibleName a címke nélküli elemeken, a szövegszínek kontrasztja legalább 4,5 : 1, rendszerszínek használata. | 10 |
Összesen: 100 pont.
Gondold végig!
Az űrlap három lapra osztása helyesebb, mint egyetlen hosszú űrlap? Mikor nem jó a füles felület, és mikor jobb az egyoldalas űrlap görgetősávval?
Egy lehetséges válasz
A füles (vagy lépésenkénti) felület akkor jó, ha az adatok logikusan csoportokra bomlanak, és a felhasználó nem akarja egyszerre látni az egészet (hosszú űrlap, több szakasz). Nem jó, ha a lapok között gyakran kell ugrálni, vagy a mezők egymástól függnek (az egyik lapon beírt adat befolyásolja a másikat, és a felhasználó nem látja a kapcsolatot). Rövid űrlapnál (5–8 mező) az egyoldalas elrendezés gyorsabb és átláthatóbb. Itt a díj és a kártya ezért mindig látható, a jobb oldali panelen.
A felület felépítése
| Vezérlő | (Name) | Beállítás |
|---|---|---|
| Form | Form1 | Text: Táborjelentkezés; StartPosition: CenterScreen; KeyPreview nem kell (ProcessCmdKey) |
| StatusStrip | ss | Dock: Bottom; elemek: tsslAllapot (ToolStripStatusLabel), tspbKesz (ToolStripProgressBar, Maximum: 6) |
| FlowLayoutPanel | flpGombok | Dock: Bottom; FlowDirection: RightToLeft; AutoSize: True, AutoSizeMode: GrowAndShrink; Padding: 8; gombok: btnMegse (&Mégse), btnKuld (&Küldés), btnTovabb (&Tovább), btnVissza (&Vissza), btnElo (Kár&tya ki-be, a jobb oldali panel összecsukásához) |
| SplitContainer | split | Dock: Fill; FixedPanel: Panel2; Panel1MinSize: 460; Panel2MinSize: 240. Fontos: a z-sorrendben ez legyen leghátul (Send to Back) |
| TabControl | tc | a split.Panel1-ben, Dock: Fill; lapok: tabAdatok, tabValasztasok, tabOsszegzes |
| TableLayoutPanel | tlpAdatok | a tabAdatok-on, Dock: Fill, 2 oszlop (AutoSize, Percent 100); sorok (AutoSize): txtNev, txtEmail, txtTelefon, dtpSzul, cmbOsztaly (mind Dock: Fill) + címkék (Anchor = Left, & betűkkel) |
| TableLayoutPanel | tlpValasztas | a tabValasztasok-on, Dock: Fill, 2 oszlop (Percent 50, 50), 3 sor (AutoSize, AutoSize, Percent 100) |
| GroupBox ×2 | grpTurnus, grpEtkezes | RadioButton ×3 mindkettőben, Tag: ár (egész szám szövegként): turnus 42000, 45000, 48000; étkezés 12000, 8000, 0 |
| FlowLayoutPanel | flpTev | CheckBox-ok: Úszás (3000), Számítástechnika (4000), Képzőművészet (2500), Foci (2000), Tag: ár; ColumnSpan: 2 |
| TextBox | txtMegj | Multiline, AcceptsReturn: True, Dock: Fill, ColumnSpan: 2 |
Panel a tabOsszegzes-en | chkElfogad, lblHibak, rtbOssz | chkElfogad: Text: Elfogadom az adatkezelési tájékoztatót; lblHibak: AutoSize (+ MaximumSize); rtbOssz: ReadOnly, Dock: Fill |
Jobb panel (split.Panel2) | tlpKartya: lblKartya, lblAr, cmbBetu | TableLayoutPanel 1 oszlop; lblKartya: AutoSize + MaximumSize (tördelés); lblAr: nagy félkövér; cmbBetu: DropDownList + címke (&Betűméret) |
| Komponensek | epHiba, ttSugo | ErrorProvider, ToolTip |
Tabulátorsorrend és mnemonikus betűk
Az űrlap felépítése után a View → Tab Order nézetben ellenőrizd a sorrendet: lapokon belül fentről lefelé, a címkék közvetlenül a mezők előtt. Mnemonikus betűk (különbözőek!): &Név, &E-mail, &Telefon, &Születési dátum → S&zületési dátum (az S foglalt), &Osztály, &Megjegyzés → Meg&jegyzés (az M a Mégse gombé), és a gombok: &Vissza, &Tovább, &Küldés, Még&se. Egyeztesd, hogy nincs ütköző betű.
Mit tegyél ide: a tervezőnézet: bal oldalon a három lapos TabControl, jobb oldalon az élő kártya és a díj, alul a gombsor és az állapotsor.Javasolt fájlnév: winforms-6-10-felulet.webp · Alt szöveg: A táborjelentkezési űrlap felülete a tervezőben
Építés lépésenként
Építsd fel lépésről lépésre, és minden lépés után próbáld ki. Itt csak a feladat és a támpont áll, a teljes kód a következő szakaszban van. Feladat: Állítsd össze a Támpont: Először a szélre dokkolt elemek (állapotsor, gombsor), a Feladat: A Támpont: Oszlopok: Feladat: Két Támpont: Egy Feladat: Egy közös Támpont: A Feladat: Az Támpont: A Feladat: A Vissza és Tovább gombok lapoznak, az utolsó lapon a Küldés látszik. Az Támpont: A rejtett gomb nem foglal helyet a Feladat: A Támpont: Feladat: Az Összegzés lap a lapra lépéskor felépíti a szöveget. A Küldés ellenőrzi az összes kötelező elemet, a hibákat jellel (✖) és szöveggel felsorolja, és az első hibás elemhez visz. Állítsd be az Támpont: Sötét árnyalatú zöld és piros (4,5 : 1 felett), 1. lépés: az elrendezés váza
StatusStrip, a flpGombok és a split vázát, a TabControl-t és a három lapot. Futtasd, és méretezd át az ablakot.SplitContainer (Fill) legyen utolsó: jobb gomb → Send to Back. Ha a gombsor vagy az állapotsor nem látszik, a z-sorrend rossz (6.2 lecke).2. lépés: az Adatok lap rácsa
tlpAdatok két oszloppal, öt sorral, címkékkel és mezőkkel. A mezők nyúljanak az ablakkal.AutoSize és Percent 100. Mezők Dock = Fill, címkék Anchor = Left (6.4 lecke).3. lépés: a Választások lap
GroupBox választógombokkal (a Tag az ár), egy FlowLayoutPanel a jelölőnégyzetekkel, a megjegyzés mező. Kösd a rácsba, hogy mindkét oszlop azonos szélességű legyen.GroupBox egy cellába kerül (Dock = Fill), az árak szöveges Tag értékek, a kódban int.TryParse-szal olvashatók (5.8 lecke).4. lépés: élő kártya és díj
Frissit() metódus minden változásra frissíti a kártyát (név, turnus, étkezés, tevékenységek), a díjat és a haladásjelzőt. Köss minden mezőt egy közös kezelőhöz rekurzív bejárással.TextBox a TextChanged, a ComboBox a SelectedIndexChanged, a CheckBox és RadioButton a CheckedChanged eseményt adja (5.5, 5.8 lecke). A formátum: {0:N0} Ft.5. lépés: érvényesítés és lapváltás
AdatokRendben(bool jelez) metódus ellenőrzi az öt mezőt. A Validating kezelők és a tc_Selecting ezt hívják. A hibát ErrorProvider és az állapotsor jelzi, jellel és szöveggel.Selecting csak az Adatok lap elhagyásánál ellenőriz (6.5 lecke). A Frissit() hívása NevOk(false) alakban jelzés nélkül ellenőriz.6. lépés: navigáció és gombok
AcceptButton a lapnak megfelelő gomb, a CancelButton a Mégse. A Enter egysoros mezőben továbblép (ProcessCmdKey), az induló fókusz a névmezőn.FlowLayoutPanelben (6.4 lecke), és nem lehet AcceptButton. A Mégse gomb CausesValidation = false (6.6, 6.7 lecke).7. lépés: betűméret
cmbBetu három betűméretet kínál (9, 12, 15 pt). A választás az ablak Font értékét állítja, a lblAr az ablak betűjéhez viszonyított méretű, és az ablak MinimumSize értékét a tartalomhoz igazítod. Mentsd a választást a Settings-be.SuspendLayout/ResumeLayout, GetPreferredSize (6.8 lecke). A betöltésnél ellenőrizd a mentett értéket.8. lépés: Küldés, Összegzés és hozzáférhetőség
AccessibleName értékeket, ellenőrizd a színek kontrasztját, és használj rendszerszíneket.SystemColors mindenhol, ahol nincs szükség saját színre (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 sokféle lehet, és a tiéd jobb is lehet. Először a lépéseket próbáld meg a támpontok alapján, és csak elakadásnál nézd meg a kódot.
Megoldás: Form1.cs (teljes kód)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
namespace Tabor
{
public partial class Form1 : Form
{
private bool betoltve = false;
private bool elkuldve = false;
private readonly Color zold = Color.FromArgb(0, 100, 40);
private readonly Color piros = Color.FromArgb(170, 20, 20);
public Form1()
{
InitializeComponent();
}
// ---- indulás ----
private void Form1_Load(object sender, EventArgs e)
{
this.AutoValidate = AutoValidate.EnableAllowFocusChange;
this.AcceptButton = btnTovabb;
this.CancelButton = btnMegse;
btnMegse.CausesValidation = false;
split.Panel1MinSize = 460;
split.Panel2MinSize = 240;
split.FixedPanel = FixedPanel.Panel2;
cmbOsztaly.Items.AddRange(new object[] { "5. osztály", "6. osztály", "7. osztály", "8. osztály", "9. osztály", "10. osztály" });
cmbBetu.Items.AddRange(new object[] { "Normál (9 pt)", "Nagy (12 pt)", "Nagyon nagy (15 pt)" });
// hozzáférhetőség: név a címke nélküli, illetve a másik panelen lévő elemeknek
txtMegj.AccessibleName = "Megjegyzés";
cmbBetu.AccessibleName = "Betűméret";
rtbOssz.AccessibleName = "A jelentkezés összegzése";
ttSugo.SetToolTip(cmbBetu, "A felület betűméretének választása");
ttSugo.SetToolTip(chkElfogad, "A küldéshez el kell fogadni a tájékoztatót.");
float mentett = Properties.Settings.Default.BetuMeret;
int index = mentett >= 15 ? 2 : (mentett >= 12 ? 1 : 0);
cmbBetu.SelectedIndex = index;
BetuBeallit(index);
this.ActiveControl = txtNev;
Kot(this);
betoltve = true;
GombFrissit();
Frissit();
}
private void Kot(Control szulo)
{
foreach (Control c in szulo.Controls)
{
if (c == cmbBetu)
{
continue;
}
if (c is TextBox)
{
((TextBox)c).TextChanged += Valtozas;
}
else if (c is ComboBox)
{
((ComboBox)c).SelectedIndexChanged += Valtozas;
}
else if (c is CheckBox)
{
((CheckBox)c).CheckedChanged += Valtozas;
}
else if (c is RadioButton)
{
((RadioButton)c).CheckedChanged += Valtozas;
}
Kot(c);
}
}
private void Valtozas(object sender, EventArgs e)
{
Frissit();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
Properties.Settings.Default.Save();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason != CloseReason.UserClosing || elkuldve)
{
return;
}
bool kitoltott = txtNev.Text.Trim().Length > 0 || txtEmail.Text.Trim().Length > 0;
if (kitoltott && MessageBox.Show("A jelentkezés még nincs elküldve. Biztosan kilépsz?", "Kilépés", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
{
e.Cancel = true;
}
}
// ---- betűméret és méretezés ----
private void BetuBeallit(int index)
{
float[] meretek = { 9f, 12f, 15f };
this.SuspendLayout();
try
{
this.Font = new Font(this.Font.FontFamily, meretek[index]);
lblAr.Font = new Font(this.Font.FontFamily, meretek[index] * 1.6f, FontStyle.Bold);
lblKartya.MaximumSize = new Size(Math.Max(120, split.Panel2.ClientSize.Width - 20), 0);
}
finally
{
this.ResumeLayout(true);
}
MinimumBeallit();
Properties.Settings.Default.BetuMeret = meretek[index];
}
private void MinimumBeallit()
{
Size kivant = tlpValasztas.GetPreferredSize(Size.Empty);
int keretSzel = this.Width - this.ClientSize.Width;
int keretMag = this.Height - this.ClientSize.Height;
// közelítés: a lap és a jobb panel minimuma, a gombsor és az állapotsor
int szel = Math.Max(kivant.Width + 40, split.Panel1MinSize) + split.Panel2MinSize + split.SplitterWidth + keretSzel;
int mag = kivant.Height + flpGombok.Height + ss.Height + 90 + keretMag;
this.MinimumSize = new Size(szel, mag);
}
private void cmbBetu_SelectionChangeCommitted(object sender, EventArgs e)
{
BetuBeallit(cmbBetu.SelectedIndex);
}
private void split_SizeChanged(object sender, EventArgs e)
{
if (betoltve)
{
lblKartya.MaximumSize = new Size(Math.Max(120, split.Panel2.ClientSize.Width - 20), 0);
}
}
private void btnElo_Click(object sender, EventArgs e)
{
split.Panel2Collapsed = !split.Panel2Collapsed;
}
// ---- ellenőrzés ----
private bool Hiba(Control mezo, string uzenet, bool jelez)
{
if (jelez)
{
epHiba.SetError(mezo, uzenet);
}
return uzenet.Length == 0;
}
private bool NevOk(bool jelez)
{
return Hiba(txtNev, txtNev.Text.Trim().Length < 3 ? "A név legalább 3 karakter legyen." : "", jelez);
}
private bool EmailOk(bool jelez)
{
string s = txtEmail.Text.Trim();
int at = s.IndexOf('@');
bool ok = at > 0 && s.LastIndexOf('.') > at + 1 && !s.EndsWith(".");
return Hiba(txtEmail, ok ? "" : "Az e-mail cím alakja: nev@pelda.hu", jelez);
}
private bool TelefonOk(bool jelez)
{
string uzenet = "";
foreach (char c in txtTelefon.Text)
{
if (!char.IsDigit(c) && c != '+' && c != ' ' && c != '-')
{
uzenet = "A telefonszám csak számjegyet, +, - jelet és szóközt tartalmazhat.";
break;
}
}
return Hiba(txtTelefon, uzenet, jelez);
}
private bool OsztalyOk(bool jelez)
{
return Hiba(cmbOsztaly, cmbOsztaly.SelectedIndex < 0 ? "Válaszd ki az osztályt." : "", jelez);
}
private bool AdatokRendben(bool jelez)
{
bool a = NevOk(jelez);
bool b = EmailOk(jelez);
bool c = TelefonOk(jelez);
bool d = OsztalyOk(jelez);
return a && b && c && d;
}
private void txtNev_Validating(object sender, CancelEventArgs e)
{
if (!NevOk(true)) { e.Cancel = true; }
}
private void txtEmail_Validating(object sender, CancelEventArgs e)
{
if (!EmailOk(true)) { e.Cancel = true; }
}
private void txtTelefon_Validating(object sender, CancelEventArgs e)
{
if (!TelefonOk(true)) { e.Cancel = true; }
}
private void cmbOsztaly_Validating(object sender, CancelEventArgs e)
{
if (!OsztalyOk(true)) { e.Cancel = true; }
}
// ---- lapok és gombok ----
private void tc_Selecting(object sender, TabControlCancelEventArgs e)
{
if (!betoltve)
{
return;
}
if (tc.SelectedTab == tabAdatok && e.TabPageIndex > 0 && !AdatokRendben(true))
{
tsslAllapot.Text = "✖ Előbb javítsd az Adatok lap hibáit.";
e.Cancel = true;
}
}
private void tc_SelectedIndexChanged(object sender, EventArgs e)
{
if (!betoltve)
{
return;
}
if (tc.SelectedTab == tabOsszegzes)
{
OsszegzesEpit();
}
GombFrissit();
}
private void GombFrissit()
{
btnVissza.Enabled = tc.SelectedIndex > 0;
btnTovabb.Visible = tc.SelectedIndex < tc.TabCount - 1;
btnKuld.Visible = tc.SelectedIndex == tc.TabCount - 1;
this.AcceptButton = btnTovabb.Visible ? btnTovabb : btnKuld;
}
private void btnTovabb_Click(object sender, EventArgs e)
{
if (tc.SelectedIndex < tc.TabCount - 1)
{
tc.SelectedIndex = tc.SelectedIndex + 1; // a Selecting ellenőriz
}
}
private void btnVissza_Click(object sender, EventArgs e)
{
if (tc.SelectedIndex > 0)
{
tc.SelectedIndex = tc.SelectedIndex - 1;
}
}
private void btnMegse_Click(object sender, EventArgs e)
{
this.Close();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Enter || keyData == (Keys.Shift | Keys.Enter))
{
TextBox mezo = this.ActiveControl as TextBox;
if (mezo != null && !mezo.Multiline)
{
this.SelectNextControl(mezo, keyData == Keys.Enter, true, true, true);
return true;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
// ---- választások, díj, kártya ----
private RadioButton ValasztottGomb(GroupBox csoport)
{
foreach (Control c in csoport.Controls)
{
RadioButton rb = c as RadioButton;
if (rb != null && rb.Checked)
{
return rb;
}
}
return null;
}
private int TagAr(Control c)
{
int ar;
if (c != null && c.Tag != null && int.TryParse(c.Tag.ToString(), out ar))
{
return ar;
}
return 0;
}
private List<CheckBox> Tevekenysegek()
{
List<CheckBox> lista = new List<CheckBox>();
foreach (Control c in flpTev.Controls)
{
CheckBox cb = c as CheckBox;
if (cb != null && cb.Checked)
{
lista.Add(cb);
}
}
return lista;
}
private int Ar()
{
int osszeg = TagAr(ValasztottGomb(grpTurnus)) + TagAr(ValasztottGomb(grpEtkezes));
foreach (CheckBox cb in Tevekenysegek())
{
osszeg = osszeg + TagAr(cb);
}
return osszeg;
}
private void Frissit()
{
if (!betoltve)
{
return;
}
int kesz = 0;
if (NevOk(false)) { kesz++; }
if (EmailOk(false)) { kesz++; }
if (OsztalyOk(false)) { kesz++; }
if (ValasztottGomb(grpTurnus) != null) { kesz++; }
if (ValasztottGomb(grpEtkezes) != null) { kesz++; }
if (chkElfogad.Checked) { kesz++; }
tspbKesz.Value = kesz;
tsslAllapot.Text = $"Kitöltöttség: {kesz}/6 kötelező elem";
RadioButton turnus = ValasztottGomb(grpTurnus);
RadioButton etkezes = ValasztottGomb(grpEtkezes);
List<string> tev = new List<string>();
foreach (CheckBox cb in Tevekenysegek())
{
tev.Add(cb.Text.Replace("&", ""));
}
lblKartya.Text = "Név: " + (txtNev.Text.Trim().Length == 0 ? "–" : txtNev.Text.Trim())
+ "\r\nTurnus: " + (turnus == null ? "–" : turnus.Text.Replace("&", ""))
+ "\r\nÉtkezés: " + (etkezes == null ? "–" : etkezes.Text.Replace("&", ""))
+ "\r\nTevékenységek: " + (tev.Count == 0 ? "–" : string.Join(", ", tev));
lblAr.Text = string.Format(CultureInfo.CurrentCulture, "{0:N0} Ft", Ar());
this.Text = txtNev.Text.Trim().Length == 0 ? "Táborjelentkezés" : "Táborjelentkezés – " + txtNev.Text.Trim();
}
private void OsszegzesEpit()
{
List<string> sorok = new List<string>();
RadioButton turnus = ValasztottGomb(grpTurnus);
RadioButton etkezes = ValasztottGomb(grpEtkezes);
sorok.Add("Név: " + txtNev.Text.Trim());
sorok.Add("E-mail: " + txtEmail.Text.Trim());
sorok.Add("Telefon: " + (txtTelefon.Text.Trim().Length == 0 ? "(nincs megadva)" : txtTelefon.Text.Trim()));
sorok.Add("Születési dátum: " + dtpSzul.Value.ToString("yyyy. MMMM d."));
sorok.Add("Osztály: " + (cmbOsztaly.SelectedIndex < 0 ? "–" : cmbOsztaly.Text));
sorok.Add("Turnus: " + (turnus == null ? "–" : turnus.Text.Replace("&", "")));
sorok.Add("Étkezés: " + (etkezes == null ? "–" : etkezes.Text.Replace("&", "")));
sorok.Add("Megjegyzés: " + (txtMegj.Text.Trim().Length == 0 ? "(nincs)" : txtMegj.Text.Trim()));
sorok.Add("Előzetes díj: " + lblAr.Text);
rtbOssz.Text = string.Join(Environment.NewLine, sorok);
}
// ---- küldés ----
private void btnKuld_Click(object sender, EventArgs e)
{
bool adatok = AdatokRendben(true);
List<string> hibak = new List<string>();
if (!adatok) { hibak.Add("Az Adatok lapon javítandó mezők vannak."); }
if (ValasztottGomb(grpTurnus) == null) { hibak.Add("Válassz turnust."); }
if (ValasztottGomb(grpEtkezes) == null) { hibak.Add("Válassz étkezést."); }
if (!chkElfogad.Checked) { hibak.Add("Fogadd el az adatkezelési tájékoztatót."); }
if (hibak.Count > 0)
{
lblHibak.ForeColor = piros;
lblHibak.Text = "✖ " + string.Join(" ✖ ", hibak);
tsslAllapot.Text = "✖ A jelentkezés nem küldhető el.";
if (!adatok)
{
tc.SelectedTab = tabAdatok;
txtNev.Focus();
}
else if (ValasztottGomb(grpTurnus) == null || ValasztottGomb(grpEtkezes) == null)
{
tc.SelectedTab = tabValasztasok;
}
return;
}
elkuldve = true;
lblHibak.ForeColor = zold;
lblHibak.Text = "✔ Minden rendben, a jelentkezés elküldve.";
tsslAllapot.Text = "✔ Elküldve.";
btnKuld.Enabled = false;
MessageBox.Show($"Köszönjük, {txtNev.Text.Trim()}! A jelentkezést rögzítettük.\r\nElőzetes díj: {lblAr.Text}", "Táborjelentkezés");
}
}
}Megoldás: miért így épül fel?
A felépítés a fejezet leckéit követi: a szélre dokkolt elemek és a Fill SplitContainer (6.2, 6.3), az űrlapok rácsai (6.4), a füles lapozás és a Selecting ellenőrzés (6.5), a fókusz, az Enter és az érvényesítés (6.6), az alapértelmezett gombok (6.7), a betűméret és a tartalomhoz igazodó minimum (6.8), és a hozzáférhetőség (6.9). Az egyik fontos szerkezeti döntés: az ellenőrző metódusok (NevOk(bool jelez)) kétféleképp is használhatók: jelzéssel (Validating, Selecting, küldés) és jelzés nélkül (a haladásjelző). Így nem kell az ellenőrzést megismételni.
Tesztlista
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | A program indul. | A fókusz a névmezőn, az Adatok lap látszik, a Vissza gomb letiltott, a haladásjelző 0/6. |
| 2 | Az ablak nagyítása és kicsinyítése a minimumig. | A mezők nyúlnak, a gombok a jobb alsó sarokban maradnak, nincs átlapolódás a minimum alatt. |
| 3 | Üres név, majd Tab. | A hibaikon és üzenet megjelenik; az AutoValidate miatt a fókusz továbbléphet. |
| 4 | Hibás e-mail (kiss.peter), majd Tovább. | A lap nem vált, az állapotsorban ✖ üzenet. |
| 5 | Helyes adatok, Tovább. | A Választások lap jelenik meg, a Vissza aktív. |
| 6 | Turnus, étkezés és két tevékenység kiválasztása. | A díj és a kártya azonnal frissül; a díj a Tag értékek összege. |
| 7 | Az Enter az egysoros mezőkben. | A következő mezőre lép; a megjegyzés mezőben új sort ad. |
| 8 | Shift+Tab és Alt+betűk. | A tabulátorsorrend logikus, az Alt+betűk a megfelelő mezőre/gombra visznek, nincs ütköző betű. |
| 9 | A Küldés hibás/hiányos adatokkal. | A hibák felsorolva ✖ jellel, az első hibás laphoz/mezőhöz visz. |
| 10 | Az adatkezelési tájékoztató elfogadása nélkül küldés. | Hibaüzenet, nem küldhető el. |
| 11 | A teljes helyes jelentkezés elküldése. | ✔ üzenet, a Küldés letiltva, a program nem kér megerősítést kilépéskor. |
| 12 | A Mégse hibás mező mellett. | Azonnal megnyomható (CausesValidation = false); kitöltött adatnál megerősítést kér. |
| 13 | Esc. | A Mégse gomb lefut (CancelButton). |
| 14 | Betűméret: nagy (12 pt), nagyon nagy (15 pt). | Az egész felület nagyobb lesz, rendezett marad; a díj felirat arányos; a minimális ablakméret nő. |
| 15 | Újraindítás a nagy betűméret után. | A betűméret megmarad (Settings). |
| 16 | A jobb oldali panel összecsukása. | A lapok kitöltik az ablakot. |
| 17 | Billentyűzet-teszt: az űrlap kitöltése és elküldése egér nélkül. | Minden elérhető, nincs fókuszcsapda. |
| 18 | Windows méretezés 150%, szövegméret 150%, kontrasztos téma. | Az űrlap használható, a szöveg olvasható. |
Gyakori hibák és önértékelés
Ezeken szoktak elcsúszni
Gondold végig, nálad előfordul-e valamelyik.
1. A gombsor vagy az állapotsor nem látszik
Az ablak aljáról hiányzik a gombsor. A Ok és javítás
Fill dokkolású SplitContainer takarja őket: a z-sorrend rossz. Jobb gomb a split vezérlőn → Send to Back (6.2 lecke).
2. A mezők nem nyúlnak az űrlapon
Az ablak nagyításakor a szövegmezők kicsik maradnak. A mezők Ok és javítás
Dock értéke nem Fill, vagy a rács második oszlopa nem Percent 100 (6.4 lecke).
3. A lapváltás nem ellenőriz (vagy végtelen hurok)
private void tc_Selecting(object sender, TabControlCancelEventArgs e)
{
if (!AdatokRendben(true))
{
tc.SelectedIndex = 0; // újabb Selecting!
}
}Hiba és javítás
A Selecting kezelőben ne váltsd a lapot: e.Cancel = true. Az ellenőrzést csak az Adatok lap elhagyásánál végezd (tc.SelectedTab == tabAdatok), különben a visszalépést is megakadályozod.
4. A haladásjelző hibát jelez a mezőkön
A A Frissit() hívása minden betű beírásánál piros ikonokat mutat a még ki nem töltött mezőkön.Ok és javítás
Frissit() a NevOk(true) változatot hívja. A haladás számolásához jelzés nélküli ellenőrzés kell (NevOk(false)): a hibaikon csak a mezők elhagyásakor és a lapváltásnál jelenjen meg.
5. A nagy betűnél szétcsúszik az űrlap
15 pontos betűnél a jobb oldali kártya kilóg, a gombok levágódnak. Valahol fix méret maradt: egy Ok és javítás
AutoSize = false címke, fix magasságú sor, fix MinimumSize. Használj AutoSize sorokat, MaximumSize a tördelő címkén, és a MinimumSize értéket számold a tartalomból (6.8 lecke).
6. A hibák csak pirossal vannak jelezve
A hibaüzenet csak piros szöveg. Adj mellé jelet (✖) és szöveges leírást, a fókuszt tedd a hibás mezőre, és sötét árnyalatot használj (4,5 : 1 felett) (6.9 lecke).Ok és javítás
Az értékeléshez ellenőrizd az alábbi szempontokat is:
AccessibleName, rendszerszínek.Bővítési ötletek, ha kész vagy
Ha maradt időd, próbáld ki:
- Testvérkedvezmény: egy
NumericUpDowna gyerekek számához; a második gyerektől 10% kedvezmény. - Piszkozat mentése: az adatok mentése fájlba (JSON vagy CSV, 10. fejezet) és visszatöltés induláskor.
- Nyomtatás: az összegzés nyomtatása (2.15 lecke).
- Adatbázis: a jelentkezések mentése SQLite-ba (3. fejezet), és egy listázó ablak.
- Hozzáférhetőségi ellenőrzés: a 6.9 lecke ellenőrzőjének bekötése egy menüpontba, amely a saját űrlapodat vizsgálja.
- Téma: sötét mód a rendszerszínek mellett (7. fejezet menüjével).
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. A kész űrlapot bővíted. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Tabulátorsorrend leírása
Írd le táblázatban a kész űrlap tabulátorsorrendjét az Adatok lapon (címkék és mezők, TabIndex szerint). Hol kell a címke és hol a mező?Megoldás megnyitása
TabIndex Vezérlő Megjegyzés 0 lblNev (&Név) nem kap fókuszt, a mnemonikus billentyű a következőre ugrik 1 txtNev induló fókusz 2 lblEmail 3 txtEmail 4–5 lblTelefon, txtTelefon 6–7 lblSzul, dtpSzul 8–9 lblOsztaly, cmbOsztaly
2. Árszámítás ellenőrzése
Számold ki kézzel a díjat: 2. turnus (45 000), félpanziós étkezés (8 000), úszás (3 000) és foci (2 000) tevékenység. Hogyan ellenőrzöd, hogy a program ugyanezt adja? 45 000 + 8 000 + 3 000 + 2 000 = 58 000 Ft. A program felirata „58 000 Ft” (a csoportosító elválasztó a nyelvi beállítástól függően szóköz). Egy teszteset (a tesztlista 6. sora) az ismert számmal ellenőrzi.Megoldás megnyitása
Közepes szint
3. Testvérkedvezmény
Egy Egy gyerek díja: nudGyerek mező (1–5) adja a gyerekek számát. Az 1. gyerek teljes árat fizet, a további gyerekek a díj 90%-át. Hogyan számolod az összdíjat, és mire kell vigyázni a kerekítésnél?Megoldás megnyitása
Ar(). Összesen: egy + (db - 1) * egy * 0,9. A pénzösszegnél a decimal típus pontosabb (4.4 lecke), és a végösszeget egészre kerekítjük (Math.Round).private decimal OsszesDij()
{
decimal egy = Ar();
int db = (int)nudGyerek.Value;
decimal osszeg = egy + (db - 1) * egy * 0.9m;
return Math.Round(osszeg, 0);
}
4. Nyomtatható összegzés gomb
Az Összegzés lapon egy Nyomtatás gomb a A rtbOssz tartalmát nyomtatja. Hol találod meg a megoldás elemeit?Megoldás megnyitása
PrintDocument és a PrintDialog a 2.15 leckében szerepel: a PrintPage eseményben a szöveget a Graphics.DrawString rajzolja. Az összegzés szövege a rtbOssz.Text.private void pdNyomtat_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
using (Font betu = new Font("Segoe UI", 12))
{
e.Graphics.DrawString(rtbOssz.Text, betu, Brushes.Black, e.MarginBounds);
}
}
private void btnNyomtat_Click(object sender, EventArgs e)
{
if (pdlNyomtat.ShowDialog() == DialogResult.OK)
{
pdNyomtat.Print();
}
}
Kihívás szint
5. Kontraszt-ellenőrzés a saját színekre
Ellenőrizd a A Kontraszt metódussal (6.9 lecke), hogy a zold és a piros szín fehér háttéren eléri-e a 4,5 : 1 arányt. Mit teszel, ha nem?Megoldás megnyitása
Hozzaferhetoseg.Kontraszt(zold, Color.White) és a piros értéke is 4,5 fölé kell kerüljön (a mintaszínek, (0, 100, 40) és (170, 20, 20), elég sötétek). Ha nem, sötétíts rajta, amíg az arány megfelelő. A kontraszt számolása automatikus tesztként is beépíthető a fejlesztésbe.private void SzinEllenorzes()
{
double z = Hozzaferhetoseg.Kontraszt(zold, Color.White);
double p = Hozzaferhetoseg.Kontraszt(piros, Color.White);
lblSzinInfo.Text = $"Zöld: {z:0.0} : 1 {(z >= 4.5 ? "✔" : "✖")} | Piros: {p:0.0} : 1 {(p >= 4.5 ? "✔" : "✖")}";
}
6. A fókusz követése az állapotsorban
Az állapotsor második felirata mutassa, melyik mezőn áll a fókusz, és milyen segítséget ad ( A Tag vagy AccessibleName). Hogyan oldod meg egyetlen közös kezelővel?Megoldás megnyitása
Kot metódus mellé egy második rekurzív metódus az Enter eseményre köt egy közös kezelőt (5.8 lecke). A kezelő a sender AccessibleName vagy Name értékét írja ki.private void FokuszKot(Control szulo)
{
foreach (Control c in szulo.Controls)
{
c.Enter += Fokusz_Enter;
FokuszKot(c);
}
}
private void Fokusz_Enter(object sender, EventArgs e)
{
Control c = (Control)sender;
string nev = string.IsNullOrEmpty(c.AccessibleName) ? c.Name : c.AccessibleName;
tsslFokusz.Text = "Fókusz: " + nev;
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Hogy utolsóként kapja a maradék helyet, és ne takarja el a szélre dokkolt gombsort és állapotsort (Send to Back). A A jelzéssel ( Hogy hibás mező mellett is azonnal megnyomható legyen, és a felhasználó ne legyen a mezőhöz láncolva. Tartalomhoz igazodó elrendezés ( A választógombok és jelölőnégyzetek Címkék és 1. Miért kell a Fill SplitContainert leghátul tartani?
2. Hogyan működik a lapváltás ellenőrzése?
Selecting eseményben az Adatok lap elhagyásánál AdatokRendben(true) fut, és hiba esetén e.Cancel = true.3. Miért kétféle ellenőrző metódus-használat (jelez igaz/hamis)?
ErrorProvider) történő ellenőrzés a mező elhagyásakor kell, a jelzés nélküli a haladásjelzőhöz. Egy metódus mindkettőt kiszolgálja.4. Miért CausesValidation = false a Mégse gombon?
5. Hogyan marad rendezett az űrlap nagyobb betűnél?
AutoSize sorok, TableLayoutPanel), az ablak Font értékének módosítása, és a MinimumSize számolása a tartalomból.6. Hogyan számolod a díjat?
Tag értékeinek összegével (int.TryParse), egy közös Frissit() metódusban.7. Mi teszi hozzáférhetővé az űrlapot?
AccessibleName, logikus billentyűzetes használat, hibajelzés színen kívül is (✖, szöveg), jó kontraszt, rendszerszínek, átméretezhető betű.
Mit tudsz már? (a 6. fejezet végén)
- Átméretezhető, rendezett űrlapot tervezel
Anchor,Dock,SplitContainer,TableLayoutPanelésFlowLayoutPanelsegítségével. - Füles felületet és csoportokat (
TabControl,GroupBox) használsz, a lapváltást ellenőrzéssel kötöd. - Az űrlapot egér nélkül is használhatóvá teszed (fókusz,
TabIndex, Enter,AcceptButton,CancelButton). - Érvényesítést építesz
Validating,ErrorProviderésValidateChildrensegítségével. - Különböző betűméreten és méretezésen is rendezett, hozzáférhető felületet készítesz.
- Elkészítettél egy teljes, valódi űrlapot ezekkel az eszközökkel.
Következő fejezet: menük, dialógusok és több ablak
A 6. fejezet kész. A 7. fejezetben a programok keretét építed ki: menük, eszköztárak, állapotsor, párbeszédablakok és több ablak közötti adatátadás.




