Grafikus C# – Windows Forms tanfolyam › 8.8 lecke

Windows Forms · 8.8 lecke · adatellenőrzés

CellValidating és adatellenőrzés

A táblázatba bárki bármit beírhat: betűt a számmezőbe, negatív árat, üres nevet, kétszer ugyanazt a kódot. Ha ezt nem fogod meg, a program hibás adattal dolgozik tovább, vagy a rács csúnya kivételablakot dob. Az 1.11 leckében az ErrorProviderrel a mezők ellenőrzését tanultad; a DataGridView saját eseményeket és hibajelzőket használ. Ebben a leckében megtanulod a szerkesztés eseményeinek sorrendjét, a CellValidating és RowValidating használatát, a hiba jelzését (ErrorText), a beírható karakterek korlátozását és a DataError kezelését.

  • kb. 110 perc
  • közepes–haladó szint
  • adatellenőrzés
Diák C# programozást gyakorol egy tanulósarokban

A lecke végére

  • ismered a cellaszerkesztés eseményeinek sorrendjét (CellBeginEdit, CellValidating, CellValidated, CellEndEdit);
  • cellaszinten ellenőrzöl (e.FormattedValue, e.Cancel), és megkülönbözteted a kényszerítő és a jelző megoldást;
  • hibát jelzel a cellán és a soron (ErrorText), és törlöd, ha az érték javult;
  • sorszintű szabályt írsz (RowValidating: több cella együtt);
  • korlátozod a beírható karaktereket (EditingControlShowing), a hibás konverziót kezeled (DataError);
  • mentés előtt az egész táblázatot ellenőrzöd.

Mielőtt elkezded

Ehhez a leckéhez a 8.3–8.4 lecke (DataGridView, BindingList), az 1.11 és 6.6 lecke (ErrorProvider, Validating) és az 5.7 lecke (feliratkozás és leiratkozás) szükséges.

8.7 Rendezés, keresés, szűrés →

A szerkesztés eseményei és a két stratégia

#EseményMikor?
1CellBeginEditA szerkesztés kezdetén (letiltható: e.Cancel = true, például egy csak olvasható cellánál).
2EditingControlShowingMegjelenik a szerkesztő vezérlő (általában TextBox): itt állítható a beírható karakterek köre.
3CellValidatingA cellát el akarják hagyni: itt ellenőrzünk; az e.FormattedValue a beírt szöveg, az e.Cancel = true a cellában tartja a felhasználót.
4CellValidatedAz érvényesítés sikeres volt (a cella értéke még nem feltétlenül a végleges).
5CellEndEditA szerkesztés befejeződött, az érték a cellában van: itt törölhető a hibajel.
6RowValidatingA sort elhagyják: sorszintű (több cellát érintő) szabályok.

Kényszerítő stratégia

A CellValidating e.Cancel = true értékkel a felhasználót a cellában tartja, amíg az érték nem javul (vagy Esc-pel vissza nem vonja). Előny: hibás adat nem kerül a táblába. Hátrány: bosszantó, ha a felhasználó máshová szeretne kattintani (például a Mégse gombra), a CausesValidation beállítással kezelhető (6.6 lecke).

Jelző stratégia

A hibát nem akadályozza, csak jelzi (ErrorText, piros felkiáltójel a cellán), és a program mentés előtt ellenőrzi az egészet. Előny: szabadabb munka (kitöltés tetszőleges sorrendben). Hátrány: hibás adat átmenetileg a táblában van, ezért a mentés előtti ellenőrzés kötelező.

Melyiket válaszd?

Rövid, sok adatot gyorsan bevivő űrlapnál a kényszerítő mód zavaró lehet. Egyszerű, egy mezős szabálynál (szám kell) használható. Összetett táblázatnál a jelző (ErrorText) a barátságosabb, de a szabályoknak egy helyen kell lenniük, hogy a mentéskor is ugyanazt ellenőrizd. Ebben a leckében a kettőt kombináljuk: a típushiba (nem szám) kényszerít, a tartalmi hiba jelez.

CellValidating: oszloponkénti szabályok

Az e.FormattedValue mindig szöveg: a felhasználó által beírt érték, még a konverzió előtt. A számot ezért magadnak kell értelmezned (TryParse). A szabályokat egy metódusba tesszük, amely a hibaüzenetet adja (üres szöveg: nincs hiba), így a mentés előtti ellenőrzés is ugyanezt használja:

Form1.cs (using System.Globalization; using System.Text.RegularExpressions;)C#
// az egy cellára vonatkozó szabály: a hibaüzenetet adja ("", ha rendben van)
private string CellaHiba(string oszlop, string szoveg, int sorIndex)
{
szoveg = (szoveg ?? "").Trim();

switch (oszlop)
{
case "colNev":
if (szoveg.Length == 0) { return "A név megadása kötelező."; }
if (szoveg.Length > 40) { return "A név legfeljebb 40 karakter lehet."; }
return "";

case "colAr":
decimal ar;

if (!decimal.TryParse(szoveg, NumberStyles.Number, CultureInfo.CurrentCulture, out ar)) { return "Az ár szám legyen (például 1250)."; }
if (ar <= 0) { return "Az ár legyen nagyobb nullánál."; }
if (ar > 1000000) { return "Az ár legfeljebb 1 000 000 lehet."; }
return "";

case "colKeszlet":
int db;

if (!int.TryParse(szoveg, NumberStyles.Integer, CultureInfo.CurrentCulture, out db)) { return "A készlet egész szám legyen."; }
if (db < 0 || db > 9999) { return "A készlet 0 és 9999 között lehet."; }
return "";
}

return "";
}

private void dgv_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (dgv.Rows[e.RowIndex].IsNewRow)
{
return; // az üres „új sor” nem ellenőrzendő
}

string oszlop = dgv.Columns[e.ColumnIndex].Name;
string hiba = CellaHiba(oszlop, Convert.ToString(e.FormattedValue), e.RowIndex);

dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = hiba; // jelző: piros ikon a cellán

if (hiba.Length > 0 && (oszlop == "colAr" || oszlop == "colKeszlet"))
{
// kényszerítő: a számmező nem hagyható el, amíg nem szám (Esc: vissza az előző értékre)
e.Cancel = true;
}
}

private void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
// az elfogadott értéknél a hibajel törlődik
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";
}
e.FormattedValueA cella szerkesztett szövege. A számot a TryParse alakítja, a gép kultúrájának megfelelően (magyar gépen tizedesvessző). A Convert.ToString a null értéket is kezeli.
IsNewRowA rács alján álló üres „új sor” is kiváltja az eseményt, amikor elhagyják: ilyenkor a nevet „kötelezőnek” látná. Mindig hagyd ki az IsNewRow sort.
ErrorTextA Cell.ErrorText piros ikont tesz a cellára, az egér fölé víve a szöveg látszik. A sor ErrorText tulajdonsága a sorfejlécben jelez. Üres szöveg: nincs hiba.
Egy helyen a szabályA CellaHiba metódus független az eseménytől, ezért a mentéskor is hívható (összes cella ellenőrzése), és tesztelhető. Az üzenet mondja meg, mit kell tenni, nem csak azt, hogy rossz.

Jósolj!

A CellValidating kezelőben e.Cancel = true van, a felhasználó viszont betűket írt a készlet cellába, és meggondolta magát: inkább nem szeretne adatot beírni. Hogyan tud kilépni a cellából?

Jóslat ellenőrzése

Az Esc billentyűvel (a szerkesztés megszakítása, a cella a korábbi értékét kapja vissza). Ezt érdemes a hibaüzenetben is megírni (Esc: visszavonás), különben a felhasználó „csapdában” érzi magát. Másik cellára kattintás vagy a Tab a kényszerítés miatt nem működik.

RowValidating: szabályok több cellára

Vannak szabályok, amelyek több cellát érintenek: az akciós ár kisebb a normál árnál, a kezdő dátum a végdátum előtt van. Ezt nem lehet egy cellánál ellenőrizni (a másik még nincs kitöltve), ezért a sor elhagyásakor, a RowValidating eseményben vizsgáljuk. A cellák ekkor már a végleges értékeket tartalmazzák:

Form1.csC#
private string SorHiba(DataGridViewRow sor)
{
decimal ar, akcios;

bool vanAr = decimal.TryParse(Convert.ToString(sor.Cells["colAr"].Value), out ar);
string akciosSzoveg = Convert.ToString(sor.Cells["colAkcios"].Value);

if (akciosSzoveg.Trim().Length == 0)
{
return ""; // az akciós ár nem kötelező
}

if (!decimal.TryParse(akciosSzoveg, out akcios))
{
return "Az akciós ár szám legyen.";
}

if (vanAr && akcios >= ar)
{
return "Az akciós ár legyen kisebb a normál árnál.";
}

return "";
}

private void dgv_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
DataGridViewRow sor = dgv.Rows[e.RowIndex];

if (sor.IsNewRow)
{
return;
}

sor.ErrorText = SorHiba(sor); // jelző: a sorfejlécben piros ikon (nem kényszerít)
}
Mikor fut?A RowValidating akkor, ha a felhasználó másik sorra lép (vagy a sor módosult). Cella-szinten ez késő lenne (nem derül ki, melyik cellát kell javítani), ezért a sor üzenete utal a cellákra („az akciós ár…”).
Sor jelzőA Row.ErrorText a sorfejléc ikonja. Üres szöveggel törlődik. Ha a sorfejléc el van rejtve (RowHeadersVisible = false), nem látszik: a hibát ilyenkor máshol jelezd (alsó hibasor).
Kényszeríteni?A e.Cancel = true a sorban tartaná a felhasználót, ami több cella javítása esetén zavaró. A sorszintű szabálynál általában a jelző mód (és a mentés előtti ellenőrzés) a barátságosabb.
Mentés előtt újraA felhasználó a hibás sort el is hagyhatja és a jelzést figyelmen kívül hagyhatja: a mentés előtt mindig újra ellenőrizd az összes sort ugyanazokkal a szabályokkal.

Jósolj!

Az akciós ár és az ár szabályát a CellValidating eseményben ellenőrzöd az akciós ár oszlopnál (a normál árat a sorból olvasva). A felhasználó előbb az akciós árat tölti ki, és csak utána az árat. Mi a gond?

Jóslat ellenőrzése

Az akciós ár ellenőrzésekor az ár még üres vagy a régi érték, ezért a szabály helytelenül fut (hibát ad, vagy elfogad egy későbbi hibás párost). Az ár módosítása utáni újabb ellenőrzést a cellaszintű szabály nem végzi el. A több cellát érintő szabálynak a sor szintjén (RowValidating) a helye.

Beírható karakterek korlátozása és a DataError

A legjobb hiba az, amit meg sem lehet elkövetni: a számmezőbe ne is lehessen betűt írni. A szerkesztő vezérlő az EditingControlShowing eseményben érhető el. Fontos, hogy a kezelőt leiratkoztatod (5.7 lecke): a szerkesztő vezérlőt a rács újrahasznosítja, ezért a kezelő különben többszörösen halmozódna:

Form1.csC#
private void dgv_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
TextBox mezo = e.Control as TextBox;

if (mezo == null)
{
return;
}

mezo.KeyPress -= SzamKarakter_KeyPress; // előbb leiratkozunk: nem halmozódik
mezo.MaxLength = 40;

string oszlop = dgv.Columns[dgv.CurrentCell.ColumnIndex].Name;

if (oszlop == "colKeszlet" || oszlop == "colAr")
{
mezo.MaxLength = 9;
mezo.KeyPress += SzamKarakter_KeyPress;
}
}

private void SzamKarakter_KeyPress(object sender, KeyPressEventArgs e)
{
string tizedes = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

// szabad: számjegy, vezérlőkarakter (törlés), tizedesjel
if (!char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar) && e.KeyChar.ToString() != tizedes)
{
e.Handled = true; // a billentyűt elnyeljük
}
}

// ha minden más mellett mégis konverziós hiba lép fel (például a kötött oszlop típusa miatt)
private void dgv_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
tsslUzenet.Text = "Érvénytelen érték a(z) " + (e.RowIndex + 1) + ". sorban. Javítsd, vagy nyomj Esc-et.";
e.ThrowException = false; // ne dobjon kivételt: az alapértelmezett hibaablak helyett a saját üzenet
}
e.HandledA KeyPress Handled = true értéke elnyeli a billentyűt, a mezőbe nem kerül be. A másolás–beillesztés (Ctrl+V) ezt megkerüli, ezért a CellValidating szabály akkor is kell.
Dupla védelemA karakterszűrés kényelmi funkció, nem biztonság: a beillesztett szöveget a CellValidating fogja meg. A két réteg együtt ad jó élményt.
DataErrorHa a rács nem tud egy értéket az oszlop típusára alakítani (kötött oszlop), a DataError eseményt váltja ki, és alapból kivételablakot mutat (angol szöveggel). A saját kezelő érthető üzenetet ad. A e.Context megmondja, milyen művelet hibázott (Parsing, Commit…).
LeiratkozásA rács ugyanazt a TextBox példányt újrahasznosítja különböző cellákhoz: a -= előbb eltávolítja a korábbi kezelőt, különben a második szerkesztéskor kétszer futna.

Jósolj!

Az EditingControlShowing kezelő mindig csak feliratkozik (mezo.KeyPress += ...), de sosem iratkozik le. A felhasználó háromszor szerkeszt egy számcellát. Hányszor fut le a SzamKarakter_KeyPress egy billentyűleütéskor?

Jóslat ellenőrzése

Háromszor (vagy annyiszor, ahányszor szerkesztettek): a rács ugyanazt a TextBox példányt használja újra, és minden szerkesztés újabb kezelőt ad hozzá. Itt még csak lassít, de más kezelőnél (például számláló, mentés) hibás eredményt adna. Ezért a -= előbb.

📷 Képernyőkép helye

Mit tegyél ide: a táblázat piros hibajelekkel (cellán és a sorfejlécben), az egér alatt a hibaüzenet-súgóval és az alsó hibalistával.Javasolt fájlnév: winforms-8-8-hibajel.webp · Alt szöveg: Hibajelzés a táblázat celláin és sorain

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. Az üres „új sor” hibát jelez

A rács alján a még ki nem töltött sor minden cellája piros.

Ok és javítás

A CellValidating a IsNewRow sort is ellenőrzi. Hagyd ki: if (dgv.Rows[e.RowIndex].IsNewRow) return;.

2. A hibajel nem tűnik el a javítás után

A cella értéke már rendben van, a piros ikon marad.

Ok és javítás

Az ErrorText-et törölni kell: a CellValidating kezelőben minden alkalommal be kell állítani (üres szöveggel, ha nincs hiba), vagy a CellEndEdit eseményben törölni.

3. A felhasználó nem tud kilépni a cellából

Hibás szám után a Tab és az egér sem visz tovább.

Ok és javítás

Az e.Cancel = true a cellában tart. Írd ki az üzenetben, hogy az Esc visszavon, vagy válaszd a jelző stratégiát (csak ErrorText, kényszerítés nélkül).

4. A KeyPress szűrő többször fut

mezo.KeyPress += SzamKarakter_KeyPress;   // leiratkozás nélkül, minden szerkesztésnél
Hiba és javítás

A rács újrahasznosítja a szerkesztő vezérlőt. Előbb -=, utána += (és csak a számoszlopoknál).

5. Hibás adat kerül a mentett fájlba

A felhasználó a jelzett hibát figyelmen kívül hagyta, és mentett.

Ok és javítás

A mentés előtt nincs teljes ellenőrzés. Hívd a CellaHiba és SorHiba metódust az összes soron, és hiba esetén ne mentsd (jelezd, hány hiba van, és ugorj az elsőre).

6. Angol kivételablak jelenik meg a rácson

Hibás típusú érték beírásakor „DataGridViewComboBoxCell value is not valid” vagy hasonló ablak ugrik fel.

Ok és javítás

A DataError eseményt nem kezeled. Írj saját kezelőt (e.ThrowException = false) érthető üzenettel, és előzd meg a hibát (CellValidating, karakterszűrés).

Mini projekt: validált árlista-szerkesztő

Készíts árlista-szerkesztőt, amely nem enged hibás adatot a mentésig:

  1. Adat: Termek osztály (Kod, Nev, Ar decimal, Keszlet int, AkciosAr decimal?), BindingList<Termek> a rácson; oszlopnevek: colKod, colNev, colAr, colKeszlet, colAkcios. A rács AllowUserToAddRows = true (új sor a rács alján).
  2. Szabályok (a CellaHiba/SorHiba metódusokban): Kód: AA-123 formájú (^[A-Z]{2}-\d{3}$) és egyedi; Név: kötelező, legfeljebb 40 karakter; Ár: szám, 0 < ár ≤ 1 000 000; Készlet: egész, 0–9999; Akciós ár: üres vagy szám, kisebb a normál árnál (sorszint).
  3. Cellaszint: a szám- és kódhibákat CellValidating jelzi (ErrorText); az Ár és Készlet oszlopnál kényszerít is (e.Cancel); a hibaüzenet leírja a teendőt, és említi az Esc billentyűt.
  4. Sorszint: RowValidating az akciós ár szabályra (jelző, nem kényszerít).
  5. Bevitel: az Ár és Készlet oszlopban csak számjegy és tizedesjel írható (EditingControlShowing + leiratkozás); a Kód mezőben automatikus nagybetűsítés (CharacterCasing.Upper).
  6. DataError: saját, érthető üzenet az állapotsorban.
  7. Hibalista: a lblHibak a hibák számát mutatja (✖ 3 hiba vagy ✔ nincs hiba), a Következő hiba gomb a következő hibás cellára ugrik.
  8. Mentés: a Mentés gomb először ellenőrzi az összes sort (MindEllenoriz()); hibánál nem ment, és az első hibás cellára ugrik; hiba nélkül egy CSV-be ment (a 8.9 leckében részletesen).
  9. Teszteld: betű az ár cellában (beillesztéssel is), negatív ár, üres név, ismétlődő kód, rossz kódforma, akciós ár nagyobb az árnál, az új sor, Esc, mentés hibával és hiba nélkül.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A szabályok a CellaHiba/SorHiba metódusokban vannak, a MindEllenoriz() mentés előtt végigmegy a sorokon, és ugyanezeket hívja, a hibákat az ErrorText tulajdonságba írva.

Form1.cs (using System.ComponentModel; using System.Globalization; using System.Text.RegularExpressions;)C#
private BindingList<Termek> lista = new BindingList<Termek>();

private void Form1_Load(object sender, EventArgs e)
{
lista.Add(new Termek { Kod = "IR-001", Nev = "Füzet", Ar = 300, Keszlet = 40 });
lista.Add(new Termek { Kod = "IR-002", Nev = "Toll", Ar = 150, Keszlet = 120, AkciosAr = 120 });

dgv.AutoGenerateColumns = false;
dgv.DataSource = lista;
colAkcios.DefaultCellStyle.NullValue = "";
HibakFrissit();
}

private string CellaHiba(string oszlop, string szoveg, int sorIndex)
{
szoveg = (szoveg ?? "").Trim();

switch (oszlop)
{
case "colKod":
if (!Regex.IsMatch(szoveg, @"^[A-Z]{2}-\d{3}$")) { return "A kód alakja AA-123 legyen (két nagybetű, kötőjel, három számjegy)."; }

for (int i = 0; i < dgv.Rows.Count; i++)
{
if (i != sorIndex && !dgv.Rows[i].IsNewRow && string.Equals(Convert.ToString(dgv.Rows[i].Cells["colKod"].Value), szoveg, StringComparison.OrdinalIgnoreCase))
{
return "Ez a kód már szerepel a listában.";
}
}

return "";

case "colNev":
if (szoveg.Length == 0) { return "A név megadása kötelező."; }
return szoveg.Length > 40 ? "A név legfeljebb 40 karakter lehet." : "";

case "colAr":
decimal ar;
if (!decimal.TryParse(szoveg, NumberStyles.Number, CultureInfo.CurrentCulture, out ar)) { return "Az ár szám legyen (Esc: visszavonás)."; }
return ar <= 0 || ar > 1000000 ? "Az ár 0 és 1 000 000 között legyen." : "";

case "colKeszlet":
int db;
if (!int.TryParse(szoveg, NumberStyles.Integer, CultureInfo.CurrentCulture, out db)) { return "A készlet egész szám legyen (Esc: visszavonás)."; }
return db < 0 || db > 9999 ? "A készlet 0 és 9999 között lehet." : "";
}

return "";
}

private string SorHiba(DataGridViewRow sor)
{
string akcios = Convert.ToString(sor.Cells["colAkcios"].Value).Trim();

if (akcios.Length == 0) { return ""; }

decimal a, ar;

if (!decimal.TryParse(akcios, out a)) { return "Az akciós ár szám legyen."; }

if (decimal.TryParse(Convert.ToString(sor.Cells["colAr"].Value), out ar) && a >= ar)
{
return "Az akciós ár legyen kisebb a normál árnál.";
}

return "";
}

private void dgv_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (dgv.Rows[e.RowIndex].IsNewRow) { return; }

string oszlop = dgv.Columns[e.ColumnIndex].Name;
string hiba = CellaHiba(oszlop, Convert.ToString(e.FormattedValue), e.RowIndex);

dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = hiba;

if (hiba.Length > 0 && (oszlop == "colAr" || oszlop == "colKeszlet"))
{
e.Cancel = true;
}
}

private void dgv_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (!dgv.Rows[e.RowIndex].IsNewRow)
{
dgv.Rows[e.RowIndex].ErrorText = SorHiba(dgv.Rows[e.RowIndex]);
}
}

private void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";
HibakFrissit();
}

private void dgv_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
TextBox mezo = e.Control as TextBox;

if (mezo == null) { return; }

mezo.KeyPress -= SzamKarakter_KeyPress;
string oszlop = dgv.Columns[dgv.CurrentCell.ColumnIndex].Name;

mezo.CharacterCasing = oszlop == "colKod" ? CharacterCasing.Upper : CharacterCasing.Normal;
mezo.MaxLength = oszlop == "colNev" ? 40 : (oszlop == "colKod" ? 6 : 12);

if (oszlop == "colAr" || oszlop == "colKeszlet" || oszlop == "colAkcios")
{
mezo.KeyPress += SzamKarakter_KeyPress;
}
}

private void SzamKarakter_KeyPress(object sender, KeyPressEventArgs e)
{
string tizedes = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

if (!char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar) && e.KeyChar.ToString() != tizedes)
{
e.Handled = true;
}
}

private void dgv_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
tsslUzenet.Text = "Érvénytelen érték a(z) " + (e.RowIndex + 1) + ". sorban. Javítsd, vagy nyomj Esc-et.";
e.ThrowException = false;
}

// az összes sor ellenőrzése: a hibák száma; az első hibás cella megjegyezve
private int MindEllenoriz(out DataGridViewCell elso)
{
int hibak = 0;
elso = null;

foreach (DataGridViewRow sor in dgv.Rows)
{
if (sor.IsNewRow) { continue; }

foreach (DataGridViewCell cella in sor.Cells)
{
string hiba = CellaHiba(dgv.Columns[cella.ColumnIndex].Name, Convert.ToString(cella.Value), sor.Index);
cella.ErrorText = hiba;

if (hiba.Length > 0) { hibak++; if (elso == null) { elso = cella; } }
}

sor.ErrorText = SorHiba(sor);

if (sor.ErrorText.Length > 0) { hibak++; if (elso == null) { elso = sor.Cells["colAkcios"]; } }
}

return hibak;
}

private void HibakFrissit()
{
DataGridViewCell elso;
int db = MindEllenoriz(out elso);

lblHibak.Text = db == 0 ? "✔ Nincs hiba" : "✖ " + db + " hiba";
}

private void btnKovetkezoHiba_Click(object sender, EventArgs e)
{
DataGridViewCell elso;

if (MindEllenoriz(out elso) > 0 && elso != null)
{
dgv.CurrentCell = elso;
}
}

private void btnMent_Click(object sender, EventArgs e)
{
DataGridViewCell elso;
int db = MindEllenoriz(out elso);

if (db > 0)
{
MessageBox.Show(this, db + " hiba van a táblázatban. A mentés előtt javítsd őket.", "Mentés", MessageBoxButtons.OK, MessageBoxIcon.Warning);
dgv.CurrentCell = elso;
return;
}

// a mentés a 8.9 leckében
tsslUzenet.Text = "A táblázat érvényes, mentésre kész.";
}
📷 Képernyőkép helye

Mit tegyél ide: a szerkesztő hibás sorokkal, piros jelekkel és az alsó „✖ 3 hiba” felirattal.Javasolt fájlnév: winforms-8-8-kesz.webp · Alt szöveg: A validált árlista-szerkesztő

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. Dátum oszlop ellenőrzése

A Lejárat oszlopba éééé. hh. nn. dátumot kell írni, amely nem lehet a múltban. Hogyan ellenőrzöd a CellValidating eseményben?

Egy lehetséges megoldás

A DateTime.TryParse segítségével (a gép kultúrájával), és az eredmény összehasonlításával a mai nappal (DateTime.Today).

Form1.csC#
case "colLejarat":
DateTime datum;

if (!DateTime.TryParse(szoveg, CultureInfo.CurrentCulture, DateTimeStyles.None, out datum))
{
return "A dátum alakja éééé. hh. nn. legyen.";
}

return datum < DateTime.Today ? "A lejárat nem lehet a múltban." : "";

2. Hibák listája egy panelen

A hibák egy ListBox-ban jelenjenek meg (3. sor, Ár: az ár szám legyen), és a listaelemre dupla kattintva a rács az adott cellára ugorjon. Hogyan őrzöd a cellát a listaelemben?

Egy lehetséges megoldás

Egy kis HibaTetel osztály (sor, oszlop, üzenet, ToString()), amelyet a listába teszel; a dupla kattintás a SelectedItem adataiból állítja a CurrentCell értéket.

Form1.csC#
public class HibaTetel
{
public int Sor;
public string Oszlop = "";
public string Uzenet = "";

public override string ToString() { return (Sor + 1) + ". sor, " + Oszlop + ": " + Uzenet; }
}

private void lstHibak_DoubleClick(object sender, EventArgs e)
{
HibaTetel h = lstHibak.SelectedItem as HibaTetel;

if (h != null)
{
dgv.CurrentCell = dgv.Rows[h.Sor].Cells[h.Oszlop];
dgv.Focus();
}
}

3. Szerkesztés tiltása

A Kód oszlop csak új sornál szerkeszthető (meglévő terméknél nem). Melyik eseményt használod?

Egy lehetséges megoldás

A CellBeginEdit eseményt: e.Cancel = true megakadályozza a szerkesztés kezdetét, ha a sor nem új.

Form1.csC#
private void dgv_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
if (dgv.Columns[e.ColumnIndex].Name == "colKod" && !dgv.Rows[e.RowIndex].IsNewRow)
{
e.Cancel = true; // a meglévő termék kódja nem módosítható
}
}

Gyakorlótár: 6 további feladat

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

Alap szint

1. Az események sorrendje

Állítsd sorrendbe: CellEndEdit, CellValidating, CellBeginEdit, CellValidated.

Megoldás megnyitása

CellBeginEdit, CellValidating, CellValidated, CellEndEdit.

2. Mit ad az e.FormattedValue?

Milyen típusú, és mit kell vele tenni egy számoszlopnál?

Megoldás megnyitása

Szöveget (a beírt értéket a konverzió előtt): TryParse alakítja számmá.

Közepes szint

3. Ismétlődés ellenőrzése

A Név oszlop egyedi, kis- és nagybetűtől függetlenül. Hogyan ellenőrzöd, és mire figyelsz a saját sorral?

Megoldás megnyitása

Végigmész a sorokon, kihagyod az aktuális (e.RowIndex) és az új sort, és StringComparison.CurrentCultureIgnoreCase összehasonlítást használsz. Nagy listánál (több ezer sor) gyorsabb egy HashSet.

4. ErrorProvider vagy ErrorText?

Mikor használsz ErrorProvidert, és mikor ErrorTextet?

Megoldás megnyitása

Önálló mezőknél (űrlap) az ErrorProvider, a DataGridView celláin és sorain az ErrorText: a rács saját hibajelzőt kínál, a cellákhoz nem is kötődik ErrorProvider.

Kihívás szint

5. Szabályok osztályba szervezve

Szervezd át a szabályokat egy TermekEllenorzo osztályba (nincs felület!), amelyet teszteléssel is ellenőrizhetsz. Mi az előnye?

Megoldás megnyitása

A szabály nem függ a rácstól, ezért egységteszttel vagy a Debug.Assert segítségével ellenőrizhető, más felületen (űrlap, import) is felhasználható. A rács csak megjeleníti az eredményt. (A 12. fejezetben a felület és a logika szétválasztását részletezzük.)

TermekEllenorzo.csC#
public static class TermekEllenorzo
{
public static string Nev(string szoveg)
{
szoveg = (szoveg ?? "").Trim();

if (szoveg.Length == 0) { return "A név megadása kötelező."; }
return szoveg.Length > 40 ? "A név legfeljebb 40 karakter lehet." : "";
}

public static string Ar(string szoveg)
{
decimal ar;

if (!decimal.TryParse(szoveg, out ar)) { return "Az ár szám legyen."; }
return ar <= 0 || ar > 1000000 ? "Az ár 0 és 1 000 000 között legyen." : "";
}
}

6. Visszavonás kényszerítés nélkül

A felhasználó hibás értéket hagyott a cellában (jelző mód). Írj Hibás cellák visszaállítása parancsot, amely a hibás cellák értékét az utolsó érvényesre állítja. Mit kell nyilvántartanod?

Megoldás megnyitása

Az utolsó érvényes értéket cellánként (például a CellBeginEdit eseményben megjegyzed az eredeti értéket egy szótárba). A visszaállítás a szótárból írja vissza.

Form1.csC#
private readonly Dictionary<string, object> elozoErtek = new Dictionary<string, object>();

private void dgv_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
elozoErtek[e.RowIndex + ":" + e.ColumnIndex] = dgv[e.ColumnIndex, e.RowIndex].Value;
}

private void Visszaallit(DataGridViewCell cella)
{
object regi;

if (elozoErtek.TryGetValue(cella.RowIndex + ":" + cella.ColumnIndex, out regi))
{
cella.Value = regi;
cella.ErrorText = "";
}
}

Gyors önellenőrzés

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

1. Melyik esemény ellenőrzi a cella értékét?

A CellValidating: itt az e.FormattedValue a beírt szöveg.

2. Mit tesz az e.Cancel = true?

A felhasználót a cellában tartja, amíg az érték nem javul vagy az Esc vissza nem vonja.

3. Hogyan jelzel hibát a cellán és a soron?

Cell.ErrorText és Row.ErrorText; üres szöveggel törlődik.

4. Hova való a több cellát érintő szabály?

A RowValidating eseménybe (a sor elhagyásakor).

5. Miért kell kihagyni az IsNewRow sort?

Az üres új sor elhagyása is kiváltja az eseményt, és hibát jelezne.

6. Miért kell leiratkozni az EditingControlShowing kezelőben?

A rács újrahasznosítja a szerkesztő vezérlőt: a feliratkozások halmozódnának.

7. Miért kell mentés előtt újra ellenőrizni mindent?

A felhasználó a jelzett hibát figyelmen kívül hagyhatja, és beillesztéssel is megkerülheti a karakterszűrést.

Mit tudsz már?

  • Ismered a szerkesztés eseményeinek sorrendjét, és a két ellenőrzési stratégiát.
  • Cellaszinten és sorszinten ellenőrzöl, ErrorText jelzéssel.
  • Korlátozod a bevitelt, és saját üzenetet adsz a DataError eseményre.
  • Mentés előtt az egész táblázatot ellenőrzöd, a szabályokat egy helyen tartva.
  • Felismered a gyakori hibákat (IsNewRow, halmozódó kezelők).

Következő lecke: Táblázat adatainak mentése és betöltése

A táblázat tartalmának tartós tárolása: CSV és XML, a hibás sorok kezelése importáláskor, a mentetlen változtatások jelzése.