Grafikus C# – Windows Forms tanfolyam › 8.8 lecke Windows Forms · 8.8 lecke · 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 CellValidating és adatellenőrzés
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.
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.
A szerkesztés eseményei és a két stratégia
| # | Esemény | Mikor? |
|---|---|---|
| 1 | CellBeginEdit | A szerkesztés kezdetén (letiltható: e.Cancel = true, például egy csak olvasható cellánál). |
| 2 | EditingControlShowing | Megjelenik a szerkesztő vezérlő (általában TextBox): itt állítható a beírható karakterek köre. |
| 3 | CellValidating | A 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. |
| 4 | CellValidated | Az érvényesítés sikeres volt (a cella értéke még nem feltétlenül a végleges). |
| 5 | CellEndEdit | A szerkesztés befejeződött, az érték a cellában van: itt törölhető a hibajel. |
| 6 | RowValidating | A 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:
// 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 = "";
}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.IsNewRow sort.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.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:
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)
}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…”).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).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.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:
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
}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.CellValidating fogja meg. A két réteg együtt ad jó élményt.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…).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.
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. A Ok és javítás
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. Az Ok és javítás
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. Az Ok és javítás
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élHiba é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. A mentés előtt nincs teljes ellenőrzés. Hívd a Ok és javítás
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. A Ok és javítás
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:
- Adat:
Termekosztály (Kod,Nev,Ardecimal,Keszletint,AkciosArdecimal?),BindingList<Termek>a rácson; oszlopnevek:colKod,colNev,colAr,colKeszlet,colAkcios. A rácsAllowUserToAddRows = true(új sor a rács alján). - Szabályok (a
CellaHiba/SorHibametódusokban): Kód:AA-123formá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). - Cellaszint: a szám- és kódhibákat
CellValidatingjelzi (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. - Sorszint:
RowValidatingaz akciós ár szabályra (jelző, nem kényszerít). - 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). - DataError: saját, érthető üzenet az állapotsorban.
- Hibalista: a
lblHibaka hibák számát mutatja (✖ 3 hiba vagy ✔ nincs hiba), a Következő hiba gomb a következő hibás cellára ugrik. - 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). - 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.
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.";
}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 A éééé. 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
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).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 Egy kis 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
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.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? A Egy lehetséges megoldás
CellBeginEdit eseményt: e.Cancel = true megakadályozza a szerkesztés kezdetét, ha a sor nem új.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? Szöveget (a beírt értéket a konverzió előtt): Megoldás megnyitása
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? Végigmész a sorokon, kihagyod az aktuális (Megoldás megnyitása
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 Önálló mezőknél (űrlap) az ErrorProvidert, és mikor ErrorTextet?Megoldás megnyitása
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 A szabály nem függ a rácstól, ezért egységteszttel vagy a TermekEllenorzo osztályba (nincs felület!), amelyet teszteléssel is ellenőrizhetsz. Mi az előnye?Megoldás megnyitása
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.)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? Az utolsó érvényes értéket cellánként (például a Megoldás megnyitása
CellBeginEdit eseményben megjegyzed az eredeti értéket egy szótárba). A visszaállítás a szótárból írja vissza.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. A A felhasználót a cellában tartja, amíg az érték nem javul vagy az Esc vissza nem vonja. A Az üres új sor elhagyása is kiváltja az eseményt, és hibát jelezne. A rács újrahasznosítja a szerkesztő vezérlőt: a feliratkozások halmozódnának. A felhasználó a jelzett hibát figyelmen kívül hagyhatja, és beillesztéssel is megkerülheti a karakterszűrést.1. Melyik esemény ellenőrzi a cella értékét?
CellValidating: itt az e.FormattedValue a beírt szöveg.2. Mit tesz az e.Cancel = true?
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?
RowValidating eseménybe (a sor elhagyásakor).5. Miért kell kihagyni az IsNewRow sort?
6. Miért kell leiratkozni az EditingControlShowing kezelőben?
7. Miért kell mentés előtt újra ellenőrizni mindent?
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,
ErrorTextjelzéssel. - Korlátozod a bevitelt, és saját üzenetet adsz a
DataErroresemé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.




