Grafikus C# – Windows Forms tanfolyam › 6.5 lecke Windows Forms · 6.5 lecke · füles felületek Amikor egy űrlapon túl sok vezérlő van, a felhasználó eltéved. A TabControl és többlapos felületek
TabControl füles lapokra osztja a felületet: a beállításokat, a dokumentumokat, az űrlap szakaszait egyszerre mindig csak egy lap mutatja. Ebben a leckében megtanulod a TabControl és a TabPage használatát a tervezőben és kódból, a lapváltás eseményeit (Selecting, SelectedIndexChanged), a lapok ellenőrzésre és késleltetett betöltésre való felhasználását, a lapok kódból való hozzáadását és bezárását, és elkészíted egy többfüles jegyzettömb alapját.
A lecke végére
- felépítesz egy többlapos felületet
TabControlésTabPagevezérlőkkel (tervezőben és kódból); - kezeled a lapfülek megjelenését (
Alignment,Appearance,SizeMode, ikonok); - használod a lapváltás eseményeit, és a
Selectingeseménnyel megakadályozod vagy ellenőrzöd a lapváltást; - tudod, hogy a
TabPage.Visiblenem rejti el a fület, és hogyan rejts el lapot helyesen; - lapokat adsz hozzá és távolítasz el futás közben, és kezeled az üres
TabControlesetet; - a lap tartalmát késleltetve tölted be, és több dokumentumot kezelsz füles felületen.
Mielőtt elkezded
Ehhez a leckéhez a 6.3 lecke (tárolók, lapozó panelek), az 5.5 lecke (változás-események), az 5.7 lecke (leiratkozás és Dispose) és a 2.14 lecke (mentetlen dokumentum) szükséges. Új projektben próbáld ki a példákat.
TabControl és TabPage
A TabControl egy tároló, amelynek gyermekei TabPage (lap) objektumok, mindegyikhez egy fül tartozik. A lapok maguk is tárolók: a vezérlők a lapra kerülnek, nem a TabControl felületére. A tervezőben a TabControl Smart Tag menüjében (Add Tab, Remove Tab) vagy a TabPages tulajdonság szerkesztőjében (… gomb) kezeled a lapokat.
| Tulajdonság | Jelentése |
|---|---|
TabPages | A lapok gyűjteménye (Add, Remove, Insert, indexelés). |
SelectedIndex, SelectedTab | A kiválasztott lap sorszáma, illetve maga a lap. Ha nincs lap, a SelectedIndex értéke -1, a SelectedTab null. |
TabCount | A lapok száma. |
Alignment | Hol vannak a fülek: Top (alap), Bottom, Left, Right. |
Appearance | Normal (fülek), Buttons, FlatButtons (gombszerű fülek). |
Multiline | Sok fül esetén több sorba törnek (különben görgethetők). |
SizeMode | A fülek mérete: Normal, FillToRight (kitöltik a sort), Fixed (egyforma szélesség az ItemSize szerint). |
ImageList és TabPage.ImageIndex | Ikon a fülön (az ImageList a 1.12 leckéből). |
// a TabControl kitölti az ablakot: Dock = Fill (tervezőben is beállítható)
tc.Dock = DockStyle.Fill;
// lapok elérése
TabPage elso = tc.TabPages[0];
TabPage aktualis = tc.SelectedTab;
lblInfo.Text = $"{tc.TabCount} lap, a kiválasztott: {aktualis.Text}";
// lap kiválasztása
tc.SelectedIndex = 1;
tc.SelectedTab = tabBeallitasok;
// a lap szövege, és a lapon lévő vezérlők
tabAdatok.Text = "Adatok";
foreach (Control c in tabAdatok.Controls)
{
c.Enabled = true;
}txtNev szülője a tabAdatok lap, nem a TabControl. A lapon lévő vezérlők elérése a tabAdatok.Controls gyűjteményen át történik. A Dock és az Anchor a lapra vonatkozik (6.2 lecke).TabControl kitölti az ablakot, és a lapok tartalmát a lapon belül rendezed (TableLayoutPanel, 6.4 lecke).TabControl beépítetten kezeli a Ctrl+Tab és Ctrl+Shift+Tab billentyűket a lapok közötti váltáshoz (ha a fókusz a TabControl-ban van).SelectedTab értéke null, és a rá hivatkozó kód NullReferenceException hibát dob. Mindig ellenőrizd a null értéket.Jósolj!
A TabControlban három lap van, a harmadik van kiválasztva. A kódból tc.TabPages.RemoveAt(2);-t hívsz. Melyik lap lesz kiválasztva, és mi a SelectedIndex értéke?
Jóslat ellenőrzése
Az utolsó lap eltávolítása után a kiválasztás általában az előző lapra kerül (a második lap, SelectedIndex = 1). Nem szabad azonban a viselkedésre építeni: egy lap bezárása után mindig állítsd be a kiválasztást kifejezetten, és ellenőrizd, hogy maradt-e lap (tc.TabCount > 0).
A lapváltás eseményei
Lapváltáskor több esemény fut, mindegyik más pillanatban:
| Esemény | Mikor? | Mire való? |
|---|---|---|
Deselecting | A régi lap elhagyása előtt. | Ellenőrzés: elhagyható-e a lap (e.Cancel). |
Selecting | Az új lap kiválasztása előtt. | Ellenőrzés: megnyitható-e a lap (e.Cancel, e.TabPage). |
Deselected | A régi lap elhagyása után. | Takarítás, mentés. |
Selected | Az új lap kiválasztása után. | Az új lap frissítése. |
SelectedIndexChanged | A kiválasztott lap sorszáma megváltozott. | Egyszerű reagálás a váltásra (a leggyakoribb). |
// 1. egyszerű reagálás a váltásra
private void tc_SelectedIndexChanged(object sender, EventArgs e)
{
if (tc.SelectedTab == null)
{
lblAllapot.Text = "Nincs megnyitott lap.";
return;
}
lblAllapot.Text = "Aktív lap: " + tc.SelectedTab.Text;
}
// 2. a lap elhagyásának megakadályozása érvénytelen adatnál
private void tc_Deselecting(object sender, TabControlCancelEventArgs e)
{
if (e.TabPage == tabAdatok && txtNev.Text.Trim().Length == 0)
{
MessageBox.Show("Előbb add meg a nevet!");
e.Cancel = true; // a lap nem váltódik
}
}
// 3. egy lap letiltása: nem választható ki
private void tc_Selecting(object sender, TabControlCancelEventArgs e)
{
if (e.TabPage == tabHaladó && !chkHaladó.Checked)
{
e.Cancel = true;
}
}e.TabPage a lap, amelyről szó van, az e.TabPageIndex a sorszáma, az e.Cancel = true megszakítja a váltást. A Selecting és a Deselecting eseményeknél van.Deselecting a régi lap elhagyását, a Selecting az új lap megnyitását ellenőrzi. Az „előbb töltsd ki az adatokat” szabály a Deselecting, a „ez a lap még nem elérhető” a Selecting esemény dolga.Selecting kezelőben a SelectedIndex átállítása újabb Selecting eseményt vált ki, és végtelen visszacsatolás lehet. Csak a Cancel értéket állítsd.Selected (vagy a SelectedIndexChanged) a jó hely: ilyenkor már az új lap látszik.Jósolj!
A tc_SelectedIndexChanged kezelőben a tc.SelectedTab.Text értéket írod ki. Az utolsó lapot bezárod a programból (tc.TabPages.Remove(lap)). Mi történik?
Jóslat ellenőrzése
Az utolsó lap eltávolítása után a SelectedTab értéke null, és a SelectedIndexChanged eseményben a .Text lekérdezése NullReferenceException kivételt dob. A kezelőben ezért mindig ellenőrizd a SelectedTab == null esetet.
Lapok kezelése kódból
// új lap hozzáadása és kiválasztása
TabPage lap = new TabPage("Új lap");
TextBox szoveg = new TextBox();
szoveg.Multiline = true;
szoveg.Dock = DockStyle.Fill;
lap.Controls.Add(szoveg);
tc.TabPages.Add(lap);
tc.SelectedTab = lap;
// lap eltávolítása és felszabadítása
tc.TabPages.Remove(lap);
lap.Dispose(); // a lapon lévő vezérlőkkel együtt
// lap elrejtése: a TabPage.Visible NEM működik, el kell távolítani, és eltárolni
TabPage rejtett = tabHaladó;
tc.TabPages.Remove(rejtett); // a hivatkozást a mezőben megőrizzük
// visszarakás (például az eredeti helyére)
tc.TabPages.Insert(2, rejtett);A TabPage.Visible nem rejti el a fület
A TabPage.Visible = false beállításnak nincs hatása a fülre: a lapfül továbbra is látszik, és a lap kiválasztható. Lap elrejtésére az a megoldás, hogy a lapot eltávolítod a TabPages gyűjteményből (Remove), és a hivatkozást egy mezőben megőrzöd, hogy később visszatehesd (Insert vagy Add). Egy lapot elég letiltani, ha csak a tartalmát akarod elérhetetlenné tenni (Enabled = false, de a fül kiválasztható marad).
Késleltetett betöltés: csak akkor töltsd, ha megnyitják
Ha egy lap tartalma lassan töltődik (adatbázis, nagy fájl), ne töltsd be induláskor az összes lapét: elég a lap első megnyitásakor. Egy jelző (a lap Tag tulajdonságában vagy egy halmazban) megjegyzi, hogy betöltötted-e már:
private readonly HashSet<TabPage> betoltott = new HashSet<TabPage>();
private void tc_Selected(object sender, TabControlEventArgs e)
{
if (e.TabPage == null || betoltott.Contains(e.TabPage))
{
return; // már betöltöttük
}
betoltott.Add(e.TabPage);
if (e.TabPage == tabStatisztika)
{
StatisztikaBetolt(); // a lassú lekérdezés csak egyszer fut
}
}Jósolj!
A tabStatisztika lapon egy DataGridView van, amelyet a Form1_Load eseményben töltesz fel. A lap nincs kiválasztva induláskor. Mit tapasztalsz a lap első megnyitásakor?
Jóslat ellenőrzése
A TabControl a nem látható lapok vezérlőit csak a lap első megnyitásakor „hozza létre teljesen” (az ablakkezelőjük később jön létre). A tapasztalat szerint a nem látható lapon lévő vezérlők adatkötése és egyes beállításai ezért késve vagy váratlanul viselkedhetnek, ezért a feltöltésüket gyakran biztonságosabb a lap megnyitásakor (Selected) végezni. A pontos viselkedés a vezérlőtől függ, ezért a saját programodban próbáld ki.
Megjelenés: fülek, ikonok, saját rajz
A lapfülek kinézetét az alábbiakkal módosíthatod:
// fülek a bal oldalon, egyforma méretben
tc.Alignment = TabAlignment.Left;
tc.SizeMode = TabSizeMode.Fixed;
tc.ItemSize = new Size(40, 120); // bal oldali fülnél a méret felcserélődik: magasság x szélesség
// ikonok a füleken
tc.ImageList = imlIkonok;
tabAdatok.ImageIndex = 0;
tabBeallitasok.ImageIndex = 1;
// saját rajzolású fülek: a mentetlen lap szövege piros
tc.DrawMode = TabDrawMode.OwnerDrawFixed;
private void tc_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage lap = tc.TabPages[e.Index];
bool kivalasztott = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
Color hatter = kivalasztott ? Color.White : Color.FromArgb(235, 235, 240);
Color szin = lap.Text.EndsWith("*") ? Color.Firebrick : Color.Black;
using (SolidBrush ecset = new SolidBrush(hatter))
{
e.Graphics.FillRectangle(ecset, e.Bounds);
}
TextRenderer.DrawText(e.Graphics, lap.Text, e.Font, e.Bounds, szin,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}Left és Right elhelyezésnél az ItemSize szélessége és magassága a forgatás miatt felcserélődik: először a (rövidebb) magasság, utána a (hosszabb) szélesség adja a fülek vízszintes kiterjedését. A pontos értéket kísérletezéssel állítsd be.DrawMode értékével a fülek rajzolása a te kezedbe kerül: a DrawItem eseményben kell megrajzolni minden fület. Az e.Bounds a fül területe, az e.State az állapota (kiválasztott).Graphics.DrawString helyett a TextRenderer a Windows vezérlők betűmegjelenítését használja, ezért a szöveg ugyanúgy néz ki, mint a beépített fülek szövege.* a végén), a tc.Invalidate() hívás kényszeríti a fülek újrarajzolását.Jósolj!
Beállítod az OwnerDrawFixed módot, de a DrawItem eseményt nem kötöd hozzá (vagy az üres). Mit látsz a füleken?
Jóslat ellenőrzése
A fülek üresen jelennek meg (vagy a rajzolás nélküli háttérrel): a rendszer a rajzolást teljesen rád bízza, és ha nem rajzolsz semmit, a fül sem mutat feliratot. A saját rajzolás a feliratot is neked kell megrajzolnod.
Mit tegyél ide: egy többfüles ablak, amelynek egyik füle piros szöveggel (mentetlen) jelenik meg, mellette a normál fülek.Javasolt fájlnév: winforms-6-5-tabok.webp · Alt szöveg: Füles felület saját rajzolású fülekkel
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 lap nem tűnik el
tabHaladó.Visible = false; // a fül továbbra is látszikHiba és javítás
A TabPage.Visible nem rejti el a fület. Távolítsd el a lapot a TabPages gyűjteményből, és a hivatkozást őrizd meg a visszarakáshoz.
2. NullReferenceException az utolsó lap bezárása után
lblCim.Text = tc.SelectedTab.Text; // ha nincs lap, a SelectedTab nullHiba és javítás
Ellenőrizd a SelectedTab != null feltételt, vagy a TabCount > 0 értéket, mielőtt használod.
3. A lassú lap minden megnyitáskor újratölt
A statisztika lap minden váltásnál másodpercekig tölt. A betöltés a Ok és javítás
SelectedIndexChanged eseményben fut, jelző nélkül. Jegyezd meg, hogy betöltötted-e már a lapot (HashSet<TabPage> vagy jelző), vagy csak adatváltozáskor frissítsd.
4. Végtelen ciklus a Selecting eseményben
private void tc_Selecting(object sender, TabControlCancelEventArgs e)
{
if (e.TabPage == tabZarolt)
{
tc.SelectedIndex = 0; // újabb Selecting esemény!
}
}Hiba és javítás
A SelectedIndex átállítása újabb Selecting eseményt vált ki. Csak az e.Cancel = true értéket állítsd, így a lap nem váltódik, és a kiválasztás a régi lapon marad.
5. A bezárt lap vezérlői tovább élnek
tc.TabPages.Remove(lap); // a Dispose hiányzikHiba és javítás
A Remove csak kiveszi a lapot a gyűjteményből, de nem szabadítja fel. Hívd a lap.Dispose() metódust (a lapon lévő vezérlőket is felszabadítja), különösen sok dinamikus lapnál (5.7 lecke).
6. A vezérlő nem a lapon, hanem a TabControlon van
A vezérlő minden lapon látszik, vagy a lapváltáskor nem váltódik. A vezérlő a Ok és javítás
TabControl fölé, nem a lapra került (a tervezőben a lap fölé húztad, de nem a lapra ejtetted). A Document Outline ablakban látható a hierarchia. Húzd a vezérlőt a lapra, vagy kódból: vezerlo.Parent = lap;.
Mini projekt: többfüles jegyzettömb
Készíts jegyzettömböt, amelyben minden dokumentum külön lapon van:
- Az ablak:
MenuStrip(Új, Megnyitás, Mentés, Bezárás),tc(TabControl,Dock = Fill), alul egylblAllapot(karakterszám). - Egy kis
Dokumentumosztály tárolja egy lap adatait: a szerkesztőt (RichTextBox), a lapot, az útvonalat, a nevet és a módosított jelzőt. - Az Új (
Ctrl+T) új lapot nyit egy üres szerkesztővel (Névtelen 1, Névtelen 2…). A Megnyitás (Ctrl+O) szövegfájlt tölt új lapra (a fájl neve a lap szövege). - A szerkesztés módosítottnak jelöli a dokumentumot: a lap szövege
*-ot kap, a fül piros színű (saját rajzolás). - A Mentés (
Ctrl+S) elmenti az aktuális lapot (új dokumentumnálSaveFileDialog), és törli a csillagot. - A Bezárás (
Ctrl+W, vagy a fül középső egérgombbal kattintva) a lapot bezárja; ha módosított, rákérdez (Igen/Nem/Mégse, 2.14 lecke). A lap felszabadítása (Dispose) és a lista tisztítása kötelező. - Az ablak bezárásakor, ha van módosított dokumentum, a program rákérdez (
FormClosing). - Teszteld: több lap, szerkesztés, mentés, bezárás mentetlenül (mindhárom válasz), az utolsó lap bezárása, a billentyűk, a középső gomb.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A lapok és dokumentumok párosítását a lap Tag tulajdonsága adja (egy Dokumentum objektum). A lambda feliratkozás a szerkesztő TextChanged eseményére kerül, miután a kezdeti szöveg már beállt, így a betöltés nem számít módosításnak.
private class Dokumentum
{
public RichTextBox Szerkeszto;
public TabPage Lap;
public string Utvonal = "";
public string Nev = "";
public bool Modosult = false;
}
private int szamlalo = 0;
private void Form1_Load(object sender, EventArgs e)
{
tc.DrawMode = TabDrawMode.OwnerDrawFixed;
UjDokumentum("", "");
}
private void CimFrissit(Dokumentum d)
{
d.Lap.Text = d.Nev + (d.Modosult ? "*" : "");
tc.Invalidate();
}
private Dokumentum UjDokumentum(string utvonal, string tartalom)
{
Dokumentum d = new Dokumentum();
d.Utvonal = utvonal;
d.Nev = utvonal.Length == 0 ? "Névtelen " + (++szamlalo) : Path.GetFileName(utvonal);
d.Szerkeszto = new RichTextBox();
d.Szerkeszto.Dock = DockStyle.Fill;
d.Szerkeszto.BorderStyle = BorderStyle.None;
d.Szerkeszto.Font = new Font("Consolas", 11);
d.Szerkeszto.Text = tartalom; // előbb a szöveg, utána a figyelés
d.Lap = new TabPage(d.Nev);
d.Lap.Tag = d;
d.Lap.Controls.Add(d.Szerkeszto);
d.Szerkeszto.TextChanged += (s, ev) =>
{
if (!d.Modosult)
{
d.Modosult = true;
CimFrissit(d);
}
Statisztika();
};
tc.TabPages.Add(d.Lap);
tc.SelectedTab = d.Lap;
d.Szerkeszto.Focus();
return d;
}
private Dokumentum Aktualis()
{
return tc.SelectedTab == null ? null : (Dokumentum)tc.SelectedTab.Tag;
}
private void Statisztika()
{
Dokumentum d = Aktualis();
lblAllapot.Text = d == null ? "Nincs megnyitott dokumentum." : "Karakterek: " + d.Szerkeszto.TextLength;
}
private void tc_SelectedIndexChanged(object sender, EventArgs e)
{
Statisztika();
}
private bool Ment(Dokumentum d)
{
if (d.Utvonal.Length == 0)
{
sfdMentes.FileName = d.Nev;
if (sfdMentes.ShowDialog() != DialogResult.OK)
{
return false;
}
d.Utvonal = sfdMentes.FileName;
d.Nev = Path.GetFileName(d.Utvonal);
}
try
{
File.WriteAllText(d.Utvonal, d.Szerkeszto.Text);
d.Modosult = false;
CimFrissit(d);
return true;
}
catch (Exception ex)
{
MessageBox.Show("A mentés nem sikerült: " + ex.Message);
return false;
}
}
private bool Bezar(Dokumentum d)
{
if (d.Modosult)
{
tc.SelectedTab = d.Lap;
DialogResult valasz = MessageBox.Show("Mented a változtatásokat: " + d.Nev + "?", "Bezárás", MessageBoxButtons.YesNoCancel);
if (valasz == DialogResult.Cancel || (valasz == DialogResult.Yes && !Ment(d)))
{
return false;
}
}
tc.TabPages.Remove(d.Lap);
d.Lap.Dispose();
Statisztika();
return true;
}
private void miUj_Click(object sender, EventArgs e)
{
UjDokumentum("", "");
}
private void miMegnyit_Click(object sender, EventArgs e)
{
if (ofdMegnyit.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
UjDokumentum(ofdMegnyit.FileName, File.ReadAllText(ofdMegnyit.FileName));
}
catch (Exception ex)
{
MessageBox.Show("A fájl nem nyitható meg: " + ex.Message);
}
}
private void miMent_Click(object sender, EventArgs e)
{
Dokumentum d = Aktualis();
if (d != null)
{
Ment(d);
}
}
private void miBezar_Click(object sender, EventArgs e)
{
Dokumentum d = Aktualis();
if (d != null)
{
Bezar(d);
}
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys.Control | Keys.T)) { miUj_Click(this, EventArgs.Empty); return true; }
if (keyData == (Keys.Control | Keys.O)) { miMegnyit_Click(this, EventArgs.Empty); return true; }
if (keyData == (Keys.Control | Keys.S)) { miMent_Click(this, EventArgs.Empty); return true; }
if (keyData == (Keys.Control | Keys.W)) { miBezar_Click(this, EventArgs.Empty); return true; }
return base.ProcessCmdKey(ref msg, keyData);
}
private void tc_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Middle)
{
return;
}
for (int i = 0; i < tc.TabCount; i++)
{
if (tc.GetTabRect(i).Contains(e.Location))
{
Bezar((Dokumentum)tc.TabPages[i].Tag);
break;
}
}
}
private void tc_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage lap = tc.TabPages[e.Index];
bool kivalasztott = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
Color hatter = kivalasztott ? Color.White : Color.FromArgb(235, 235, 240);
Color szin = lap.Text.EndsWith("*") ? Color.Firebrick : Color.Black;
using (SolidBrush ecset = new SolidBrush(hatter))
{
e.Graphics.FillRectangle(ecset, e.Bounds);
}
TextRenderer.DrawText(e.Graphics, lap.Text, e.Font, e.Bounds, szin, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason != CloseReason.UserClosing)
{
return;
}
for (int i = tc.TabCount - 1; i >= 0; i--)
{
if (!Bezar((Dokumentum)tc.TabPages[i].Tag))
{
e.Cancel = true; // a felhasználó a Mégse gombot választotta
return;
}
}
}Mit tegyél ide: a többfüles jegyzettömb három lappal, az egyik piros fülű (mentetlen), alul a karakterszám.Javasolt fájlnév: winforms-6-5-kesz.webp · Alt szöveg: A többfüles jegyzettömb
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. Beállítóablak füles felülettel
Készíts beállítóablakot Általános, Megjelenés és Hálózat lappal és OK, Mégse, Alkalmaz gombbal (a A gombok nem a lapon vannak, hanem az ablakon, a TabControl alatt, Anchor = Bottom, Right). Mi a TabControl és a gombok viszonya?Egy lehetséges megoldás
TabControl alatt, ezért minden lapnál láthatók. A TabControl Anchor értéke négy irány, a magassága a gombsor fölött végződik. Az Alkalmaz gomb csak módosításkor aktív.private bool valtozas = false;
private void Form1_Load(object sender, EventArgs e)
{
btnAlkalmaz.Enabled = false;
// minden módosítható vezérlő jelzi a változást
chkIndit.CheckedChanged += Valtozott;
txtProxy.TextChanged += Valtozott;
cmbTema.SelectedIndexChanged += Valtozott;
}
private void Valtozott(object sender, EventArgs e)
{
valtozas = true;
btnAlkalmaz.Enabled = true;
}
private void btnAlkalmaz_Click(object sender, EventArgs e)
{
// beállítások mentése
valtozas = false;
btnAlkalmaz.Enabled = false;
}
private void btnOk_Click(object sender, EventArgs e)
{
if (valtozas)
{
btnAlkalmaz_Click(sender, e);
}
this.Close();
}
2. Ellenőrzés lapelhagyáskor
Egy regisztrációs űrlap Adatok, Jelszó és Összegzés lapjából a Jelszó lapra csak akkor lehet átlépni, ha a név és az e-mail ki van töltve. Melyik eseményt használod, és mit írsz az üzenetben? A Egy lehetséges megoldás
Deselecting esemény a régi lap (Adatok) elhagyását ellenőrzi. Hibánál a hibás mezőre teszi a fókuszt, hogy a felhasználó tudja, mit kell javítania.private void tc_Deselecting(object sender, TabControlCancelEventArgs e)
{
if (e.TabPage != tabAdatok)
{
return;
}
if (txtNev.Text.Trim().Length == 0)
{
MessageBox.Show("A név kötelező.");
e.Cancel = true;
txtNev.Focus();
}
else if (!txtEmail.Text.Contains("@"))
{
MessageBox.Show("Az e-mail címnek tartalmaznia kell a @ jelet.");
e.Cancel = true;
txtEmail.Focus();
}
}
3. Lap bezárása a fülön lévő ✕ jelre kattintva
A fülek szövege végén ✕ látszik (a lap szövege: Névtelen 1 ✕). A ✕ jelre kattintva a lap bezárul. Hogyan állapítod meg, hogy a jelre kattintottak? A Egy lehetséges megoldás
MouseUp eseményben megkeressük a kattintott fület (GetTabRect), és ellenőrizzük, hogy a kattintás a fül jobb szélső sávjában történt-e (például a jobb szélétől 20 képpontra). Pontosabb megoldás a fül saját rajzolása és a jel területének kiszámítása.private void tc_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
{
return;
}
for (int i = 0; i < tc.TabCount; i++)
{
Rectangle ful = tc.GetTabRect(i);
Rectangle xTerulet = new Rectangle(ful.Right - 22, ful.Top, 22, ful.Height); // a jobb szélső sáv
if (xTerulet.Contains(e.Location))
{
TabPage lap = tc.TabPages[i];
tc.TabPages.Remove(lap);
lap.Dispose();
return;
}
}
}
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. Új lap kódból
Egy gomb új lapot ad a A vezérlőt a lap tc vezérlőhöz Lap N szöveggel (N a lap sorszáma), és kiválasztja. Mit kell tenned, hogy a lap tartalmazzon egy Label vezérlőt?Megoldás megnyitása
Controls gyűjteményéhez kell adni (nem a TabControl-hoz).private void btnUjLap_Click(object sender, EventArgs e)
{
TabPage lap = new TabPage("Lap " + (tc.TabCount + 1));
Label cimke = new Label();
cimke.Text = "Ez a(z) " + lap.Text + " tartalma.";
cimke.AutoSize = true;
cimke.Location = new Point(12, 12);
lap.Controls.Add(cimke);
tc.TabPages.Add(lap);
tc.SelectedTab = lap;
}
2. Az aktív lap neve
Az ablak címsorában mindig az aktív lap neve szerepel (Szerkesztő – Beállítások). Melyik eseményt használod, és mire kell vigyázni? A Megoldás megnyitása
SelectedIndexChanged eseményt. A SelectedTab lehet null (nincs lap), ezért ellenőrizni kell. Induláskor is be kell állítani a címet (a Load eseményben).private void CimFrissit()
{
this.Text = tc.SelectedTab == null ? "Szerkesztő" : "Szerkesztő – " + tc.SelectedTab.Text;
}
private void tc_SelectedIndexChanged(object sender, EventArgs e)
{
CimFrissit();
}
private void Form1_Load(object sender, EventArgs e)
{
CimFrissit();
}
Közepes szint
3. Lapok ki- és bekapcsolása
A Haladó lap csak akkor látszik, ha a A lap hivatkozása megmarad a chkHalado jelölőnégyzet be van jelölve. Hogyan oldod meg, hogy a lap tartalma megmaradjon a ki- és bekapcsolás között, és a lap az eredeti helyére (az utolsó helyre) kerüljön vissza?Megoldás megnyitása
tabHalado mezőben (a tervező létrehozta), ezért a tartalom sem vész el az eltávolításkor. A visszarakás az Add (a végére) vagy az Insert (adott helyre). Ellenőrizni kell, hogy a lap nincs-e már a gyűjteményben (Contains).private void chkHalado_CheckedChanged(object sender, EventArgs e)
{
if (chkHalado.Checked)
{
if (!tc.TabPages.Contains(tabHalado))
{
tc.TabPages.Add(tabHalado);
}
}
else
{
tc.TabPages.Remove(tabHalado); // a hivatkozás megmarad a tabHalado mezőben
}
}
4. Késleltetett betöltés ellenőrzése
Szimuláld a lassú betöltést a A betöltés előtt a kurzor tabStatisztika lapon (Thread.Sleep(1500) a betöltő metódusban), és igazold, hogy a betöltés csak egyszer fut. Hogyan jelzed a felhasználónak a várakozást?Megoldás megnyitása
WaitCursor, utána visszaáll. A jelző biztosítja az egyszeri futást. (A lassú művelet természetesen lefagyasztja az ablakot, ezt a 11. fejezet oldja meg.)private bool statBetoltve = false;
private void tc_Selected(object sender, TabControlEventArgs e)
{
if (e.TabPage != tabStatisztika || statBetoltve)
{
return;
}
this.Cursor = Cursors.WaitCursor;
try
{
System.Threading.Thread.Sleep(1500); // a lassú betöltés szimulációja
lblStat.Text = "Betöltve: " + DateTime.Now.ToString("HH:mm:ss");
statBetoltve = true;
}
finally
{
this.Cursor = Cursors.Default;
}
}
Kihívás szint
5. Ctrl+1 … Ctrl+9 gyorsbillentyűk
A A Ctrl+1 az első, a Ctrl+2 a második lapot választja ki (legfeljebb a kilencedikig). Hol kezeled, és mi történik, ha nincs annyi lap?Megoldás megnyitása
ProcessCmdKey metódusban (5.4 lecke). A Keys.D1 + i a számbillentyűket adja. Ha a sorszám túl nagy, a billentyűt nem nyeljük el (a többi kezelő kaphat esélyt), vagy figyelmen kívül hagyjuk.protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
for (int i = 0; i < 9; i++)
{
Keys billentyu = Keys.Control | (Keys)((int)Keys.D1 + i);
if (keyData == billentyu)
{
if (i < tc.TabCount)
{
tc.SelectedIndex = i;
}
return true;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
6. Kijelölt lap kiemelése színnel
Saját rajzolással a kiválasztott fül háttere legyen lila, a szövege fehér; a többi fül világosszürke, sötét szöveggel. Mire kell figyelni a rajzolás végén? A Megoldás megnyitása
DrawItem eseményben az e.State jelzi a kiválasztott fület. A kiválasztás változásakor a fülek újrarajzolása automatikus, de a lap szövegének módosításánál a tc.Invalidate() hívás kell.private void tc_DrawItem(object sender, DrawItemEventArgs e)
{
bool kivalasztott = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
Color hatter = kivalasztott ? Color.FromArgb(104, 64, 165) : Color.FromArgb(238, 238, 242);
Color szoveg = kivalasztott ? Color.White : Color.FromArgb(40, 40, 50);
using (SolidBrush ecset = new SolidBrush(hatter))
{
e.Graphics.FillRectangle(ecset, e.Bounds);
}
TextRenderer.DrawText(e.Graphics, tc.TabPages[e.Index].Text, e.Font, e.Bounds, szoveg,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A El kell távolítani a A A A Mindent: a fül hátterét és a feliratát is a 1. Hova kerülnek a vezérlők a TabControlban?
TabPage lapokra: azok a lapok Controls gyűjteményében vannak, nem a TabControlban.2. Hogyan rejtesz el egy lapot?
TabPages gyűjteményből (Remove), és a hivatkozást megőrizni a visszarakáshoz. A TabPage.Visible nem rejti el a fület.3. Mi a különbség a Selecting és a Deselecting között?
Deselecting a régi lap elhagyását, a Selecting az új lap megnyitását ellenőrzi; mindkettő megszakítható (e.Cancel).4. Mi a SelectedTab értéke üres TabControlnál?
null (és a SelectedIndex -1), ezért mindig ellenőrizd.5. Miért kell a Dispose lap eltávolítása után?
Remove csak kiveszi a lapot, a lap és a vezérlői nem szabadulnak fel; sok dinamikus lapnál szivárgás keletkezhet.6. Hogyan töltöd a lap tartalmát csak az első megnyitáskor?
Selected eseményben, egy jelzővel (vagy HashSet-tel), amely megjegyzi, hogy a lapot betöltötted-e.7. Mit kell rajzolnod az OwnerDrawFixed módban?
DrawItem eseményben. A rendszer nem rajzol semmit helyetted.
Mit tudsz már?
- Többlapos felületet építesz
TabControlésTabPagesegítségével. - Kezeled a lapváltás eseményeit, és ellenőrzéssel megakadályozod a váltást.
- Lapokat adsz hozzá, rejtesz el és távolítasz el kódból, a
Disposealkalmazásával. - Késleltetve töltöd be a lapok tartalmát, és saját rajzolású füleket készítesz.
- Felismered a gyakori hibákat (
Visible,nulllap, végtelenSelecting).
Következő lecke: TabIndex, fókusz és billentyűzetes navigáció
Egér nélkül is használható felület: a tabulátorsorrend, a fókusz kezelése és a gyorsbillentyűk.




