Grafikus C# – Windows Forms tanfolyam › 8.7 lecke

Windows Forms · 8.7 lecke · rendezés és szűrés

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

Száz sor fölött a táblázatban már nem lehet „átfutni” az adatokat: kell a rendezés (a legolcsóbb elöl), a szűrés (csak a papírok) és a keresés (hol van a „radír”). A DataGridView ezekhez nem ad mindent magától: attól függ, mire kötötted. Ebben a leckében megtanulod, miért nem rendezhető a BindingList, hogyan oldja meg a DataTable és a DataView (Sort, RowFilter), hogyan szűrsz LINQ-val egy sima listát, hogyan lesz a keresés ékezetfüggetlen, és hogyan építesz kényelmes kereső-felületet.

  • kb. 110 perc
  • közepes szint
  • rendezés és szűrés
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • tudod, mely adatforrásnál rendezhető automatikusan a DataGridView (és mely nem);
  • kézzel rendezel listát (List.Sort) magyar ábécé szerint, rendezési nyíllal (SortGlyphDirection);
  • a BindingSource.Sort és Filter tulajdonságot használod DataTable felett;
  • helyes RowFilter kifejezést építesz (idézőjelek, LIKE, számok, hibák);
  • LINQ-val szűrsz listát, és ékezetfüggetlen keresést írsz;
  • kereső felületet készítesz (szöveg, kategória, tartomány, találatszám, üres eredmény).

Mielőtt elkezded

Ehhez a leckéhez a 8.3–8.4 lecke (DataGridView, BindingList, BindingSource), a 3.8 lecke (DataTable) és az 5.5 lecke (TextChanged) szükséges.

8.6 Adatkötés →

Rendezés: mikor működik a fejlécre kattintás?

AdatforrásFejlécre kattintva rendez?Megjegyzés
DataTable (vagy BindingSource felette)Igen, automatikusanA DataView támogatja a rendezést és a szűrést.
List<T>, BindingList<T>NemNincs beépített rendezés: a rács a fejlécre kattintva semmit sem csinál. Kézzel kell megoldani.
Rács adatforrás nélkül (kézzel feltöltött sorok)Igen (dgv.Sort, SortCompare)Szöveg szerint rendez; számoknál a SortCompare eseményt kell kezelni.
Form1.cs (using System.Collections.Generic; using System.ComponentModel;)C#
private List<Termek> adatok = new List<Termek>();
private string rendezOszlop = "";
private ListSortDirection irany = ListSortDirection.Ascending;

private void dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
DataGridViewColumn oszlop = dgv.Columns[e.ColumnIndex];

// ugyanarra az oszlopra kattintva az irány fordul, új oszlopnál növekvő
if (rendezOszlop == oszlop.DataPropertyName)
{
irany = irany == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
}
else
{
rendezOszlop = oszlop.DataPropertyName;
irany = ListSortDirection.Ascending;
}

Rendez();
}

private void Rendez()
{
int elojel = irany == ListSortDirection.Ascending ? 1 : -1;

switch (rendezOszlop)
{
case "Nev":
// StringComparison.CurrentCulture: a magyar ábécé szerint (cs, sz, ö, ő...)
adatok.Sort((a, b) => elojel * string.Compare(a.Nev, b.Nev, StringComparison.CurrentCultureIgnoreCase));
break;

case "Netto":
adatok.Sort((a, b) => elojel * a.Netto.CompareTo(b.Netto));
break;

case "Keszlet":
adatok.Sort((a, b) => elojel * a.Keszlet.CompareTo(b.Keszlet));
break;
}

bs.ResetBindings(false); // a rács újrarajzolja a (már rendezett) listát

// a rendezési nyíl a fejlécben
foreach (DataGridViewColumn o in dgv.Columns)
{
o.HeaderCell.SortGlyphDirection = o.DataPropertyName == rendezOszlop
? (irany == ListSortDirection.Ascending ? SortOrder.Ascending : SortOrder.Descending)
: SortOrder.None;
}
}
A rendezés a lista dolgaA BindingList/List rendezéséhez magát az adatot rendezed (List.Sort), majd frissíted a rácsot (ResetBindings). A rács csak megjeleníti a lista sorrendjét.
ÖsszehasonlításA Sort lambdája negatív, nulla vagy pozitív számot ad: az elojel szorzó fordítja az irányt. A számokat CompareTo, a szöveget string.Compare hasonlítja.
Magyar ábécéA StringComparison.CurrentCulture (IgnoreCase változat) a magyar rendezési szabályt követi (a, á, b, c, cs...). Az Ordinal összehasonlítás a karakterkód szerint rendez, ezért az ékezetes betűk a lista végére kerülnek: kerüld felhasználói listáknál.
SortGlyphDirectionA fejléc kis nyilat mutat a rendezés irányáról. Ha a rács maga rendez (DataTable), ezt automatikusan teszi; kézi rendezésnél be kell állítani.

Jósolj!

A dgv.DataSource egy BindingList<Termek>, az oszlopok SortMode értéke Automatic. A felhasználó a Nettó ár fejlécre kattint. Mi történik?

Jóslat ellenőrzése

Gyakran semmi (a sorrend nem változik, nincs nyíl): a BindingList<T> nem támogatja a rendezést, a rács pedig nem rendez helyette. Ezért kell a ColumnHeaderMouseClick kezelő és a lista kézi rendezése, vagy DataTable használata.

DataView, Sort és RowFilter

A DataTable rendezését és szűrését a DataView (a DefaultView tulajdonság) vagy a BindingSource (Sort, Filter tulajdonságok) végzi: nem módosítja az adatot, csak a nézetet. A szűrő és a rendezés szöveges kifejezés:

Form1.cs (using System.Data; using System.Globalization;)C#
DataTable tabla = new DataTable();
tabla.Columns.Add("Nev", typeof(string));
tabla.Columns.Add("Kategoria", typeof(string));
tabla.Columns.Add("Ar", typeof(decimal));
tabla.Columns.Add("Keszlet", typeof(int));

BindingSource bs = new BindingSource();
bs.DataSource = tabla;
dgv.DataSource = bs;

// rendezés: oszlopnév, ASC (növekvő) vagy DESC (csökkenő), vesszővel több oszlop
bs.Sort = "Kategoria ASC, Ar DESC";

// szűrés: SQL-szerű feltétel
bs.Filter = "Kategoria = 'Papír' AND Ar >= 100";
bs.Filter = "Nev LIKE '%fü%'"; // tartalmaz (a % bármi)
bs.Filter = "Keszlet > 0 OR Kategoria = 'Technika'";
bs.Filter = ""; // a szűrő törlése: minden sor látszik

lblDb.Text = bs.Count + " / " + tabla.Rows.Count + " termék";

// a feltételben szereplő szöveg biztonságos beírása
private string Idezo(string s)
{
return s.Replace("'", "''"); // az aposztróf kettőzve
}

private string LikeSzoveg(string s)
{
// a LIKE speciális karaktereit szögletes zárójellel semlegesítjük, előbb a [ jelet
return s.Replace("[", "[[]").Replace("%", "[%]").Replace("*", "[*]").Replace("'", "''");
}

// a szám a feltételben mindig pont tizedessel (kultúrafüggetlenül)
string szamSzoveg = 1250.5m.ToString(CultureInfo.InvariantCulture); // "1250.5"
CélSzűrőkifejezés
Egyenlő (szöveg)Kategoria = 'Papír' (szöveg aposztrófok között)
Tartalmaz / kezdődik / végződikNev LIKE '%fü%', Nev LIKE 'fü%', Nev LIKE '%et'
Szám összehasonlításaAr >= 100 AND Ar <= 500 (a tizedes pont)
Több lehetőségKategoria IN ('Papír', 'Technika')
TagadásNOT (Keszlet = 0), Kategoria <> 'Papír'
DátumDatum >= #2026-10-01# (a # jelek között, kultúrafüggetlen alak ellenőrzendő)
A nézet, nem az adatA Filter és a Sort a megjelenítést változtatja: a DataTable sorai megmaradnak, a törölt szűrő után újra látszanak. A bs.Count a szűrt sorok számát adja, a tabla.Rows.Count az összeset.
Aposztróf-kettőzésHa a felhasználó O'Neil nevet keres, az aposztróf megtöri a kifejezést (SyntaxErrorException), és ez kifejezés-beszúrás (injection) is lehet. Mindig kettőzd ('').
LIKE speciális karakterekA % és * a LIKE-ban helyettesítő karakter, a [ pedig tartomány kezdete. Ha a felhasználó ezeket szó szerint keresi, szögletes zárójelbe kell tenni őket.
Szám és kultúraA szűrőkifejezésben a tizedes pont. A 12,5.ToString() magyar gépen 12,5 lenne: használj CultureInfo.InvariantCulture-t.

Jósolj!

A felhasználó a keresőmezőbe beírja: d'Artagnan, a kód pedig a szűrőt "Nev LIKE '%" + txtKeres.Text + "%'" módon építi, escape nélkül. Mi történik?

Jóslat ellenőrzése

Az aposztróf megtöri a kifejezést, és a Filter beállítása kivételt dob (SyntaxErrorException/EvaluateException), a program összeomlik a gépelés közben. A szűrőkifejezés összeállításakor az Idezo (és LikeSzoveg) segédmetódus elengedhetetlen, és a Filter beállítását érdemes try–catch védelemmel is ellátni.

Szűrés és keresés listán: LINQ és ékezetfüggetlenség

Ha az adat egy sima listában van (List<Termek>), a szűrést LINQ végzi (using System.Linq;): az eredmény új lista, amelyet a rácsra kötsz. A kereséshez érdemes az ékezeteket figyelmen kívül hagyni: aki „fuzet”-et gépel, találja meg a „füzet”-et:

Form1.cs (using System.Linq; using System.Globalization; using System.Text;)C#
// ékezetek és kis-nagybetű nélküli alak: ő → o, Á → a
private static string Egyszerusit(string szoveg)
{
string bontott = (szoveg ?? "").Normalize(NormalizationForm.FormD); // a betű és az ékezet szétválik
StringBuilder epito = new StringBuilder();

foreach (char c in bontott)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
epito.Append(c); // az ékezetet (kombináló jel) kihagyjuk
}
}

return epito.ToString().Normalize(NormalizationForm.FormC).ToLowerInvariant();
}

private void Szur()
{
string keres = Egyszerusit(txtKeres.Text.Trim());
decimal maxAr = nudMax.Value;

List<Termek> talalat = adatok
.Where(t => keres.Length == 0 || Egyszerusit(t.Nev).Contains(keres))
.Where(t => cmbKat.SelectedIndex <= 0 || t.Kategoria == cmbKat.Text)
.Where(t => maxAr == 0 || t.Netto <= maxAr)
.OrderBy(t => t.Nev, StringComparer.CurrentCultureIgnoreCase)
.ToList();

dgv.DataSource = new BindingList<Termek>(talalat);
lblDb.Text = talalat.Count + " / " + adatok.Count + " termék";
}

// keresés a rácsban: a következő egyező sorra ugrik, a végén körbe
private bool KovetkezoKeres(string keresett)
{
string k = Egyszerusit(keresett);
int kezdet = dgv.CurrentRow == null ? 0 : dgv.CurrentRow.Index + 1;

for (int lepes = 0; lepes < dgv.Rows.Count; lepes++)
{
int i = (kezdet + lepes) % dgv.Rows.Count;

if (Egyszerusit(Convert.ToString(dgv.Rows[i].Cells["colNev"].Value)).Contains(k))
{
dgv.ClearSelection();
dgv.CurrentCell = dgv.Rows[i].Cells["colNev"]; // kijelöli és láthatóvá görgeti
return true;
}
}

return false;
}
Where és OrderByA Where a feltételnek megfelelő elemeket, az OrderBy a rendezettet adja; a ToList() kiértékeli az eredményt (a halasztott végrehajtás miatt enélkül a lekérdezés minden használatkor újra lefutna).
Normalize FormDA FormD alak a ő betűt o + kombináló kettős ékezet jelre bontja; az ékezet-jelek (NonSpacingMark) kihagyásával marad az alap betű. A ToLowerInvariant a kis-nagybetű különbséget is eltünteti.
Új lista a rácsraA szűrt eredmény új BindingList: a dgv.DataSource újrakötése frissíti a rácsot. Az eredeti adatok lista változatlan, a szűrő törlése után újra elérhető.
CurrentCellA dgv.CurrentCell beállítása kijelöli a cellát és szükség esetén odagörgeti a rácsot. A keresés a végén az elejére ugrik (% osztásmaradék).

Jósolj!

A keresés a t.Nev.Contains(keres) hívással történik (egyszerűsítés nélkül), a lista egyik elemének neve Füzet. A felhasználó azt keresi: füzet, majd azt: fuzet. Melyiket találja meg?

Jóslat ellenőrzése

Egyiket sem: a Contains(string) kis- és nagybetű-érzékeny (a Füzet nagy F-fel kezdődik), az ékezetes és ékezet nélküli alak is különbözik. A füzet keresés csak az IndexOf(..., StringComparison.CurrentCultureIgnoreCase) hívással találna meg, a fuzet pedig csak ékezetfüggetlen (Egyszerusit) összehasonlítással.

Kényelmes kereső felület

ElemJó megoldásMiért?
Szűrés időpontjaMinden gépelésnél (TextChanged), nagy adatnál késleltetve (Timer 300 ms)A felhasználó azonnal látja az eredményt; a késleltetés megspórolja a felesleges számolást.
KategóriaComboBox Összes kategória első elemmelA szűrő kikapcsolása is választható (SelectedIndex == 0).
ÁrtartományKét NumericUpDown (-tól, -ig), a nulla jelenti a „nincs korlát” értéketNincs külön kapcsoló.
Találatszám12 / 50 termék feliratA felhasználó látja a szűrés hatását.
Nincs találatBarátságos szöveg a rács felett (Nincs a feltételeknek megfelelő termék. Módosítsd a szűrőt.)Az üres rács megtévesztő.
Szűrők törléseSzűrők törlése gomb, amely mindent alaphelyzetbe állítNem kell mezőnként törölni.
Rendezés és szűrés együttA szűrés megőrzi a rendezési sorrendetNe ugorjon vissza a sorrend szűréskor.
Form1.cs (tmrSzur: Timer, Interval 300)C#
private void Szuro_Valtozott(object sender, EventArgs e)
{
tmrSzur.Stop(); // minden gépelés újraindítja a várakozást
tmrSzur.Start();
}

private void tmrSzur_Tick(object sender, EventArgs e)
{
tmrSzur.Stop();
Szur(); // csak akkor szűrünk, ha a felhasználó 300 ms-ig nem gépelt
}

private void btnTorol_Click(object sender, EventArgs e)
{
txtKeres.Clear();
cmbKat.SelectedIndex = 0;
nudMin.Value = 0;
nudMax.Value = 0;
chkRaktaron.Checked = false;
Szur();
txtKeres.Focus();
}

Jósolj!

A szűrés minden gépelésnél lefut, 50 000 soros listán, és a szűrő LINQ-val újraépíti a listát. Mit érez a felhasználó gyors gépelésnél?

Jóslat ellenőrzése

A gépelés akadozik: minden betű után lefut a teljes szűrés (és a rács újrarajzolása), a program lassúnak tűnik. A késleltetett szűrés (Timer, 300 ms) csak a gépelés szünetében számol. Nagyon nagy adatnál az adatbázis szűrése (SQL, 3. fejezet) a helyes út.

📷 Képernyőkép helye

Mit tegyél ide: az árlista kereső: a szűrősor (kereső, kategória, ártartomány), a rács rendezési nyíllal és a „12 / 50 termék” felirat.Javasolt fájlnév: winforms-8-7-kereso.webp · Alt szöveg: Árlista kereső szűrőkkel

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. A fejlécre kattintva nem rendez a rács

A BindingList<T>-ra kötött rács nem rendez.

Ok és javítás

A lista nem támogatja a rendezést. Kezeld a ColumnHeaderMouseClick eseményt, rendezd a listát (Sort), és frissítsd a rácsot; vagy használj DataTable-t.

2. A szűrés összeomlik aposztrófnál

bs.Filter = "Nev LIKE '%" + txtKeres.Text + "%'";   // d'Artagnan → kivétel
Hiba és javítás

Az aposztrófot kettőzni kell, a LIKE speciális karaktereit zárójelbe kell tenni (LikeSzoveg), a Filter beállítását try–catch védje.

3. A szűrő nem működik tizedes számra

bs.Filter = "Ar <= " + nudMax.Value;   // magyar gépen: Ar <= 99,5 → hiba vagy rossz eredmény
Hiba és javítás

A tizedesvessző miatt a kifejezés érvénytelen. Használd az InvariantCulture alakot: nudMax.Value.ToString(CultureInfo.InvariantCulture).

4. A szűrő törlése után sem látszik minden sor

A szűrő törlése után is hiányoznak sorok.

Ok és javítás

A LINQ-szűrőnél a rácsra kötött lista a szűrt lista, nem az eredeti. A szűrő törlésekor az eredeti adatok listából építsd újra a nézetet (Szur() üres feltételekkel), ne a már szűrt listából.

5. Az ékezetes keresés nem talál

A „fuzet” keresésre nem jelenik meg a „Füzet”.

Ok és javítás

A Contains ékezet- és kisbetű-érzékeny. Használj Egyszerusit (Normalize + ToLowerInvariant) mindkét oldalon.

6. A rendezés a szűrés után elvész

A rendezett táblázat szűréskor visszaáll az eredeti sorrendbe.

Ok és javítás

A szűrés új listát épít rendezés nélkül. A szűrés végén alkalmazd újra az aktuális rendezést (Rendez() vagy OrderBy), vagy DataTable esetén a Sort tulajdonság megmarad a Filter módosításakor.

Mini projekt: árlista kereső

Készíts árlista-keresőt egy DataTable felett:

  1. Adat: egy DataTable (Nev, Kategoria, Ar decimal, Keszlet int) legalább 30 sorral (kód generálja, ismétlődő kategóriákkal: Irodaszer, Papír, Technika, Sport), ékezetes nevekkel (Füzet, Radír, Tűzőgép, Ceruza, Ollő…).
  2. Felület: txtKeres, cmbKat (Összes kategória + a kategóriák), nudMin, nudMax (0 = nincs korlát), chkRaktaron (Csak raktáron), btnTorol, cmbRendez (Név szerint, Ár szerint növekvő, Ár szerint csökkenő, Kategória, majd név), dgv, lblDb (12 / 30 termék), lblUres (nincs találat üzenet).
  3. A BindingSource Filter tulajdonsága a mezők alapján épül (kategória, tartalmazza a keresett szöveget, ártartomány, készlet), az aposztróf és a LIKE speciális karakterei védve, a számok InvariantCulture alakban.
  4. A szöveges keresés legyen kis- és nagybetű-független; (bónusz) ékezetfüggetlen: a DataTable LIKE ékezetérzékeny lehet, ezért a bónuszhoz külön EgyszerusitettNev oszlopot tölts fel (Egyszerusit(Nev)), és abban keress.
  5. A rendezés legördülő listából és a fejlécre kattintva is működik; a rács a bs.Sort értékét követi.
  6. A szűrés késleltetett (Timer 300 ms); a Szűrők törlése gomb mindent alaphelyzetbe állít.
  7. Ha nincs találat, a lblUres látszik a rács fölött (nem üres fehér terület).
  8. Teszteld: minden szűrő külön és együtt, d'Artagnan, %, [ a keresőben, tizedes ár (99,5), üres szűrő, nincs találat, rendezés szűrés közben.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A szűrőkifejezést egy Szurot_Epit() metódus állítja össze a mezőkből; a try–catch védi a Filter beállítását. A rendezést a legördülő lista a bs.Sort szövegére képezi le.

Form1.cs (using System.Data; using System.Globalization; using System.Collections.Generic;)C#
private DataTable tabla = new DataTable();
private BindingSource bs = new BindingSource();

private readonly string[] rendezesek = { "Nev ASC", "Ar ASC", "Ar DESC", "Kategoria ASC, Nev ASC" };

private void Form1_Load(object sender, EventArgs e)
{
tabla.Columns.Add("Nev", typeof(string));
tabla.Columns.Add("Kategoria", typeof(string));
tabla.Columns.Add("Ar", typeof(decimal));
tabla.Columns.Add("Keszlet", typeof(int));

string[] nevek = { "Füzet", "Radír", "Tűzőgép", "Ceruza", "Ollő", "Toll", "Jegyzettömb", "Hátizsák", "Focilabda", "Mappa" };
string[] kategoriak = { "Irodaszer", "Papír", "Technika", "Sport" };
Random r = new Random(7);

for (int i = 0; i < 40; i++)
{
tabla.Rows.Add(nevek[i % nevek.Length] + " " + (i / nevek.Length + 1), kategoriak[r.Next(kategoriak.Length)], Math.Round((decimal)(r.Next(500, 25000)) / 10, 1), r.Next(0, 80));
}

bs.DataSource = tabla;
dgv.DataSource = bs;

cmbKat.Items.Add("Összes kategória");
cmbKat.Items.AddRange(kategoriak);
cmbKat.SelectedIndex = 0;

cmbRendez.Items.AddRange(new object[] { "Név szerint", "Ár szerint növekvő", "Ár szerint csökkenő", "Kategória, majd név" });
cmbRendez.SelectedIndex = 0;

Szur();
}

private string Idezo(string s) { return s.Replace("'", "''"); }

private string LikeSzoveg(string s)
{
return s.Replace("[", "[[]").Replace("%", "[%]").Replace("*", "[*]").Replace("'", "''");
}

private string SzurotEpit()
{
List<string> feltetelek = new List<string>();

string keres = txtKeres.Text.Trim();

if (keres.Length > 0)
{
feltetelek.Add("Nev LIKE '%" + LikeSzoveg(keres) + "%'");
}

if (cmbKat.SelectedIndex > 0)
{
feltetelek.Add("Kategoria = '" + Idezo(cmbKat.Text) + "'");
}

if (nudMin.Value > 0)
{
feltetelek.Add("Ar >= " + nudMin.Value.ToString(CultureInfo.InvariantCulture));
}

if (nudMax.Value > 0)
{
feltetelek.Add("Ar <= " + nudMax.Value.ToString(CultureInfo.InvariantCulture));
}

if (chkRaktaron.Checked)
{
feltetelek.Add("Keszlet > 0");
}

return string.Join(" AND ", feltetelek);
}

private void Szur()
{
try
{
bs.Filter = SzurotEpit();
bs.Sort = rendezesek[cmbRendez.SelectedIndex];
}
catch (Exception ex)
{
bs.Filter = "";
MessageBox.Show(this, "A szűrő nem alkalmazható: " + ex.Message, "Szűrés", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

lblDb.Text = bs.Count + " / " + tabla.Rows.Count + " termék";
lblUres.Visible = bs.Count == 0;
dgv.Visible = bs.Count > 0;
}

private void Szuro_Valtozott(object sender, EventArgs e)
{
tmrSzur.Stop();
tmrSzur.Start();
}

private void tmrSzur_Tick(object sender, EventArgs e)
{
tmrSzur.Stop();
Szur();
}

private void btnTorol_Click(object sender, EventArgs e)
{
txtKeres.Clear();
cmbKat.SelectedIndex = 0;
nudMin.Value = 0;
nudMax.Value = 0;
chkRaktaron.Checked = false;
Szur();
txtKeres.Focus();
}
📷 Képernyőkép helye

Mit tegyél ide: a kész árlista kereső szűrt találatokkal és a „nincs találat” üzenettel.Javasolt fájlnév: winforms-8-7-kesz.webp · Alt szöveg: A kész árlista kereső program

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. Rendezés több oszlopra

Írd meg a List<Termek> rendezését kategória, majd azon belül ár szerint csökkenően. Mi a kulcs egy összetett összehasonlításban?

Egy lehetséges megoldás

Ha az első összehasonlítás nem nulla, azt adod vissza; különben a másodikat. LINQ-val: OrderBy(...).ThenByDescending(...).

Form1.csC#
adatok.Sort((a, b) =>
{
int kat = string.Compare(a.Kategoria, b.Kategoria, StringComparison.CurrentCultureIgnoreCase);

return kat != 0 ? kat : b.Netto.CompareTo(a.Netto); // a kategórián belül az ár csökkenő
});

// LINQ változat:
var rendezett = adatok.OrderBy(t => t.Kategoria, StringComparer.CurrentCultureIgnoreCase).ThenByDescending(t => t.Netto).ToList();

2. IN feltétel kategóriákkal

A CheckedListBox kipipált kategóriái szerint szűrj (Kategoria IN ('Papír', 'Sport')). Mi legyen, ha egy kategória sincs kipipálva?

Egy lehetséges megoldás

Ha nincs kipipálva semmi, ne legyen kategória-feltétel (minden kategória látszik): az üres IN () kifejezés érvénytelen lenne.

Form1.csC#
List<string> kivalasztott = new List<string>();

foreach (object o in clbKat.CheckedItems)
{
kivalasztott.Add("'" + o.ToString().Replace("'", "''") + "'");
}

if (kivalasztott.Count > 0)
{
feltetelek.Add("Kategoria IN (" + string.Join(", ", kivalasztott) + ")");
}

3. A talált szöveg kiemelése a rácsban

A keresett szöveget tartalmazó sorok háttere sárga legyen (és ne szűrjön). Melyik eseményben állítod a színt?

Egy lehetséges megoldás

A CellFormatting eseményben (soronként hívódik): a sor háttere a cella értékétől függ. A színt a kijelölés és a kontraszt miatt világos árnyalatra állítsd (6.9 lecke).

Form1.csC#
private void dgv_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e){
string keres = Egyszerusit(txtKeres.Text.Trim());

if (keres.Length == 0 || e.RowIndex < 0) { return; }

string nev = Convert.ToString(dgv.Rows[e.RowIndex].Cells["colNev"].Value);

dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Egyszerusit(nev).Contains(keres) ? Color.LemonChiffon : dgv.DefaultCellStyle.BackColor;
}

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. Sort kifejezések

Írd meg a bs.Sort szövegét: (a) ár szerint csökkenően; (b) kategória növekvően, azon belül név szerint.

Megoldás megnyitása

(a) "Ar DESC"; (b) "Kategoria ASC, Nev ASC".

2. Filter kifejezések

Írd meg a szűrőt: (a) raktáron lévő papírok; (b) a név „t”-vel kezdődik; (c) az ár 100 és 500 között.

Megoldás megnyitása

(a) "Kategoria = 'Papír' AND Keszlet > 0"; (b) "Nev LIKE 't%'"; (c) "Ar >= 100 AND Ar <= 500".

Közepes szint

3. Szűrés dátumra

A DataTable Datum oszlopára szűrsz: az adott hónap sorai. Hogyan írod a kifejezést?

Megoldás megnyitása

A dátumot # jelek közé teszed, tartományként (>= elsőnap AND < következő hónap első napja). A dátum alakját a DataColumn kultúrafüggetlenül (yyyy-MM-dd/MM/dd/yyyy) várja; érdemes kipróbálni a saját gépeden.

Form1.csC#
DateTime elso = new DateTime(2026, 10, 1);
DateTime kovetkezo = elso.AddMonths(1);

bs.Filter = "Datum >= #" + elso.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + "# AND Datum < #" + kovetkezo.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + "#";

4. Rendezés számként, nem szövegként

A DataTable Ar oszlopa string típusú, ezért a 100 a 20 előtt áll. Miért, és mi a megoldás?

Megoldás megnyitása

A szövegek betűrendben rendeződnek ("100" < "20"). Az oszlop típusa legyen szám (typeof(decimal)); a rendezés típus szerint történik.

Kihívás szint

5. Mentett szűrők

A felhasználó elmentheti a beállított szűrőt névvel (Olcsó papírok), és később visszatöltheti. Mit tárolsz?

Megoldás megnyitása

A szűrő beállításait (szöveg, kategória-index, tartomány, jelölő), nem a kész kifejezést: így a mezők is visszaállíthatók és a kifejezés biztonságosan újraépül. Egy kis SzuroBeallitas osztály (név + mezők) listában, szövegként a Settings-ben vagy fájlban (10. fejezet).

SzuroBeallitas.csC#
public class SzuroBeallitas
{
public string Nev = "";
public string Keres = "";
public int KategoriaIndex = 0;
public decimal Min = 0;
public decimal Max = 0;
public bool Raktaron = false;

public override string ToString() { return Nev; }
}

6. Oldalakra bontott találatok

1000 találatot 50-esével jelenítesz meg lapozva. Hogyan számolod az oldal tartalmát LINQ-val?

Megoldás megnyitása

A Skip((oldal - 1) * meret).Take(meret) hívással; az oldalak száma (int)Math.Ceiling(osszes / (double)meret).

Form1.csC#
private const int Meret = 50;
private int oldal = 1;

private void OldalMutat(List<Termek> osszes)
{
int oldalak = Math.Max(1, (int)Math.Ceiling(osszes.Count / (double)Meret));
oldal = Math.Max(1, Math.Min(oldal, oldalak));

dgv.DataSource = new BindingList<Termek>(osszes.Skip((oldal - 1) * Meret).Take(Meret).ToList());
lblOldal.Text = oldal + " / " + oldalak + ". oldal";
}

Gyors önellenőrzés

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

1. Melyik adatforrásnál rendez a rács magától?

DataTable (DataView) esetén. A List és BindingList nem támogatja.

2. Hogyan rendezel listát a magyar ábécé szerint?

string.Compare(a, b, StringComparison.CurrentCultureIgnoreCase) a Sort lambdában.

3. Mi a Filter és a Sort hatása az adatra?

Nincs: csak a nézetet változtatják, az adat megmarad.

4. Miért kell az aposztrófot kettőzni a szűrőben?

Különben megtöri a kifejezést (kivétel, beszúrás); a '' szó szerinti aposztrófot jelent.

5. Hogyan írsz tizedes számot a szűrőbe?

Ponttal, CultureInfo.InvariantCulture segítségével.

6. Hogyan lesz a keresés ékezetfüggetlen?

Normalize(FormD), az ékezet-jelek kihagyása és ToLowerInvariant mindkét oldalon.

7. Mire jó a késleltetett szűrés?

Nagy adatnál gépelés közben nem számol minden betű után, csak a szünetben.

Mit tudsz már?

  • Tudod, mikor rendez a rács magától, és mikor kell kézi rendezés.
  • A BindingSource.Sort és Filter tulajdonságot helyes, biztonságos kifejezéssel használod.
  • LINQ-val szűrsz, és ékezetfüggetlen keresést írsz.
  • Kényelmes kereső felületet építesz találatszámmal és üres eredmény kezeléssel.
  • Felismered a tipikus hibákat (aposztróf, tizedes, elvesző rendezés).

Következő lecke: CellValidating és adatellenőrzés

A táblázatba írt adat ellenőrzése: érvénytelen érték megakadályozása, hibajelzés a cellán és a soron, a DataError esemény.