Grafikus C# – Windows Forms tanfolyam › 2.10 lecke

Windows Forms · 2.10 lecke · záró projekt

MiniWord: az összetett szövegszerkesztő összeépítése

Elkészült az összes darab: megnyitás, mentés, formázás, igazítás, keresés, névmező és About. Most összeállítjuk belőlük a teljes MiniWordöt. A lecke tartalmazza a vezérlők listáját, a teljes forráskódot, a File menü bekötését és egy tesztlistát, amellyel ellenőrizheted, hogy minden működik-e.

  • kb. 90 perc
  • záró projekt
  • összeépítés és tesztelés
Diáklány C# programot készít laptopon

A lecke végére

  • összeállítod a MiniWord felületét az összes vezérlővel;
  • a File menü elemeit a Megnyitas() és a Mentes() metódusokkal kötöd össze;
  • ellenőrzöd, hogy a program teljesíti a feladat minden pontját;
  • egy tesztlista alapján végigpróbálod a programot;
  • felismered az összeépítés tipikus hibáit;
  • a projektet úgy csomagolod, hogy másnak is elküldhesd.

Mielőtt elkezded

Ehhez a leckéhez a 2.1–2.9 leckék mindegyikét el kell készítened. Ha valamelyik rész még hiányzik, itt a teljes kód, de érdemes előbb a megfelelő leckét elvégezni.

2.9 Névmező és About →

Mit kell tudnia a MiniWordnek?

A MiniWord egy Word-szerű szövegszerkesztő. A következő táblázat összefoglalja, mit kell tudnia, és melyik lecke tanította:

Mit tud a program?Lecke
RTF fájlok megnyitása, a megnyitáskor választható fájltípusok: rtf, docx, minden fájl2.2
A szöveges mezőbe írt szöveg mentése a felhasználó által megadott néven, rtf kiterjesztéssel2.3
Bekezdésigazítás: balra, jobbra, középre, sorkizárt2.6 és 2.7
A kijelölt szöveg formázása: félkövér, dőlt, aláhúzott2.4
Betűméret (legalább 3 féle) és betűtípus (legalább 3 féle) választása legördülő listából2.5
Keresés megadott szövegre, az összes előfordulás kiemelésével2.8
Beviteli mező, amelybe írt nevet felugró ablak mutat meg2.9
File menü (mentés, megnyitás, kilépés) és About menü (ki és mikor készítette, „Tanára:” után a névmező tartalma)2.9 és 2.10
Címke, amely folyamatosan mutatja a szövegmezőben lévő karakterek számát2.1

Minden pont megvan

A leckék együtt lefedik a MiniWord összes funkcióját. Ebben a leckében a megmaradt darabot, a File menü bekötését készítjük el, és összeállítjuk az egészet.

A felület: vezérlők és nevek

A kód csak akkor működik, ha a vezérlők nevei pontosan egyeznek. Ellenőrizd a Properties ablakban a (Name) tulajdonságokat:

Vezérlő(Name)Szerepe
RichTextBoxrtbDokumentumA dokumentum szövege. HideSelection: False.
LabellblKarakterszamA karakterek száma.
OpenFileDialogofdMegnyitasFájl megnyitása.
SaveFileDialogsfdMentesFájl mentése.
ButtonbtnFelkover, btnDolt, btnAlahuzottF, D, A stílusgombok.
ComboBoxcmbBetutipus, cmbMeretBetűtípus és méret (DropDownList).
ButtonbtnBalra, btnKozep, btnJobbra, btnSorkizartIgazítás.
TextBox és ButtontxtKeres, btnKeres, btnOsszesKeresés és kiemelés.
TextBox és ButtontxtTanar, btnNevNévmező és felugró ablak.
MenuStripmiFile, miMegnyitas, miMentes, miKilepes, miAboutMenüsor.
📷 Képernyőkép helye

Mit tegyél ide: a kész MiniWord teljes ablaka tervezőnézetben vagy futás közben. Felül a menüsor (File, About), alatta a gombok és legördülő listák, a keresősáv, a névmező, középen a nagy szövegdoboz, alul a karakterszámláló.Javasolt fájlnév: winforms-2-10-miniword-felulet.webp · Alt szöveg: A teljes MiniWord szövegszerkesztő felülete

Az elrendezés rajtad múlik

A vezérlők pontos helye nincs megszabva: tedd őket úgy, hogy áttekinthető legyen. Az Anchor (2.1) segít, hogy a szövegdoboz együtt nőjön az ablakkal. Az elrendezés finomhangolását a 6. fejezetben tanuljuk.

A teljes Form1.cs

Az alábbi kód a 2.1–2.9 leckék összes részletét tartalmazza. Hasonlítsd össze a saját megoldásoddal. Ha valami nálad másképp van, keresd meg a különbséget:

Form1.cs (teljes)C#
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace MiniWord
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

// Az ablak betöltése: sorkizárt támogatás bekapcsolása
private void Form1_Load(object sender, EventArgs e)
{
RichTextBoxHelper.EngedelyezSorkizartot(rtbDokumentum);
}

// 2.1: karakterszámláló
private void rtbDokumentum_TextChanged(object sender, EventArgs e)
{
lblKarakterszam.Text = $"Karakterek: {rtbDokumentum.Text.Length}";
}

// 2.2: megnyitás
private void Megnyitas()
{
ofdMegnyitas.Title = "Dokumentum megnyitása";
ofdMegnyitas.Filter = "RTF dokumentum (*.rtf)|*.rtf|Word dokumentum (*.docx)|*.docx|Szövegfájl (*.txt)|*.txt|Minden fájl (*.*)|*.*";

if (ofdMegnyitas.ShowDialog() == DialogResult.OK)
{
string fajl = ofdMegnyitas.FileName;
string kiterjesztes = Path.GetExtension(fajl).ToLower();

try
{
if (kiterjesztes == ".rtf")
{
rtbDokumentum.LoadFile(fajl, RichTextBoxStreamType.RichText);
}
else if (kiterjesztes == ".txt")
{
rtbDokumentum.Text = File.ReadAllText(fajl);
}
else
{
MessageBox.Show("Ezt a fájltípust a MiniWord egyelőre nem tudja megnyitni: " + kiterjesztes);
}
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült megnyitni a fájlt: " + ex.Message);
}
}
}

// 2.3: mentés
private void Mentes()
{
sfdMentes.Title = "Dokumentum mentése";
sfdMentes.Filter = "RTF dokumentum (*.rtf)|*.rtf";
sfdMentes.DefaultExt = "rtf";
sfdMentes.AddExtension = true;
sfdMentes.FileName = "dokumentum";

if (sfdMentes.ShowDialog() == DialogResult.OK)
{
try
{
rtbDokumentum.SaveFile(sfdMentes.FileName, RichTextBoxStreamType.RichText);
MessageBox.Show("A dokumentum mentve.");
}
catch (Exception ex)
{
MessageBox.Show("Nem sikerült menteni a fájlt: " + ex.Message);
}
}
}

// 2.4: félkövér, dőlt, aláhúzott
private void StilusValt(FontStyle stilus)
{
Font regi = rtbDokumentum.SelectionFont;

if (regi != null)
{
rtbDokumentum.SelectionFont = new Font(regi, regi.Style ^ stilus);
}
else
{
MessageBox.Show("A kijelölésben többféle formázás van. Jelölj ki egységes szövegrészt!");
}

rtbDokumentum.Focus();
}

private void btnFelkover_Click(object sender, EventArgs e) { StilusValt(FontStyle.Bold); }
private void btnDolt_Click(object sender, EventArgs e) { StilusValt(FontStyle.Italic); }
private void btnAlahuzott_Click(object sender, EventArgs e) { StilusValt(FontStyle.Underline); }

// 2.5: betűtípus és betűméret
private void cmbBetutipus_SelectedIndexChanged(object sender, EventArgs e)
{
Font regi = rtbDokumentum.SelectionFont;

if (regi == null || cmbBetutipus.SelectedItem == null)
{
MessageBox.Show("A kijelölésben többféle formázás van, vagy nincs kiválasztva betűtípus.");
}
else
{
string csalad = cmbBetutipus.SelectedItem.ToString();
rtbDokumentum.SelectionFont = new Font(csalad, regi.Size, regi.Style);
}

rtbDokumentum.Focus();
}

private void cmbMeret_SelectedIndexChanged(object sender, EventArgs e)
{
Font regi = rtbDokumentum.SelectionFont;
float meret;

if (regi == null)
{
MessageBox.Show("A kijelölésben többféle formázás van. Jelölj ki egységes szövegrészt!");
}
else if (float.TryParse(cmbMeret.Text, out meret))
{
rtbDokumentum.SelectionFont = new Font(regi.FontFamily, meret, regi.Style);
}

rtbDokumentum.Focus();
}

// 2.6 és 2.7: igazítás
private void btnBalra_Click(object sender, EventArgs e)
{
rtbDokumentum.SelectionAlignment = HorizontalAlignment.Left;
rtbDokumentum.Focus();
}

private void btnKozep_Click(object sender, EventArgs e)
{
rtbDokumentum.SelectionAlignment = HorizontalAlignment.Center;
rtbDokumentum.Focus();
}

private void btnJobbra_Click(object sender, EventArgs e)
{
rtbDokumentum.SelectionAlignment = HorizontalAlignment.Right;
rtbDokumentum.Focus();
}

private void btnSorkizart_Click(object sender, EventArgs e)
{
RichTextBoxHelper.Sorkizart(rtbDokumentum);
rtbDokumentum.Focus();
}

// 2.8: keresés
private void btnKeres_Click(object sender, EventArgs e)
{
string keresett = txtKeres.Text;

if (keresett.Length == 0)
{
MessageBox.Show("Írd be, mit keressek!");
}
else
{
int hely = rtbDokumentum.Text.IndexOf(keresett, StringComparison.OrdinalIgnoreCase);

if (hely >= 0)
{
rtbDokumentum.Select(hely, keresett.Length);
rtbDokumentum.Focus();
}
else
{
MessageBox.Show("Nincs találat.");
}
}
}

private void btnOsszes_Click(object sender, EventArgs e)
{
string keresett = txtKeres.Text;

if (keresett.Length == 0)
{
MessageBox.Show("Írd be, mit keressek!");
}
else
{
TorolKiemeleseket();

string szoveg = rtbDokumentum.Text;
int darab = 0;
int hely = szoveg.IndexOf(keresett, 0, StringComparison.OrdinalIgnoreCase);

while (hely >= 0)
{
rtbDokumentum.Select(hely, keresett.Length);
rtbDokumentum.SelectionBackColor = Color.Yellow;
darab = darab + 1;

hely = szoveg.IndexOf(keresett, hely + keresett.Length, StringComparison.OrdinalIgnoreCase);
}

rtbDokumentum.Select(0, 0);
MessageBox.Show($"Találatok száma: {darab}");
}
}

private void TorolKiemeleseket()
{
rtbDokumentum.SelectAll();
rtbDokumentum.SelectionBackColor = rtbDokumentum.BackColor;
rtbDokumentum.Select(0, 0);
}

// 2.9: névmező és About
private void btnNev_Click(object sender, EventArgs e)
{
string nev = txtTanar.Text.Trim();

if (nev.Length == 0)
{
MessageBox.Show("Kérlek, írj be egy nevet!");
}
else
{
MessageBox.Show("A beírt név: " + nev);
}
}

private void miAbout_Click(object sender, EventArgs e)
{
string tanar = txtTanar.Text.Trim();

if (tanar.Length == 0)
{
tanar = "(még nincs megadva)";
}

string uzenet = "MiniWord szövegszerkesztő" + Environment.NewLine
+ "Készítette: Minta Anna" + Environment.NewLine
+ "Dátum: 2026. október 24." + Environment.NewLine
+ "Tanára: " + tanar;

MessageBox.Show(uzenet, "About");
}

// 2.9 és 2.10: File menü
private void miMegnyitas_Click(object sender, EventArgs e)
{
Megnyitas();
}

private void miMentes_Click(object sender, EventArgs e)
{
Mentes();
}

private void miKilepes_Click(object sender, EventArgs e)
{
this.Close();
}
}
}

Nem kell mindent átgépelned

Ez a kód a korábbi leckék darabjaiból áll össze. Ha az előző leckéket elvégezted, a legtöbb részlet már megvan nálad. Csak azt kell ellenőrizned, hogy minden metódus egyszer szerepel, a nevek egyeznek, és a menüpontok a Megnyitas() és Mentes() metódust hívják. A RichTextBoxHelper.cs külön fájlban van (2.7 lecke).

Tesztlista: működik-e minden?

Egy program akkor kész, ha kipróbáltad. Végezd el sorban az alábbi teszteket, és pipáld ki, ami működik:

#TesztVárt eredmény
1Írj be szövegetA karakterszámláló azonnal frissül.
2File → Megnyitás, válaszd az RTF típust, nyiss meg egy RTF fájltA szöveg formázással együtt megjelenik, a számláló frissül.
3A megnyitás ablakban nézd meg a típuslistátRTF, DOCX, TXT és minden fájl lehetőség látszik.
4Nyiss meg egy DOCX fájltÉrthető üzenet jelenik meg, a program nem omlik össze.
5Jelölj ki egy szót, majd F, D, A gombA szó formázása változik, újra megnyomva visszaáll.
6Válassz másik betűtípust és méretetA kijelölt szöveg változik, a félkövér megmarad.
7Balra, középre, jobbra, sorkizárt gombA bekezdés igazítása ennek megfelelően változik.
8Keress egy szót, majd Összes kiemeléseMinden előfordulás sárga, az üzenet mutatja a számot.
9Üres keresésÜzenet jelenik meg, a program nem fagy le.
10Írj nevet a névmezőbe, nyomd meg a gombotA felugró ablak megmutatja a nevet.
11About menüA készítő neve, a dátum és a „Tanára:” után a névmező tartalma látszik.
12File → Mentés, majd újranyitásA mentett fájl .rtf kiterjesztésű, és a formázás megmarad.
13File → KilépésAz ablak bezárul.

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. Kétszer szerepel a metódus

// CS0111: Type 'Form1' already defines a member called 'Mentes' with the same parameter types
Ok és javítás

Fordítási hiba: a kódot kétszer másoltad be, ezért ugyanaz a metódus kétszer szerepel a Form1 osztályban. Töröld az egyik példányt. Használd a Visual Studio keresését (Ctrl+F) a metódus nevére.

2. Kétszer lefut az eseménykezelő

A Mentés gomb megnyomására a mentés ablak kétszer jelenik meg.

Ok és javítás

Az eseményhez az eseménykezelő kétszer van hozzákötve. Ez akkor fordul elő, ha a Designer is létrehozta, és te is kézzel hozzáadtad. Nyisd meg a Properties ablak Events nézetét, nézd meg a Click sort, és hagyd meg egyetlen eseménykezelőt.

3. Ismeretlen Path vagy Color

// CS0103: The name 'Path' does not exist in the current context
Ok és javítás

Fordítási hiba: hiányzik a using System.IO; sor (a Path és a File miatt), vagy a using System.Drawing; sor (a Color és a Font miatt). A fájl elejére add hozzá.

4. Sárga szavak a mentett fájlban

A mentett dokumentum megnyitásakor a keresett szavak sárgák maradtak.

Ok és javítás

A kiemelés a dokumentum formázásának része. Mentés előtt hívd meg a TorolKiemeleseket() metódust a Mentes() elején, vagy törölj minden kiemelést kézzel.

5. A sorkizárt nem működik

A sorkizárt gombra kattintasz, de nem történik semmi.

Ok és javítás

Ellenőrizd, hogy a RichTextBoxHelper.cs a projekt része-e, és a kód pontosan egyezik-e a 2.7 leckében lévővel. Írj egy több soros bekezdést, mert egysoros szövegen a sorkizárt nem látszik.

Csomagolás: így add oda másnak

Ha a programot be kell mutatnod vagy be kell adnod, a következőképpen csomagold össze:

  1. Zárd be a Visual Studiót, és a Fájlkezelőben keresd meg a solution mappát (0.5 lecke).
  2. Töröld a projekt mappájából a bin és az obj mappát, vagy hagyd ki őket a csomagolásnál. Ezeket a Visual Studio újra elkészíti.
  3. Tömörítsd az egész solution mappát ZIP fájlba.
  4. Próbáld ki: csomagold ki egy másik helyre, nyisd meg a .sln fájlt, és futtasd az F5-tel.
  5. Ellenőrizd a tesztlistát még egyszer a kicsomagolt példányon.

Kész a MiniWord

Ha a tesztlista mind a 13 pontja működik, a MiniWord teljesíti a feladat minden követelményét. Gratulálok, egy valódi szövegszerkesztőt építettél.

Önálló gyakorlófeladatok

Ha a MiniWord már kész, bővítheted. Először készíts saját megoldást, utána ellenőrizd.

1. Új dokumentum

Adj a File menühöz egy Új menüpontot (miUj), amely kitörli a dokumentumot.

Egy lehetséges megoldás
private void miUj_Click(object sender, EventArgs e)
{
rtbDokumentum.Clear();
}

2. Gyorsbillentyűk

A Mentés menüponthoz tartozzon a Ctrl+S, a Megnyitáshoz a Ctrl+O billentyűkombináció.

Egy lehetséges megoldás

Jelöld ki a menüpontot a tervezőben, és a Properties ablakban állítsd be a ShortcutKeys tulajdonságot (legördülő lista: Ctrl és a betű kiválasztása). Kódot nem kell írnod.

3. Sorok száma

Tegyél a karakterszámláló mellé egy lblSorok címkét, amely a dokumentum sorainak számát mutatja.

Egy lehetséges megoldás
lblSorok.Text = $"Sorok: {rtbDokumentum.Lines.Length}";

Tedd a rtbDokumentum_TextChanged eseménykezelőbe. A Lines a dokumentum sorainak tömbje, a Length pedig a sorok számát adja.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Ezek a feladatok az összeépítés tipikus nehézségeit gyakoroltatják: nevek, eseménykezelők, tesztelés. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. A vezérlőnév hibája

A fordító hibát jelez: CS1061: A „Form1” nem tartalmaz „rtbDokumentum” nevű definíciót (vagy hasonlót). Mi lehet az ok, és hol javítod?

Megoldás megnyitása

A kódban használt név nem egyezik a vezérlő (Name) tulajdonságával (elírás, kis- és nagybetű, vagy a vezérlő még az alapértelmezett richTextBox1 nevet viseli). Jelöld ki a vezérlőt a tervezőben, és a Properties ablak (Name) mezőjében javítsd, vagy írd át a kódot. A Visual Studio az átnevezéskor a kódot is át tudja írni (jobb gomb a névre, Rename), így nem marad ellentmondás.

2. Egy metódus két helyről

A Megnyitás gomb és a File → Megnyitás menüpont ugyanazt csinálja. Hogyan írod meg úgy, hogy a kód csak egyszer legyen meg?

Megoldás megnyitása

A kódot egy külön metódusba tesszük (Megnyitas()), és mindkét eseménykezelő csak ezt hívja. Így egy javítás mindkét helyen érvényesül.

Form1.csC#
private void Megnyitas()
{
// a megnyitás kódja egy helyen
}

private void btnMegnyitas_Click(object sender, EventArgs e)
{
Megnyitas();
}

private void miMegnyitas_Click(object sender, EventArgs e)
{
Megnyitas();
}

Közepes szint

3. Gyorsbillentyűk a menühöz

Add a Megnyitás menüponthoz a Ctrl+O, a Mentés menüponthoz a Ctrl+S gyorsbillentyűt.

Megoldás megnyitása

A tervezőben jelöld ki a menüpontot, és a Properties ablakban a ShortcutKeys tulajdonságnál pipáld be a Ctrl módosítót, és válaszd az O, illetve S billentyűt. Kódot nem kell írnod: a billentyű a menüpont Click eseményét hívja.

4. A karakterszámláló megnyitás után

Egy fájl megnyitása után a lblKarakterszam még a régi értéket mutatja. Miért nem, ha jól van megírva? Mi a hiba, ha mégis így lenne?

Megoldás megnyitása

A LoadFile a szöveget megváltoztatja, ezért a TextChanged esemény magától lefut, és frissíti a számlálót. Ha mégis a régi érték látszik, valószínűleg a TextChanged eseménykezelő nincs a vezérlő eseményéhez kötve (a metódus megvan, de a tervezőben az Events nézet TextChanged sora üres). Ellenőrizd a Properties ablak villám ikonja alatt.

Kihívás szint

5. Saját tesztterv

Írj tesztet a MiniWordhöz legalább 10 lépésben. Minden lépésnél legyen: mit csinálsz, mi az elvárt eredmény.

Megoldás megnyitása
#TesztElvárt eredmény
1Gépelj szövegetA karakterszám azonnal frissül.
2Mentés új névenAz .rtf fájl létrejön.
3Megnyitás, majd MégseNem történik semmi.
4A mentett fájl újranyitásaA szöveg és a formázás megegyezik.
5Félkövér és dőlt egy szónMindkét stílus megjelenik, a többi szó nem változik.
6Betűméret váltása félkövér szónA szó félkövér marad.
7Keresés többször előforduló szóraMinden előfordulás kiemelt.
8Keresés üres szöveggelÜzenet, nem fagy le.
9Név beírása és AboutA név a Tanára: sorban látszik.
10DOCX fájl megnyitásaÉrthető üzenet, nem omlik össze.

6. Három hibás kód

Keresd meg a hibát mindhárom kódrészletben, és javítsd őket.

Megoldás megnyitása

A) Mégse gomb után is mentés:

sfdMentes.ShowDialog();
rtbDokumentum.SaveFile(sfdMentes.FileName, RichTextBoxStreamType.RichText);

Javítás: az eredményt vizsgálni kell: if (sfdMentes.ShowDialog() == DialogResult.OK) { ... }. Különben a Mégse gomb után üres fájlnévvel próbál menteni, és hibát kapsz.

B) A félkövér elvész a betűváltáskor:

rtbDokumentum.SelectionFont = new Font("Arial", 14);

Javítás: a régi stílust át kell adni: new Font("Arial", 14, regi.Style), ahol a regi a SelectionFont korábbi értéke (és előbb null ellenőrzés kell).

C) A keresés lefagy:

int hely = szoveg.IndexOf(keresett);
while (hely >= 0)
{
rtbDokumentum.Select(hely, keresett.Length);
rtbDokumentum.SelectionBackColor = Color.Yellow;
hely = szoveg.IndexOf(keresett, hely);
}

Javítás: a következő keresés a találat vége után induljon: IndexOf(keresett, hely + keresett.Length). A hibás kódban mindig ugyanazt a helyet találja meg, ezért a ciklus végtelen.

Gyors önellenőrzés

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

1. Miért hívja a menüpont és a gomb ugyanazt a metódust?

Hogy a kódot egyszer kelljen megírni és javítani, és a kettő mindig ugyanúgy működjön.

2. Mit jelent a Name tulajdonság egyezése?

A kódban használt név és a Properties ablakban megadott (Name) pontosan egyezik. Ha nem, fordítási hibát kapsz.

3. Miért kell törölni a kiemeléseket mentés előtt?

Mert a kiemelés a dokumentum formázása, így a fájlba is belekerül.

4. Mi az újranyitási teszt?

Elmented a dokumentumot, majd újra megnyitod, és ellenőrzöd, hogy a tartalom és a formázás megmaradt-e.

5. Mit kell kihagyni a projekt csomagolásakor?

A bin és az obj mappát, mert a Visual Studio újra elkészíti őket.

Mit tudsz már?

  • Összeállítod a teljes MiniWord felületét és kódját.
  • A File menü a gombokkal azonos metódusokat hívja.
  • Végigteszteled a programot egy tesztlista alapján.
  • Felismered az összeépítés tipikus hibáit.
  • Elcsomagolod a projektet másnak.

Következő lecke: Kijelölés és SelectionChanged

Az alapfeladat kész. A 2.11–2.16 leckék a MiniWordöt igazi szövegszerkesztővé fejlesztik: állapotsor, szerkesztés menü, vágólap, védelem a mentetlen munka ellen és nyomtatás.