Grafikus C# – Windows Forms tanfolyam › 4.6 lecke Windows Forms · 4.6 · önálló gyakorlás A legtöbb valódi program adatokat kezel: felvesz, módosít, töröl, keres és szűr. Ebben az önálló feladatban egy teendőlistát építesz (vagy, ha jobban tetszik, címjegyzéket: a szerkezet ugyanaz, a változtatásokat külön táblázatban találod). Az egész a 3. fejezetben tanult eszközöket gyakoroltatja: SQLite, rétegek, paraméterek, szűrés, hibakezelés. A leírás és a támpontok a tieid, a teljes megoldás a végén lenyitható.Önálló feladat VI.: Címjegyzék vagy teendőlista

A feladat végére
- önállóan megtervezel egy egytáblás adatbázist, és rétegekre bontott alkalmazást építesz rá;
- új, módosítás, törlés és „kész” jelölés műveletet valósítasz meg biztonságosan;
- szűrsz állapot szerint, keresel a szövegben, és a hiányzó értékeket (
NULL) kezeled a rendezésben; - a határidőt ISO formátumban tárolod, és a lejártakat kiemeled;
- a hibákat érthető üzenettel kezeled, a részleteket naplózod;
- tesztlistával és önértékeléssel ellenőrzöd a munkádat.
Mielőtt elkezded
A feladathoz a 3. fejezet leckéi kellenek, különösen a 3.5 (szűrés, rendezés), 3.6 (módosítás), 3.7 (paraméterek, dátumok, napló) és 3.9 (rétegek). Új projektet készíts, és használd újra a DB.cs segédosztályt (UjKapcsolat, IsoDatum, LikeVedelem, Naploz).
A feladat leírása
Készíts Teendőlista nevű Windows Forms alkalmazást SQLite adatbázissal. Egy teendőnek van címe (kötelező), leírása (nem kötelező), határideje (nem kötelező), fontos jelölése és kész állapota. Az ablak bal oldalán a teendők táblázata áll szűrővel (összes, nyitott, kész) és kereső mezővel; jobb oldalon a kijelölt teendő szerkesztő mezői. A határidő lejárt, nyitott teendőit a táblázat pirossal emeli ki. Alul egy összefoglaló felirat mutatja a nyitott, a kész és a késésben lévő teendők számát.
Ha a címjegyzéket választod
Ugyanezt a feladatot megoldhatod címjegyzékként is. A szerkezet, a követelmények és a pontozás azonos, csak a téma változik:
| Teendőlista | Címjegyzék |
|---|---|
Tábla: teendok | Tábla: kontaktok |
| Cím (kötelező) | Név (kötelező) |
| Leírás, határidő | Telefonszám, e-mail (nem kötelező, de ha megadod, legyen érvényes: az e-mail tartalmazzon @ jelet és pontot a jel után) |
| Fontos jelölés | Kedvenc jelölés |
| Kész / nyitott szűrő | Összes / kedvencek szűrő |
| Késésben lévő (piros) | Névnap vagy születésnap közelsége (haladó bővítés: a születésnap 7 napon belül) |
Mit tegyél ide: egy papírra rajzolt terv: balra a szűrő és a kereső, alatta a táblázat, jobbra a szerkesztő mezők és gombok, alul az összefoglaló felirat.Javasolt fájlnév: winforms-4-6-terv.webp · Alt szöveg: A teendőlista programjának terve
Követelmények és pontozás
| Rész | Követelmény | Pont |
|---|---|---|
| Adatbázis és modell (15) | Az teendok tábla (id, cim, leiras, hatarido, fontos, kesz), mintaadatokkal; a .db fájl a program mellé másolódik. | 7 |
Teendo osztály tulajdonságokkal és Hiba() ellenőrzéssel (a cím kötelező és legfeljebb 80 karakter; a leírás legfeljebb 500). | 8 | |
| Műveletek (30) | Új teendő felvétele a mezőkből. | 8 |
| Kijelölt teendő módosítása (a kijelölés betölti a mezőket). | 8 | |
| Törlés megerősítéssel. | 6 | |
Kész/nyitott jelölés egy gombbal; a határidő nélküli teendő is kezelhető (NULL). | 8 | |
| Megjelenítés és szűrés (25) | Táblázat: cím, határidő, fontos jel, állapot; rendezés: nyitottak előbb, azon belül a közeli határidő, a határidő nélküliek a nyitottak végén. | 10 |
| Szűrő (összes, nyitott, kész) és kereső (cím vagy leírás), biztonságosan. | 8 | |
| A lejárt, nyitott teendők pirosak; összefoglaló felirat a darabszámokkal. | 7 | |
| Hibakezelés (15) | Paraméteres SQL, érthető üzenetek, a váratlan hibák naplózása, üres és hibás bevitel kezelése. | 15 |
| Szerkezet (15) | Az SQL külön adatkezelő osztályban (TeendoTar), az ablakban nincs SQL; beszédes nevek, rövid metódusok. | 15 |
Összesen: 100 pont.
Időbeosztás (150 perc)
| Perc | Mit csinálj? |
|---|---|
| 0–20 | Terv: mezők, táblázat oszlopai, a műveletek listája. Készítsd el az adatbázist a DB Browserben. |
| 20–35 | Új projekt, NuGet csomag, DB.cs, az adatbázis a program mellé másolódik. |
| 35–60 | Teendo osztály és TeendoTar (Betolt, Mentes, Torles, KeszValt). |
| 60–90 | A felület és a lista: Lista(szuro, kereso) lekérdezés és a táblázat. |
| 90–115 | Kijelölés, mezők betöltése, Új, Mentés, Törlés, Kész gomb. |
| 115–135 | Késések kiemelése, összefoglaló felirat, hibakezelés és napló. |
| 135–150 | Tesztlista végigpróbálása és javítás. |
Támpontok (kész megoldás nélkül)
Hiányzó határidő: nullázható dátum
A DateTime nem lehet null. A nullázható változata a DateTime?: HasValue mondja meg, van-e értéke, és az .Value adja az értéket. A felületen egy chkVanHatarido jelölőnégyzet kapcsolja a DateTimePicker használatát (dtpHatarido.Enabled). Az adatbázisban a hiányzó határidő NULL, a megadott ISO szöveg (yyyy-MM-dd).
Egy tárolt logikai érték
Az SQLite-nak nincs logikai típusa: a fontos és a kesz oszlop egész szám (0 vagy 1). A C# oldalon bool, a két típus között a mentéskor kesz ? 1 : 0, olvasáskor Convert.ToInt32(...) == 1 alakít.
A rendezés a NULL határidővel
Az SQLite növekvő rendezésnél a NULL értékeket előre teszi, de a határidő nélküli teendők a végén legyenek. Megoldás: előbb egy CASE kifejezés szerint rendezz (CASE WHEN hatarido IS NULL THEN 1 ELSE 0 END), és csak utána a határidő szerint. Az ORDER BY szövege rögzített, nem a felhasználóé.
Szűrő és kereső egy lekérdezésben
Használd a 3.5 lecke „választható szűrő” mintáját: (@szuro = 0 OR (@szuro = 1 AND kesz = 0) OR (@szuro = 2 AND kesz = 1)). A kereső a 3.7 lecke szerint LIKE @minta ESCAPE '^', a minta speciális karaktereit védd. A leiras lehet NULL, ezért COALESCE(leiras, '') kell.
A késés megállapítása
Késésben van az a teendő, amely nyitott, van határideje, és a határideje kisebb a mai dátumnál. ISO szövegeknél a szöveges összehasonlítás időrend szerint dolgozik. A C# oldalon a string.Compare(a, b, StringComparison.Ordinal) negatív, ha a korábbi. Az összefoglalóhoz a SUM(CASE WHEN ... THEN 1 ELSE 0 END) kifejezést használhatod, COALESCE-szel, hogy üres tábla esetén is szám legyen.
A kijelölés és a frissítés
A táblázat frissítése közben a kijelölés többször változik. Használj frissites jelzőt, mint a 3.6 leckében, és frissítés után az azonosító alapján jelöld ki újra a sort.
Mintamegoldás
Előbb oldd meg önállóan!
A mintamegoldás csak az ellenőrzésre való, és nem az egyetlen helyes megoldás. Előbb próbálkozz saját kóddal, és csak elakadásnál vagy a végén nyisd meg.
Megoldás: adatbázis és a felület
CREATE TABLE teendok (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cim TEXT NOT NULL,
leiras TEXT,
hatarido TEXT, -- yyyy-MM-dd vagy NULL
fontos INTEGER NOT NULL DEFAULT 0,
kesz INTEGER NOT NULL DEFAULT 0
);
INSERT INTO teendok (cim, leiras, hatarido, fontos, kesz) VALUES
('Beadandó leadása', 'A C# feladat PDF-ben', '2026-10-30', 1, 0),
('Könyvtári könyv visszavitele', NULL, '2026-10-20', 0, 0),
('Tanulás: SQL JOIN', 'A 3.10 lecke feladatai', NULL, 0, 0),
('Edzés', NULL, '2026-10-27', 0, 0),
('Születésnapi ajándék', 'Vegyél valamit!', '2026-10-15', 1, 1);| Vezérlő | (Name) | Beállítás |
|---|---|---|
| ComboBox | cmbSzuro | DropDownList; elemek: Összes, Nyitott, Kész |
| TextBox | txtKereso | keresés a címben és a leírásban |
| DataGridView | dgvTeendok | ReadOnly, FullRowSelect, MultiSelect: False, AllowUserToAddRows: False |
| TextBox | txtCim, txtLeiras | txtLeiras: Multiline, MaxLength: 500; txtCim: MaxLength: 80 |
| CheckBox, DateTimePicker | chkVanHatarido, dtpHatarido | Format: Short; dtpHatarido.Enabled a jelölőnégyzettől függ |
| CheckBox | chkFontos | Text: Fontos |
| Button ×4 | btnUj, btnMent, btnKesz, btnTorol | Új, Mentés, Kész/Nyitott, Törlés |
| Label ×2 | lblUzenet, lblStat | visszajelzés és összefoglaló |
Megoldás: Teendo.cs és TeendoTar.cs
// ---- Teendo.cs ----
public class Teendo
{
public int Id { get; set; }
public string Cim { get; set; } = "";
public string Leiras { get; set; } = "";
public DateTime? Hatarido { get; set; } // null: nincs határidő
public bool Fontos { get; set; }
public bool Kesz { get; set; }
public string Hiba()
{
if (Cim.Length == 0)
{
return "A cím kötelező.";
}
if (Cim.Length > 80)
{
return "A cím legfeljebb 80 karakteres lehet.";
}
if (Leiras.Length > 500)
{
return "A leírás legfeljebb 500 karakteres lehet.";
}
return "";
}
}
// ---- TeendoTar.cs ----
public static class TeendoTar
{
// szuro: 0 összes, 1 nyitott, 2 kész
public static DataTable Lista(int szuro, string kereso)
{
string sql = "SELECT id, cim, COALESCE(hatarido, '') AS hatarido, "
+ "CASE WHEN fontos = 1 THEN '★' ELSE '' END AS fontos, "
+ "CASE WHEN kesz = 1 THEN 'kész' ELSE 'nyitott' END AS allapot "
+ "FROM teendok "
+ "WHERE (@szuro = 0 OR (@szuro = 1 AND kesz = 0) OR (@szuro = 2 AND kesz = 1)) "
+ "AND (cim LIKE @minta ESCAPE '^' OR COALESCE(leiras, '') LIKE @minta ESCAPE '^') "
+ "ORDER BY kesz, CASE WHEN hatarido IS NULL THEN 1 ELSE 0 END, hatarido, fontos DESC, cim";
DataTable tabla = new DataTable();
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@szuro", szuro);
parancs.Parameters.AddWithValue("@minta", "%" + DB.LikeVedelem(kereso) + "%");
using (SQLiteDataAdapter adapter = new SQLiteDataAdapter(parancs))
{
adapter.Fill(tabla);
}
}
}
return tabla;
}
public static Teendo Betolt(int id)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("SELECT id, cim, leiras, hatarido, fontos, kesz FROM teendok WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
using (SQLiteDataReader olvaso = parancs.ExecuteReader())
{
if (!olvaso.Read())
{
return null;
}
Teendo t = new Teendo();
t.Id = Convert.ToInt32(olvaso["id"]);
t.Cim = Convert.ToString(olvaso["cim"]);
t.Leiras = Convert.ToString(olvaso["leiras"]);
t.Fontos = Convert.ToInt32(olvaso["fontos"]) == 1;
t.Kesz = Convert.ToInt32(olvaso["kesz"]) == 1;
if (olvaso["hatarido"] != DBNull.Value)
{
t.Hatarido = DateTime.Parse(Convert.ToString(olvaso["hatarido"]), CultureInfo.InvariantCulture);
}
return t;
}
}
}
}
public static int Mentes(Teendo t)
{
string hiba = t.Hiba();
if (hiba.Length > 0)
{
throw new ArgumentException(hiba);
}
object hatarido = t.Hatarido.HasValue ? (object)DB.IsoDatum(t.Hatarido.Value) : DBNull.Value;
object leiras = t.Leiras.Length == 0 ? (object)DBNull.Value : t.Leiras;
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
string sql = t.Id == 0
? "INSERT INTO teendok (cim, leiras, hatarido, fontos, kesz) VALUES (@cim, @leiras, @hatarido, @fontos, @kesz)"
: "UPDATE teendok SET cim = @cim, leiras = @leiras, hatarido = @hatarido, fontos = @fontos, kesz = @kesz WHERE id = @id";
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@cim", t.Cim);
parancs.Parameters.AddWithValue("@leiras", leiras);
parancs.Parameters.AddWithValue("@hatarido", hatarido);
parancs.Parameters.AddWithValue("@fontos", t.Fontos ? 1 : 0);
parancs.Parameters.AddWithValue("@kesz", t.Kesz ? 1 : 0);
parancs.Parameters.AddWithValue("@id", t.Id);
int erintett = parancs.ExecuteNonQuery();
if (t.Id != 0 && erintett != 1)
{
throw new InvalidOperationException("A teendő már nem létezik.");
}
}
return t.Id == 0 ? (int)kapcsolat.LastInsertRowId : t.Id;
}
}
public static void KeszValt(int id)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("UPDATE teendok SET kesz = 1 - kesz WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("A teendő már nem létezik.");
}
}
}
}
public static void Torles(int id)
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("DELETE FROM teendok WHERE id = @id", kapcsolat))
{
parancs.Parameters.AddWithValue("@id", id);
if (parancs.ExecuteNonQuery() != 1)
{
throw new InvalidOperationException("A teendő már nem létezik.");
}
}
}
}
// nyitott, kész és késésben lévő teendők száma
public static int[] Statisztika()
{
string sql = "SELECT COALESCE(SUM(CASE WHEN kesz = 0 THEN 1 ELSE 0 END), 0), "
+ "COALESCE(SUM(CASE WHEN kesz = 1 THEN 1 ELSE 0 END), 0), "
+ "COALESCE(SUM(CASE WHEN kesz = 0 AND hatarido IS NOT NULL AND hatarido < @ma THEN 1 ELSE 0 END), 0) FROM teendok";
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand(sql, kapcsolat))
{
parancs.Parameters.AddWithValue("@ma", DB.IsoDatum(DateTime.Today));
using (SQLiteDataReader olvaso = parancs.ExecuteReader())
{
olvaso.Read();
return new int[] { Convert.ToInt32(olvaso[0]), Convert.ToInt32(olvaso[1]), Convert.ToInt32(olvaso[2]) };
}
}
}
}
}Megoldás: Form1.cs
private int kijeloltId = 0;
private bool frissites = false;
private bool betoltve = false;
private void Uzenet(string szoveg, bool hiba)
{
lblUzenet.Text = szoveg;
lblUzenet.ForeColor = hiba ? Color.Firebrick : Color.SeaGreen;
}
private void Form1_Load(object sender, EventArgs e)
{
cmbSzuro.SelectedIndex = 1; // alapból a nyitott teendők
UjTeendo();
betoltve = true;
Frissit(0);
}
private void UjTeendo()
{
kijeloltId = 0;
txtCim.Clear();
txtLeiras.Clear();
chkVanHatarido.Checked = false;
chkFontos.Checked = false;
btnMent.Text = "Új teendő felvétele";
btnKesz.Enabled = false;
txtCim.Focus();
}
private void chkVanHatarido_CheckedChanged(object sender, EventArgs e)
{
dtpHatarido.Enabled = chkVanHatarido.Checked;
}
private void Frissit(int kijelolendoId)
{
if (!betoltve)
{
return;
}
frissites = true;
try
{
dgvTeendok.DataSource = TeendoTar.Lista(cmbSzuro.SelectedIndex, txtKereso.Text.Trim());
dgvTeendok.Columns["id"].Visible = false;
dgvTeendok.Columns["cim"].HeaderText = "Teendő";
dgvTeendok.Columns["hatarido"].HeaderText = "Határidő";
dgvTeendok.Columns["fontos"].HeaderText = "!";
dgvTeendok.Columns["allapot"].HeaderText = "Állapot";
foreach (DataGridViewRow sor in dgvTeendok.Rows)
{
if (Convert.ToInt32(sor.Cells["id"].Value) == kijelolendoId)
{
dgvTeendok.CurrentCell = sor.Cells["cim"];
sor.Selected = true;
break;
}
}
int[] stat = TeendoTar.Statisztika();
lblStat.Text = $"Nyitott: {stat[0]} | Kész: {stat[1]} | Késésben: {stat[2]}";
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A lista betöltése most nem sikerült.");
}
finally
{
frissites = false;
}
MezokTolt();
}
private void dgvTeendok_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
string ma = DB.IsoDatum(DateTime.Today);
foreach (DataGridViewRow sor in dgvTeendok.Rows)
{
string hatarido = Convert.ToString(sor.Cells["hatarido"].Value);
bool nyitott = Convert.ToString(sor.Cells["allapot"].Value) == "nyitott";
if (nyitott && hatarido.Length > 0 && string.Compare(hatarido, ma, StringComparison.Ordinal) < 0)
{
sor.DefaultCellStyle.BackColor = Color.MistyRose;
sor.DefaultCellStyle.ForeColor = Color.Firebrick;
}
}
}
private void MezokTolt()
{
if (dgvTeendok.CurrentRow == null)
{
return;
}
int id = Convert.ToInt32(dgvTeendok.CurrentRow.Cells["id"].Value);
Teendo t = TeendoTar.Betolt(id);
if (t == null)
{
return;
}
kijeloltId = t.Id;
txtCim.Text = t.Cim;
txtLeiras.Text = t.Leiras;
chkFontos.Checked = t.Fontos;
chkVanHatarido.Checked = t.Hatarido.HasValue;
if (t.Hatarido.HasValue)
{
dtpHatarido.Value = t.Hatarido.Value;
}
btnMent.Text = "Módosítás mentése";
btnKesz.Enabled = true;
btnKesz.Text = t.Kesz ? "Újra nyitott" : "Kész";
}
private void dgvTeendok_SelectionChanged(object sender, EventArgs e)
{
if (!frissites)
{
MezokTolt();
}
}
private void SzuroValtozott(object sender, EventArgs e)
{
Frissit(kijeloltId);
}
private void btnUj_Click(object sender, EventArgs e)
{
dgvTeendok.ClearSelection();
UjTeendo();
Uzenet("", false);
}
private void btnMent_Click(object sender, EventArgs e)
{
Teendo t = new Teendo();
t.Id = kijeloltId;
t.Cim = txtCim.Text.Trim();
t.Leiras = txtLeiras.Text.Trim();
t.Fontos = chkFontos.Checked;
if (chkVanHatarido.Checked)
{
t.Hatarido = dtpHatarido.Value.Date;
}
if (kijeloltId != 0)
{
Teendo regi = TeendoTar.Betolt(kijeloltId);
t.Kesz = regi != null && regi.Kesz;
}
string hiba = t.Hiba();
if (hiba.Length > 0)
{
Uzenet(hiba, true);
return;
}
try
{
int id = TeendoTar.Mentes(t);
Uzenet("A teendő mentve.", false);
Frissit(id);
}
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 btnKesz_Click(object sender, EventArgs e)
{
if (kijeloltId == 0)
{
return;
}
try
{
TeendoTar.KeszValt(kijeloltId);
Frissit(kijeloltId);
}
catch (InvalidOperationException ex)
{
Uzenet(ex.Message, true);
Frissit(0);
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A módosítás most nem sikerült.");
}
}
private void btnTorol_Click(object sender, EventArgs e)
{
if (kijeloltId == 0)
{
Uzenet("Előbb válassz ki egy teendőt!", true);
return;
}
DialogResult valasz = MessageBox.Show($"Biztosan törlöd: {txtCim.Text}?", "Törlés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (valasz != DialogResult.Yes)
{
return;
}
try
{
TeendoTar.Torles(kijeloltId);
Uzenet("A teendő 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.");
}
UjTeendo();
Frissit(0);
}Megoldás: miért így épül fel?
A szerkezet a 3.9 lecke mintáját követi: az ablak csak a felületet kezeli, a Teendo osztály az adatot és az ellenőrzést adja, a TeendoTar az SQL-t. A hiányzó határidő DateTime? a modellben és NULL az adatbázisban. A rendezés CASE kifejezése a NULL határidőket a végére teszi. A szűrő és a kereső egyetlen lekérdezésben él (választható szűrő minta, ESCAPE). A késés kiemelése a DataBindingComplete eseményben történik, ISO szövegek összehasonlításával. A Kesz állapot a módosításnál megmarad (a mentés nem írja át véletlenül), és külön gomb váltja.
Tesztlista
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | A program indul. | A nyitott teendők látszanak, a lejártak pirosak, az összefoglaló feliraton a számok helyesek. |
| 2 | Új teendő felvétele csak címmel. | Megjelenik, a határidő üres, a nyitottak végén áll. |
| 3 | Új teendő üres címmel. | Hibaüzenet, nem kerül az adatbázisba. |
| 4 | Teendő határidővel és fontos jelöléssel. | A táblázatban látszik a dátum és a csillag, a sorrend határidő szerint helyes. |
| 5 | Egy teendő módosítása (cím és határidő). | A lista frissül, a módosított sor kijelölve marad. |
| 6 | A határidő törlése (jelölőnégyzet kikapcsolása). | A teendő határidő nélküli lesz (NULL). |
| 7 | Kész jelölés, majd vissza nyitottra. | A szűrő szerint eltűnik/megjelenik, a számok frissülnek. |
| 8 | Szűrő: Összes, Nyitott, Kész. | A találatok szűrőnek megfelelőek. |
| 9 | Keresés: ékezetes betűvel, %, _, ' jellel. | Nincs hiba, a speciális karakterek szó szerint értendők. |
| 10 | Keresés a leírásban (egy leírás nélküli teendő is a listában). | A leírás nélküli teendők nem okoznak hibát. |
| 11 | Törlés megerősítéssel, és a Nem válasz. | Igen: törlődik; Nem: marad. |
| 12 | Lejárt határidő kijavítása jövőbelire. | A sor már nem piros. |
| 13 | A DB Browserben törölt sor módosítása a programból. | Érthető üzenet, nem omlik össze. |
| 14 | Az adatbázisfájl átnevezése, majd indítás. | Érthető hibaüzenet, a naplóban a részletek. |
| 15 | A program újraindítása. | Az adatok megmaradtak. |
Gyakori hibák és önértékelés
Ezeken szoktak elcsúszni
Gondold végig, nálad előfordul-e valamelyik.
1. Minden teendőnek lesz határideje
A határidő nélküli teendő mentés után a mai dátumot kapja. A Ok és javítás
DateTimePicker.Value mindig tartalmaz dátumot. A chkVanHatarido döntse el, hogy a határidő számít-e: ha nincs bejelölve, a Hatarido maradjon null, és az adatbázisba DBNull.Value kerüljön.
2. A határidő nélküliek a lista elején állnak
ORDER BY kesz, hataridoHiba és javítás
Az SQLite növekvő rendezésnél a NULL értéket a legkisebbnek veszi, ezért elöl jelenik meg. Rendezz előbb egy CASE WHEN hatarido IS NULL THEN 1 ELSE 0 END kifejezés szerint, utána a határidő szerint.
3. A szűrő és a kereső összekeveri a sorokat
WHERE kesz = 0 AND cim LIKE @minta OR leiras LIKE @mintaHiba és javítás
Az AND erősebb, ezért a leiras találatai a kesz szűrőtől függetlenül megjelennek. Zárójelezd: kesz = 0 AND (cim LIKE @minta OR leiras LIKE @minta). A NULL leírású teendő a LIKE vizsgálatban nem találat, ezért kell a COALESCE.
4. InvalidCastException a Kész oszlopnál
bool kesz = (bool)olvaso["kesz"];Hiba és javítás
Az SQLite-ban a kesz egész szám (0 vagy 1), amely a C# oldalon Int64. A bool típusra a közvetlen átalakítás hibát ad. Használd: Convert.ToInt32(olvaso["kesz"]) == 1.
5. A lista frissítése után más sor van kijelölve
Mentés vagy a Kész gomb után az első sor kerül kijelölésre, és a mezők a másik teendőt mutatják. A frissítés újratölti a táblázatot, és a kijelölés elveszik. Add át az azonosítót a Ok és javítás
Frissit(id) metódusnak, amely a frissítés után újra kijelöli a sort. A frissites jelző kerüli el, hogy a közbeeső kijelölések mezőtöltést indítsanak.
6. A lejárt teendők nem pirosak
Egy tegnapi határidejű, nyitott teendő is normál színű. Ellenőrizd, hogy a határidő ISO formátumban (Ok és javítás
yyyy-MM-dd) van-e az adatbázisban és az összehasonlításban is, és hogy a színezés a DataBindingComplete eseményben fut (a DataSource beállítása után). Nyitott állapotot és nem üres határidőt is vizsgálj.
Az értékeléshez ellenőrizd az alábbi szempontokat is:
ORDER BY csak rögzített szöveg; a LIKE speciális karakterei védettek.Teendo.Hiba() metódusban van.Bővítési ötletek, ha kész vagy
Ha maradt időd, próbáld ki:
- Prioritás: egy 1–3 értékű oszlop (
ALTER TABLE) és egyComboBoxa szerkesztéshez. - Kategóriák: egy második tábla (
kategoriak) idegen kulccsal (3.10 lecke). - Ismétlődő teendők: kész jelölésnél a határidő eltolása egy héttel.
- Export: a nyitott teendők szöveges fájlba mentése.
- Értesítés: induláskor üzenet a mai és a lejárt teendőkről.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Az első kettő a DB Browserben, a többi a kész teendőlistán oldható meg. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. A mai és a lejárt teendők
Írj lekérdezést, amely a ma (2026-10-26) esedékes és a már lejárt, nyitott teendőket adja, a legrégebbi határidővel előre. A Megoldás megnyitása
hatarido <= '2026-10-26' a mait és a korábbiakat is tartalmazza. A NULL határidő a feltételben nem igaz, ezért kimarad.SELECT cim, hatarido
FROM teendok
WHERE kesz = 0 AND hatarido <= '2026-10-26'
ORDER BY hatarido;
2. Fontos és nyitott
Add meg a fontos, még nem kész teendők számát, és azt, hány teendőnek nincs határideje. Az összesítés Megoldás megnyitása
COUNT(*), a hiányzó határidőt az IS NULL vizsgálja.SELECT COUNT(*) FROM teendok WHERE fontos = 1 AND kesz = 0;
SELECT COUNT(*) FROM teendok WHERE hatarido IS NULL;
Közepes szint
3. Gyors felvétel Enterrel
Egy A txtGyors mezőbe címet írva az Enter megnyomására azonnal új teendő kerüljön a listára (határidő nélkül), és a mező ürüljön.Megoldás megnyitása
KeyDown eseményben az Enter billentyűt vizsgáljuk. Az e.SuppressKeyPress = true megakadályozza a csengő hangot. Az ellenőrzést a Teendo.Hiba() metódus végzi, ugyanúgy, mint a Mentés gombnál.private void txtGyors_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode != Keys.Enter)
{
return;
}
e.SuppressKeyPress = true;
Teendo t = new Teendo();
t.Cim = txtGyors.Text.Trim();
string hiba = t.Hiba();
if (hiba.Length > 0)
{
Uzenet(hiba, true);
return;
}
try
{
int id = TeendoTar.Mentes(t);
txtGyors.Clear();
Frissit(id);
}
catch (Exception ex)
{
DB.Naploz(ex);
MessageBox.Show("A mentés most nem sikerült.");
}
}
4. A kész teendők törlése
Egy Kész teendők törlése gomb törölje az összes kész teendőt, de előbb kérdezzen rá a darabszámmal (3 kész teendőt törölsz. Folytatod?), és utána írja ki, hányat törölt. Előbb megszámoljuk a törlendő sorokat, így a megerősítésben a pontos szám szerepel. Az Megoldás megnyitása
ExecuteNonQuery a törölt sorok számát adja. Ha nincs kész teendő, a program ezt jelzi, és nem kérdez.// TeendoTar.cs
public static int KeszekSzama()
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("SELECT COUNT(*) FROM teendok WHERE kesz = 1", kapcsolat))
{
return Convert.ToInt32(parancs.ExecuteScalar());
}
}
}
public static int KeszekTorlese()
{
using (SQLiteConnection kapcsolat = DB.UjKapcsolat())
{
kapcsolat.Open();
using (SQLiteCommand parancs = new SQLiteCommand("DELETE FROM teendok WHERE kesz = 1", kapcsolat))
{
return parancs.ExecuteNonQuery();
}
}
}
// Form1.cs
private void btnKeszTorol_Click(object sender, EventArgs e)
{
int db = TeendoTar.KeszekSzama();
if (db == 0)
{
Uzenet("Nincs kész teendő.", false);
return;
}
if (MessageBox.Show($"{db} kész teendőt törölsz. Folytatod?", "Törlés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
{
return;
}
int torolt = TeendoTar.KeszekTorlese();
Uzenet($"{torolt} kész teendő törölve.", false);
UjTeendo();
Frissit(0);
}
Kihívás szint
5. Prioritás
Adj a teendőkhöz egy Három helyen változik: (1) az adatbázis: új oszlop prioritas oszlopot (1 = magas, 2 = közepes, 3 = alacsony, alapérték 2), válaszd ki egy cmbPrioritas listából, és rendezd úgy a listát, hogy a nyitottak között a magasabb prioritás előbb áll. Mi változik a modellben, az SQL-ben és az ablakban?Megoldás megnyitása
ALTER TABLE-lel, (2) a modell: Prioritas tulajdonság és ellenőrzés (1–3), (3) az adatkezelő és az ablak: a Mentes és a Betolt kezeli az oszlopot, a rendezés a prioritas szerint (a határidő előtt vagy után, a követelmény szerint). A rétegek miatt az ablak kódja alig változik.-- egyszer, a DB Browserben:
ALTER TABLE teendok ADD COLUMN prioritas INTEGER NOT NULL DEFAULT 2;
// Teendo.cs
public int Prioritas { get; set; } = 2;
// Teendo.Hiba():
if (Prioritas < 1 || Prioritas > 3)
{
return "A prioritás 1 és 3 közötti legyen.";
}
// TeendoTar.Lista ORDER BY: a prioritás a határidő elé kerül
// ... ORDER BY kesz, prioritas, CASE WHEN hatarido IS NULL THEN 1 ELSE 0 END, hatarido, cim
6. A nyitott teendők exportálása
Egy Export gomb mentse a nyitott teendőket szöveges fájlba, soronként [ ] Cím (határidő) alakban, a lejártakat !! jelzéssel. Mire kell figyelni a fájlkezelésnél? A Megoldás megnyitása
StreamWriter using blokkban zárja a fájlt. A fájlnevet a SaveFileDialog kéri, a hibákat (írásvédett hely) try–catch kezeli. A lekérdezés ugyanaz a Lista metódus a nyitott szűrővel és üres kereséssel.private void btnExport_Click(object sender, EventArgs e)
{
if (sfdExport.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
DataTable tabla = TeendoTar.Lista(1, "");
string ma = DB.IsoDatum(DateTime.Today);
using (System.IO.StreamWriter iro = new System.IO.StreamWriter(sfdExport.FileName))
{
foreach (DataRow sor in tabla.Rows)
{
string hatarido = Convert.ToString(sor["hatarido"]);
bool lejart = hatarido.Length > 0 && string.Compare(hatarido, ma, StringComparison.Ordinal) < 0;
iro.WriteLine("[ ] " + sor["cim"] + (hatarido.Length > 0 ? " (" + hatarido + ")" : "") + (lejart ? " !!" : ""));
}
}
Uzenet("Az export elkészült.", false);
}
catch (Exception ex)
{
DB.Naploz(ex);
Uzenet("Az export nem sikerült.", true);
}
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A nullázható dátum. Akkor kell, ha egy dátum hiányozhat, mint a nem kötelező határidő. A Egész számként (0 vagy 1). Olvasáskor Előbb A Hogy a felület és az adatkezelés szétváljon, és egy változtatás egy helyen történjen. Az ablak csak a A szűrő az állapotot korlátozza (kész/nyitott), a kereső a szövegre szűr. Egyetlen lekérdezésben, zárójelezve, 1. Mi a DateTime? típus, és miért kell?
HasValue megmondja, van-e értéke.2. Hogyan tárolsz logikai értéket SQLite-ban?
Convert.ToInt32(...) == 1, íráskor érték ? 1 : 0.3. Hogyan rendezed a NULL határidőket a végére?
CASE WHEN hatarido IS NULL THEN 1 ELSE 0 END szerint, utána a határidő szerint rendezel.4. Miért kell a COALESCE a keresésben?
NULL leírás a LIKE vizsgálatban sem igaz, sem hamis, ezért a COALESCE(leiras, '') üres szöveggé alakítja.5. Miért nincs SQL az ablakban?
TeendoTar metódusait hívja.6. Mi a különbség a szűrő és a kereső között a lekérdezésben?
AND kapcsolja őket.
Mit tudsz már?
- Önállóan felépítesz egy rétegekre bontott, adatbázisos nyilvántartást.
- Kezeled a hiányzó értékeket a modellben, az SQL-ben és a rendezésben.
- Szűrést és keresést építesz egy biztonságos lekérdezésbe.
- Határidőt kezelsz, és a lejártakat kiemeled.
- Tesztlistával ellenőrzöd, és önértékeled a saját munkád.
Következő: önálló projekt megtervezése és tesztelése
A három önálló alkalmazás (számológép, rajzoló, nyilvántartás) után a következő lépés a saját ötleted megtervezése: követelmények, tervrajz és tesztterv.



