Grafikus C# – Windows Forms tanfolyam › 7.6 lecke Windows Forms · 7.6 lecke · saját párbeszédablak Ha a Új Form hozzáadása és modalitás
MessageBox kevés (adatbevitel, több mező, saját gombfeliratok), saját párbeszédablakot készítesz: egy új Form-ot. Ebben a leckében megtanulod az új ablak hozzáadását a projekthez, a párbeszédablak tervezési szabályait (keret, gombok, kezdőhely), a modalitás fajtáit, és egy fontos mintát: hogyan kommunikál egy nem modális ablak (keresés és csere) a főablakkal úgy, hogy közben nem „ismeri” azt (események, 5.1 lecke).
A lecke végére
- új
Form-ot adsz a projekthez, és elnevezed a konvenció szerint; - a párbeszédablakot a szabályok szerint állítod be (
FixedDialog,CenterParent,AcceptButton,CancelButton…); - megkülönbözteted az alkalmazás-, a tulajdonos- és a nem modális ablakot;
- nem modális keresőablakot kötsz a főablakhoz eseményekkel (laza csatolás);
- kezeled a keresés és csere műveleteit egy
RichTextBox-ban (Find,SelectedText); - felismered a gyakori hibákat (szoros csatolás,
HideésClosekeveredése).
Mielőtt elkezded
Ehhez a leckéhez a 7.5 lecke (Show, ShowDialog, élettartam), az 5.1 és 5.7 lecke (események, saját esemény, leiratkozás) és a 6.7 lecke (AcceptButton, CancelButton) szükséges.
Új Form hozzáadása a projekthez
- A Solution Explorerben jobb gomb a projekten → Add → Form (Windows Forms) (újabb Visual Studióban Add → New Item → Windows Forms → Form).
- Add meg a nevet: a konvenció szerint a név
…Form-ra végződik (KeresForm,BeallitasokForm,NevjegyForm). A név a fájl- és az osztálynév is lesz. - A Visual Studio három fájlt hoz létre:
KeresForm.cs(a te kódod),KeresForm.Designer.cs(a tervező generálta, ne szerkeszd kézzel) ésKeresForm.resx(erőforrások). AzAddután megnyílik a tervező. - Tervezd meg a felületet (6. fejezet eszközei), és állítsd be a Properties ablakban a párbeszédablak jellemzőit (lásd lent).
- Nyisd meg a főablak kódjából:
new KeresForm().ShowDialog(this)vagyShow(this)(7.5 lecke).
partial: a KeresForm.cs és a Designer.cs egyetlen osztályt alkot (12.2 lecke). A te kódod mindig az .cs fájlban legyen.BeallitasokForm), nem a sorszámot (Form2). Az átnevezéshez használd a Rename parancsot (F2), hogy a hivatkozások is frissüljenek.new KeresForm()).Forms, Dialogs), de a névtér módosításra figyelj.A párbeszédablak tervezési szabályai
| Tulajdonság | Érték | Miért? |
|---|---|---|
FormBorderStyle | FixedDialog | Nem méretezhető, párbeszédablak-szerű (6.1 lecke). |
MinimizeBox, MaximizeBox | False | A dialógust nem kicsinyítjük és nem nagyítjuk. |
ShowInTaskbar | False | Nincs külön tálcagomb: a főablak része. |
StartPosition | CenterParent | A tulajdonos ablak közepén jelenik meg. |
ShowIcon | False | Tisztább megjelenés. |
AcceptButton, CancelButton | az OK és a Mégse | Enter és Esc (6.7 lecke). |
Text | beszédes cím | Mit csinál az ablak (Keresés és csere). |
| Elrendezés | gombok jobb alul, TableLayoutPanel | Szokott elrendezés; betűmérethez igazodik (6.8 lecke). |
AutoSize | True, GrowAndShrink | Az ablak a tartalomhoz igazodik (opcionális). |
public BeallitasokForm()
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MinimizeBox = false;
this.MaximizeBox = false;
this.ShowInTaskbar = false;
this.ShowIcon = false;
this.StartPosition = FormStartPosition.CenterParent;
this.AcceptButton = btnOk;
this.CancelButton = btnMegse;
}Jósolj!
A dialógus StartPosition értéke CenterParent, de a hívó ShowDialog() hívást tulajdonos nélkül használ. Hol jelenik meg az ablak?
Jóslat ellenőrzése
A CenterParent értékhez tulajdonos kell: tulajdonos nélkül az ablak a rendszer által választott helyen jelenik meg (nem biztos, hogy a főablak közepén). Ezért mindig ShowDialog(this). (Tulajdonos nélküli ShowDialog() esetén a .NET az aktív ablakot használja tulajdonosként, de erre ne építs.)
A modalitás fajtái
| Fajta | Hogyan? | Viselkedés |
|---|---|---|
| Tulajdonos-modális | ShowDialog(owner) | A tulajdonos (és az alkalmazás többi ablaka) le van tiltva, amíg a dialógus nyitva van. |
| Egymásba ágyazott modális | a dialógusból újabb ShowDialog(this) | Verem: a legutóbbi aktív, az alatta lévők letiltva. Bezárás fordított sorrendben. |
| Nem modális, tulajdonossal | Show(owner) | Mindkét ablak használható; a tulajdonolt a tulajdonos fölött marad. |
| Nem modális, független | Show() | Független ablak (a főablak mögé is kerülhet). |
Timer a főablakban a dialógus alatt is tüzel, és a letiltott főablakot módosíthatja.Load eseményében hívsz ShowDialog-ot, a főablak csak a dialógus bezárása után válik láthatóvá. Bejelentkezésre inkább a Program.cs (6.7 lecke) való.Jósolj!
A főablakban egy 1 másodperces Timer az állapotsorban az időt frissíti. A felhasználó megnyit egy modális dialógust. Frissül-e az állapotsor idő?
Jóslat ellenőrzése
Igen, az óra tovább jár: a modális ablak a felhasználói bevitelt tiltja le, az üzenetfeldolgozás (időzítők, rajzolás) folytatódik. Ha a háttérben futó kód a dialógus alatt módosítaná a főablak adatait, ez zavart okozhat, ezért az ilyen kódot is érdemes a modalitáshoz igazítani.
Nem modális ablak és a főablak összekötése eseményekkel
A keresőablak Show(this) hívással nyílik, és a főablak szerkesztőjében keres. Hogyan éri el az ablak a szerkesztőt? Két lehetőség van:
Szoros csatolás (kerüld)
A kereső ablak megkapja a főablakot vagy a RichTextBox-ot, és közvetlenül dolgozik vele (fo.rtbSzoveg.Find(...)). Gyors, de a kereső csak ezzel az egy főablakkal működik, és a főablak belső vezérlői nyilvánosak lesznek.
Laza csatolás: események (ajánlott)
A kereső ablak eseményt vált ki (Keresés kérése), amelyre a főablak feliratkozik, és ő végzi a keresést. A kereső nem ismeri a főablakot, bármelyik ablakkal együtt használható, és a főablak vezérlői privátak maradnak (5.1, 5.7 lecke).
public class KeresEventArgs : EventArgs
{
public string Keresett { get; set; }
public string Csere { get; set; }
public bool KisbetuErzekeny { get; set; }
}
public partial class KeresForm : Form
{
// az ablak három műveletet kér a tulajdonostól
public event EventHandler<KeresEventArgs> KovetkezoKeres;
public event EventHandler<KeresEventArgs> CsereKeres;
public event EventHandler<KeresEventArgs> MindetCsereliKeres;
private KeresEventArgs Argumentumok()
{
return new KeresEventArgs
{
Keresett = txtKeres.Text,
Csere = txtCsere.Text,
KisbetuErzekeny = chkKisbetu.Checked
};
}
private void btnKovetkezo_Click(object sender, EventArgs e)
{
KovetkezoKeres?.Invoke(this, Argumentumok());
}
private void btnCsere_Click(object sender, EventArgs e)
{
CsereKeres?.Invoke(this, Argumentumok());
}
private void btnMind_Click(object sender, EventArgs e)
{
MindetCsereliKeres?.Invoke(this, Argumentumok());
}
// a bezárás elrejtés: az állapot (szöveg, jelölőnégyzet) megmarad
private void KeresForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.Hide();
}
}
}KeresEventArgs osztály hordozza az adatot (5.2 lecke). Így az esemény paramétere típusos, nem kell a vezérlőkre hivatkozni.FormClosing a felhasználói bezárást elrejtésre cseréli. A program kilépésekor (CloseReason más) az ablak ténylegesen bezárul, ezért nem akadályozza a kilépést.Jósolj!
A KeresForm a FormClosing eseményben mindig megszakítja a bezárást (e.Cancel = true), a CloseReason vizsgálata nélkül. Be tud-e záródni a program a keresőablak nyitva hagyásával?
Jóslat ellenőrzése
Nem rendesen: a főablak bezárásakor a tulajdonolt ablak is bezáródna, de a Cancel megakadályozza, ezért a kilépés elakad (vagy a program a háttérben marad). Ezért kell a CloseReason vizsgálata: csak a UserClosing (a felhasználó X-e) esetén rejtsd el, minden más okra engedd a bezárást.
Mit tegyél ide: a szerkesztő ablak és fölötte a „Keresés és csere” ablak a mezőkkel, jelölőnégyzettel és négy gombbal.Javasolt fájlnév: winforms-7-6-kereso.webp · Alt szöveg: A keresés és csere ablak a szerkesztő fölött
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 dialógus nem a főablak közepén jelenik meg
Az új ablak a képernyő egy véletlen pontján nyílik. A Ok és javítás
StartPosition nem CenterParent, vagy nincs tulajdonos. Állítsd CenterParent értékre, és hívd a ShowDialog(this) metódust.
2. A kereső nem működik másik szerkesztővel
// KeresForm.cs
private void btnKovetkezo_Click(object sender, EventArgs e)
{
((Form1)this.Owner).rtbSzoveg.Find(txtKeres.Text); // csak a Form1-gyel működik
}Hiba és javítás
Szoros csatolás: az ablak ismeri a főablak típusát és belső vezérlőjét. Használj eseményt (vagy interfészt), így a kereső bármelyik ablakkal együtt működik, és a főablak vezérlői nem kell, hogy nyilvánosak legyenek.
3. A kereső nem zárul be, és a program nem lép ki
A főablak bezárása után a program még fut. A Ok és javítás
FormClosing minden bezárást megakadályoz. Csak a felhasználói bezárást rejtsd el: if (e.CloseReason == CloseReason.UserClosing).
4. A dialógus bezárása után az adatok elvesznek
A beállítóablak újranyitásakor üres mezők jelennek meg. A Ok és javítás
ShowDialog ablakot minden nyitáskor új példányként hozod létre, és a mezőket nem töltöd fel a meglévő értékekkel. Add át az aktuális értékeket nyitáskor (konstruktor vagy tulajdonságok, 7.7 lecke).
5. Az OK gomb nem zár
A felhasználó az OK-t megnyomja, de az ablak nyitva marad. A gomb Ok és javítás
DialogResult értéke None, és a kód sem állítja be a form DialogResult értékét. Állítsd be a gombon (OK), vagy sikeres ellenőrzés után kódból (6.7 lecke).
6. NullReferenceException az eseménynél
KovetkezoKeres(this, Argumentumok()); // nincs feliratkozó: nullHiba és javítás
Az eseményt ?.Invoke(...) módon váltsd ki: ha nincs feliratkozó, nem történik semmi (5.1 lecke).
Mini projekt: keresés és csere
Készíts szerkesztőt keresés és csere ablakkal:
- Főablak:
rtbSzoveg(RichTextBox,Dock = Fill), menü: Szerkesztés → Keresés és csere... (Ctrl+F és Ctrl+H is), állapotsor (tsslUzenet). - KeresForm:
txtKeres,txtCsere,chkKisbetu(Kis- és nagybetű megkülönböztetése), gombok: Következő, Csere, Mindet cseréli, Bezár (CancelButton, elrejt). Párbeszédablak-beállítások:FixedDialog,ShowInTaskbar = False,CenterParent. - A
KeresFormhárom eseményt vált ki (KovetkezoKeres,CsereKeres,MindetCsereliKeres)KeresEventArgsadattal; nem ismeri a főablakot. - A főablak egypéldányos mintával kezeli az ablakot: nyitáskor a kijelölt szöveg a keresőmezőbe kerül, a fókusz a
txtKeresmezőre. - Következő keresés: a kijelölés vége után keres (
RichTextBox.Find), a találatot kijelöli és láthatóvá görgeti; a dokumentum végén az elejétől folytatja (körbe); ha nincs találat, az állapotsor üzen (Nincs találat). - Csere: ha a kijelölés megegyezik a keresett szöveggel, kicseréli, majd a következő találatra lép; különben csak keres.
- Mindet cseréli: az összes előfordulást kicseréli, az állapotsor mutatja a darabszámot (5 csere).
- Teszteld: kis- és nagybetű, a dokumentum vége (körbe), nincs találat, csere majd következő, mindet cseréli, az ablak bezárása és újranyitása (a mezők megmaradnak), a főablak bezárása nyitott keresővel.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A keresés a RichTextBox.Find(szöveg, kezdet, beállítások) metódusra épül, amely a találat kezdő indexét adja (vagy -1 értéket). A RichTextBoxFinds.MatchCase a kisbetű-érzékenység. A Mindet cseréli a szöveg tartalmát egyszerre cseréli (a formázás elveszhet a Text felülírásánál), ezért a példa ciklusban, találatonként dolgozik.
private KeresForm kereso = null;
private void miKeres_Click(object sender, EventArgs e)
{
if (kereso == null || kereso.IsDisposed)
{
kereso = new KeresForm();
kereso.KovetkezoKeres += Kereso_KovetkezoKeres;
kereso.CsereKeres += Kereso_CsereKeres;
kereso.MindetCsereliKeres += Kereso_MindetCsereliKeres;
}
if (!kereso.Visible)
{
kereso.Show(this);
}
kereso.Activate();
kereso.Elokeszit(rtbSzoveg.SelectedText); // a KeresForm metódusa: a kijelölt szöveg a keresőmezőbe
}
private bool Keres(KeresEventArgs a)
{
if (a.Keresett.Length == 0)
{
return false;
}
RichTextBoxFinds opciok = a.KisbetuErzekeny ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None;
int kezdet = rtbSzoveg.SelectionStart + rtbSzoveg.SelectionLength;
int talalat = rtbSzoveg.Find(a.Keresett, Math.Min(kezdet, rtbSzoveg.TextLength), opciok);
if (talalat < 0 && kezdet > 0)
{
talalat = rtbSzoveg.Find(a.Keresett, 0, opciok); // körbe: az elejétől
}
if (talalat < 0)
{
tsslUzenet.Text = "Nincs találat: " + a.Keresett;
return false;
}
rtbSzoveg.Select(talalat, a.Keresett.Length);
rtbSzoveg.ScrollToCaret();
tsslUzenet.Text = "Találat a(z) " + (talalat + 1) + ". karakternél.";
return true;
}
private void Kereso_KovetkezoKeres(object sender, KeresEventArgs e)
{
Keres(e);
this.Activate(); // a főablak szerkesztőjére a fókusz visszaadása (a kereső nyitva marad)
}
private void Kereso_CsereKeres(object sender, KeresEventArgs e)
{
StringComparison osszehasonlitas = e.KisbetuErzekeny ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
if (e.Keresett.Length > 0 && string.Equals(rtbSzoveg.SelectedText, e.Keresett, osszehasonlitas))
{
rtbSzoveg.SelectedText = e.Csere;
}
Keres(e);
}
private void Kereso_MindetCsereliKeres(object sender, KeresEventArgs e)
{
if (e.Keresett.Length == 0)
{
return;
}
RichTextBoxFinds opciok = e.KisbetuErzekeny ? RichTextBoxFinds.MatchCase : RichTextBoxFinds.None;
int db = 0;
int hely = 0;
while (hely < rtbSzoveg.TextLength)
{
int talalat = rtbSzoveg.Find(e.Keresett, hely, opciok);
if (talalat < 0)
{
break;
}
rtbSzoveg.Select(talalat, e.Keresett.Length);
rtbSzoveg.SelectedText = e.Csere;
hely = talalat + e.Csere.Length; // a cserélt szövegen túl folytatjuk
db++;
}
tsslUzenet.Text = db + " csere";
}
// KeresForm.cs
public void Elokeszit(string kijelolt)
{
if (kijelolt.Length > 0 && !kijelolt.Contains(Environment.NewLine))
{
txtKeres.Text = kijelolt;
}
txtKeres.Focus();
txtKeres.SelectAll();
}Mit tegyél ide: a szerkesztő kijelölt találattal, a keresőablak és az állapotsor „5 csere” üzenettel.Javasolt fájlnév: winforms-7-6-kesz.webp · Alt szöveg: Keresés és csere a szerkesztőben
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. Névjegy ablak
Készíts Modális ablak NevjegyForm ablakot (program neve, verzió, szerző, OK gomb), a párbeszédablak szabályaival. Hogyan nyitod meg a Súgó → Névjegy menüpontból?Egy lehetséges megoldás
using blokkban. Az OK gomb AcceptButton és CancelButton is lehet (nincs más gomb).private void miNevjegy_Click(object sender, EventArgs e)
{
using (NevjegyForm ablak = new NevjegyForm())
{
ablak.ShowDialog(this);
}
}
2. Kérdező dialógus eredménnyel
A A statikus KerdesForm egy kérdést és egy szövegmezőt mutat. A hívó string választ kap (vagy null Mégse esetén). Hogyan oldod meg a statikus metódussal (6.7 lecke)?Egy lehetséges megoldás
Kerdez metódus létrehozza az ablakot, megjeleníti, kiolvassa a választ, és felszabadít (using).public static string Kerdez(IWin32Window tulajdonos, string kerdes, string alap)
{
using (KerdesForm ablak = new KerdesForm())
{
ablak.lblKerdes.Text = kerdes;
ablak.txtValasz.Text = alap;
ablak.txtValasz.SelectAll();
return ablak.ShowDialog(tulajdonos) == DialogResult.OK ? ablak.txtValasz.Text : null;
}
}
3. A kereső ablak mindig a szerkesztő mellett
A keresőablak induláskor a főablak jobb szélén jelenjen meg (ne takarja a szöveget), de a képernyőn belül maradjon. Hogyan számolod a helyét? A Egy lehetséges megoldás
StartPosition = Manual és a főablak helyéből, valamint a munkaterületből számolt Location (6.1 lecke).private void KeresoHelye(Form ablak)
{
Rectangle munka = Screen.FromControl(this).WorkingArea;
ablak.StartPosition = FormStartPosition.Manual;
int x = Math.Min(this.Right - ablak.Width - 20, munka.Right - ablak.Width);
int y = Math.Max(munka.Top, this.Top + 80);
ablak.Location = new Point(x, y);
}
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. Párbeszédablak beállításai
Sorolj fel hat tulajdonságot, amelyet egy jó párbeszédablaknál beállítasz.Megoldás megnyitása
FormBorderStyle = FixedDialog, MinimizeBox = False, MaximizeBox = False, ShowInTaskbar = False, StartPosition = CenterParent, AcceptButton és CancelButton.
2. Nevek
Nevezd át a következő osztályokat a konvenció szerint: Form2 (kereső), Form3 (beállítások), Form4 (névjegy).Megoldás megnyitása
KeresForm, BeallitasokForm, NevjegyForm (a Visual Studio Rename parancsával, F2).
Közepes szint
3. Modális megerősítő dialógus
Készíts A gombfeliratok a műveletet nevezik meg (az Igen/Nem helyett), ami érthetőbb. A három gomb MegerositForm ablakot saját gombfeliratokkal (Mentés, Elvetés, Mégse), amely DialogResult.Yes, No, Cancel értéket ad. Miért jobb, mint a MessageBox?Megoldás megnyitása
DialogResult értéke a tervezőben beállítható, kód nem kell.
4. Esemény helyett visszahívás
A keresőablak konstruktora kapjon egy A visszahívással az ablak eredményt is kap (találat volt-e), és egyetlen feldolgozója van; az esemény több feliratkozót és nincs visszatérési értéket enged. Az esemény a megszokottabb Windows Forms minta, a visszahívás egyszerűbb, ha egy válasz kell.Func<string, bool> típusú függvényt (a keresés végrehajtását). Mi a különbség az eseménnyel szemben?Megoldás megnyitása
private readonly Func<string, bool> keres;
public KeresForm(Func<string, bool> keresFuggveny)
{
InitializeComponent();
keres = keresFuggveny;
}
private void btnKovetkezo_Click(object sender, EventArgs e)
{
lblEredmeny.Text = keres(txtKeres.Text) ? "Találat." : "Nincs találat.";
}
Kihívás szint
5. Interfész a laza csatoláshoz
Definiálj A szerződés kifejezett: a kereső tudja, mit vár a tulajdonostól, és az eredmény közvetlenül visszajön. Hátrány: a kereső ismer egy interfészt (de nem a főablak típusát).IKeresheto interfészt (bool KeresKovetkezo(string, bool)), amelyet a főablak megvalósít, és a kereső ablak ezt kapja. Mi az előnye az eseménnyel szemben?Megoldás megnyitása
public interface IKeresheto
{
bool KeresKovetkezo(string keresett, bool kisbetuErzekeny);
}
// Form1 : Form, IKeresheto — a metódus megvalósítása a Keres(...) hívása
// KeresForm: public KeresForm(IKeresheto cel) { InitializeComponent(); this.cel = cel; }
6. Az ablak állapotának megőrzése
A Beállítások ablak a bezárás után is emlékezzen a fülre, amelyen állt. Hogyan oldod meg modális ablaknál, amelyet minden nyitáskor újra létrehozol? A főablak (vagy a Megoldás megnyitása
Settings) tárolja az utolsó fül sorszámát, és nyitáskor átadja az ablaknak (tulajdonság vagy konstruktor), zárás után kiolvassa.private int utolsoFul = 0;
private void miBeallitasok_Click(object sender, EventArgs e)
{
using (BeallitasokForm ablak = new BeallitasokForm())
{
ablak.AktivFul = utolsoFul;
ablak.ShowDialog(this);
utolsoFul = ablak.AktivFul; // a zárás után is elérhető
}
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Add → Form (Windows Forms), beszédes névvel ( Ha döntést vagy adatot kér a főablaktól függő adatokra, és a felhasználónak előbb be kell fejeznie. Az ablak nem ismeri a főablakot, ezért újrahasználható, és a főablak vezérlői privátak maradnak. Hogy a program kilépésekor az ablak bezáródjon; csak a felhasználói bezárást ( Az 1. Hogyan adsz új ablakot a projekthez?
…Form).2. Melyik FormBorderStyle való a párbeszédablaknak?
FixedDialog, kicsinyítő és nagyító gomb nélkül.3. Mikor modális a dialógus?
4. Mi a laza csatolás előnye?
5. Miért kell a CloseReason vizsgálata az elrejtésnél?
UserClosing) rejtsd el.6. Hogyan keresel a RichTextBox-ban?
Find(szöveg, kezdőindex, RichTextBoxFinds): a találat indexét adja (vagy -1), a Select és ScrollToCaret mutatja.7. Mit tegyél, ha az események nincs feliratkozó?
?.Invoke(...) hívás biztonságos: feliratkozó nélkül nem történik semmi.
Mit tudsz már?
- Új ablakot adsz a projekthez, és párbeszédablakként állítod be.
- Megkülönbözteted a modalitás fajtáit.
- Nem modális ablakot kötsz a főablakhoz eseményekkel (laza csatolás).
- Keresést és cserét valósítasz meg egy szerkesztőben.
- Felismered a gyakori hibákat (szoros csatolás,
FormClosingés kilépés).
Következő lecke: Adatátadás két Form között
Hogyan kerülnek az adatok az egyik ablakból a másikba: konstruktor, tulajdonság, esemény, és a másolat–hivatkozás különbsége.



