Grafikus C# – Windows Forms tanfolyam › 6.6 lecke

Windows Forms · 6.6 lecke · fókusz és navigáció

TabIndex, fókusz és billentyűzetes navigáció

Nem mindenki használ egeret. Sokan a billentyűzetet szeretik, mert gyorsabb, és vannak, akik egyáltalán nem tudnak egeret használni. Egy jó űrlap egér nélkül is teljesen használható: a Tab billentyű logikus sorrendben járja be a mezőket, az Alt+betű a megfelelő mezőre ugrik, az Enter továbblép, és az ellenőrzés a megfelelő helyen szól. Ebben a leckében megtanulod a fókusz fogalmát, a TabIndex és a TabStop szerepét, a mnemonikus betűket, és a mezők ellenőrzését (Validating, ErrorProvider).

  • kb. 100 perc
  • közepes szint
  • fókusz és navigáció
Diáklány C# programot készít laptopon

A lecke végére

  • érted a fókusz fogalmát, és használod a Focus(), ActiveControl, Enter és Leave lehetőségeit;
  • beállítod a tabulátorsorrendet (TabIndex, TabStop, View → Tab Order), és érted a beágyazott tárolók szabályát;
  • mnemonikus betűket adsz a mezőkhöz (& a címkén);
  • megvalósítod az Enter és Shift+Enter navigációt;
  • a Validating, ValidateChildren, AutoValidate és CausesValidation segítségével érvényesítesz, és a hibát ErrorProvider-rel jelzed;
  • billentyűzettel teszteled az űrlapot, és felismered a gyakori fókuszhibákat.

Mielőtt elkezded

Ehhez a leckéhez a 5.4 lecke (billentyűk, ProcessCmdKey, mnemonikus billentyűk), az 5.5 lecke (változás-események), az 1.11 lecke (ErrorProvider) és a 6.4 lecke (TableLayoutPanel) szükséges. Új projektben próbáld ki a példákat.

6.5 TabControl →

A fókusz: ki kapja a billentyűket?

A fókusz (focus) azt jelzi, melyik vezérlő kapja a billentyűzet bevitelét: a fókuszban lévő szövegmezőbe kerülnek a betűk, a fókuszban lévő gombot nyomja meg a Szóköz. Egyszerre mindig csak egy vezérlőnél lehet fókusz. A fókuszban lévő vezérlőt a Windows látható jelzéssel látja el (szövegmezőnél villogó kurzor, gombnál szaggatott keret).

TagJelentés
vezérlő.Focus()A fókuszt a vezérlőre teszi (igazat ad, ha sikerült).
vezérlő.Select()Hasonló: kiválasztja a vezérlőt, a fókuszt nem mindig kéri el.
vezérlő.FocusedIgaz, ha a vezérlő most a fókuszban van.
vezérlő.CanFocusIgaz, ha a vezérlő fókuszt kaphat (látható, engedélyezett).
this.ActiveControlAz ablak aktív (fókuszban lévő) vezérlője; beállítható az induló fókuszhoz.
Enter, Leave eseményA vezérlő megkapja, illetve elveszíti a fókuszt (5.8 lecke).
GotFocus, LostFocusAlacsonyabb szintű párja az Enter és Leave eseménynek. Az Enter és Leave általában elég.
Validating, ValidatedA fókusz elhagyásakor, az érvényesítéshez (lásd lent).
Form1.csC#
private void Form1_Shown(object sender, EventArgs e)
{
txtNev.Focus(); // az induló fókusz (az ablak megjelenése után)
// vagy: this.ActiveControl = txtNev;
}

private void txtNev_Enter(object sender, EventArgs e)
{
lblSugo.Text = "Add meg a teljes neved."; // súgó a fókuszba kerülő mezőhöz
txtNev.SelectAll(); // a tartalom kijelölve: gépeléssel felülírható
}

private void txtNev_Leave(object sender, EventArgs e)
{
lblSugo.Text = "";
}

private void btnMentes_Click(object sender, EventArgs e)
{
// a gomb megnyomása után a fókusz a gombon marad: visszaadjuk a szerkesztőnek
rtbSzoveg.Focus();
}
Load vagy Shown?A Load eseményben az ablak még nem látható, ezért a Focus() hívás nem mindig sikeres. Az induló fókuszt a Shown eseményben, vagy a this.ActiveControl = vezérlő; értékadással a Load-ban állítsd be.
Mi nem kaphat fókuszt?A letiltott (Enabled = false), rejtett (Visible = false) vezérlő, és a Label, Panel, PictureBox alapból nem (ezek nem választhatók Tab billentyűvel).
Gomb a fókuszbanEgy kattintás után az ablak gombja a fókuszban marad, ezért a következő Szóköz újra megnyomja. A szerkesztőt kezelő gomboknál (félkövér, mentés) érdemes a fókuszt a szerkesztőre visszaadni. A ToolStrip gombjai nem veszik át a fókuszt.
SelectAll az Enter eseménybenA kattintással érkező fókusznál a SelectAll néha nem érvényesül (az egérkattintás törli a kijelölést). A billentyűzettel (Tab) érkezéskor jól működik.

Jósolj!

A Form1_Load eseményben a txtNev.Focus(); sort írod, de indításkor a fókusz mégsem a névmezőn van. Miért?

Jóslat ellenőrzése

A Load az ablak megjelenése előtt fut, és a vezérlő ekkor még nem tud fókuszt kapni (a Focus() hamis értéket ad). A javítás: this.ActiveControl = txtNev; a Load-ban (ez az induló fókuszt jelöli ki), vagy a Focus() hívása a Shown eseményben (5.6 lecke).

TabIndex és TabStop: a tabulátorsorrend

A Tab billentyű a fókuszt a következő vezérlőre viszi, a Shift+Tab az előzőre. A sorrendet a TabIndex tulajdonság adja (kisebb szám előbb), a TabStop pedig megmondja, megáll-e rajta a fókusz (igaz) vagy átugorja (hamis).

TabIndex

Egész szám, amely a tárolón belüli sorrendet adja. A vezérlők a tervezőben a létrehozásuk sorrendjében kapnak indexet, ezért ha a felületet átrendezed, a sorrend könnyen zavaros lesz. Beállítás: View → Tab Order a Visual Studio menüjében: a vezérlők fölött számok jelennek meg, sorban rájuk kattintva újraszámozod őket.

TabStop

Ha hamis, a Tab átugorja a vezérlőt (egérrel továbbra is elérhető). Alapból minden fókuszt fogadó vezérlőn igaz. Hamisra állítsd azokat, amelyek nem tartoznak az űrlap kitöltéséhez (súgó gomb, eszköz gomb).

Form1.csC#
// a sorrend kódból is beállítható
txtNev.TabIndex = 0;
txtEmail.TabIndex = 1;
dtpSzuletes.TabIndex = 2;
btnKuld.TabIndex = 3;

// ne álljon meg a súgó gombon és a kép felett
btnSugo.TabStop = false;

// a tabulátorsorrend automatikus beállítása a vezérlők helye szerint:
// fentről lefelé, soron belül balról jobbra
private void RendezTab(Control szulo)
{
List<Control> gyermekek = new List<Control>();

foreach (Control c in szulo.Controls)
{
gyermekek.Add(c);
}

gyermekek.Sort((a, b) =>
{
int kulonbseg = a.Top.CompareTo(b.Top);
return kulonbseg != 0 ? kulonbseg : a.Left.CompareTo(b.Left);
});

for (int i = 0; i < gyermekek.Count; i++)
{
gyermekek[i].TabIndex = i;
RendezTab(gyermekek[i]); // a beágyazott tároló tartalma
}
}
Hierarchikus sorrendA TabIndex a tárolón belüli sorrend. A Tab egy GroupBox belsejét a GroupBox saját TabIndex pozíciójában járja be: előbb a GroupBox összes gyermekét, aztán lép a következő, a GroupBox utáni vezérlőre. A Tab Order nézetben ezért 1.0, 1.1 alakú számok jelennek meg.
A választógombokEgy választógomb-csoportban a Tab csak a bejelölt gombra áll meg, a csoporton belül a nyilak váltanak. Ha egyik sincs bejelölve, az első gombon áll meg.
Comparison lambdaA Sort((a, b) => ...) a lista elemeit a megadott összehasonlítás szerint rendezi: itt előbb a Top, azonos magasságnál a Left érték dönt (5.1 lecke, lambda).
Nem minden vezérlőA Label és a Panel alapból TabStop = false, de a TabIndex a címke mnemonikus billentyűjénél számít (lásd lent).

Jósolj!

Az ablakon van egy txtNev (TabIndex 0), egy GroupBox (TabIndex 1, benne két választógomb 0 és 1 indexszel) és egy btnKuld (TabIndex 2). Milyen sorrendben járja be a fókusz a vezérlőket a Tab billentyűvel?

Jóslat ellenőrzése

txtNev → (a választógomb-csoport bejelölt gombja) → btnKuld. A GroupBox maga nem kap fókuszt, de a belső vezérlői a GroupBox sorrendi helyén következnek. A csoport két gombjából a Tab csak a bejelöltre áll meg (vagy az elsőre, ha egyik sincs bejelölve), a másikat a nyilakkal éred el.

Mnemonikus betűk: Alt + betű

Az 5.4 leckében láttad az & jelet: a címke szövegében &Név: aláhúzza az N betűt, és az Alt+N billentyű a vezérlőre ugrik. A Label nem kaphat fókuszt, ezért a mnemonikus billentyű a tabulátorsorrendben a címke után következő vezérlőre teszi a fókuszt. Ezért kell, hogy a címke TabIndex értéke közvetlenül a mezőé előtt álljon:

Form1.cs (vagy a tervezőben)C#
lblNev.Text = "&Név:";      lblNev.TabIndex = 0;     txtNev.TabIndex = 1;
lblEmail.Text = "&E-mail:"; lblEmail.TabIndex = 2; txtEmail.TabIndex = 3;
btnKuld.Text = "&Küldés"; // a gomb Alt+K billentyűre aktiválódik

// két azonos betű ne legyen egy ablakon: ilyenkor az Alt+betű váltogat közöttük
A címke és a mező sorrendjeA címke TabIndex értéke legyen eggyel kisebb, mint a hozzá tartozó mezőé. Ha a sorrend más, az Alt+betű rossz mezőre ugrik.
UseMnemonicA Label.UseMnemonic (alapból igaz) bekapcsolja az & értelmezését. Ha magát az & jelet kell kiírni, duplázd: &&.
Gomb és menüA gombok és a menüpontok Text szövegében is használható az &: közvetlenül aktiválják a vezérlőt.
BetűválasztásMagyar felületen az első betűt szokás használni; ütközésnél a másik betűt (Mente&s). Az aláhúzás csak Alt megnyomása után látszik (a Windows beállítástól függően).

Jósolj!

A lblNev (&Név:) címke TabIndex értéke 0, de a mellette lévő txtNev TabIndex értéke 5, és közéjük kerül egy másik mező (TabIndex 1–4). Az Alt+N melyik vezérlőre ugrik?

Jóslat ellenőrzése

A sorrendben következő fókuszt kaphatő vezérlőre (TabIndex 1) ugrik, vagyis nem a névmezőre. Ezért kell a címke és a mező TabIndex értékének közvetlenül egymás után állnia.

Enter-navigáció

Az űrlapkitöltők gyakran az Enter billentyűt használják továbblépésre (a Tab helyett). Az 5.4 leckében a KeyDown eseménnyel oldottuk meg mezőnként. Központi, az egész ablakra vonatkozó megoldás a ProcessCmdKey-ben. Az egysoros szövegmezőnél lép tovább, a többsorosnál (Multiline) új sort ad, a gombnál megnyomja a gombot:

Form1.csC#
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Enter || keyData == (Keys.Shift | Keys.Enter))
{
TextBox mezo = this.ActiveControl as TextBox;

if (mezo != null && !mezo.Multiline)
{
bool elore = keyData == Keys.Enter; // Shift+Enter: visszafelé
this.SelectNextControl(mezo, elore, true, true, true);
return true;
}
}

return base.ProcessCmdKey(ref msg, keyData);
}
SelectNextControlA következő (előző) vezérlőre lép a tabulátorsorrend szerint. Paraméterek: honnan, előre, csak TabStop-ú vezérlők, beágyazva (tárolókba is), körbe (az utolsó után az első).
Csak az egysoros TextBoxA többsoros mezőben az Enter sortörést jelent, a gombon a megnyomást. Ezeket a kivételeket a Multiline és a típusvizsgálat adja.
Ha van AcceptButtonHa az ablaknak van alapértelmezett gombja (6.7 lecke), az Enter azt nyomja meg. A két viselkedés ütközhet: döntsd el, melyiket használod.
AkadálymentességAz Enter navigáció megváltoztatja a megszokott billentyűket, ezért a Tab továbbra is működjön, és a viselkedés legyen kiszámítható (6.9 lecke).

Jósolj!

A ProcessCmdKey az Enter billentyűt minden vezérlőnél továbblépésre használja (a Multiline ellenőrzés nélkül). Mi történik az Enter lenyomásakor egy többsoros megjegyzés mezőben?

Jóslat ellenőrzése

A fókusz továbblép a következő mezőre, és a felhasználó nem tud új sort írni a megjegyzésbe. Ezért a többsoros mezőket ki kell hagyni (!mezo.Multiline). A ProcessCmdKey az összes billentyűt elkapja, ezért a kivételeket mindig meg kell határozni.

Érvényesítés: Validating, ValidateChildren, ErrorProvider

A Validating esemény a fókusz elhagyásakor fut (5.2 lecke): itt ellenőrizheted a mezőt, és az e.Cancel = true a fókuszt a mezőn tartja. Három tulajdonság szabályozza a működést:

Tulajdonság / metódusJelentése
CausesValidationA cél vezérlőn: ha hamis (például a Mégse gombon), a fókusz oda ellenőrzés nélkül mehet. Alapból igaz.
AutoValidate (Form)A Form érvényesítési módja: EnablePreventFocusChange (alap: a hibás mezőből nem lehet kilépni), EnableAllowFocusChange (kilépni lehet, de a hiba jelzett), Disable (nincs automatikus ellenőrzés).
ValidateChildren()Az összes gyermek Validating eseményét lefuttatja; hamis értéket ad, ha valamelyik hibás. A Küldés gomb ezzel ellenőrzi az egész űrlapot.
Form1.cs (epHiba: ErrorProvider)C#
private void Form1_Load(object sender, EventArgs e)
{
this.AutoValidate = AutoValidate.EnableAllowFocusChange; // a felhasználó nincs mezőhöz láncolva
btnMegse.CausesValidation = false; // a Mégse mindig megnyomható
}

private void txtNev_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
if (txtNev.Text.Trim().Length == 0)
{
epHiba.SetError(txtNev, "A név kötelező.");
e.Cancel = true;
}
else
{
epHiba.SetError(txtNev, ""); // a hibajelzés törlése
}
}

private void btnKuld_Click(object sender, EventArgs e)
{
if (!this.ValidateChildren())
{
lblUzenet.Text = "Javítsd a jelölt mezőket!";
return;
}

lblUzenet.Text = "Az adatok rendben vannak.";
}
EnablePreventFocusChangeAz alapértelmezett mód: ha a Validating hibát jelez, a fókusz nem hagyhatja el a mezőt. Ez a fókuszcsapda zavaró lehet (a felhasználó sem a Mégse gombra, sem az ablak bezárására nem tud átlépni, ha a CausesValidation igaz).
EnableAllowFocusChangeA felhasználó szabadon mozoghat, a hibás mező továbbra is jelölt, és a ValidateChildren() a mentéskor újra ellenőriz. Ez a barátságosabb mód.
ErrorProviderPiros felkiáltójelet tesz a mező mellé, az egérrel a hiba szövege látszik (1.11 lecke). A hiba törlése: SetError(mező, "").
Mindig ellenőrizz mentéskor isA mezőt a felhasználó el sem látogathatta. A ValidateChildren() az összes mezőt lefuttatja, tehát a nem érintettek hibája is kiderül.

Jósolj!

Az AutoValidate értéke az alapérték, és a névmező hibás (üres), a Validating kezelő e.Cancel = true értéket ad. A felhasználó az ablak jobb felső sarkában lévő X gombra kattint. Bezáródik az ablak?

Jóslat ellenőrzése

Az alapértelmezett EnablePreventFocusChange módban a bezárás is megakadhat (a bezárás a fókuszváltást is érvényesíti): a hibás mező „fogva tart”. Ezért állítsd az AutoValidate értékét EnableAllowFocusChange értékre, és a bezáró/Mégse funkciókat tedd CausesValidation = false gombra. A viselkedést a Windows és a keretrendszer verziója is befolyásolhatja, ezért próbáld ki a saját programodban.

📷 Képernyőkép helye

Mit tegyél ide: egy űrlap, amelyen a hibás névmező mellett piros felkiáltójel (ErrorProvider) látszik, és alul a „Javítsd a jelölt mezőket!” üzenet.Javasolt fájlnév: winforms-6-6-hiba.webp · Alt szöveg: Érvényesítés ErrorProvider jelzé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. A Tab össze-vissza ugrál

A Tab billentyű a mezőket nem a megjelenés sorrendjében járja be.

Ok és javítás

A TabIndex értékek a létrehozás sorrendjét követik, nem a helyet. Használd a View → Tab Order nézetet, és kattints sorban a vezérlőkre, vagy számold újra kódból a helyük szerint (RendezTab).

2. Az Alt+betű rossz mezőre ugrik

Az Alt+N nem a névmezőre, hanem a következő vezérlőre visz.

Ok és javítás

A címke TabIndex értéke nem közvetlenül a mező előtt áll. A mnemonikus billentyű a tabulátorsorrendben a címke után következő fókuszt kapható vezérlőre ugrik. Rendezd át a sorrendet.

3. A Focus() nem működik indításkor

private void Form1_Load(object sender, EventArgs e)
{
txtNev.Focus(); // az ablak még nem látható
}
Hiba és javítás

Használd a this.ActiveControl = txtNev; értékadást, vagy hívd a Focus() metódust a Shown eseményben.

4. Nem lehet kilépni az ablakból

A Mégse gomb és az X nem működik, amíg a mező hibás.

Ok és javítás

A Validating esemény e.Cancel = true értéke a fókuszt a mezőre kényszeríti, és a CausesValidation igaz a Mégse gombon. Állítsd a Mégse gomb CausesValidation értékét hamisra, és az AutoValidate értékét EnableAllowFocusChange értékre.

5. A kép és a címke nem fókuszálható

pbKep.TabStop = true;   // de a PictureBox nem fogad billentyűket
Hiba és javítás

A PictureBox, a Label és a Panel alapból nem kap fókuszt (CanFocus hamis, a TabStop beállítása nem változtat). Ha a kép interaktív (kattintható), használj Button vezérlőt képpel, vagy saját vezérlőt, amely fókuszt fogad.

6. A gomb megnyomása után a szerkesztés nem folytatható

A Félkövér gomb megnyomása után a gépelés nem a szerkesztőbe kerül.

Ok és javítás

A fókusz a gombon maradt. Adj a végén rtbSzoveg.Focus(); hívást a gomb kezelőjébe. A ToolStrip gombjai nem veszik át a fókuszt, így ott ez a probléma nem jelentkezik.

Mini projekt: billentyűzettel kezelhető jelentkezési űrlap

Készíts jelentkezési űrlapot, amelyet egér nélkül is gyorsan ki lehet tölteni:

  1. Az ablak: egy TableLayoutPanel (tlp, 2 oszlop: AutoSize, Percent 100) a következő sorokkal: Név (txtNev), E-mail (txtEmail), Telefon (txtTelefon), Szint (cmbSzint: Kezdő, Haladó), Megjegyzés (txtMegjegyzes, többsoros), egy GroupBox Mikor érsz rá? három jelölőnégyzettel, és alul egy FlowLayoutPanel a Küldés (btnKuld) és Mégse (btnMegse) gombbal.
  2. Tabulátorsorrend: logikus (fentről lefelé); a címkék közvetlenül a mezőik előtt; a Mégse gomb TabStop értéke hamis.
  3. Mnemonikus betűk: &Név, &E-mail, &Telefon, &Szint, &Megjegyzés, a &Küldés gombon is, mind különböző betűvel.
  4. Induló fókusz: a névmezőn (ActiveControl).
  5. Enter-navigáció: az egysoros mezőkben az Enter előre, a Shift+Enter hátra lép, a megjegyzés mezőben sortörés (ProcessCmdKey).
  6. Súgó: minden mező Enter eseménye (közös kezelő, rekurzív bejárással, 5.8 lecke) kiírja az lblSugo címkébe a mező nevét és a megadott súgószöveget (a Tag tulajdonságban).
  7. Érvényesítés: a név kötelező, az e-mail tartalmazza az @ jelet, a telefon (ha van) csak számjegyet, + és szóköz karaktert. Hiba esetén ErrorProvider. Az AutoValidate legyen EnableAllowFocusChange, a Mégse gombon CausesValidation = false.
  8. A Küldés gomb ValidateChildren() hívásával ellenőriz, és hiba esetén az első hibás mezőre teszi a fókuszt.
  9. Egy Tab-sorrend gomb (btnSorrend) listába írja a vezérlőket a TabIndex szerint, behúzással a hierarchia szerint.
  10. Teszteld: csak billentyűzettel kitöltés, rossz adatok, az Alt+betűk, a Shift+Tab, a Mégse gomb hibás mező mellett.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A súgó szövege a vezérlők Tag tulajdonságába kerül a Load-ban, a rekurzív feliratkozás a TextBox, ComboBox és CheckBox vezérlőkre köt egy közös Enter kezelőt. Az érvényesítést külön metódusok végzik, hogy a Validating kezelők rövidek maradjanak.

Form1.cs (using System.Collections.Generic; using System.ComponentModel;)C#
private void Form1_Load(object sender, EventArgs e)
{
this.AutoValidate = AutoValidate.EnableAllowFocusChange;
btnMegse.CausesValidation = false;
btnMegse.TabStop = false;

cmbSzint.Items.AddRange(new object[] { "Kezdő", "Haladó" });
cmbSzint.SelectedIndex = 0;

txtNev.Tag = "Add meg a teljes neved.";
txtEmail.Tag = "Az e-mail címnek tartalmaznia kell a @ jelet.";
txtTelefon.Tag = "Nem kötelező. Számjegyek, + jel és szóköz használható.";
cmbSzint.Tag = "Válaszd ki a tudásszinted.";
txtMegjegyzes.Tag = "Szabad szöveg; az Enter új sort kezd.";

SugoKot(this);
this.ActiveControl = txtNev;
}

private void SugoKot(Control szulo)
{
foreach (Control c in szulo.Controls)
{
if (c is TextBox || c is ComboBox || c is CheckBox)
{
c.Enter += Mezo_Enter;
}

SugoKot(c);
}
}

private void Mezo_Enter(object sender, EventArgs e)
{
Control c = (Control)sender;
lblSugo.Text = c.Tag == null ? "" : c.Tag.ToString();
}

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Enter || keyData == (Keys.Shift | Keys.Enter))
{
TextBox mezo = this.ActiveControl as TextBox;

if (mezo != null && !mezo.Multiline)
{
this.SelectNextControl(mezo, keyData == Keys.Enter, true, true, true);
return true;
}
}

return base.ProcessCmdKey(ref msg, keyData);
}

private void txtNev_Validating(object sender, CancelEventArgs e)
{
Hiba(txtNev, txtNev.Text.Trim().Length == 0 ? "A név kötelező." : "", e);
}

private void txtEmail_Validating(object sender, CancelEventArgs e)
{
Hiba(txtEmail, txtEmail.Text.Contains("@") ? "" : "Az e-mail címnek tartalmaznia kell a @ jelet.", e);
}

private void txtTelefon_Validating(object sender, CancelEventArgs e)
{
string uzenet = "";

foreach (char c in txtTelefon.Text)
{
if (!char.IsDigit(c) && c != '+' && c != ' ')
{
uzenet = "A telefonszám csak számjegyet, + jelet és szóközt tartalmazhat.";
break;
}
}

Hiba(txtTelefon, uzenet, e);
}

private void Hiba(Control mezo, string uzenet, CancelEventArgs e)
{
epHiba.SetError(mezo, uzenet);

if (uzenet.Length > 0)
{
e.Cancel = true;
}
}

private void btnKuld_Click(object sender, EventArgs e)
{
if (!this.ValidateChildren())
{
lblUzenet.Text = "Javítsd a jelölt mezőket!";

if (epHiba.GetError(txtNev).Length > 0) { txtNev.Focus(); }
else if (epHiba.GetError(txtEmail).Length > 0) { txtEmail.Focus(); }
else { txtTelefon.Focus(); }

return;
}

lblUzenet.Text = "Köszönjük a jelentkezést, " + txtNev.Text.Trim() + "!";
}

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

private void btnSorrend_Click(object sender, EventArgs e)
{
lstSorrend.Items.Clear();
Sorrend(this, 0);
}

private void Sorrend(Control szulo, int szint)
{
List<Control> gyermekek = new List<Control>();

foreach (Control c in szulo.Controls)
{
gyermekek.Add(c);
}

gyermekek.Sort((a, b) => a.TabIndex.CompareTo(b.TabIndex));

foreach (Control c in gyermekek)
{
lstSorrend.Items.Add(new string(' ', szint * 2) + c.TabIndex + ": " + c.Name + (c.TabStop ? "" : " (TabStop nélkül)"));
Sorrend(c, szint + 1);
}
}
📷 Képernyőkép helye

Mit tegyél ide: a kész űrlap: a kitöltött mezők, a súgósor, a hibás e-mail melletti piros jel és a tabulátorsorrend-lista.Javasolt fájlnév: winforms-6-6-kesz.webp · Alt szöveg: A billentyűzettel kezelhető jelentkezési űrlap

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. Automatikus tabulátorsorrend

Írj Tab-sorrend rendezése gombot, amely a vezérlők helye szerint (fentről lefelé, azonos magasságnál balról jobbra) újraszámolja a TabIndex értékeket, a beágyazott tárolókban is. Mikor hasznos, és mi a korlátja?

Egy lehetséges megoldás

Hasznos egy átrendezett, kusza felületnél. A korlátja, hogy csak a helyet nézi: a címke–mező mnemonikus párosításhoz a címke mindig közvetlenül a mezője előtt kell, hogy álljon (ez a hely szerint általában teljesül, ha a címke bal oldalt van). A kód a leckebeli RendezTab metódus.

2. Kiemelt mező és a fókusz visszaadása

A fókuszban lévő szövegmező háttere legyen világossárga, ha elhagyja a fókusz, álljon vissza. A Töröl gomb a mezőt kiüríti, és a fókuszt visszaadja a mezőnek, amelyben volt. Hogyan tudod, melyik mezőben volt a fókusz, amikor a gombra kattintottak?

Egy lehetséges megoldás

Egy mezőben jegyzed az utolsó fókuszt kapott szövegmezőt az Enter eseményben, mert a gombra kattintás után az ActiveControl már a gomb.

Form1.csC#
private TextBox utolso = null;

private void Mezo_Enter(object sender, EventArgs e)
{
utolso = (TextBox)sender;
utolso.BackColor = Color.LightYellow;
}

private void Mezo_Leave(object sender, EventArgs e)
{
((TextBox)sender).BackColor = SystemColors.Window;
}

private void btnTorol_Click(object sender, EventArgs e)
{
if (utolso != null)
{
utolso.Clear();
utolso.Focus();
}
}

3. Érvényesítés Mégse gombbal

Készíts kétmezős űrlapot (név, kor), amelynek Mentés gombja ValidateChildren() hívással ellenőriz, a Mégse gomb pedig mindig bezárja az ablakot hibás mező mellett is. A kor 1 és 120 közötti szám. Melyik beállítások kellenek?

Egy lehetséges megoldás

A Mégse gombon CausesValidation = false, a formon AutoValidate = EnableAllowFocusChange. A kor ellenőrzése int.TryParse és tartományvizsgálat.

Form1.csC#
private void txtKor_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
int kor;

if (!int.TryParse(txtKor.Text, out kor) || kor < 1 || kor > 120)
{
epHiba.SetError(txtKor, "A kor 1 és 120 közötti szám legyen.");
e.Cancel = true;
}
else
{
epHiba.SetError(txtKor, "");
}
}

private void btnMent_Click(object sender, EventArgs e)
{
if (this.ValidateChildren())
{
MessageBox.Show("Mentve.");
}
}

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. TabStop

Mit tesz a TabStop = false beállítás, és mikor használnád?

Megoldás megnyitása

A Tab billentyű átugorja a vezérlőt (egérrel továbbra is használható). Olyan vezérlőkön használom, amelyek nem tartoznak az űrlap kitöltéséhez (súgó gomb, eszköz gomb, Mégse).

2. Induló fókusz

Állítsd be, hogy az ablak a txtKereso mezőn induljon, két módon.

Megoldás megnyitása

Az ActiveControl értékadásával a Load-ban, vagy a Focus() hívásával a Shown eseményben.

Form1.csC#
private void Form1_Load(object sender, EventArgs e)
{
this.ActiveControl = txtKereso; // 1. mód
}

private void Form1_Shown(object sender, EventArgs e)
{
txtKereso.Focus(); // 2. mód
}

Közepes szint

3. Címke és mnemonikus mező

A lblEmail (&E-mail:) címke után két másik mező áll, mielőtt az txtEmail. Hogyan javítod?

Megoldás megnyitása

A címke után közvetlenül az txtEmail legyen a tabulátorsorrendben: a Tab Order nézetben állítsd át, vagy kódból: lblEmail.TabIndex = txtEmail.TabIndex - 1; (és a közbeeső mezők indexét igazítsd).

4. Az AutoValidate módjai

Hasonlítsd össze a három AutoValidate módot egy hibás mezőnél: mi történik, ha a felhasználó (a) másik mezőre kattint, (b) a bezáró gombra kattint?

Megoldás megnyitása
Mód(a) másik mező(b) bezárás
EnablePreventFocusChangenem lehet kilépnimegakadhat
EnableAllowFocusChangekilép, a hiba jelzettbezárul
Disablenincs ellenőrzésbezárul

Kihívás szint

5. Fókusznapló

Készíts hibakereső listát, amely minden vezérlő fókuszváltását naplózza (12:03:11 – txtNev megkapta a fókuszt), a beágyazottakéit is. Mire jó?

Megoldás megnyitása

A napló megmutatja a tényleges fókuszsorrendet, és hogy melyik vezérlő veszi el váratlanul a fókuszt. A rekurzív feliratkozás az Enter eseményre köt egy közös kezelőt.

Form1.csC#
private void NaploKot(Control szulo)
{
foreach (Control c in szulo.Controls)
{
c.Enter += (s, e) =>
lstNaplo.Items.Insert(0, DateTime.Now.ToString("HH:mm:ss") + " – " + ((Control)s).Name);

NaploKot(c);
}
}

private void Form1_Load(object sender, EventArgs e)
{
NaploKot(this);
}

6. Nyilak a mezők között

A Le nyíl az egysoros szövegmezőkben a következő mezőre, a Fel nyíl az előzőre lép (mint egy táblázatban). Hol kezeled, és mire vigyázol?

Megoldás megnyitása

A ProcessCmdKey-ben, csak egysoros TextBox-nál (a többsorosnál a nyilak a szövegben mozgatják a kurzort). Más vezérlőknél (ComboBox, NumericUpDown) a nyilaknak saját funkciójuk van, ezeket ne nyeld el.

Form1.csC#
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
TextBox mezo = this.ActiveControl as TextBox;

if (mezo != null && !mezo.Multiline)
{
if (keyData == Keys.Down)
{
this.SelectNextControl(mezo, true, true, true, true);
return true;
}

if (keyData == Keys.Up)
{
this.SelectNextControl(mezo, false, true, true, true);
return true;
}
}

return base.ProcessCmdKey(ref msg, keyData);
}

Gyors önellenőrzés

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

1. Mi a fókusz?

Az a vezérlő, amely a billentyűzet bevitelét kapja. Egyszerre egy vezérlőnél van.

2. Mit ad meg a TabIndex?

A tárolón belüli tabulátorsorrendet: a kisebb szám előbb következik. A beágyazott tárolók hierarchikusan járódnak be.

3. Hogyan állítod át a sorrendet a tervezőben?

View → Tab Order, és sorban a vezérlőkre kattintva.

4. Miért kell a címke TabIndex értékének közvetlenül a mező előtt állnia?

Mert a mnemonikus billentyű a tabulátorsorrendben a címke után következő fókuszt kapható vezérlőre ugrik.

5. Hol állítod be az induló fókuszt?

this.ActiveControl = mezo; a Load-ban, vagy Focus() a Shown-ban. A Load-beli Focus() nem megbízható.

6. Mire való a ValidateChildren()?

Az összes gyermek Validating eseményét lefuttatja, és hamisat ad, ha valamelyik hibás: a mentés előtti teljes ellenőrzéshez.

7. Miért kell a Mégse gombon CausesValidation = false?

Hogy hibás mező mellett is megnyomható legyen az ellenőrzés nélkül, és a felhasználó ne legyen a mezőhöz láncolva.

Mit tudsz már?

  • Érted a fókuszt, és beállítod az induló fókuszt és a tabulátorsorrendet.
  • Mnemonikus betűkkel és Enter navigációval gyorsítod a kitöltést.
  • A Validating, ValidateChildren, AutoValidate és ErrorProvider segítségével érvényesítesz.
  • Az űrlapot billentyűzettel teszteled.
  • Felismered a gyakori fókuszhibákat (fókuszcsapda, rossz sorrend, Load-beli Focus).

Következő lecke: AcceptButton, CancelButton és alapértelmezett gombok

Az Enter és az Esc gomb: hogyan rendelhetsz az ablakhoz alapértelmezett és megszakító gombot.