Grafikus C# – Windows Forms tanfolyam › 1.13 lecke

Windows Forms · 1.13 lecke · záró projekt

Mini projekt: adatbekérő és egyszerű kalkulátor

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.

  • kb. 90–120 perc
  • záró projekt
  • összeépítés
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

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.

1.12 PictureBox és ImageList →

A terv: mit tud a program?

RészFunkció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átorKé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ésSzí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
TextBoxtxtNevMaxLength: 40
TextBoxtxtEmail
NumericUpDownnudKorMinimum: 0, Maximum: 120, Value: 18
CheckedListBoxclbErdeklodesCheckOnClick: True; elemek: Programozás, Zene, Sport, Olvasás
ButtonbtnMentText: Mentés
LabellblOsszefoglaloAutoSize: False, nagyobb méret
TextBoxtxtA, txtBa két szám
ComboBoxcmbMuveletDropDownStyle: DropDownList; elemek: +, -, *, /
ButtonbtnSzamolText: Számol
LabellblEredmenyaz eredmény
LabellblVisszajelzesalul, AutoSize: False
TimertmrUzenetInterval: 3000
ErrorProviderepHibaBlinkStyle: NeverBlink
ToolTipttSugo
MenuStripmiFile, miUj, miKilepes, miAboutFile: Új, elválasztó, Kilépés; About
📷 Képernyőkép helye

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:

Form1.csC#
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:

#TesztVárt eredmény
1Mentés üres mezőkkelPiros hibajelzés a névnél és az e-mailnél, piros üzenet alul, ami eltűnik.
2Életkor 10 évre állítvaHibajelzés az életkornál.
3Minden 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ésAz összefoglalóban „nincs megadva” szerepel.
5Kalkulátor: 7 + 9Eredmény: 16.
6Kalkulátor: alma + 3Piros üzenet: Hibás szám!
7Kalkulátor: 8 / 0Piros üzenet nullával osztásra.
8Kalkulátor művelet kiválasztása nélkülPiros üzenet: Válassz műveletet!
9File → ÚjA mezők és az eredmények törlődnek.
10AboutA készítő neve látszik.
11File → Kilépés, majd NemAz ablak nyitva marad.
12File → Kilépés, majd IgenAz ablak bezárul.
13Egér a név mezőnSú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.

Ok és javítás

A kódban szereplő szöveg és a ComboBox elemei nem egyeznek (például "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.

Ok és javítás

Hiányzik az 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 context
Hiba é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:

MűködésA tesztlista mind a 13 pontja teljesül.
NevekMinden vezérlő beszédes előtagos nevet kapott (txt, btn, lbl, nud stb.).
SzerkezetA kód metódusokra van bontva, nincs kétszer megírt ugyanaz a rész.
Hibák kezeléseÜres, hibás és szélsőséges bevitelre is értelmesen reagál a program (üres név, betűs szám, nullával osztás).
HasználhatóságAz üzenetek érthetőek, színesek és eltűnnek, a hibajelzés a hibás mező mellett látszik.
KülalakAz ablak rendezett, a vezérlők nincsenek egymásra csúszva, a feliratok olvashatók.
📷 Képernyőkép helye

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 ^ 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);
}

A 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 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

A 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 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

Mindkét mező 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.

Form1.csC#
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 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

Az 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.

Form1.csC#
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 lstElozmenyek ListBoxba: 12 + 8 = 20. A legújabb legyen legfelül.

Megoldás megnyitása

Az Items.Insert(0, elem) az elemet a lista elejére illeszti be, így a legújabb látszik elsőként.

Form1.csC#
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 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

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ó.

Form1.csC#
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 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

A végső ár: ár × (100 − kedvezmény) / 100. A NumericUpDown értéke decimal, ezért (double) átalakítással használjuk a double számításban.

Form1.csC#
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 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

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.

Form1.csC#
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.

1. Miért bontottuk a kódot külön metódusokra?

Hogy áttekinthető legyen, és ugyanaz a kód ne forduljon elő kétszer. Az Uzenet például minden eseménykezelőből ugyanúgy hívható.

2. Mit jelent a return a kalkulátor hibás ágaiban?

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.

3. Miért kell az ErrorProvider hibajelzést törölni helyes bevitelnél?

Mert a jelzés magától nem tűnik el.

4. Mikor jobb a Label üzenet, mint a MessageBox?

Apró tájékoztatásnál, amely nem zavarja a munkát. A MessageBox kérdéshez vagy fontos figyelmeztetéshez való.

5. Melyik komponens tünteti el az üzenetet három másodperc után?

A Timer (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.