Grafikus C# – Windows Forms tanfolyam › 2.16 lecke

Windows Forms · 2.16 lecke · záró projekt

Mini projekt: jegyzettömb és szövegszerkesztő

A 2. fejezet minden funkciója megvan: megnyitás és mentés, formázás, igazítás, keresés, szerkesztés, vágólap, védelem a mentetlen munka ellen és nyomtatás. Most egyetlen, rendezett programmá fésüljük össze őket. Ez a lecke a legnehezebb részekre koncentrál: arra, hogy a különböző leckékből származó kódok ne ütközzenek egymással.

  • kb. 120 perc
  • záró projekt
  • teljes szövegszerkesztő
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • a 2.1–2.15 leckék kódját egyetlen projektben fésülöd össze;
  • megtalálod és megoldod a gyakori ütközéseket (kétszer definiált metódusok, többször használt események);
  • a Mentés és a Mentés másként funkciót szétválasztod (aktuális fájl megjegyzése);
  • a címsor a fájl nevét és a módosítást is mutatja;
  • tesztlistával végigellenőrzöd a teljes programot;
  • önértékelő szempontok alapján eldöntöd, hogy kész-e a projekt.

Mielőtt elkezded

Ehhez a leckéhez a 2.1–2.15 leckék kódjára van szükséged. Ha valamelyik rész hiányzik, vagy hibás, előbb azt javítsd ki.

2.15 Nyomtatás →

2.10 A MiniWord összeépítése →

A funkciótérkép

Az összefésülés előtt nézd át, mit tud a program, és melyik lecke adta hozzá:

FunkcióFontosabb metódusok és eseményekLecke
KarakterszámlálórtbDokumentum_TextChanged2.1
Megnyitás (RTF, TXT)Megnyitas()2.2
Mentés RTF-kéntMentes()2.3, 2.14
Félkövér, dőlt, aláhúzottStilusValt()2.4
Betűtípus és méretcmbBetutipus_…, cmbMeret_…2.5
Igazítás (balra, középre, jobbra, sorkizárt)btnBalra_Click és társai, RichTextBoxHelper2.6, 2.7
Keresés és kiemelésbtnKeres_Click, btnOsszes_Click, TorolKiemeleseket()2.8
Névmező és AboutbtnNev_Click, miAbout_Click2.9
Állapotsor, gombok követésertbDokumentum_SelectionChanged, GombokFrissitese()2.11
Szerkesztés menümiSzerkesztes_DropDownOpening és a Click eseménykezelők2.12, 2.13
Védelem a mentetlen munka ellenModositva(), Mentve(), FolytathatE(), Form1_FormClosing2.14
NyomtatáspdDokumentum_BeginPrint, pdDokumentum_PrintPage2.15

A fájl szerkezete

A Form1.cs fájl tetején állnak a mezők (valtozott, aktualisFajl, frissites, nyomtatandoSzoveg, pozicio), utána a segédmetódusok, végül az eseménykezelők. A hosszú fájlban a // ---- kezdetű megjegyzések (például // ---- Mentés ----) segítenek eligazodni.

Gyakori ütközések az összefésüléskor

Ha a különböző leckék kódját egymás alá másolod, ugyanabból az eseménykezelőből több változat kerülhet a fájlba. A C# egy osztályban nem enged két azonos nevű metódust (CS0111), ezért az ilyen kódokat egyesíteni kell. A három leggyakoribb eset:

A Form1_Load

Több leckében is volt Form1_Load (sorkizárt, súgók, kezdőállapot). A program csak egyet tartalmazhat, amelynek belseje az összes feladatot elvégzi:

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
// 2.7: sorkizárt támogatás
RichTextBoxHelper.EngedelyezSorkizartot(rtbDokumentum);

// 2.9, 1.11: súgók
ttSugo.SetToolTip(txtKeres, "Írd be a keresett szót.");
ttSugo.SetToolTip(txtTanar, "A tanár neve az About ablakhoz.");

// 2.14: tiszta kezdőállapot
Mentve();
}

A TextChanged

A karakterszámláló (2.1) és a módosításjelző (2.14) ugyanahhoz az eseményhez tartozik:

Form1.csC#
private void rtbDokumentum_TextChanged(object sender, EventArgs e)
{
lblKarakterszam.Text = $"Karakterek: {rtbDokumentum.Text.Length}";
Modositva();
}

A SelectionChanged

Az állapotsor (2.11) az egyetlen rtbDokumentum_SelectionChanged eseménykezelőben él. Ha egy másik leckéből is létrejött egy ilyen eseménykezelő, a tartalmukat vond össze, és a feleslegeset töröld. A formázó gombok (StilusValt, igazítás, betűtípus és méret) legvégén hívd meg a Modositva(); metódust, hogy a formázás is módosításnak számítson (2.14).

Hogyan keresd meg az ütközést?

A Visual Studio a fordításkor a CS0111 hibát mutatja, és megmondja a duplán definiált metódus nevét. Az Error List ablakban a hibára duplán kattintva a kódra ugrik. Töröld az egyiket, a tartalmát pedig tedd át a másikba. Ugyanez a helyzet, ha az ablakot a tervezőben lévő dupla kattintással újra elindítod, és új, üres eseménykezelő jön létre: a régi mellett kétszer lenne meg.

Mentés és Mentés másként

A 2.3 és a 2.14 leckében a Mentes() mindig megkérdezte a fájl nevét. Egy igazi szövegszerkesztő viszont megjegyzi, melyik fájlon dolgozol, és a Mentés csak felülírja azt. A névkérdést a Mentés másként teszi fel. Ehhez egy új mezőre (aktualisFajl) és három egyszerű metódusra van szükség:

Form1.csC#
private string aktualisFajl = "";

// A tényleges kiírás. Igaz, ha sikerült.
private bool FajlbaIr(string ut)
{
try
{
rtbDokumentum.SaveFile(ut, RichTextBoxStreamType.RichText);
aktualisFajl = ut;
Mentve();
return true;
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült menteni a fájlt: " + ex.Message);
return false;
}
}

// Mentés másként: mindig megkérdezi a nevet.
private bool MentesMaskent()
{
sfdMentes.Title = "Dokumentum mentése";
sfdMentes.Filter = "RTF dokumentum (*.rtf)|*.rtf";
sfdMentes.DefaultExt = "rtf";
sfdMentes.AddExtension = true;

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

return FajlbaIr(sfdMentes.FileName);
}

// Mentés: ha már van fájlnév, felülírja, különben a Mentés másként.
private bool Mentes()
{
if (aktualisFajl.Length == 0)
{
return MentesMaskent();
}

return FajlbaIr(aktualisFajl);
}
FajlbaIr(string ut)Egyetlen helyen végzi a tényleges mentést. A megjegyzi a fájl útvonalát, törli a módosításjelzőt, és visszaadja, sikerült-e.
MentesMaskent()A mentés ablakkal kérdezi a nevet. Ha a felhasználó a Mégsét választja, hamis értékkel tér vissza.
Mentes()Ha még nincs fájlnév (aktualisFajl üres), a Mentés másként fut le. Ha van, azonnal ment.
Miért három metódus?Mindegyik egy feladatot végez. A kód kisebb, áttekinthetőbb, és a FolytathatE() továbbra is a Mentes() eredményét használja.

A Megnyitas() metódusban is jegyezd meg a fájlt, a betöltés után: aktualisFajl = fajl; Az Új menüpontban pedig állítsd vissza üresre: aktualisFajl = ""; A File menübe tegyél két külön menüpontot (miMentes és miMentesMaskent), amelyek a Mentes(), illetve a MentesMaskent() metódust hívják.

Jósolj!

Megnyitsz egy level.rtf fájlt, módosítasz benne egy szót, és a Mentés menüpontot választod. Megjelenik a mentés ablak? Mi történik, ha a Mentés másként menüpontot választod?

Jóslat ellenőrzése

A Mentésnél nem jelenik meg ablak: az aktualisFajl értéke a level.rtf útvonala (a megnyitáskor megjegyezted), ezért a program közvetlenül felülírja. A Mentés másként viszont mindig megnyitja a mentés ablakot, hogy új nevet adhass.

A címsor: fájlnév és módosítás

A felhasználó a címsorból látja, melyik fájlon dolgozik, és hogy van-e mentetlen változtatás. Egy CimFrissit() metódus állítja össze a címet, és a Modositva() és a Mentve() is ezt hívja:

Form1.csC#
private void CimFrissit()
{
string nev = "Névtelen";

if (aktualisFajl.Length > 0)
{
nev = System.IO.Path.GetFileName(aktualisFajl);
}

string cim = "MiniWord – " + nev;

if (valtozott)
{
cim = cim + " *";
}

this.Text = cim;
}

private void Modositva()
{
valtozott = true;
CimFrissit();
}

private void Mentve()
{
valtozott = false;
CimFrissit();
}
Path.GetFileName(útvonal)Az útvonalból csak a fájl nevét adja vissza (például level.rtf).
valtozottA mező megmondja, kell-e csillag.
Modositva() és Mentve()Egyszerűbbek lettek: csak a jelzőt állítják, és a címsor összeállítását a CimFrissit()-re bízzák.
📷 Képernyőkép helye

Mit tegyél ide: két kép egymás mellett a MiniWord címsoráról: „MiniWord – level.rtf” módosítás nélkül, és „MiniWord – level.rtf *” módosítás után.Javasolt fájlnév: winforms-2-16-cimsor.webp · Alt szöveg: A MiniWord címsora a fájl nevével, mentett és módosított állapotban

Tesztlista

Próbáld ki sorban a teljes programot:

#TesztVárt eredmény
1Gépelj szövegetA karakterszámláló frissül, a címsorban csillag jelenik meg.
2Mentés (még nincs fájlnév)Megjelenik a mentés ablak; mentés után a címsor a fájl nevét mutatja, csillag nélkül.
3Módosítás, majd MentésNem kérdez nevet, felülírja a fájlt.
4Mentés máskéntMindig megkérdezi a nevet.
5Új dokumentum módosított állapotbanRákérdez a mentésre (Igen, Nem, Mégse).
6Megnyitás RTF fájlraA szöveg formázással betöltődik, nincs csillag a címsorban.
7Megnyitás TXT fájlra, DOCX fájlraA TXT betöltődik, a DOCX érthető üzenetet ad.
8Félkövér, dőlt, aláhúzott, betűtípus, méretA kijelölt szöveg változik, a gombok követik az állapotot.
9Igazítások, köztük a sorkizártMind a négy igazítás működik.
10Keresés és az összes találat kiemeléseA találatok sárgák, a darabszám látszik.
11Szerkesztés menüA menüpontok állapota megfelel (szürke, ha nincs mit visszavonni vagy kijelölni).
12Nyomtatás PDF-nyomtatóraA dokumentum több oldalon is jól tördelődik.
13Bezárás mentetlen módosítással, MégseAz ablak nyitva marad.
14AboutA készítő neve, a dátum és a tanár neve látszik.

Önértékelés: kész a projekt?

Ellenőrizd az alábbi szempontokat, mielőtt kész állapotúnak nyilvánítod a munkát:

MűködésA tesztlista mind a 14 pontja teljesül, és a program nem omlik össze hibás bevitelnél (üres keresés, DOCX megnyitása, hiányzó fájl).
AdatvédelemA mentetlen munka nem veszik el sem bezáráskor, sem megnyitáskor, sem új dokumentumnál.
KódszerkezetA kód metódusokra van bontva, nincs két azonos nevű metódus, és minden eseménykezelő egyszer szerepel.
ElnevezésekA vezérlők neve beszédes előtagot kap, a mezők és metódusok neve magyarul is érthető.
HasználhatóságA menü és a gombok logikus rendben vannak, a gyorsbillentyűk (Ctrl+O, Ctrl+S) működnek.
KinézetAz ablak átméretezhető, a szövegdoboz együtt nő az ablakkal, a feliratok olvashatók.

Ha mind teljesül

Akkor egy teljes értékű, többfunkciós szövegszerkesztőt építettél, amely túlmutat az alapfeladaton. A következő fejezet az adatbázisok világába vezet.

📷 Képernyőkép helye

Mit tegyél ide: a kész szövegszerkesztő teljes ablaka: menüsor (File, Szerkesztés, About), eszközsor (formázógombok, listák, igazítások), a keresősáv, a dokumentum többféle formázással és az állapotsor.Javasolt fájlnév: winforms-2-16-kesz.webp · Alt szöveg: A kész szövegszerkesztő teljes ablaka

Három önálló bővítési feladat

Ha kész vagy, bővítsd a programot. Egyszerre csak egyet csinálj.

1. Gyorsbillentyűk

Add a Megnyitáshoz a Ctrl+O, a Mentéshez a Ctrl+S, a Mentés másként menüponthoz a Ctrl+Shift+S billentyűkombinációt.

Egy lehetséges megoldás

Jelöld ki a menüpontot a tervezőben, és a Properties ablakban állítsd a ShortcutKeys tulajdonságot a legördülő listából. Kódot nem kell írni. Figyelj arra, hogy a Szerkesztés menü billentyűit (Ctrl+C, Ctrl+V) ne így állítsd be (2.12 lecke).

2. Sortörés ki- és bekapcsolása

Készíts egy Nézet → Sortörés menüpontot (miSortores, CheckOnClick: True), amely a RichTextBox.WordWrap tulajdonságot állítja.

Egy lehetséges megoldás
private void miSortores_CheckedChanged(object sender, EventArgs e)
{
rtbDokumentum.WordWrap = miSortores.Checked;
}

A menüpont Checked értéke kezdetben legyen True, mert a RichTextBox alapból tördeli a sorokat.

3. Nagyítás

Tegyél az ablakra egy trbNagyitas csúszkát (Minimum: 50, Maximum: 300, Value: 100), amellyel a szöveg megjelenítése nagyítható.

Egy lehetséges megoldás
private void trbNagyitas_Scroll(object sender, EventArgs e)
{
rtbDokumentum.ZoomFactor = trbNagyitas.Value / 100f;
}

A ZoomFactor a nagyítás szorzója (1 = 100%). Az f betű a szám végén azt jelenti, hogy a szám float típusú, így a 150 / 100 eredménye 1,5, nem 1.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A feladatok a teljes MiniWord összeállításához kapcsolódnak. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Hogyan találod meg az ütközést?

Az összefésülés után a fordító CS0111 hibát jelez. Írd le lépésekben, hogyan találod meg és javítod a hibát.

Megoldás megnyitása

(1) Nyisd meg az Error List ablakot (View → Error List). (2) A hibára duplán kattintva a Visual Studio a duplán definiált metódusra ugrik, a hibaüzenet megnevezi a metódust (például Form1_Load). (3) Keresd meg a másik példányt (Ctrl+F a fájlban). (4) Másold át az egyik törzsét a másikba, hogy minden feladat egy helyen legyen. (5) Töröld a feleslegessé vált példányt. (6) Építsd újra a projektet (Ctrl+Shift+B). (7) Ellenőrizd a tervezőben a Properties → Events nézetben, hogy az esemény a megmaradt metódushoz kapcsolódik-e.

2. Új dokumentum menüpont

A File → Új menüpont törölje a szöveget és az aktuális fájlnevet, és a címsor induljon újra. (A FolytathatE(), Mentve() és aktualisFajl a 2.14 és 2.16 szerint áll rendelkezésre.)

Megoldás megnyitása

Először a védelem kérdez (FolytathatE()), csak utána töröl. A Mentve() a jelzőt és a címsort is rendbe teszi.

Form1.csC#
private void miUj_Click(object sender, EventArgs e)
{
if (!FolytathatE())
{
return;
}

rtbDokumentum.Clear();
rtbDokumentum.ClearUndo();
aktualisFajl = "";
Mentve();
}

Közepes szint

3. Utolsó fájl újranyitása

A File → Utolsó fájl menüpont nyissa meg újra az utoljára megnyitott vagy elmentett fájlt a fájlválasztó nélkül.

Megoldás megnyitása

A megnyitás kódját két részre bontjuk: a fájlválasztó ablak (Megnyitas()) és maga a betöltés (FajlMegnyitasa(string ut)). Az útvonalat az aktualisFajl mező őrzi, így az Utolsó fájl menüpont csak a betöltést hívja.

Form1.csC#
private void FajlMegnyitasa(string ut)
{
try
{
rtbDokumentum.LoadFile(ut, RichTextBoxStreamType.RichText);
rtbDokumentum.ClearUndo();
aktualisFajl = ut;
Mentve();
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült megnyitni a fájlt: " + ex.Message);
}
}

private void miUtolsoFajl_Click(object sender, EventArgs e)
{
if (aktualisFajl.Length == 0)
{
MessageBox.Show("Még nincs megnyitott vagy mentett fájl.");
return;
}

if (FolytathatE())
{
FajlMegnyitasa(aktualisFajl);
}
}

4. Ctrl+F a keresőmezőbe

A Ctrl+F billentyűkombináció tegye a kurzort a txtKeres keresőmezőbe, bárhol áll éppen a fókusz az ablakban.

Megoldás megnyitása

A Form KeyPreview tulajdonságát állítsd True értékre: ekkor az ablak előbb látja a billentyűleütést, mint a vezérlők. A KeyDown eseményben megvizsgáljuk a kombinációt, és az SuppressKeyPress megakadályozza, hogy a leütés a szövegmezőbe is bekerüljön.

Form1.csC#
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.F)
{
txtKeres.Focus();
txtKeres.SelectAll();
e.SuppressKeyPress = true;
}
}

Kihívás szint

5. Szószám az összeállított programban

Egészítsd ki a TextChanged eseménykezelőt úgy, hogy a karakterszámláló, a módosításjelző és a szószám is frissüljön egy kódban, és az eseménykezelő rövid maradjon.

Megoldás megnyitása

A számításokat külön metódusba (Statisztika()) tesszük, az eseménykezelő csak hívja őket. Így egyetlen TextChanged metódus marad, amely nem okoz ütközést.

Form1.csC#
private void Statisztika()
{
int szavak = 0;

foreach (string sor in rtbDokumentum.Lines)
{
szavak = szavak + sor.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
}

lblKarakterszam.Text = $"Karakterek: {rtbDokumentum.TextLength} | Szavak: {szavak}";
}

private void rtbDokumentum_TextChanged(object sender, EventArgs e)
{
Statisztika();
Modositva();
}

6. Hibakatalógus

Szándékosan idézz elő öt hibahelyzetet, és írd le, mit kell tennie a programnak.

Megoldás megnyitása
HibahelyzetElvárt viselkedés
Hiányzó fájl megnyitása (a fájlt közben törölték)Érthető hibaüzenet, a szöveg nem vész el.
Mentés írásvédett mappábaÉrthető hibaüzenet, a jelző továbbra is módosítottat mutat.
DOCX fájl megnyitásaÜzenet, hogy a formátum nem támogatott.
Formázás kijelölés nélkülA program jelez, vagy nem csinál semmit, de nem omlik össze.
Keresés üres szöveggelÜzenet, a program nem fagy le.

Gyors önellenőrzés

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

1. Miért nem lehet két Form1_Load metódus ugyanabban az osztályban?

Mert a C# egy osztályban nem enged két azonos nevű, azonos paraméterű metódust (CS0111). A tartalmukat egyetlen metódusba kell összevonni.

2. Mi a különbség a Mentés és a Mentés másként között?

A Mentés felülírja az aktuális fájlt (ha van), a Mentés másként mindig fájlnevet kér.

3. Miért tároljuk az aktualisFajl értékét mezőben?

Hogy a metódusok között is megmaradjon, és a Mentés tudja, melyik fájlt kell felülírnia.

4. Mit csinál a Path.GetFileName?

Az útvonalból csak a fájl nevét adja vissza.

5. Miért kell a formázógombok eseménykezelőjében is a Modositva() hívás?

Mert a formázás nem biztos, hogy kiváltja a TextChanged eseményt, de a dokumentum így is módosult.

Mit tudsz már?

  • Egyetlen programmá fésülöd össze a különböző leckékből származó kódot.
  • Megoldod a duplán definiált metódusok ütközését.
  • Szétválasztod a Mentés és a Mentés másként funkciót.
  • A címsor a fájl nevét és a módosítást is mutatja.
  • Tesztlistával ellenőrzöd a teljes szövegszerkesztőt.

Következő fejezet: SQLite és adatbázisos alkalmazások

A szövegszerkesztő kész. A 3. fejezetben adatokat tárolunk adatbázisban, és ablakos programmal kezeljük őket.