Grafikus C# – Windows Forms tanfolyam › 1.13 lecke Windows Forms · 1.13 lecke · záró projekt Az 1. fejezet végén mindent összerakunk. Egyetlen ablakban egy adatlap-bekérő és egy négyműveletes számológép lesz, hibajelzéssel, színes visszajelzéssel és menüvel. Közben végigmész az eddig tanult vezérlőkön, és látod, hogy fognak együtt működni.Mini projekt: adatbekérő és egyszerű kalkulátor

A lecke végére
- egy összetett ablakot építesz fel több vezérlőből és komponensből;
- a kódot kisebb, jól elnevezett metódusokra bontod;
- a bevitelt ellenőrzöd, és színes, eltűnő visszajelzést adsz;
- menüt kötsz a programhoz;
- a projektet a tesztlista alapján végigpróbálod;
- önértékelő szempontok alapján ellenőrzöd a munkádat.
Mielőtt elkezded
Az 1. fejezet 1.1–1.12 leckéiből minden vezérlőt használunk: TextBox, NumericUpDown, CheckedListBox, ComboBox, Label, Timer, ErrorProvider, ToolTip, MenuStrip. Ha valamelyik még bizonytalan, nézd meg újra a megfelelő leckét.
A terv: mit tud a program?
| Rész | Funkció | Lecke |
|---|---|---|
| Adatbekérő | Név, e-mail, életkor, érdeklődési körök megadása; ellenőrzés hibajelzéssel; összefoglaló kiírása. | 1.2, 1.5, 1.9, 1.11 |
| Kalkulátor | Két szám, négy művelet legördülő listából, hibás bevitel és nullával osztás kezelése. | 1.3, 1.6 |
| Visszajelzés | Színes (piros/zöld) üzenet, amely három másodperc múlva eltűnik. | 1.8, 1.10 |
| Menü | File (Új, Kilépés megerősítéssel) és About. | 1.7 |
| Súgó | Súgószöveg a fontosabb mezőkhöz. | 1.11 |
Haladj kis lépésekben
Ne az egészet egyszerre építsd. Előbb a felület, utána az üzenet (Uzenet), majd az adatbekérő, a kalkulátor, a menü, végül a súgók. Minden lépés után futtasd és próbáld ki a programot.
A felület és a nevek
Az ablakot két GroupBox osztja ketté: bal oldalt az adatbekérő (grpAdat), jobb oldalt a kalkulátor (grpSzamolo). Alul egy visszajelző felirat van. A vezérlők nevei:
| Vezérlő | (Name) | Beállítás |
|---|---|---|
| TextBox | txtNev | MaxLength: 40 |
| TextBox | txtEmail | |
| NumericUpDown | nudKor | Minimum: 0, Maximum: 120, Value: 18 |
| CheckedListBox | clbErdeklodes | CheckOnClick: True; elemek: Programozás, Zene, Sport, Olvasás |
| Button | btnMent | Text: Mentés |
| Label | lblOsszefoglalo | AutoSize: False, nagyobb méret |
| TextBox | txtA, txtB | a két szám |
| ComboBox | cmbMuvelet | DropDownStyle: DropDownList; elemek: +, -, *, / |
| Button | btnSzamol | Text: Számol |
| Label | lblEredmeny | az eredmény |
| Label | lblVisszajelzes | alul, AutoSize: False |
| Timer | tmrUzenet | Interval: 3000 |
| ErrorProvider | epHiba | BlinkStyle: NeverBlink |
| ToolTip | ttSugo | |
| MenuStrip | miFile, miUj, miKilepes, miAbout | File: Új, elválasztó, Kilépés; About |
Mit tegyél ide: a kész ablak tervezőnézete két GroupBoxszal (balra az adatbekérő mezőkkel, jobbra a kalkulátor), a menüsorral felül és a visszajelző felirattal alul.Javasolt fájlnév: winforms-1-13-felulet.webp · Alt szöveg: Az adatbekérő és kalkulátor projekt felülete két csoportra osztva
A teljes kód
A kód négy részből áll: visszajelzés, adatbekérő, kalkulátor és menü. Hasonlítsd össze a saját megoldásoddal:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AdatbekeroKalkulator
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// ---- Visszajelzés (1.10) ----
private void Uzenet(string szoveg, bool hiba)
{
lblVisszajelzes.Text = szoveg;
lblVisszajelzes.ForeColor = hiba ? Color.Firebrick : Color.SeaGreen;
tmrUzenet.Stop();
tmrUzenet.Start();
}
private void tmrUzenet_Tick(object sender, EventArgs e)
{
lblVisszajelzes.Text = "";
tmrUzenet.Stop();
}
// ---- Adatbekérő (1.9, 1.11) ----
private bool AdatEllenoriz()
{
bool jo = true;
if (txtNev.Text.Trim().Length == 0)
{
epHiba.SetError(txtNev, "A név megadása kötelező.");
jo = false;
}
else
{
epHiba.SetError(txtNev, "");
}
string email = txtEmail.Text.Trim();
if (!email.Contains("@") || !email.Contains("."))
{
epHiba.SetError(txtEmail, "Adj meg érvényes e-mail címet.");
jo = false;
}
else
{
epHiba.SetError(txtEmail, "");
}
if (nudKor.Value < 14)
{
epHiba.SetError(nudKor, "14 év alatt nem lehet regisztrálni.");
jo = false;
}
else
{
epHiba.SetError(nudKor, "");
}
return jo;
}
private void btnMent_Click(object sender, EventArgs e)
{
if (!AdatEllenoriz())
{
Uzenet("Javítsd a jelölt mezőket!", true);
return;
}
string erdeklodes = "";
foreach (object elem in clbErdeklodes.CheckedItems)
{
erdeklodes = erdeklodes + elem + ", ";
}
if (erdeklodes.Length > 0)
{
erdeklodes = erdeklodes.Substring(0, erdeklodes.Length - 2);
}
else
{
erdeklodes = "nincs megadva";
}
int kor = (int)nudKor.Value;
lblOsszefoglalo.Text = $"Név: {txtNev.Text.Trim()}{Environment.NewLine}E-mail: {txtEmail.Text.Trim()}{Environment.NewLine}Életkor: {kor}{Environment.NewLine}Érdeklődés: {erdeklodes}";
Uzenet("Az adatok elmentve.", false);
}
// ---- Kalkulátor (1.3, 1.6) ----
private void btnSzamol_Click(object sender, EventArgs e)
{
double a;
double b;
if (!double.TryParse(txtA.Text, out a) || !double.TryParse(txtB.Text, out b))
{
Uzenet("Hibás szám!", true);
return;
}
string muvelet = cmbMuvelet.Text;
double eredmeny;
if (muvelet == "+")
{
eredmeny = a + b;
}
else if (muvelet == "-")
{
eredmeny = a - b;
}
else if (muvelet == "*")
{
eredmeny = a * b;
}
else if (muvelet == "/")
{
if (b == 0)
{
Uzenet("Nullával nem lehet osztani!", true);
return;
}
eredmeny = a / b;
}
else
{
Uzenet("Válassz műveletet!", true);
return;
}
lblEredmeny.Text = $"Eredmény: {eredmeny}";
Uzenet("Kiszámolva.", false);
}
// ---- Menü (1.7) ----
private void miUj_Click(object sender, EventArgs e)
{
txtNev.Clear();
txtEmail.Clear();
txtA.Clear();
txtB.Clear();
lblOsszefoglalo.Text = "";
lblEredmeny.Text = "";
Uzenet("Új munkamenet.", false);
}
private void miKilepes_Click(object sender, EventArgs e)
{
DialogResult valasz = MessageBox.Show("Biztosan kilépsz?", "Kilépés", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (valasz == DialogResult.Yes)
{
this.Close();
}
}
private void miAbout_Click(object sender, EventArgs e)
{
MessageBox.Show("Adatbekérő és kalkulátor" + Environment.NewLine + "Készítő: Minta Anna", "About");
}
// ---- Súgók (1.11) ----
private void Form1_Load(object sender, EventArgs e)
{
ttSugo.SetToolTip(txtNev, "Írd be a teljes neved.");
ttSugo.SetToolTip(txtEmail, "Például: nev@example.com");
ttSugo.SetToolTip(nudKor, "Legalább 14 év.");
ttSugo.SetToolTip(cmbMuvelet, "Válaszd ki a műveletet.");
}
}
}Jósolj!
A kalkulátorban az első mezőbe 8-at, a másodikba 0-t írsz, a művelet pedig a / jel. Mi történik?
Jóslat ellenőrzése
Piros üzenet jelenik meg: Nullával nem lehet osztani!. A return kilép az eseménykezelőből, ezért az eredmény nem íródik ki, és az eredmeny változó sem kerül felhasználásra, amíg nincs értéke.
Miért nincs hiba a nem beállított eredményeknél?
Az eredmeny változó minden olyan ágban kap értéket, amely nem lép ki return-nel. A C# végigköveti az ágakat, és csak akkor engedi használni a változót, ha az minden úton értéket kapott. Ezért kell a return a hibás ágakban.
Tesztlista
Próbáld ki sorban, és pipáld ki, ami működik:
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | Mentés üres mezőkkel | Piros hibajelzés a névnél és az e-mailnél, piros üzenet alul, ami eltűnik. |
| 2 | Életkor 10 évre állítva | Hibajelzés az életkornál. |
| 3 | Minden mező helyesen kitöltve, két érdeklődés bepipálva | Összefoglaló jelenik meg, zöld üzenet. |
| 4 | Érdeklődés nélkül mentés | Az összefoglalóban „nincs megadva” szerepel. |
| 5 | Kalkulátor: 7 + 9 | Eredmény: 16. |
| 6 | Kalkulátor: alma + 3 | Piros üzenet: Hibás szám! |
| 7 | Kalkulátor: 8 / 0 | Piros üzenet nullával osztásra. |
| 8 | Kalkulátor művelet kiválasztása nélkül | Piros üzenet: Válassz műveletet! |
| 9 | File → Új | A mezők és az eredmények törlődnek. |
| 10 | About | A készítő neve látszik. |
| 11 | File → Kilépés, majd Nem | Az ablak nyitva marad. |
| 12 | File → Kilépés, majd Igen | Az ablak bezárul. |
| 13 | Egér a név mezőn | Súgóbuborék jelenik meg. |
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. Felcserélt paraméterek
Uzenet(true, "Hibás szám!");
// CS1503: Argument 1: cannot convert from 'bool' to 'string'Hiba és javítás
Fordítási hiba (CS1503): az Uzenet első paramétere a szöveg, a második a hibajelző. A hívásnál a sorrendet be kell tartani: Uzenet("Hibás szám!", true);
2. Hiányzó return az ágban
else
{
Uzenet("Válassz műveletet!", true);
}
lblEredmeny.Text = $"Eredmény: {eredmeny}";
// CS0165: Use of unassigned local variable 'eredmeny'Hiba és javítás
Fordítási hiba (CS0165): az else ágban az eredmeny nem kap értéket, és a program mégis eljut a kiírásig. Tegyél az ág végére return; sort.
3. Betűs művelet
A kalkulátor a műveletnél semmit nem csinál, pedig a ComboBoxban van kiválasztott elem. A kódban szereplő szöveg és a ComboBox elemei nem egyeznek (például Ok és javítás
"x" a "*" helyett). A szövegeknek karakterre pontosan egyezniük kell. Nézd meg az Items tulajdonságot.
4. A hibajelzés nem tűnik el
A név helyes, de a piros ikon ott marad mellette. Hiányzik az Ok és javítás
else ág az epHiba.SetError(txtNev, "") hívással. A hibajelzést a helyes bevitelnél törölni kell (1.11 lecke).
5. Ismeretlen vezérlő
lblOsszefoglalo.Text = "...";
// CS0103: The name 'lblOsszefoglalo' does not exist in the current contextHiba és javítás
Fordítási hiba (CS0103): a vezérlő (Name) tulajdonsága nem egyezik a kódban használt névvel. Ellenőrizd a táblázatot, és javítsd a nevet a Properties ablakban.
Önértékelés: kész a projekt?
Mielőtt kész állapotúnak nyilvánítod a munkát, ellenőrizd az alábbi szempontokat:
txt, btn, lbl, nud stb.).Mit tegyél ide: a kész program futás közben: bal oldalt kitöltött adatlap és az összefoglaló, jobb oldalt a kalkulátor eredménnyel, alul zöld visszajelzés.Javasolt fájlnév: winforms-1-13-kesz.webp · Alt szöveg: A kész adatbekérő és kalkulátor program futás közben
Három önálló bővítési feladat
Ha kész vagy, bővítsd a programot. Egyszerre csak egyet csinálj.
1. Hatványozás
Adj a ComboBoxhoz egy A ^ műveletet, amely az első számot a második hatványára emeli.Egy lehetséges megoldás
else if (muvelet == "^")
{
eredmeny = Math.Pow(a, b);
}Math.Pow(alap, kitevő) a hatványozás. Ne felejtsd el az új elemet a ComboBox Items listájához is hozzáadni.
2. Számolási napló
Tegyél az ablakra egy lstNaplo ListBoxot, amely minden sikeres számítást felvesz, például: 8 / 2 = 4.Egy lehetséges megoldás
// az eredmény kiírása után:
lstNaplo.Items.Add($"{a} {muvelet} {b} = {eredmeny}");
3. Gyorsbillentyűk és Enter
Add a Kilépéshez a A Ctrl+Q gyorsbillentyűt, és állítsd be, hogy az Enter megnyomása a Számol gombot indítsa (ehhez egy AcceptButton kell, de az egyszerre csak egy gombhoz köthető).Egy lehetséges megoldás
miKilepes ShortcutKeys tulajdonságát állítsd Ctrl+Q értékre. Az ablak AcceptButton tulajdonságát állítsd btnSzamol-ra. Gondold végig, hogy ez a Mentés gombhoz képest zavaró-e, és dönts az elrendezés alapján.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Mindegyik feladat a záró projekt gondolatmenetét ismétli más helyzetben. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. A gomb csak kitöltött mezőknél aktív
A Mindkét mező btnSzamol gomb legyen letiltva, amíg a txtA és a txtB mező bármelyike üres. Kitöltés után legyen engedélyezett.Megoldás megnyitása
TextChanged eseményét ugyanahhoz a metódushoz kötjük. Az Enabled tulajdonság egy feltétel eredményét kapja. Az ablak betöltésekor is meghívjuk, hogy a gomb induláskor is megfelelő állapotban legyen.private void GombFrissit()
{
btnSzamol.Enabled = txtA.Text.Trim().Length > 0 && txtB.Text.Trim().Length > 0;
}
private void Mezo_TextChanged(object sender, EventArgs e)
{
GombFrissit();
}
private void Form1_Load(object sender, EventArgs e)
{
GombFrissit();
}
2. Szépen formázott eredmény
Az eredmény Az double értékét ezres tagolással és két tizedessel írd ki (12 345,60). Mi a különbség az N2 és az F2 formátum között?Megoldás megnyitása
N2 ezres tagolást és két tizedest ad (12 345,60), az F2 csak két tizedest tizedes elválasztással, ezres tagolás nélkül (12345,60). A tizedesjel és a tagoló a gép nyelvi beállításától függ.double eredmeny = 12345.6;
lblEredmeny.Text = eredmeny.ToString("N2"); // 12 345,60
lblEredmeny.Text = eredmeny.ToString("F2"); // 12345,60
Közepes szint
3. Előzménylista
Minden sikeres összeadás után kerüljön egy sor az Az lstElozmenyek ListBoxba: 12 + 8 = 20. A legújabb legyen legfelül.Megoldás megnyitása
Items.Insert(0, elem) az elemet a lista elejére illeszti be, így a legújabb látszik elsőként.private void btnSzamol_Click(object sender, EventArgs e)
{
double a;
double b;
if (!double.TryParse(txtA.Text, out a) || !double.TryParse(txtB.Text, out b))
{
lblEredmeny.Text = "Számot írj be!";
return;
}
double osszeg = a + b;
lblEredmeny.Text = "Összeg: " + osszeg;
lstElozmenyek.Items.Insert(0, $"{a} + {b} = {osszeg}");
}
4. Alaphelyzet
A A külön metódus előnye, hogy később más helyről (például menüből vagy az ablak betöltésekor) is hívható.btnUj gomb állítson vissza mindent: töröld a mezőket, az eredményt és az előzménylistát, és a kurzor kerüljön az első mezőbe. Tedd külön metódusba.Megoldás megnyitása
private void Alaphelyzet()
{
txtA.Clear();
txtB.Clear();
lblEredmeny.Text = "";
lstElozmenyek.Items.Clear();
txtA.Focus();
}
private void btnUj_Click(object sender, EventArgs e)
{
Alaphelyzet();
}
Kihívás szint
5. Kedvezményszámító
A A végső ár: txtAr mezőbe írt árból vond le a nudKedvezmeny (0–100) százalékát, és írd ki a végső árat egész forintra kerekítve. Hibás ár esetén jelezz.Megoldás megnyitása
ár × (100 − kedvezmény) / 100. A NumericUpDown értéke decimal, ezért (double) átalakítással használjuk a double számításban.private void btnKedvezmeny_Click(object sender, EventArgs e)
{
double ar;
if (!double.TryParse(txtAr.Text, out ar) || ar < 0)
{
lblEredmeny.Text = "Adj meg érvényes árat!";
return;
}
double kedvezmeny = (double)nudKedvezmeny.Value;
double vegso = ar * (100 - kedvezmeny) / 100;
lblEredmeny.Text = "Végső ár: " + Math.Round(vegso).ToString("N0") + " Ft";
}
6. Mini bevásárlókosár
A A termék árát a lista sorszáma alapján választjuk. A teljes összeg mezőben gyűlik, hogy a gombnyomások között megmaradjon.cmbTermek lista három terméket tartalmaz (alma: 300 Ft, kenyér: 500 Ft, tej: 400 Ft). Az nudDb a darabszám. A btnKosar gomb vegye fel a tételt a lstKosar listába (2 × alma = 600 Ft), a lblVegosszeg pedig mutassa a kosár teljes értékét. Termék nélkül ne vegyen fel semmit.Megoldás megnyitása
private int kosarErtek = 0;
private void btnKosar_Click(object sender, EventArgs e)
{
if (cmbTermek.SelectedIndex < 0)
{
MessageBox.Show("Válassz terméket!");
return;
}
int egysegar = 0;
if (cmbTermek.SelectedIndex == 0)
{
egysegar = 300;
}
else if (cmbTermek.SelectedIndex == 1)
{
egysegar = 500;
}
else
{
egysegar = 400;
}
int db = (int)nudDb.Value;
int tetel = db * egysegar;
lstKosar.Items.Add($"{db} × {cmbTermek.SelectedItem} = {tetel} Ft");
kosarErtek = kosarErtek + tetel;
lblVegosszeg.Text = "Végösszeg: " + kosarErtek + " Ft";
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Hogy áttekinthető legyen, és ugyanaz a kód ne forduljon elő kétszer. Az Kilép az eseménykezelőből, így a számítás és a kiírás már nem fut le hibás adatokkal. Mert a jelzés magától nem tűnik el. Apró tájékoztatásnál, amely nem zavarja a munkát. A MessageBox kérdéshez vagy fontos figyelmeztetéshez való. A Timer (1. Miért bontottuk a kódot külön metódusokra?
Uzenet például minden eseménykezelőből ugyanúgy hívható.2. Mit jelent a return a kalkulátor hibás ágaiban?
3. Miért kell az ErrorProvider hibajelzést törölni helyes bevitelnél?
4. Mikor jobb a Label üzenet, mint a MessageBox?
5. Melyik komponens tünteti el az üzenetet három másodperc után?
tmrUzenet, Interval: 3000), amely a Tick eseményben törli a feliratot, és leáll.
Mit tudsz már?
- Összetett ablakot építesz több vezérlőből és komponensből.
- A kódot metódusokra bontod, és visszatérési értéket használsz.
- Kezeled az üres, hibás és szélsőséges bevitelt.
- Színes, eltűnő visszajelzést és hibajelzést adsz.
- Tesztlista alapján végigpróbálod és értékeled a programodat.
Következő fejezet: RichTextBox és szövegszerkesztés
Az alapvezérlőkkel végeztünk. A 2. fejezetben egy Word-szerű szövegszerkesztőt építünk a RichTextBox segítségével.




