Grafikus C# – Windows Forms tanfolyam › 3.4 lecke

Windows Forms · 3.4 lecke · adatok felvétele és listázása

Regisztráció SQLite-tal és DataGridView

Eddig az adatbázis felhasználóit a DB Browserben vettük fel. Most a program maga is tud új felhasználót regisztrálni (INSERT), és az adatbázis tartalmát táblázatban mutatja meg (DataGridView). Közben két fontos új dolgot tanulunk: az ExecuteNonQuery módosító parancsot és a DataTable használatát.

  • kb. 100 perc
  • közepes szint
  • INSERT és táblázat
Diáklány C# programot készít laptopon

A lecke végére

  • paraméterezett INSERT utasítással új sort veszel fel az adatbázisba (ExecuteNonQuery);
  • megakadályozod az azonos nevű felhasználók felvételét;
  • a DataGridView vezérlőben megjeleníted a felhasználók listáját;
  • érted a DataTable és az SQLiteDataAdapter szerepét (Fill, DataSource);
  • a listában nem mutatod a jelszót;
  • a lista frissítését külön metódusba teszed, és minden változtatás után meghívod.

Mielőtt elkezded

Ehhez a leckéhez kell a 3.3 lecke (DB.cs, paraméterezett lekérdezés, bool visszatérési érték) és az 1.9–1.10 leckék (jelszómező, visszajelző felirat).

3.3 Bejelentkezés SQLite-tal →

A felület

Bővítsd az előző leckék projektjét, vagy hozz létre újat (belepes.db, NuGet csomag, DB.cs a 3.2 és 3.3 szerint). Az ablakra kerül:

txtFelhasznalo, txtJelszoA név és a jelszó mezője (a jelszó UseSystemPasswordChar: True).
btnRegisztralEgy gomb Regisztráció felirattal.
dgvFelhasznalokEgy DataGridView (Toolbox, Data csoport): a felhasználók táblázata. Az előtag dgv.
lblUzenetA visszajelző felirat.

A DataGridView néhány hasznos tulajdonságát állítsd a Properties ablakban:

ReadOnly: TrueA felhasználó nem szerkesztheti a cellákat a táblázatban.
AllowUserToAddRows: FalseEltűnik az üres „új sor” az alján.
AutoSizeColumnsMode: FillAz oszlopok kitöltik a táblázat szélességét.
SelectionMode: FullRowSelectKattintásra az egész sor kijelölődik.
📷 Képernyőkép helye

Mit tegyél ide: a tervezőnézet a két beviteli mezővel, a Regisztráció gombbal és az üres DataGridView-val, mellette a Properties ablak a beállított tulajdonságokkal.Javasolt fájlnév: winforms-3-4-felulet.webp · Alt szöveg: A regisztrációs ablak felülete DataGridView-val

Új felhasználó felvétele: INSERT

Az INSERT új sort vesz fel a táblába (3.1 lecke). A C# programban az SQLiteCommand ExecuteNonQuery() metódusával hajtjuk végre: ez olyan parancsokhoz való, amelyek módosítanak (INSERT, UPDATE, DELETE), és az érintett sorok számát adja vissza. A felhasználó beírt szövegét paraméterként adjuk át (3.3 lecke):

DB.cs (a DB osztályba)C#
public static bool Regisztral(string nev, string jelszo)
{
using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

// 1. Létezik már ez a név?
using (SQLiteCommand ellenor = new SQLiteCommand("SELECT COUNT(*) FROM belepesi_adatok WHERE felhnev = @nev", kapcsolat))
{
ellenor.Parameters.AddWithValue("@nev", nev);

if (Convert.ToInt32(ellenor.ExecuteScalar()) > 0)
{
return false;
}
}

// 2. Felvétel
using (SQLiteCommand beszur = new SQLiteCommand("INSERT INTO belepesi_adatok (felhnev, jelszo) VALUES (@nev, @jelszo)", kapcsolat))
{
beszur.Parameters.AddWithValue("@nev", nev);
beszur.Parameters.AddWithValue("@jelszo", jelszo);
beszur.ExecuteNonQuery();
}

return true;
}
}
ExecuteNonQuery()Végrehajtja a módosító parancsot. Itt az eredményt (az érintett sorok számát) nem használjuk fel.
Előbb az ellenőrzésHa a név már létezik, a metódus false értékkel tér vissza, és nem vesz fel új sort. A felhasználó érthető üzenetet kap.
UNIQUE a védőhálóAz adatbázis felhnev oszlopa egyedi (3.1 lecke), ezért ha mégis két azonos nevet próbálna felvenni a program (például egyszerre két kattintás), az adatbázis hibát jelez, és a gomb try–catch blokkja elkapja.
@nev, @jelszoParaméterek, nem összefűzött szövegek (3.3 lecke). Az aposztrófos nevek is biztonságosan működnek.

A gomb eseménykezelője a bevitelt ellenőrzi, a DB.Regisztral hívja, és a választól függően jelez:

Form1.csC#
private void Uzenet(string szoveg, bool hiba)
{
lblUzenet.Text = szoveg;
lblUzenet.ForeColor = hiba ? Color.Firebrick : Color.SeaGreen;
}

private void btnRegisztral_Click(object sender, EventArgs e)
{
string nev = txtFelhasznalo.Text.Trim();
string jelszo = txtJelszo.Text;

if (nev.Length == 0)
{
Uzenet("Add meg a felhasználónevet!", true);
return;
}

if (jelszo.Length < 6)
{
Uzenet("A jelszó legalább 6 karakter legyen!", true);
return;
}

try
{
if (DB.Regisztral(nev, jelszo))
{
Uzenet("Sikeres regisztráció.", false);
txtFelhasznalo.Clear();
txtJelszo.Clear();
Frissit();
}
else
{
Uzenet("Ez a felhasználónév már foglalt.", true);
}
}
catch (Exception ex)
{
MessageBox.Show("Adatbázis-hiba: " + ex.Message);
}
}

Jósolj!

Kétszer egymás után regisztrálod ugyanazt a nevet. Mit ír ki a program másodszor? Hány sor lesz a táblában ebből a névből?

Jóslat ellenőrzése

Másodszor a Ez a felhasználónév már foglalt. üzenetet. A névből csak egy sor lesz a táblában, mert a DB.Regisztral az ellenőrzés után false értékkel tért vissza, az INSERT pedig nem futott le.

Az adatok táblázatban: DataTable és DataGridView

A DataGridView táblázatos nézetben mutat adatokat. Nem közvetlenül az adatbázisból olvas, hanem egy DataTable objektumból, amely egy memóriában lévő táblázat (sorok és oszlopok). Az SQLiteDataAdapter a „közvetítő”: lefuttatja a SELECT lekérdezést, és az eredményt a DataTable-be tölti (Fill). A DataGridView.DataSource tulajdonságának ezt a táblát megadva a táblázat megjelenik:

DB.cs (a DB osztályba; a fájl elejére: using System.Data;)C#
public static DataTable Felhasznalok()
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteDataAdapter adapter = new SQLiteDataAdapter("SELECT id, felhnev FROM belepesi_adatok ORDER BY felhnev", kapcsolat))
{
adapter.Fill(tabla);
}
}

return tabla;
}
DataTableMemóriában lévő táblázat. A using System.Data; sor kell a használatához.
SQLiteDataAdapter(sql, kapcsolat)Összekapcsolja az SQL lekérdezést és a kapcsolatot.
adapter.Fill(tabla)Lefuttatja a lekérdezést, és az eredményt a tabla objektumba tölti.
SELECT id, felhnevCsak ezt a két oszlopot kérjük le. A jelszó oszlopot szándékosan kihagyjuk, mert a jelszót soha nem mutatjuk meg listában.
A metódus DataTable-t ad visszaA visszatérési típus nem void és nem bool, hanem DataTable: a hívó közvetlenül a táblázat adatforrásává teheti.
A kapcsolat lezárulA using blokk a Fill után lezárja a kapcsolatot, a DataTable az adatokat megőrzi.

A jelszó nem tartozik a listába

Ha egy lekérdezés SELECT * formában minden oszlopot lekérne, a jelszavak is megjelennének a táblázatban. Mindig csak azokat az oszlopokat kérd le, amelyekre a felületnek szüksége van. Valódi programban a jelszavak hash-ként vannak tárolva, de akkor sem mutatnánk meg őket.

A lista frissítése

Az INSERT után az adatbázis megváltozik, de a táblázat nem frissül magától: a DataTable egy pillanatfelvétel, amelyet a lekérdezés pillanatában készítettünk. Ezért a lista kitöltését egy külön Frissit() metódusba tesszük, és meghívjuk az ablak betöltésekor és minden sikeres felvétel után:

Form1.csC#
private void Frissit()
{
try
{
dgvFelhasznalok.DataSource = DB.Felhasznalok();

dgvFelhasznalok.Columns["id"].HeaderText = "Azonosító";
dgvFelhasznalok.Columns["felhnev"].HeaderText = "Felhasználónév";
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült betölteni a listát: " + ex.Message);
}
}

private void Form1_Load(object sender, EventArgs e)
{
Frissit();
}
DataSource = DB.Felhasznalok()A táblázat adatforrása az új lekérdezés eredménye. A DataGridView automatikusan létrehozza az oszlopokat és a sorokat.
Columns["felhnev"].HeaderTextAz oszlop fejléce: a megjelenő felirat. Alapból az SQL oszlopnév látszik (felhnev), ezt itt szebbre cseréljük.
Hol hívjuk?A Form1_Load eseményben (induláskor) és a regisztráció után. Így a lista mindig friss.
try–catchHa az adatbázis nem elérhető, a program üzenetet ad, és nem omlik össze.

Jósolj!

A Frissit() hívás kimarad a sikeres regisztráció után. A felvétel megtörténik, de mit lát a felhasználó a táblázatban? És az adatbázisban?

Jóslat ellenőrzése

A táblázat a régi állapotot mutatja (az új felhasználó nem látszik), az adatbázisban viszont már ott van a sor. A program újraindítása után, amikor a Form1_Load újra lefut, megjelenik. A hiba a frissítés kimaradása, az adatok nem vesztek el.

📷 Képernyőkép helye

Mit tegyél ide: a futó program a DataGridView-ban három felhasználóval (Azonosító és Felhasználónév oszlop, jelszó nélkül), és a zöld „Sikeres regisztráció.” üzenettel.Javasolt fájlnév: winforms-3-4-datagridview.webp · Alt szöveg: A felhasználók táblázata a regisztráció után

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 táblázat nem frissül

Beszúrtad az új felhasználót, de a táblázat változatlan marad.

Ok és javítás

A DataGridView nem frissül magától az INSERT után. A sikeres felvétel után hívd meg a Frissit() metódust, amely újra lekérdezi az adatokat, és az új DataTable-t adja a DataSource-nak.

2. Ismeretlen DataTable

// CS0246: The type or namespace name 'DataTable' could not be found
Hiba és javítás

Fordítási hiba (CS0246): hiányzik a using System.Data; sor a DB.cs fájl elejéről.

3. Üres táblázat

DataTable tabla = new DataTable();
// ... az adapter létrejön, de a Fill hívás kimarad ...
return tabla;
Hiba és javítás

Rossz működés: az adapter.Fill(tabla) hívás nélkül a DataTable üres marad, és a táblázat is üres lesz. A Fill hozza át az adatokat az adatbázisból.

4. Összefűzött INSERT

string sql = "INSERT INTO belepesi_adatok (felhnev, jelszo) VALUES ('" + nev + "', '" + jelszo + "')";
Hiba és javítás

Biztonsági hiba (SQL-befecskendezés, 3.3 lecke): az aposztrófos név hibát okoz, és a felhasználó az SQL jelentését is megváltoztathatja. Használj paramétereket: VALUES (@nev, @jelszo).

5. UNIQUE hiba a felhasználónál

// SQLiteException: constraint failed
// UNIQUE constraint failed: belepesi_adatok.felhnev
Hiba és javítás

Az adatbázis védelme jelzett: már van ilyen nevű felhasználó. A DB.Regisztral előzetes ellenőrzése ezt általában megelőzi, de a try–catch a váratlan esetre is megvéd. A felhasználónak érthető üzenetet kell adni: Ez a felhasználónév már foglalt.

6. Látszanak a jelszavak

new SQLiteDataAdapter("SELECT * FROM belepesi_adatok", kapcsolat)
Hiba és javítás

A SELECT * minden oszlopot lekér, a jelszót is. A listához csak azokat az oszlopokat kérd, amelyeket a felhasználónak látnia kell: SELECT id, felhnev FROM belepesi_adatok.

Mini projekt: regisztráció és felhasználólista

Állítsd össze a teljes programot:

  1. Ablak: txtFelhasznalo, txtJelszo, btnRegisztral, dgvFelhasznalok (ReadOnly, nincs új sor, a teljes szélességet kitölti), lblUzenet.
  2. A DB.cs osztályba: Regisztral és Felhasznalok metódus a leckében látott módon.
  3. A Frissit() metódus és a Form1_Load a lista betöltéséhez.
  4. A gomb ellenőrzi a bevitelt, regisztrál, és jelez; sikernél törli a mezőket és frissíti a listát.
  5. Próbáld ki: új név, ugyanaz a név még egyszer, rövid jelszó, aposztrófos név (például O'Hara), üres név.
  6. Nézd meg a DB Browserben, hogy a sorok ott vannak-e (előbb zárd be a DB Browsert, vagy írd ki a változtatásait: Ctrl+S).
📷 Képernyőkép helye

Mit tegyél ide: a kész program a felhasználólistával és az „Ez a felhasználónév már foglalt.” piros üzenettel.Javasolt fájlnév: winforms-3-4-kesz.webp · Alt szöveg: A kész regisztrációs program felhasználólistával

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. Hány felhasználó van?

Egy lblDarab felirat mutassa a táblázat soraiban lévő felhasználók számát: Felhasználók száma: 5.

Egy lehetséges megoldás
// a Frissit() metódusban, a DataSource beállítása után:
lblDarab.Text = $"Felhasználók száma: {dgvFelhasznalok.Rows.Count}";

A Rows.Count a táblázat sorainak száma. Mivel az új sor ki van kapcsolva (AllowUserToAddRows: False), nincs üres plusz sor.

2. Szűrés névrészletre

Tegyél az ablakra egy txtKeres mezőt. Gépelés közben a táblázat csak azokat a felhasználókat mutassa, amelyek neve tartalmazza a beírt szöveget.

Egy lehetséges megoldás
// DB.cs:
public static DataTable Kereses(string resz)
{
DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteCommand parancs = new SQLiteCommand("SELECT id, felhnev FROM belepesi_adatok WHERE felhnev LIKE @minta ORDER BY felhnev", kapcsolat))
{
parancs.Parameters.AddWithValue("@minta", "%" + resz + "%");

using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(parancs))
{
adapter.Fill(tabla);
}
}
}

return tabla;
}

// Form1.cs:
private void txtKeres_TextChanged(object sender, EventArgs e)
{
dgvFelhasznalok.DataSource = DB.Kereses(txtKeres.Text.Trim());
}

A % jeleket (bármennyi karakter) a C# kód fűzi a szöveghez, és az egész minta megy paraméterként az SQL-be. A felhasználó szövege itt sem kerül az SQL szövegébe, ezért a megoldás biztonságos. A SQLiteDataAdapter(parancs) változat egy kész parancsot kap, így a paramétere is érvényes.

3. Jelszó megerősítése

Tegyél az ablakra egy második jelszómezőt (txtJelszo2), és csak akkor engedd a regisztrációt, ha a két jelszó egyezik.

Egy lehetséges megoldás
if (jelszo != txtJelszo2.Text)
{
Uzenet("A két jelszó nem egyezik!", true);
return;
}

A != jel azt jelenti: nem egyenlő. Az ellenőrzést a jelszó hosszának vizsgálata mellé tedd, még az adatbázis-hívás előtt.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A feladatokhoz a 3.4 lecke projektje kell (DB.cs a Regisztral és Felhasznalok metódussal, dgvFelhasznalok, lblUzenet, Frissit()). Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Fordított rendezés

Egy chkCsokkeno jelölőnégyzet váltson a felhasználók növekvő és csökkenő név szerinti sorrendje között. Az SQL-t ne a felhasználó szövegéből állítsd össze.

Megoldás megnyitása

A két SQL szöveg rögzített, a jelölőnégyzet csak azt választja ki, melyiket használjuk. Így biztonságos marad: a felhasználó nem szövegrészt ad meg, csak egy igaz/hamis döntést.

DB.csC#
public static DataTable Felhasznalok(bool csokkeno)
{
string sql = csokkeno
? "SELECT id, felhnev FROM belepesi_adatok ORDER BY felhnev DESC"
: "SELECT id, felhnev FROM belepesi_adatok ORDER BY felhnev";

DataTable tabla = new DataTable();

using (SQLiteConnection kapcsolat = UjKapcsolat())
{
kapcsolat.Open();

using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(sql, kapcsolat))
{
adapter.Fill(tabla);
}
}

return tabla;
}
Form1.csC#
private void chkCsokkeno_CheckedChanged(object sender, EventArgs e)
{
dgvFelhasznalok.DataSource = DB.Felhasznalok(chkCsokkeno.Checked);
}

2. A kijelölt felhasználó neve

Ha a felhasználó egy sorra kattint a táblázatban, a lblKijelolt feliraton jelenjen meg a kijelölt név.

Megoldás megnyitása

A SelectionChanged esemény a kijelölés megváltozásakor fut. A CurrentRow az aktuális sor (lehet null), a Cells["felhnev"] a név cellája.

Form1.csC#
private void dgvFelhasznalok_SelectionChanged(object sender, EventArgs e)
{
if (dgvFelhasznalok.CurrentRow != null)
{
lblKijelolt.Text = "Kijelölt: " + dgvFelhasznalok.CurrentRow.Cells["felhnev"].Value;
}
}

Közepes szint

3. Jelszószabály függvénnyel

Írj egy bool JelszoJo(string jelszo) metódust: legalább 6 karakter, és legalább egy számjegy. A regisztráció csak jó jelszóval menjen tovább.

Megoldás megnyitása

A számjegy keresése a foreach ciklussal, a char.IsDigit metódussal történik. A szabályt külön metódusba tesszük, hogy a regisztráció kódja rövid maradjon, és a szabály egy helyen módosítható legyen.

Form1.csC#
private bool JelszoJo(string jelszo)
{
if (jelszo.Length < 6)
{
return false;
}

foreach (char betu in jelszo)
{
if (char.IsDigit(betu))
{
return true;
}
}

return false;
}

// a gombban:
if (!JelszoJo(jelszo))
{
Uzenet("A jelszó legalább 6 karakter legyen, és tartalmazzon számjegyet!", true);
return;
}

4. Az új sor kijelölése

Sikeres regisztráció után a táblázatban jelölődjön ki az új felhasználó sora.

Megoldás megnyitása

A frissítés után végigmegyünk a sorokon, megkeressük a nevet, és kijelöljük. A CurrentCell beállítása görgeti is a táblázatot, ha a sor nem látszana.

Form1.csC#
private void SorKijelolese(string nev)
{
foreach (DataGridViewRow sor in dgvFelhasznalok.Rows)
{
if (sor.Cells["felhnev"].Value.ToString() == nev)
{
dgvFelhasznalok.CurrentCell = sor.Cells["felhnev"];
sor.Selected = true;
return;
}
}
}

// a regisztráció sikeres ága:
Frissit();
SorKijelolese(nev);

Kihívás szint

5. Az adatbázis hibájának felismerése

A regisztráció catch blokkja ismerje fel, ha az adatbázis a UNIQUE szabály megsértése miatt utasította el a felvételt (például két egyszerre indított regisztráció miatt), és érthető üzenetet adjon.

Megoldás megnyitása

Az SQLiteException ResultCode tulajdonsága megmondja a hiba fajtáját, a SQLiteErrorCode.Constraint a szabálysértést jelöli. A pontos kódérték a csomag verziójától függhet, ezért érdemes kipróbálni, és szükség esetén az ex.Message szövegét is megvizsgálni (az UNIQUE constraint failed szöveg szerepel benne).

Form1.csC#
catch (SQLiteException ex)
{
if (ex.ResultCode == SQLiteErrorCode.Constraint || ex.Message.Contains("UNIQUE"))
{
Uzenet("Ez a felhasználónév már foglalt.", true);
}
else
{
MessageBox.Show("Adatbázis-hiba: " + ex.Message);
}
}

6. A lista exportálása CSV fájlba

Egy gomb mentse a felhasználók azonosítóját és nevét egy pontosvesszővel tagolt szöveges fájlba (1;kata). A fájlnevet egy sfdExport SaveFileDialog kérdezze meg. A jelszó ne kerüljön bele.

Megoldás megnyitása

A DataTable sorait a foreach járja végig, a StreamWriter soronként írja a fájlba. A using blokk a végén lezárja a fájlt. A DB.Felhasznalok() eleve csak az id és a felhnev oszlopot kéri le, így a jelszó véletlenül sem kerülhet az exportba.

Form1.csC#
private void btnExport_Click(object sender, EventArgs e)
{
sfdExport.Filter = "CSV fájl (*.csv)|*.csv";
sfdExport.DefaultExt = "csv";

if (sfdExport.ShowDialog() != DialogResult.OK)
{
return;
}

try
{
DataTable tabla = DB.Felhasznalok();

using (System.IO.StreamWriter iro = new System.IO.StreamWriter(sfdExport.FileName))
{
foreach (DataRow sor in tabla.Rows)
{
iro.WriteLine(sor["id"] + ";" + sor["felhnev"]);
}
}

Uzenet("Az exportálás sikeres.", false);
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült exportálni: " + ex.Message);
}
}

Gyors önellenőrzés

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

1. Mi a különbség az ExecuteScalar és az ExecuteNonQuery között?

Az ExecuteScalar egyetlen értéket kérdez le (például COUNT), az ExecuteNonQuery módosító parancsot hajt végre (INSERT, UPDATE, DELETE), és az érintett sorok számát adja vissza.

2. Mire való a DataTable és az adapter.Fill?

A DataTable egy memóriában lévő táblázat. A Fill az SQL lekérdezés eredményét tölti bele.

3. Mire való a DataSource?

Megmondja a DataGridView-nak, honnan jöjjenek az adatok. Ha egy DataTable-t kap, megjeleníti a sorait és oszlopait.

4. Miért nem frissül magától a táblázat egy INSERT után?

Mert a DataTable a lekérdezés pillanatában készült pillanatfelvétel. Az új adat megjelenítéséhez újra le kell kérdezni.

5. Miért nem szerepel a jelszó a listában?

Mert a lekérdezés csak az id és a felhnev oszlopot kéri. A jelszót a felületen soha nem mutatjuk meg.

6. Mi védi meg az azonos nevű felhasználók felvételét?

Az előzetes ellenőrzés (COUNT(*)), és az adatbázis UNIQUE beállítása védőhálóként.

Mit tudsz már?

  • Paraméterezett INSERT-tel új sort veszel fel.
  • Megakadályozod az azonos nevű felhasználókat.
  • DataGridView-ban megjeleníted az adatokat DataTable segítségével.
  • A jelszót nem mutatod a listában.
  • A lista frissítését külön metódusba teszed.

Következő témák: SELECT, szűrés és módosítás

A felvétel és a listázás megvan. A következő leckében a lekérdezéseket (WHERE, ORDER BY), utána a módosítást és a törlést (UPDATE, DELETE) tanuljuk meg.