Grafikus C# – Windows Forms tanfolyam › 3.9 lecke Windows Forms · 3.9 lecke · rendezett szerkezet A 3.6 leckében egyetlen ablakba tettünk mindent: a táblázatot, a mezőket és az SQL hívásokat. Ez kis programnál működik, de ahogy a program nő, egyre nehezebb átlátni. Ebben a leckében ugyanazt a nyilvántartót építjük fel rendezetten: saját adat-osztály (Űrlap és adatbázis: CRUD műveletek rendezett szerkezetben
Dolgozo), külön adatkezelő osztály, és egy önálló szerkesztő párbeszédablak, amely az Új és a Módosítás műveletet is kezeli.
A lecke végére
- érted, miért érdemes a programot rétegekre bontani (ablak, adat-osztály, adatkezelő);
- saját osztályt (
Dolgozo) írsz tulajdonságokkal és ellenőrző metódussal; - az adatkezelő osztály
Betolt,MentesésTorlesmetódusát megírod, azExecuteReadersegítségével; - külön szerkesztő párbeszédablakot készítesz (
ShowDialog,DialogResult,AcceptButton,CancelButton); - megérted, miért nem szabad a párbeszédablaknak az eredeti objektumot módosítania;
- kezeled a konkurens módosítás egyszerű esetét (közben törölték a sort).
Mielőtt elkezded
Ehhez a leckéhez kell a 3.6 lecke (INSERT, UPDATE, DELETE, a nyilvántartó), a 2.9 lecke (egy második ablak megnyitása) és a 3.7 lecke (hibanapló, DB.Naploz).
Miért érdemes szétválasztani?
A 3.6 leckében az ablak kódja egyszerre kezelte a felületet (mezők, gombok), az üzleti szabályokat (kötelező név) és az adatbázist (SQL). Ha később például a név szabályát módosítani kell, vagy másik adatbázisra váltasz, a változtatásokat az ablak kódjában szétszórva kell megkeresni. Jobb, ha minden rész egyetlen feladatot végez:
| Réteg | Feladata | Mit nem tehet |
|---|---|---|
| Ablak | Mezők kitöltése, gombok, üzenetek. | Nem tartalmaz SQL-t. |
| Dolgozo osztály | Egy dolgozó adatai és az adatok ellenőrzése. | Nem ismeri az ablakot és az adatbázist. |
| DolgozoTar | Az SQL: betöltés, mentés, törlés. | Nem mutat üzenetet a felhasználónak, nem ismer vezérlőt. |
Jósolj!
Később az adatbázisról átváltanál egy másik adatbázis-kezelőre. Melyik réteg kódját kellene módosítanod, ha a program a leírt módon van szétosztva? Mennyi marad az ablakok kódjából?
Jóslat ellenőrzése
Csak a DolgozoTar (és a kapcsolatot létrehozó DB) osztályt. Az ablakok és a Dolgozo osztály kódja változatlan maradhat, mert azok nem tudnak az SQL-ről. Ez a szétválasztás legnagyobb haszna: a változtatás egy helyre koncentrálódik.
A Dolgozo osztály
Az osztály saját adattípus: összefogja egy dolgozó adatait. Az osztály tulajdonságokból (property) áll, amelyeket { get; set; } jelöléssel hozunk létre: a get kiolvassa, a set beállítja az értéket. Az osztály metódust is tartalmazhat: itt az adatok ellenőrzését (Hiba). A projekten jobb gomb: Add → Class, a fájl neve Dolgozo.cs:
public class Dolgozo
{
public int Id { get; set; } // 0: még nincs az adatbázisban
public string Vnev { get; set; } = "";
public string Knev { get; set; } = "";
public int Fizetes { get; set; }
public string Cim { get; set; } = "";
public string TeljesNev
{
get { return Vnev + " " + Knev; }
}
// üres szöveget ad, ha az adatok rendben vannak, különben a hiba leírását
public string Hiba()
{
if (Vnev.Length == 0 || Knev.Length == 0)
{
return "A vezeték- és a keresztnév kötelező.";
}
if (Vnev.Length > 40 || Knev.Length > 40 || Cim.Length > 40)
{
return "A nevek és a város legfeljebb 40 karakteresek lehetnek.";
}
if (Fizetes < 0)
{
return "A fizetés nem lehet negatív.";
}
return "";
}
}public azt jelenti, hogy a program bármely része használhatja.Vnev sosem null, és a Length hibát nem okoz.set): a két nevet adja össze.Új dolgozót így hozhatsz létre és tölthetsz ki:
Dolgozo d = new Dolgozo();
d.Vnev = "Fekete";
d.Knev = "Dóra";
d.Fizetes = 390000;
d.Cim = "Győr";
string hiba = d.Hiba(); // "", mert minden rendben
// rövidebb írásmód (objektum-inicializáló):
Dolgozo e = new Dolgozo { Vnev = "Orbán", Knev = "Réka", Fizetes = 400000 };Jósolj!
Két változó mutat ugyanarra a dolgozóra: Dolgozo a = new Dolgozo(); Dolgozo b = a; b.Vnev = "Kiss"; Mi a a.Vnev értéke?
Jóslat ellenőrzése
Kiss. Az osztály referencia típus: a b = a nem másolja az objektumot, csak egy második hivatkozást hoz létre ugyanarra az objektumra. Ez fontos lesz a párbeszédablaknál: ha az ablak az átadott objektumot módosítja, a hívó objektuma is megváltozik, akár Mégse esetén is. A szám és a szöveg típusok (int, string) ezzel szemben értéket másolnak.
Az adatkezelő osztály: DolgozoTar
A DolgozoTar az egyetlen hely, ahol a dolgozókhoz tartozó SQL él. Három fő metódusa van. A Betolt egy sort olvas ki az ExecuteReader segítségével: ez egy olvasó objektumot ad, amely a lekérdezés eredményét soronként járja be (a Read() a következő sorra lép, és hamis, ha nincs több sor):
public static class DolgozoTar
{
// egy dolgozó betöltése azonosító alapján; null, ha nincs ilyen
public static Dolgozo Betolt(int id)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("SELECT id, vnev, knev, fizetes, cim FROM adatok WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
using (SQLiteDataReader olvaso = parancs.ExecuteReader())
{
if (!olvaso.Read())
{
return null;
}
Dolgozo d = new Dolgozo();
d.Id = Convert.ToInt32(olvaso["id"]);
d.Vnev = Convert.ToString(olvaso["vnev"]);
d.Knev = Convert.ToString(olvaso["knev"]);
d.Fizetes = olvaso["fizetes"] == DBNull.Value ? 0 : Convert.ToInt32(olvaso["fizetes"]);
d.Cim = Convert.ToString(olvaso["cim"]);
return d;
}
}
}
}
// új sor (Id == 0) vagy módosítás (Id > 0); visszaadja a sor azonosítóját
public static int Mentes(Dolgozo d)
{
string hiba = d.Hiba();
if (hiba.Length > 0)
{
throw new ArgumentException(hiba);
}
object cim = d.Cim.Length == 0 ? (object)DBNull.Value : d.Cim;
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
if (d.Id == 0)
{
using (SQLiteCommand parancs = new SQLiteCommand("INSERT INTO adatok (vnev, knev, fizetes, cim) VALUES (@vnev, @knev, @fizetes, @cim)", kapcsolat))
{
parancs.Parameters.AddWithValue("@vnev", d.Vnev);
parancs.Parameters.AddWithValue("@knev", d.Knev);
parancs.Parameters.AddWithValue("@fizetes", d.Fizetes);
parancs.Parameters.AddWithValue("@cim", cim);
parancs.ExecuteNonQuery();
}
return (int)kapcsolat.LastInsertRowId;
}
using (SQLiteCommand parancs = new SQLiteCommand("UPDATE adatok SET vnev = @vnev, knev = @knev, fizetes = @fizetes, cim = @cim WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@vnev", d.Vnev);
parancs.Parameters.AddWithValue("@knev", d.Knev);
parancs.Parameters.AddWithValue("@fizetes", d.Fizetes);
parancs.Parameters.AddWithValue("@cim", cim);
parancs.Parameters.AddWithValue("@id", d.Id);
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("A dolgozó már nem létezik (közben törölték).");
}
}
return d.Id;
}
}
public static void Torles(int id)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("DELETE FROM adatok WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("A dolgozó már nem létezik (közben törölték).");
}
}
}
}
}Read() a következő sorra lép, és hamis, ha nincs több (itt legfeljebb egy sort várunk). Az oszlopot névvel érjük el: olvaso["vnev"].null a válasz. A hívónak ezt ellenőriznie kell.Id dönt: 0 az INSERT, több az UPDATE. Az adatkezelő nem tud az ablakról: az eredményt visszaadja, a hibát kivételként dobja.try–catch elkapja, és érthető üzenetet mutat a felhasználónak.Mentes maga is ellenőrzi a Hiba() metódussal az adatot. Így akkor sem kerülhet hibás adat az adatbázisba, ha egy ablak elfelejtené az ellenőrzést.long típusú azonosító int típusra alakítása. Ebben a kis táblában az azonosítók belefér az int tartományába.A szerkesztő párbeszédablak
Az új dolgozó felvételéhez és a módosításhoz ugyanazt a külön ablakot használjuk. Add hozzá a projekthez: Add → Form (Windows Forms), neve DolgozoUrlap. Elemei: txtVnev, txtKnev, txtCim (MaxLength: 40), nudFizetes, lblHiba, btnMent és btnMegse. Beállítások:
Enter billentyű a Mentés gombot nyomja.Esc billentyű a Mégse gombot nyomja.Cancel választ kap.public partial class DolgozoUrlap : Form
{
private int id;
// az eredmény: egy ÚJ objektum, amely csak OK esetén áll rendelkezésre
public Dolgozo Eredmeny { get; private set; }
public DolgozoUrlap(Dolgozo d)
{
InitializeComponent();
id = d.Id;
this.Text = id == 0 ? "Új dolgozó" : "Dolgozó szerkesztése";
txtVnev.Text = d.Vnev;
txtKnev.Text = d.Knev;
nudFizetes.Value = d.Fizetes;
txtCim.Text = d.Cim;
}
private void btnMent_Click(object sender, EventArgs e)
{
Dolgozo uj = new Dolgozo();
uj.Id = id;
uj.Vnev = txtVnev.Text.Trim();
uj.Knev = txtKnev.Text.Trim();
uj.Fizetes = (int)nudFizetes.Value;
uj.Cim = txtCim.Text.Trim();
string hiba = uj.Hiba();
if (hiba.Length > 0)
{
lblHiba.Text = hiba;
return;
}
Eredmeny = uj;
this.DialogResult = DialogResult.OK; // ez zárja be az ablakot
}
}Dolgozo (Id = 0).btnMent_Click új Dolgozo objektumot hoz létre, nem az átadott objektumot módosítja. Így a hívó oldali adat Mégse esetén biztosan nem változik meg.DialogResult beállítása bezárja az ablakot, és ez az érték lesz a ShowDialog() visszatérési értéke.Jósolj!
A felhasználó az X gombbal (az ablak jobb felső sarkában) zárja be az ablakot. Milyen DialogResult értéket kap a hívó? Mi lenne, ha a btnMent DialogResult tulajdonságát a tervezőben OK értékre állítanád?
Jóslat ellenőrzése
Az X gombbal bezárt ablak DialogResult.Cancel értéket ad. Ha a btnMent tervezői DialogResult tulajdonsága OK lenne, a gombnyomás azonnal bezárná az ablakot, még az ellenőrzés eredményétől függetlenül, és az Eredmeny null maradna. Ezért kell a tulajdonságnak None értéken maradnia, és az OK beállításának a kódban, az ellenőrzés után történnie.
Mit tegyél ide: a szerkesztő párbeszédablak tervezőnézete (négy mező, Mentés és Mégse gomb, hibafelirat), mellette a Properties ablak az AcceptButton és CancelButton beállításával.Javasolt fájlnév: winforms-3-9-urlap.webp · Alt szöveg: A szerkesztő párbeszédablak tervezőnézete
A főablak: lista és három gomb
A főablakban csak egy táblázat (dgvDolgozok), három gomb (btnUj, btnSzerkeszt, btnTorol) és egy lblUzenet marad. Az SQL eltűnt az ablakból: a DolgozoTar végzi, a kivételeket pedig itt fogjuk el, és alakítjuk felhasználóbarát üzenetté. (A Uzenet, Frissit, SorKijelolese metódus a 3.6 leckéből ismerős.)
private int KijeloltId()
{
if (dgvDolgozok.CurrentRow == null)
{
return 0;
}
return Convert.ToInt32(dgvDolgozok.CurrentRow.Cells["id"].Value);
}
private void Szerkeszt(Dolgozo d)
{
using (DolgozoUrlap urlap = new DolgozoUrlap(d))
{
if (urlap.ShowDialog(this) != DialogResult.OK)
{
return; // Mégse vagy X: nem történik semmi
}
try
{
int id = DolgozoTar.Mentes(urlap.Eredmeny);
Frissit(id);
Uzenet("A dolgozó mentve.", false);
}
catch (InvalidOperationException ex)
{
Uzenet(ex.Message, true);
Frissit(0);
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A mentés most nem sikerült.");
}
}
}
private void btnUj_Click(object sender, EventArgs e)
{
Szerkeszt(new Dolgozo());
}
private void SzerkesztKijelolt()
{
int id = KijeloltId();
if (id == 0)
{
Uzenet("Előbb válassz ki egy dolgozót!", true);
return;
}
Dolgozo d = DolgozoTar.Betolt(id);
if (d == null)
{
Uzenet("A dolgozó már nem létezik.", true);
Frissit(0);
return;
}
Szerkeszt(d);
}
private void btnSzerkeszt_Click(object sender, EventArgs e)
{
SzerkesztKijelolt();
}
private void dgvDolgozok_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0)
{
SzerkesztKijelolt();
}
}
private void btnTorol_Click(object sender, EventArgs e)
{
int id = KijeloltId();
if (id == 0)
{
Uzenet("Előbb válassz ki egy dolgozót!", true);
return;
}
DialogResult valasz = MessageBox.Show("Biztosan törlöd a kijelölt dolgozót?", "Törlés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (valasz != DialogResult.Yes)
{
return;
}
try
{
DolgozoTar.Torles(id);
Uzenet("A dolgozó törölve.", false);
}
catch (InvalidOperationException ex)
{
Uzenet(ex.Message, true);
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A törlés most nem sikerült.");
}
Frissit(0);
}using blokkban használjuk, hogy a bezárás után az erőforrásai felszabaduljanak. (A ShowDialog után az ablak nem szabadul fel magától, csak elrejtőzik.)this a szülő, így az ablak a főablak közepén jelenik meg.Exception ág a váratlan hibákat naplózza.Jósolj!
Megnyitsz egy dolgozót szerkesztésre. Amíg az ablak nyitva van, egy másik programban (például a DB Browserben) törlöd ezt a sort, majd a párbeszédablakban a Mentésre kattintasz. Mi történik?
Jóslat ellenőrzése
Az UPDATE nulla sort érint, ezért a DolgozoTar.Mentes InvalidOperationException kivételt dob. A főablak catch ága piros üzenetet jelenít meg (A dolgozó már nem létezik), és frissíti a listát. A program nem omlik össze, és a felhasználó érti, mi történt. A kérdés a konkurens módosítás: több program (vagy felhasználó) egyszerre változtatja ugyanazt az adatot. Ennek a legegyszerűbb esetét kezeltük, a bonyolultabbak (két módosítás ütközése) már haladó téma.
Mit tegyél ide: a főablak a táblázattal, és előtte a szerkesztő párbeszédablak egy kijelölt dolgozóval, a hibafelirattal („A vezeték- és a keresztnév kötelező.”).Javasolt fájlnév: winforms-3-9-fo-es-urlap.webp · Alt szöveg: A főablak és a szerkesztő párbeszédablak
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 Mentés gomb hibás adatnál is bezárja az ablakot
Üres névvel is bezárul a párbeszédablak, és hiba keletkezik a mentésnél. A Ok és javítás
btnMent tervezői DialogResult tulajdonsága OK értéken áll: a gombnyomás bezárja az ablakot az ellenőrzés előtt is. Állítsd None értékre, és a DialogResult.OK beállítása legyen a kódban, az ellenőrzés után.
2. A Mégse után is megváltozik az adat
private Dolgozo dolgozo; // az átadott objektum
private void btnMent_Click(object sender, EventArgs e)
{
dolgozo.Vnev = txtVnev.Text; // az eredetit írja át!
// ...
}Hiba és javítás
Az osztály referencia típus, ezért a párbeszédablak az eredeti objektumot módosítja, amelyet a főablak is használ. Ha az ellenőrzés hibát talál, vagy a felhasználó utána a Mégsét nyomja, a főablakban már a módosított (el nem mentett) adat látszik. A megoldás: a mentéskor új objektumot hozz létre (lásd a leckét), és csak OK esetén használd.
3. Minden mentés új sort hoz létre
A módosított dolgozó mentésekor a lista egy új sort mutat a régi mellett. Az új objektum Ok és javítás
Id értéke 0 maradt, ezért a Mentes INSERT-et futtatott. A párbeszédablak a konstruktorban megjegyzi az azonosítót (id = d.Id), és a mentéskor átmásolja az új objektumba (uj.Id = id).
4. NullReferenceException a Betolt eredményén
Dolgozo d = DolgozoTar.Betolt(id);
txtVnev.Text = d.Vnev;Hiba és javítás
A Betolt null értéket ad, ha nincs ilyen azonosító (például közben törölték). A használat előtt ellenőrizd: if (d == null) { ...üzenet... return; }.
5. Az üres cím szöveg lett az adatbázisban
Az Az üres szöveg és a IS NULL lekérdezés nem találja a cím nélküli dolgozókat, mert az üres cím üres szövegként került be.Ok és javítás
NULL különbözik. A Mentes az üres címet DBNull.Value értékre alakítja (d.Cim.Length == 0 ? (object)DBNull.Value : d.Cim). Figyelj a (object) típusátalakításra: a feltételes kifejezés két ágának közös típusúnak kell lennie.
6. A dupla kattintás a fejlécen hibát okoz
private void dgvDolgozok_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
SzerkesztKijelolt();
}Hiba és javítás
A fejlécen kattintva az e.RowIndex értéke -1, és előfordulhat, hogy nincs kijelölt sor, vagy az előző sor szerkesztődik. Ellenőrizd: if (e.RowIndex >= 0).
Mini projekt: eseménynyilvántartó ugyanezzel a szerkezettel
Alkalmazd a leckében látott szerkezetet az esemenyek táblára (id, nev, datum, ar a 3.7 leckéből):
Esemenyosztály:Id,Nev,Datum(DateTime),Ar, és egyHiba()metódus (a név kötelező, az ár nemnegatív, a dátum nem lehet 2000 előtti).EsemenyTarosztály:Betolt,Mentes(INSERT vagy UPDATE azIdalapján, a dátumot ISO szövegként tárolva) ésTorles.EsemenyUrlappárbeszédablak:txtNev,dtpDatum,nudAr,lblHiba, Mentés (DialogResult: None) és Mégse gomb; új objektummal tér vissza.- Főablak: táblázat az események listájával, Új, Szerkesztés (dupla kattintás is), Törlés megerősítéssel.
- Teszteld: üres név, Mégse, sor törlése közben szerkesztés (DB Browser), a dátum helyes tárolása és visszaolvasása.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A szerkezet azonos a Dolgozo megoldással. A különbség a dátum kezelésében van: a mentéskor ISO szöveggé alakítjuk (3.7 lecke), visszaolvasáskor DateTime.Parse alakítja vissza.
public class Esemeny
{
public int Id { get; set; }
public string Nev { get; set; } = "";
public DateTime Datum { get; set; } = DateTime.Today;
public int Ar { get; set; }
public string Hiba()
{
if (Nev.Length == 0) return "A név kötelező.";
if (Nev.Length > 60) return "A név legfeljebb 60 karakteres lehet.";
if (Ar < 0) return "Az ár nem lehet negatív.";
if (Datum.Year < 2000) return "A dátum nem lehet 2000 előtti.";
return "";
}
}
// EsemenyTar.Betolt: a dátum visszaolvasása
e.Datum = DateTime.Parse(Convert.ToString(olvaso["datum"]), System.Globalization.CultureInfo.InvariantCulture);
// EsemenyTar.Mentes: a dátum ISO szövegként
parancs.Parameters.AddWithValue("@datum", e.Datum.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture));Mit tegyél ide: a kész eseménynyilvántartó: a lista és a szerkesztő párbeszédablak a dátumválasztóval.Javasolt fájlnév: winforms-3-9-kesz.webp · Alt szöveg: Az eseménynyilvántartó rétegekre bontva
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. Figyelmeztetés a Mégse gombnál
Ha a felhasználó a párbeszédablakban módosított valamit, és a Mégse gombot nyomja (vagy az X-et), kérdezzen rá a program: Eldobod a változtatásokat? A Egy lehetséges megoldás
FormClosing eseményben (0.8, 2.14 lecke) vizsgáljuk, hogy az ablak Cancel eredménnyel zárul-e, és történt-e módosítás. Az eredeti értékeket a konstruktorban mentjük el egy Dolgozo másolatban, és összehasonlítjuk a mezők aktuális tartalmával. Az OK esetén nincs kérdés.private string eredetiAdatok = "";
private string MezokOsszefuzve()
{
return txtVnev.Text + "|" + txtKnev.Text + "|" + nudFizetes.Value + "|" + txtCim.Text;
}
// a konstruktor végén:
eredetiAdatok = MezokOsszefuzve();
private void DolgozoUrlap_FormClosing(object sender, FormClosingEventArgs e)
{
if (this.DialogResult == DialogResult.OK || MezokOsszefuzve() == eredetiAdatok)
{
return;
}
DialogResult valasz = MessageBox.Show("Eldobod a változtatásokat?", "Mégse", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (valasz == DialogResult.No)
{
e.Cancel = true;
}
}
2. Törlés a Delete billentyűvel
A táblázatban a A Delete billentyű ugyanazt tegye, mint a Törlés gomb (a megerősítő kérdéssel együtt).Egy lehetséges megoldás
DataGridView.KeyDown eseményben a Keys.Delete billentyűt vizsgáljuk. Az e.Handled = true megakadályozza, hogy a táblázat maga is kezelje a billentyűt (a DataGridView alapból törölne a DataTable-ből, és ez nem kívánt).private void dgvDolgozok_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete)
{
btnTorol_Click(sender, e);
e.Handled = true;
}
}
3. Ki törölte? A konkurencia kipróbálása
Próbáld ki a konkurens módosítás két esetét: (a) szerkesztés megnyitása, majd a sor törlése a DB Browserben, utána Mentés; (b) a törlés gomb megnyomása olyan soron, amelyet közben törölt valaki. Mit látsz, és hol kezeli a program? (a) A Egy lehetséges megoldás
Mentes UPDATE utasítása nulla sort érint, ezért az InvalidOperationException kivételt a főablak catch ága kezeli (piros üzenet, a lista frissül). (b) A Torles DELETE utasítása nulla sort érint, ugyanaz a kivétel keletkezik, és a törlés gomb catch ága ugyanígy kezeli. Fontos: a DB Browser Write Changes (Ctrl+S) nélkül a törlés nem kerül a fájlba, ezért a teszthez ezt is el kell végezni, és a DB Browser nyitott írása közben a program database is locked hibát is kaphat.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Az első négy feladat önálló, kis osztályokról szól, a másik kettő a nyilvántartó projektjéhez kapcsolódik. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Könyv osztály
Írj egy Az automatikus tulajdonságok (Konyv osztályt: Id (egész szám), Cim, Szerzo (szöveg), Ev (egész szám). A szövegek kezdőértéke üres szöveg legyen. Hozz létre egy példányt, és írd ki a címét egy MessageBox-ban.Megoldás megnyitása
{ get; set; }) rövidek. A kezdőérték (= "") megakadályozza a null szövegeket.public class Konyv
{
public int Id { get; set; }
public string Cim { get; set; } = "";
public string Szerzo { get; set; } = "";
public int Ev { get; set; }
}
// Form1.cs
Konyv k = new Konyv();
k.Cim = "Egri csillagok";
k.Szerzo = "Gárdonyi Géza";
k.Ev = 1901;
MessageBox.Show(k.Cim);
2. Ellenőrző metódus
Adj a A szabályokat sorban vizsgáljuk, az első hibánál visszatérünk a leírásával. Így az ablak egyetlen hívással megkapja a hibaüzenetet.Konyv osztályhoz egy Hiba() metódust: a cím kötelező, a szerző kötelező, az év 1000 és 2100 között legyen. Üres szöveget ad, ha minden rendben.Megoldás megnyitása
public string Hiba()
{
if (Cim.Length == 0)
{
return "A cím kötelező.";
}
if (Szerzo.Length == 0)
{
return "A szerző kötelező.";
}
if (Ev < 1000 || Ev > 2100)
{
return "Az év 1000 és 2100 között legyen.";
}
return "";
}
Közepes szint
3. A ToString felülírása
A A Konyv objektumok kerüljenek egy ListBox-ba úgy, hogy a lista Gárdonyi Géza: Egri csillagok (1901) alakban mutassa őket, és a kijelölt elemből visszakapd a Konyv objektumot.Megoldás megnyitása
ListBox az elemek szövegét a ToString() metódusból veszi. Az override felülírja az osztály alapértelmezett szövegét. A kijelölt elem (SelectedItem) az eredeti objektum, amelyet Konyv típusúra kell alakítani (as vagy típusátalakítás).// Konyv.cs
public override string ToString()
{
return $"{Szerzo}: {Cim} ({Ev})";
}
// Form1.cs
lstKonyvek.Items.Add(k); // az objektum kerül a listába, a szöveg a ToString()-ből jön
private void lstKonyvek_SelectedIndexChanged(object sender, EventArgs e)
{
Konyv kijelolt = lstKonyvek.SelectedItem as Konyv;
if (kijelolt != null)
{
lblEv.Text = "Megjelenés éve: " + kijelolt.Ev;
}
}
4. Másolat a konstruktorral
Írj a A másolat független az eredetitől: ha az ablak a másolatot módosítja, az eredeti nem változik. Ha a konstruktor paraméter nélkül is kell, külön meg kell írni (Dolgozo osztályhoz másoló konstruktort, amely egy másik Dolgozo adataiból készít új, független objektumot. Miért hasznos ez a párbeszédablaknál?Megoldás megnyitása
public Dolgozo() { }), mert az osztály a saját konstruktor írásakor az automatikusat már nem adja.public Dolgozo()
{
}
public Dolgozo(Dolgozo masik)
{
Id = masik.Id;
Vnev = masik.Vnev;
Knev = masik.Knev;
Fizetes = masik.Fizetes;
Cim = masik.Cim;
}
// használat:
Dolgozo masolat = new Dolgozo(eredeti);
masolat.Vnev = "Más"; // az eredeti Vnev nem változik
Kihívás szint
5. Keresés a tárban
Írj a A keresés SQL-je a DolgozoTar osztályba egy Keres(string nevresz) metódust, amely a névrészletre szűrt DataTable-t adja vissza (paraméterezetten, LIKE és ESCAPE segítségével, a 3.7 lecke szerint), és használd a főablak keresőmezőjéből.Megoldás megnyitása
DolgozoTar-ban van, az ablak csak a szöveget adja át, és a táblázatba teszi az eredményt. A LikeVedelem segédmetódus a 3.7 leckéből jön (a DB osztályban public legyen, vagy másold ide).public static DataTable Keres(string nevresz)
{
DataTable tabla = new DataTable();
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
string sql = "SELECT id, vnev, knev, fizetes, cim FROM adatok WHERE vnev LIKE @minta ESCAPE '^' OR knev LIKE @minta ESCAPE '^' ORDER BY vnev, knev";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@minta", "%" + DB.LikeVedelem(nevresz) + "%");
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(parancs))
{
adapter.Fill(tabla);
}
}
}
return tabla;
}
6. Melyik réteg?
Sorold be az alábbi nyolc feladatot a három réteg valamelyikébe (ablak, Dolgozo osztály, DolgozoTar), és indokold: (a) a név kötelező ellenőrzése, (b) INSERT futtatása, (c) üzenet pirosan, (d) a DBNull átalakítása, (e) MessageBox a törlés előtt, (f) a teljes név összefűzése, (g) DataGridView kijelölése, (h) az érintett sorok számának ellenőrzése.Megoldás megnyitása
Feladat Réteg Indok (a) a név kötelező ellenőrzése DolgozoÜzleti szabály, egy helyen legyen ( Hiba()).(b) INSERT futtatásaDolgozoTarSQL, csak az adatkezelőben. (c) piros üzenet Ablak Megjelenítés. (d) DBNull átalakításaDolgozoTarAz adatbázis jellegzetessége, a többi réteg nem ismeri. (e) MessageBox törlés előttAblak Felhasználói interakció. (f) teljes név összefűzése DolgozoAz adat származtatott értéke ( TeljesNev).(g) DataGridView kijelöléseAblak Vezérlő kezelése. (h) az érintett sorok számának ellenőrzése DolgozoTarAz adatbázis művelet eredményének értelmezése; hibánál kivételt dob.
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Hogy minden rész egyetlen feladatot végezzen, és egy változtatás egy helyen legyen (például az SQL csak az adatkezelőben). Az osztály adata, amelyet Az értékadás csak egy második hivatkozást hoz létre ugyanarra az objektumra. Ha egy ablak az átadott objektumot módosítja, a hívó objektuma is megváltozik, akár Mégse esetén is. Modálisan megnyitja az ablakot (addig nem használható a hívó), és a bezáráskor a Hogy a gomb ne zárja be magától az ablakot. A Lekérdezés eredményét soronként járja be: a Nincs ilyen azonosítójú sor (például közben törölték). A program ezt kivétellel jelzi, a felhasználó pedig érthető üzenetet kap.1. Miért bontjuk a programot rétegekre?
2. Mi a tulajdonság (property)?
get olvas, set állít. A { get; set; } az automatikus tulajdonság rövid írásmódja.3. Miért referencia típus az osztály, és mi ennek a veszélye?
4. Mit csinál a ShowDialog(), és mit ad vissza?
DialogResult értékét adja vissza (OK, Cancel...).5. Miért None a Mentés gomb DialogResult tulajdonsága?
DialogResult.OK beállítása a kódban történik, az ellenőrzés sikere után.6. Mire való az ExecuteReader és a Read()?
Read() a következő sorra lép, és hamis, ha nincs több sor.7. Mit jelent, ha az UPDATE nulla sort érint?
Mit tudsz már?
- Saját osztályt írsz tulajdonságokkal és ellenőrző metódussal.
- Az SQL-t külön adatkezelő osztályba szervezed.
- Párbeszédablakot nyitsz (
ShowDialog,DialogResult), és az eredményt új objektumban veszed át. - Érted a referencia típus veszélyét, és másolattal védekezel.
- A várt és váratlan hibákat külön kezeled.
Következő lecke: kapcsolatok, kulcsok és tranzakciók
Eddig egyetlen táblával dolgoztunk. A következő leckében két táblát kapcsolunk össze idegen kulccsal, és megtanuljuk, hogyan lehet több műveletet egyetlen, oszthatatlan egységként végrehajtani.



