Grafikus C# – Windows Forms tanfolyam › 8.9 lecke

Windows Forms · 8.9 lecke · mentés és betöltés

Táblázat adatainak mentése és betöltése

A szépen szerkeszthető, ellenőrzött táblázat addig ér valamit, amíg nem zárod be a programot. A tartós tároláshoz fájlba kell írni az adatokat, és induláskor vissza kell olvasni. Ebben a leckében a leggyakoribb, ember- és Excel-barát formátumot, a CSV-t tanulod meg (a hozzá tartozó apró csapdákkal: pontosvessző, idézőjelek, kódolás, tizedesjel), az DataTable XML-mentését, az importálás hibakezelését (nem veszíthetsz adatot egy hibás fájl miatt), a mentetlen változtatások jelzését és a biztonságos mentést.

  • kb. 110 perc
  • közepes–haladó szint
  • CSV és XML
Diáklány C# programot készít laptopon

A lecke végére

  • kiválasztod a helyzethez illő formátumot (CSV, XML, később JSON és adatbázis);
  • helyes CSV-t írsz (idézőjelek, pontosvessző, UTF8Encoding(true), InvariantCulture);
  • idézőjel-kezelő CSV-sorfeldolgozót írsz, és hibás sorokat jelentesz;
  • az importálás teljes beolvasás után cseréli az adatot (nem veszíthetsz adatot);
  • a DataTable-t XML-be mented és olvasod vissza;
  • jelzed a mentetlen változtatást, megerősítést kérsz kilépéskor, és biztonságosan mentesz.

Mielőtt elkezded

Ehhez a leckéhez a 7.4 és 7.8 lecke (MessageBox, fájlválasztók), a 8.4 lecke (BindingList), a 8.8 lecke (adatellenőrzés) és az 5.6 lecke (FormClosing, Settings) szükséges. A fájlkezelés részleteit a 10. fejezet tárgyalja, itt a File osztály metódusait használjuk.

8.8 CellValidating →

Milyen formátumba mentsünk?

FormátumElőnyHátrányMikor?
CSV (szöveg, pontosvesszővel)Egyszerű, ember által olvasható, Excelben megnyílik.Nincs típus (minden szöveg), nincs beágyazás; a speciális karakterekre figyelni kell.Táblázatos adat, import és export másik programmal.
XMLTípust és szerkezetet is tárol, ember által olvasható.Bőbeszédű, nagy fájl.Összetettebb adat, DataTable mentése (10.7 lecke).
JSONTömör, népszerű, objektumok mentésére való.Táblázat-szerkesztővel nem nyitható.Objektumok és beállítások (10.6 lecke).
SQLiteLekérdezhető, nagy adatot is kezel, tranzakciók.Összetettebb kezelés.Sok adat, több felhasználó, kapcsolatok (3. fejezet).

Hol legyen a fájl?

A felhasználó által választott fájlt a mentés párbeszédablak adja (7.8 lecke). A program saját adatainak helye a Environment.SpecialFolder.ApplicationData alatti mappa (írási jog, felhasználónként külön). A program mappájába (Program Files) ne írj.

CSV mentése helyesen

A CSV első ránézésre egyszerű: soronként egy rekord, a mezők elválasztóval. A csapdák: (1) a pontosvessző az elválasztó magyar Excelben (a vessző a tizedesjel); (2) ha a mező maga tartalmaz pontosvesszőt, idézőjelet vagy sortörést, idézőjelek közé kell tenni, a benne lévő idézőjelet megkettőzve; (3) az Excel csak BOM-os UTF-8 fájlban ismeri fel az ékezeteket; (4) a számokat és dátumokat rögzített alakban írd (InvariantCulture), hogy más nyelvű gépen is beolvasható legyen:

Form1.cs (using System.IO; using System.Text; using System.Globalization;)C#
// egy mező biztonságos kiírása: ha speciális karaktert tartalmaz, idézőjelek közé kerül
private static string CsvMezo(string szoveg)
{
szoveg = szoveg ?? "";

string idezo = ((char)34).ToString(); // az idézőjel (")

if (szoveg.IndexOfAny(new char[] { ';', (char)34, (char)13, (char)10 }) >= 0)
{
szoveg = idezo + szoveg.Replace(idezo, idezo + idezo) + idezo; // a belső idézőjel megkettőzve
}

return szoveg;
}

private void CsvMent(string utvonal)
{
List<string> sorok = new List<string>();
sorok.Add("Kod;Nev;Ar;Keszlet"); // fejléc

foreach (Termek t in lista)
{
sorok.Add(CsvMezo(t.Kod) + ";" + CsvMezo(t.Nev) + ";"
+ t.Ar.ToString(CultureInfo.InvariantCulture) + ";"
+ t.Keszlet.ToString(CultureInfo.InvariantCulture));
}

// UTF8Encoding(true): BOM-mal, hogy az Excel helyesen mutassa az ékezeteket
File.WriteAllLines(utvonal, sorok, new UTF8Encoding(true));
}
Idézőjelek kettőzéseA Mackó "Pu" név a fájlban "Mackó ""Pu""" alakú. Ha ezt kihagyod, az idézőjel megtöri a sort, és a beolvasás hibás mezőket ad (vagy a CSV „injekciót” teszi lehetővé, amikor a mező egy képletet tartalmaz).
FejlécAz első sor az oszlopnevek: betöltéskor ellenőrizhető, hogy a fájl valóban a mi formátumunk-e.
InvariantCultureA 1250,5 (magyar gép) és 1250.5 (angol gép) különbözik. Rögzített alakban (pont) mentve minden gép beolvassa. Ha a fájlt kizárólag magyar Excelben nyitnák, a CurrentCulture kényelmesebb; ezt a döntést dokumentálni kell.
BOMA BOM (byte order mark) a fájl elején álló jel, amely jelzi az UTF-8 kódolást. Nélküle az Excel ékezetes betűit hibásan (é) mutatja. A saját beolvasó (Encoding.UTF8) a BOM-ot kezeli.

Jósolj!

A Nev mező értéke Toll; kék (benne pontosvessző), és a mentés nem idézőjelezi. Hány mezőt lát a beolvasás ebben a sorban?

Jóslat ellenőrzése

Ötöt négy helyett: a név két mezőre bomlik, az ár és a készlet eltolódik (az „ár” a " kék" szöveg lenne). Ezért kell az idézőjelezés mentéskor, és a mezőszám ellenőrzése beolvasáskor.

CSV betöltése: sorfeldolgozás és hibajelentés

A sor.Split(';') a legegyszerűbb, de az idézőjeles mezőkkel nem boldogul. Egy kis állapotgép (idézőjelben vagy kívül) pontosan feldolgozza a sort. A fontosabb elv: az importálás soha ne cseréljen adatot, amíg a teljes fájl feldolgozása nem készült el. Először új listába olvasunk, a hibákat gyűjtjük, és a felhasználó dönt:

Form1.csC#
// egy CSV-sor mezőkre bontása (az idézőjeles mezők és a kettőzött idézőjel kezelésével)
private static List<string> CsvSor(string sor)
{
List<string> mezok = new List<string>();
StringBuilder aktualis = new StringBuilder();
bool idezoben = false;

for (int i = 0; i < sor.Length; i++)
{
char c = sor[i];

if (idezoben)
{
if (c == (char)34)
{
if (i + 1 < sor.Length && sor[i + 1] == (char)34)
{
aktualis.Append((char)34); // kettőzött idézőjel: egy szó szerinti idézőjel
i++;
}
else
{
idezoben = false; // az idézőjeles rész vége
}
}
else
{
aktualis.Append(c);
}
}
else if (c == (char)34)
{
idezoben = true;
}
else if (c == ';')
{
mezok.Add(aktualis.ToString());
aktualis.Clear();
}
else
{
aktualis.Append(c);
}
}

mezok.Add(aktualis.ToString());
return mezok;
}

private List<Termek> CsvBeolvas(string utvonal, List<string> hibak)
{
List<Termek> uj = new List<Termek>();
string[] sorok = File.ReadAllLines(utvonal, Encoding.UTF8);

if (sorok.Length == 0 || sorok[0].Trim() != "Kod;Nev;Ar;Keszlet")
{
hibak.Add("A fájl nem árlista (a fejléc nem egyezik).");
return uj;
}

for (int i = 1; i < sorok.Length; i++)
{
if (sorok[i].Trim().Length == 0)
{
continue; // üres sor
}

List<string> m = CsvSor(sorok[i]);
decimal ar;
int db;

if (m.Count != 4)
{
hibak.Add((i + 1) + ". sor: 4 mező kellene, " + m.Count + " van.");
}
else if (!decimal.TryParse(m[2], NumberStyles.Number, CultureInfo.InvariantCulture, out ar) || ar <= 0)
{
hibak.Add((i + 1) + ". sor: érvénytelen ár (" + m[2] + ").");
}
else if (!int.TryParse(m[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out db) || db < 0)
{
hibak.Add((i + 1) + ". sor: érvénytelen készlet (" + m[3] + ").");
}
else
{
uj.Add(new Termek { Kod = m[0].Trim(), Nev = m[1].Trim(), Ar = ar, Keszlet = db });
}
}

return uj;
}

private void Megnyit(string utvonal)
{
List<string> hibak = new List<string>();
List<Termek> uj;

try
{
uj = CsvBeolvas(utvonal, hibak);
}
catch (IOException ex)
{
MessageBox.Show(this, "A fájl nem olvasható: " + ex.Message, "Megnyitás", MessageBoxButtons.OK, MessageBoxIcon.Error);
return; // a jelenlegi adat érintetlen
}

if (hibak.Count > 0)
{
string szoveg = hibak.Count + " hibás sor van, az első: " + hibak[0] + Environment.NewLine + "A hibás sorok nélkül (" + uj.Count + " sor) betöltsem?";

if (uj.Count == 0 || MessageBox.Show(this, szoveg, "Importálás", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes)
{
return;
}
}

// csak a teljes feldolgozás után cseréljük az adatot
lista.Clear();

foreach (Termek t in uj)
{
lista.Add(t);
}
}
ÁllapotgépA idezoben jelző tárolja, hogy idézőjelben vagyunk-e. Idézőjelben a pontosvessző szó szerinti karakter; a kettőzött idézőjel egy idézőjel. Ez az egyszerű szabály a CSV lényege.
Soronként egy hibaA hibás sorokat nem dobjuk el csendben, hanem számmal (i + 1: a fájl sorszáma) és okkal jelentjük, hogy a felhasználó ki tudja javítani a fájlt.
Cserélés a végénAz új lista elkészül, az ellenőrzések lefutnak, és csak ezután töröljük a régi adatot. Egy hibás fájl így nem tud adatot törölni.
Mit nem tudA sorokra bontás (ReadAllLines) feltételezi, hogy a mező nem tartalmaz sortörést. A sortörést is tartalmazó mezőhöz folyamatos olvasás kell (StreamReader, 10.2 lecke) vagy kész CSV-könyvtár (NuGet).

Jósolj!

A betöltés először törli a listát (lista.Clear()), és utána olvassa be a fájlt, amelynek a 12. sora hibás: a beolvasás kivételt dob. Mi marad a programban?

Jóslat ellenőrzése

Egy félig betöltött vagy üres lista: a régi adat elveszett, az új pedig nem készült el. Ezért olvasunk először új listába, és a régit csak a sikeres feldolgozás után cseréljük.

DataTable mentése XML-be

Ha az adat DataTable-ben van (3.8 és 8.7 lecke), az XML-mentés egy sor: a WriteXml a sorokat és (kérésre) a szerkezetet is kiírja, a ReadXml visszaolvassa:

Form1.cs (using System.Data;)C#
private void XmlMent(DataTable tabla, string utvonal)
{
tabla.TableName = "Termek"; // kötelező: név nélkül kivételt dob
tabla.WriteXml(utvonal, XmlWriteMode.WriteSchema); // az oszlopok típusát is menti
}

private DataTable XmlBetolt(string utvonal)
{
DataTable uj = new DataTable("Termek");
uj.ReadXml(utvonal); // a séma a fájlban van, az oszlopok létrejönnek

return uj;
}
WriteSchemaA XmlWriteMode.WriteSchema a fájlba írja az oszlopok nevét és típusát (decimal, int), így a beolvasáskor nem lesz minden oszlop szöveg. Séma nélkül (IgnoreSchema) a ReadXml kitalálja az oszlopokat, de mind szöveg lesz.
TableNameA DataTable neve nélkül a WriteXml kivételt dob (nincs beállítva a tábla neve).
Új tábla a betöltéshezA ReadXml az új táblába olvasson, és a felület csak a sikeres beolvasás után cserélje az adatforrást (a CSV-nél tanult elv).
Hibás XMLA sérült fájl XmlException kivételt dob: try–catch kell, és az eredeti adatot meg kell tartani.

Jósolj!

A tábla mentése XmlWriteMode.IgnoreSchema módban történt, a betöltés után az Ár oszlop szerint rendezel. Milyen sorrendet kapsz?

Jóslat ellenőrzése

Szöveg szerinti sorrendet (például 100, 20, 300): séma nélkül minden oszlop string típusú, ezért a rendezés betűrend szerinti. A WriteSchema ezt megelőzi.

Mentetlen változtatás és biztonságos mentés

Form1.csC#
private string fajl = "";
private bool modosult = false;

private void Modosult()
{
if (!modosult)
{
modosult = true;
CimFrissit();
}
}

// a változás forrásai: sor hozzáadása/törlése, cella módosítása
lista.ListChanged += (s, e) => Modosult();

private void CimFrissit()
{
string nev = fajl.Length == 0 ? "Névtelen" : Path.GetFileName(fajl);
this.Text = nev + (modosult ? "*" : "") + " – Árlista";
}

// biztonságos mentés: ideiglenes fájlba, majd csere (az eredeti nem maradhat félkész)
private void BiztonsagosMent(string utvonal)
{
string ideiglenes = utvonal + ".tmp";

CsvMent(ideiglenes); // ha itt hiba van, az eredeti fájl épen marad

if (File.Exists(utvonal))
{
File.Copy(utvonal, utvonal + ".bak", true); // az előző változat mentése
File.Delete(utvonal);
}

File.Move(ideiglenes, utvonal);
}
ListChangedA BindingList.ListChanged esemény jelzi a sor hozzáadását és törlését (és a cella-módosítást, ha az elem INotifyPropertyChanged-et valósít meg, 8.6 lecke). Egyszerű osztályoknál a dgv.CellValueChanged eseményt is kezeld.
Jelzés a címbenA * jel az ismerős jelzés. Mentés után a modosult = false beállítása és a cím frissítése nem maradhat el.
Ideiglenes fájlHa a mentés közben összeomlik a program vagy elfogy a hely, az eredeti fájl épen marad. A .bak az előző változatot őrzi, ha a felhasználó hibás adatot mentett.
KilépéskorA mentetlen változtatásnál a FormClosing eseményben YesNoCancel kérdés (7.4 lecke); a sikertelen mentés megakadályozza a kilépést.

Jósolj!

A mentés közvetlenül az eredeti fájlba ír (File.WriteAllLines(utvonal, ...)), és a lemez a mentés közepén megtelik. Mi marad az eredeti fájlból?

Jóslat ellenőrzése

Egy félig kiírt, csonka fájl: a mentés a fájl tartalmát előbb törli, és az írás megszakad. Az ideiglenes fájlos mentésnél az eredeti érintetlen marad, és a hiba csak az ideiglenes fájlt érinti.

📷 Képernyőkép helye

Mit tegyél ide: az árlista program a „* – Árlista” címmel, a Fájl menü és az Importálás hibajelentő ablaka („2 hibás sor van…”).Javasolt fájlnév: winforms-8-9-fajl.webp · Alt szöveg: Árlista mentéssel és importálási hibajelentéssel

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. Az Excelben hibásak az ékezetek

A Füzet szó helyett Füzet látszik.

Ok és javítás

A fájl BOM nélküli UTF-8, az Excel régi kódlapként értelmezi. Mentsd new UTF8Encoding(true) kódolással (BOM-mal).

2. Az Excel egyetlen oszlopba teszi az egész sort

A CSV megnyitásakor nincsenek oszlopok.

Ok és javítás

Magyar Excelben a pontosvessző az elválasztó (a vessző a tizedesjel). Vesszővel mentett fájlnál az Excel nem bontja a mezőket. Használj pontosvesszőt.

3. A név mezőbe kerül az ár

sorok.Add(t.Kod + ";" + t.Nev + ";" + t.Ar + ";" + t.Keszlet);   // a név pontosvesszőt tartalmazhat
Hiba és javítás

A mezőket nem idézőjelezi. Használd a CsvMezo metódust a szöveges mezőknél, a számokat InvariantCulture alakban.

4. A betöltés magyar gépen jó, angolon nem

A 1250,5 ár az egyik gépen 1250,5, a másikon 12505.

Ok és javítás

A számot a gép kultúrája szerint írtad/olvastad. Mentsd és olvasd CultureInfo.InvariantCulture alakban (pont a tizedesjel).

5. A hibás fájl törli az adataimat

A hibás CSV megnyitása után a lista üres.

Ok és javítás

A betöltés előbb törölte a listát. Olvass új listába, ellenőrizz, és csak a sikeres feldolgozás után cserélj.

6. A kilépéskor elvész a munka

A felhasználó bezárja az ablakot, és a módosítások eltűnnek.

Ok és javítás

Nincs módosítás-jelzés és megerősítés. Használd a modosult jelzőt (ListChanged), és a FormClosing eseményben kérdezz (YesNoCancel).

Mini projekt: árlista fájlkezeléssel

Egészítsd ki az árlista-szerkesztőt (8.8 lecke) fájlkezeléssel:

  1. Fájl menü: Új (Ctrl+N), Megnyitás... (Ctrl+O), Mentés (Ctrl+S), Mentés másként..., Kijelöltek exportálása..., Importálás hozzáfűzéssel..., Kilépés.
  2. CSV formátum: pontosvessző, fejléc (Kod;Nev;Ar;Keszlet), BOM-os UTF-8, szám InvariantCulture alakban; a szöveges mezők idézőjelezve, ha kell (CsvMezo).
  3. Betöltés: CsvSor állapotgép; a fejléc ellenőrzése; hibás sorok gyűjtése sorszámmal; hibánál a felhasználó dönt (a hibás sorok nélkül betölti, vagy megszakítja); az adat csak a teljes feldolgozás után cserélődik.
  4. Importálás hozzáfűzéssel: a másik fájl sorait a meglévő listához adja (az ismétlődő kódot kihagyja, és jelzi, hány sort hagyott ki).
  5. Kijelöltek exportálása: csak a rácsban kijelölt sorokat menti új fájlba.
  6. Mentetlen változtatás: a modosult jelző (ListChanged és CellValueChanged), a cím * jele, megerősítés Új, Megnyitás és Kilépés előtt (YesNoCancel), sikertelen mentésnél nem folytat.
  7. Biztonságos mentés: ideiglenes fájl, .bak az előző változatról; a mentés előtt az adatellenőrzés (8.8 lecke) fut, hibánál nem ment.
  8. Utolsó mappa és fájl: a Settings-ben (UtolsoMappa), a fájlválasztók ott kezdenek.
  9. Teszteld: Excelben nyitás (ékezetek, oszlopok), pontosvesszős és idézőjeles név, tizedes ár, üres fájl, rossz fejléc, hibás sorok (2–3 hiba), kihagyott duplikátum, kijelöltek exportja, mentés közbeni hiba (írásvédett fájl), kilépés mentetlenül.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A CsvMezo, CsvSor, CsvBeolvas metódusokat a lecke mutatja. Itt a fájlkezelő menüpontok és a mentetlen adat kezelése áll.

Form1.csC#
private BindingList<Termek> lista = new BindingList<Termek>();
private string fajl = "";
private bool modosult = false;

private void Form1_Load(object sender, EventArgs e)
{
dgv.AutoGenerateColumns = false;
dgv.DataSource = lista;
lista.ListChanged += (s, ev) => Modosult();

string mappa = Properties.Settings.Default.UtolsoMappa;

if (Directory.Exists(mappa))
{
ofd.InitialDirectory = mappa;
sfd.InitialDirectory = mappa;
}

ofd.Filter = sfd.Filter = "Árlista (*.csv)|*.csv|Minden fájl (*.*)|*.*";
sfd.DefaultExt = "csv";
CimFrissit();
}

private void dgv_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
Modosult();
}

private void Modosult()
{
if (!modosult)
{
modosult = true;
CimFrissit();
}
}

private void CimFrissit()
{
string nev = fajl.Length == 0 ? "Névtelen" : Path.GetFileName(fajl);
this.Text = nev + (modosult ? "*" : "") + " – Árlista";
}

private bool Ment(bool maskent)
{
DataGridViewCell elso;

if (MindEllenoriz(out elso) > 0) // a 8.8 lecke ellenőrzése
{
MessageBox.Show(this, "A táblázatban hibák vannak. A mentés előtt javítsd őket.", "Mentés", MessageBoxButtons.OK, MessageBoxIcon.Warning);
dgv.CurrentCell = elso;
return false;
}

string ut = fajl;

if (maskent || ut.Length == 0)
{
sfd.FileName = ut.Length == 0 ? "arlista" : Path.GetFileName(ut);

if (sfd.ShowDialog(this) != DialogResult.OK)
{
return false;
}

ut = sfd.FileName;
}

try
{
BiztonsagosMent(ut);
fajl = ut;
modosult = false;
CimFrissit();
Properties.Settings.Default.UtolsoMappa = Path.GetDirectoryName(ut);
Properties.Settings.Default.Save();
tsslUzenet.Text = "Mentve: " + Path.GetFileName(ut);
return true;
}
catch (Exception ex)
{
MessageBox.Show(this, "A mentés nem sikerült." + Environment.NewLine + ex.Message, "Mentés", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}

private bool Folytathat()
{
if (!modosult)
{
return true;
}

switch (MessageBox.Show(this, "Mented a változtatásokat?", "Árlista", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning))
{
case DialogResult.Yes: return Ment(false);
case DialogResult.No: return true;
default: return false;
}
}

private void miUj_Click(object sender, EventArgs e)
{
if (Folytathat())
{
lista.Clear();
fajl = "";
modosult = false;
CimFrissit();
}
}

private void miMegnyit_Click(object sender, EventArgs e)
{
if (Folytathat() && ofd.ShowDialog(this) == DialogResult.OK)
{
// a lecke Megnyit metódusát alakítsd bool visszatérésűvé: false hibánál vagy megszakításnál, true siker esetén
if (Megnyit(ofd.FileName))
{
fajl = ofd.FileName;
modosult = false;
CimFrissit();
}
}
}

private void miImport_Click(object sender, EventArgs e)
{
if (ofd.ShowDialog(this) != DialogResult.OK) { return; }

List<string> hibak = new List<string>();
List<Termek> uj;

try { uj = CsvBeolvas(ofd.FileName, hibak); }
catch (IOException ex) { MessageBox.Show(this, ex.Message, "Importálás", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }

int kihagyott = 0;

foreach (Termek t in uj)
{
bool van = false;

foreach (Termek m in lista) { if (string.Equals(m.Kod, t.Kod, StringComparison.OrdinalIgnoreCase)) { van = true; break; } }

if (van) { kihagyott++; } else { lista.Add(t); }
}

tsslUzenet.Text = (uj.Count - kihagyott) + " sor hozzáfűzve, " + kihagyott + " ismétlődő kihagyva, " + hibak.Count + " hibás sor.";
}

private void miExport_Click(object sender, EventArgs e)
{
if (dgv.SelectedRows.Count == 0) { return; }

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

List<string> sorok = new List<string>();
sorok.Add("Kod;Nev;Ar;Keszlet");

foreach (DataGridViewRow r in dgv.SelectedRows)
{
Termek t = r.DataBoundItem as Termek;

if (t != null)
{
sorok.Add(CsvMezo(t.Kod) + ";" + CsvMezo(t.Nev) + ";" + t.Ar.ToString(CultureInfo.InvariantCulture) + ";" + t.Keszlet.ToString(CultureInfo.InvariantCulture));
}
}

File.WriteAllLines(sfd.FileName, sorok, new UTF8Encoding(true));
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing && !Folytathat())
{
e.Cancel = true;
}
}
📷 Képernyőkép helye

Mit tegyél ide: az árlista program megnyitott fájllal, a menü és a státuszüzenet.Javasolt fájlnév: winforms-8-9-kesz.webp · Alt szöveg: Az árlista program fájlkezeléssel

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. Fejléc a DataGridView-ból

A CSV fejlécét ne kézzel írd, hanem a rács oszlopaiból állítsd elő (HeaderText vagy DataPropertyName). Melyiket használod a fájlban, és miért?

Egy lehetséges megoldás

A DataPropertyName (stabil, kódban használt név); a HeaderText a felhasználónak szánt felirat, amely nyelvváltáskor módosulhat, a fájlformátumnak pedig állandónak kell lennie.

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

foreach (DataGridViewColumn o in dgv.Columns)
{
if (o.Visible)
{
fejlec.Add(o.DataPropertyName);
}
}

string fejlecSor = string.Join(";", fejlec);

2. Automatikus mentés

Egy időzítő 2 percenként .autosave fájlba menti a módosított adatot (az eredeti fájlt nem írja felül). Indításkor, ha ilyen fájl van, felajánlja a visszaállítást. Hogyan oldod meg?

Egy lehetséges megoldás

Az időzítő a modosult jelző esetén ment az ideiglenes fájlba; a rendes mentés és az új dokumentum törli. Induláskor a File.Exists alapján kérdez (YesNo).

Form1.csC#
private string AutoUtvonal(){
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Arlista", "auto.csv");
}

private void tmrAuto_Tick(object sender, EventArgs e)
{
if (modosult)
{
Directory.CreateDirectory(Path.GetDirectoryName(AutoUtvonal()));
CsvMent(AutoUtvonal());
}
}

3. Fejléc szerinti oszlopsorrend

A beolvasás ne függjön az oszlopok sorrendjétől: a fejléc nevei szerint keresse meg az oszlopokat (Nev;Kod;Keszlet;Ar sorrend is jó). Hogyan?

Egy lehetséges megoldás

A fejlécből szótárt építesz (oszlopnév → index), és a mezőket név szerint éred el.

Form1.csC#
Dictionary<string, int> index = new Dictionary<string, int>();
List<string> fejlec = CsvSor(sorok[0]);

for (int i = 0; i < fejlec.Count; i++)
{
index[fejlec[i].Trim()] = i;
}

// használat egy sorban:
string nev = m[index["Nev"]];
string kod = m[index["Kod"]];

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. Mezők idézőjelezése

Írd meg a fájlba kerülő alakot: (a) Toll; (b) Toll; kék; (c) Mackó "Pu".

Megoldás megnyitása

(a) Toll (nem kell idézőjel); (b) "Toll; kék"; (c) "Mackó ""Pu""" (a belső idézőjel kettőzve).

2. Formátumválasztás

Melyik formátumot választod: (a) szülők névsora Excelbe; (b) program beállításai; (c) 10 000 sor lekérdezhető adat?

Megoldás megnyitása

(a) CSV; (b) JSON vagy a Settings (10.6 és 10.8 lecke); (c) SQLite (3. fejezet).

Közepes szint

3. Dátum a CSV-ben

Az árlista kapjon Datum mezőt. Milyen alakban írod a fájlba, és hogyan olvasod vissza?

Megoldás megnyitása

Rögzített alakban: yyyy-MM-dd (InvariantCulture), olvasáskor DateTime.TryParseExact ugyanezzel az alakkal. Így nem függ a gép beállításától.

Form1.csC#
string szoveg = t.Datum.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);

DateTime d;
bool ok = DateTime.TryParseExact(m[4], "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out d);

4. Vessző az elválasztó

Egy külföldi partner fájlja vesszővel elválasztott. Hogyan teszed állíthatóvá az elválasztót?

Megoldás megnyitása

Az elválasztó karakter paraméter (char elvalaszto) a CsvMezo, CsvSor metódusokban (alapértelmezett ;); a felhasználó egy legördülő listából választja, vagy a fájl első soráról kitalálod (sep=; sor, vagy a gyakoribb karakter).

Kihívás szint

5. Nagy fájl folyamatosan

Egy 500 MB-os CSV-t nem olvashatsz be egyszerre. Hogyan olvasod soronként, és mit mutatsz a felhasználónak?

Megoldás megnyitása

Az File.ReadLines (halasztott, soronkénti) vagy StreamReader olvas; a haladást (ProgressBar) a feldolgozott bájtok arányából számolod. A hosszú művelet nem futhat a felület szálán (11. fejezet).

Form1.csC#
int szam = 0;foreach (string sor in File.ReadLines(utvonal, Encoding.UTF8)){
szam++;
// a sor feldolgozása (CsvSor), a memóriában csak az aktuális sor van
}

6. Adat-verzió a fájlban

A program új változata új oszlopot kap. Hogyan olvassa a régi fájlokat is? Mit teszel a fájl első soraiba?

Megoldás megnyitása

A fájlformátum verziószámot kap (#verzio=2 első sor, vagy fejléc-ellenőrzés). A beolvasó a régebbi verziókhoz alapértelmezett értéket tölt az új oszlopra. A fájlformátumot dokumentálni kell.

Gyors önellenőrzés

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

1. Mi a CSV elválasztója magyar Excelben, és miért?

A pontosvessző, mert a vessző a tizedesjel.

2. Mikor kell idézőjelbe tenni egy mezőt?

Ha pontosvesszőt, idézőjelet vagy sortörést tartalmaz; a belső idézőjelet kettőzni kell.

3. Miért UTF8Encoding(true)?

A BOM jelzi az Excelnek az UTF-8 kódolást, így az ékezetek helyesek.

4. Miért új listába olvasol betöltéskor?

Hogy hibás fájl ne törölhesse a meglévő adatot: a régit csak a sikeres feldolgozás után cseréled.

5. Mire való az XmlWriteMode.WriteSchema?

Az oszlopok típusát is menti, így a betöltés után nem lesz minden szöveg.

6. Hogyan mentesz biztonságosan?

Ideiglenes fájlba írsz, és csak siker után cseréled az eredetit (és őrzöl .bak másolatot).

7. Honnan tudod, hogy a dokumentum módosult?

ListChanged és CellValueChanged események alapján egy modosult jelző; megjelenítése a címben (*).

Mit tudsz már?

  • Kiválasztod a megfelelő tárolási formátumot, és helyes CSV-t írsz és olvasol.
  • Hibás sorokat jelentesz, és az importálás nem veszít adatot.
  • A DataTable-t XML-be mented és olvasod vissza.
  • Jelzed a mentetlen változtatást, és biztonságosan mentesz.
  • Felismered a tipikus hibákat (BOM, elválasztó, kultúra, idézőjelek).

Következő lecke: mini projekt – táblázatos nyilvántartás

A 8. fejezet záró projektje: egy teljes táblázatos nyilvántartás keresővel, ellenőrzéssel, fájlkezeléssel és összesítéssel.