Grafikus C# – Windows Forms tanfolyam › 7.2 lecke

Windows Forms · 7.2 lecke · helyi menü

ContextMenuStrip és helyi menü

A jobb egérgombbal előhívott helyi menü (context menu) a kattintott dologra vonatkozó parancsokat kínálja: egy fájlon Átnevezés, egy szövegen Másolás, egy feladaton Kész. Jól megírva gyorsítja a munkát, rosszul megírva (rossz elemre hat, mindig ugyanazt mutatja) félrevezet. Ebben a leckében megtanulod a ContextMenuStrip használatát: hozzárendelést, a menü előzetes igazítását (Opening), a kattintás helyének azonosítását (SourceControl, IndexFromPoint), a dinamikus elemeket, és egy közös menüt több szövegmezőhöz.

  • kb. 100 perc
  • közepes szint
  • helyi menü
Diáklány C# programot készít laptopon

A lecke végére

  • a ContextMenuStrip vezérlőt hozzárendeled vezérlőkhöz, és tudod, mikor jelenik meg;
  • az Opening eseményben igazítod a menüt a helyzethez, vagy megakadályozod a megnyitást (e.Cancel);
  • a SourceControl segítségével egy menüt több vezérlőhöz használsz;
  • a lista elemét a jobb kattintás helyén jelölöd ki (IndexFromPoint);
  • dinamikus menüt építesz (állapottól függő feliratok, kapcsolók, almenü);
  • tudod, hogy a helyi menü sosem lehet az egyetlen út egy funkcióhoz.

Mielőtt elkezded

Ehhez a leckéhez a 7.1 lecke (menüelemek, ToolStripMenuItem), az 5.3 lecke (egéresemények, MouseDown, SourceControl) és az 5.8 lecke (közös kezelő, Tag) szükséges.

7.1 MenuStrip és ToolStrip →

Hozzárendelés és megjelenés

A ContextMenuStrip egy komponens (a tervező alsó sávjában jelenik meg), amelynek elemei ugyanolyan ToolStripItem elemek, mint a MenuStripéi (7.1 lecke). Egy vezérlőhöz a ContextMenuStrip tulajdonsággal rendeled hozzá. A menü ezután megjelenik a jobb egérgomb felengedésekor, illetve a billentyűzeten a Menu billentyűre vagy a Shift+F10 kombinációra, külön kód nélkül:

Form1.csC#
// a tervezőben: a vezérlő Properties → ContextMenuStrip → a menü kiválasztása
// kódból:
lstFeladatok.ContextMenuStrip = cmsFeladat;
txtUj.ContextMenuStrip = cmsSzoveg;

// kódból megjeleníthető tetszőleges helyen is (például egy gombnyomásra):
cmsFeladat.Show(btnTobb, new Point(0, btnTobb.Height)); // a gomb alatt
cmsFeladat.Show(Cursor.Position); // az egérmutatónál (képernyő-koordináta)
Alapértelmezett menüA TextBox és a RichTextBox saját, beépített helyi menüt kínál (Visszavonás, Kivágás, Másolás…). Ha hozzárendelsz egy ContextMenuStripet, az helyettesíti a beépítettet.
Gyermek vezérlőkHa egy panelnek van helyi menüje, a benne lévő vezérlők, amelyeknek nincs sajátjuk, nem öröklik: minden vezérlőhöz külön kell rendelni (vagy közös menüt több vezérlőhöz).
Mikor jelenik megA jobb gomb felengedésekor, ezért a MouseDown még a menü megnyitása előtt lefut (az elem kijelölésére ezt használjuk).
Kódból megjelenítésA Show(vezérlő, pont) a vezérlőhöz viszonyított, a Show(pont) a képernyő-koordinátában megadott helyen jeleníti meg. Ilyenkor a SourceControl a megadott vezérlő.

Jósolj!

Egy Panel ContextMenuStrip értéke a cmsPanel, a panelen egy Label van (saját helyi menü nélkül). Jobb gombbal a címkére kattintasz. Megjelenik-e a cmsPanel?

Jóslat ellenőrzése

A helyi menü nem öröklődik: a ContextMenuStrip mindig az adott vezérlő saját tulajdonsága, ezért a címkén jobb kattintásra a cmsPanel nem jelenik meg (csak ott, ahol a panel maga az egérpont alatt van). Ha a címkén is szeretnéd, a menüt hozzá is rendeld (egy menü több vezérlőhöz is hozzárendelhető). A viselkedést a saját programodban érdemes kipróbálni.

Opening és SourceControl

Az Opening esemény közvetlenül a menü megjelenítése előtt fut. Itt igazíthatod a menüt a helyzethez, vagy megakadályozhatod a megnyitását az e.Cancel = true beállítással. A SourceControl tulajdonság megmondja, melyik vezérlő hívta elő a menüt, ezért egy menü több vezérlőhöz is használható:

Form1.cs (cmsSzoveg: mind a három szövegmezőhöz hozzárendelve)C#
private void cmsSzoveg_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
TextBoxBase mezo = cmsSzoveg.SourceControl as TextBoxBase;

if (mezo == null)
{
e.Cancel = true; // nem szövegmezőből hívták: nincs mit mutatni
return;
}

bool vanKijeloles = mezo.SelectionLength > 0;

miVisszavon.Enabled = mezo.CanUndo;
miKivag.Enabled = vanKijeloles && !mezo.ReadOnly;
miMasol.Enabled = vanKijeloles;
miBeillesztes.Enabled = Clipboard.ContainsText() && !mezo.ReadOnly;
miMindent.Enabled = mezo.TextLength > 0;
}

// ugyanaz a kezelő mind az öt menüponton: a Tag a művelet neve
private void SzovegMuvelet_Click(object sender, EventArgs e)
{
ToolStripMenuItem pont = (ToolStripMenuItem)sender;
ContextMenuStrip menu = (ContextMenuStrip)pont.Owner;
TextBoxBase mezo = menu.SourceControl as TextBoxBase;

if (mezo == null)
{
return;
}

switch (pont.Tag.ToString())
{
case "visszavon": mezo.Undo(); break;
case "kivag": mezo.Cut(); break;
case "masol": mezo.Copy(); break;
case "beilleszt": mezo.Paste(); break;
case "mindent": mezo.SelectAll(); break;
}
}
TextBoxBaseA TextBox és a RichTextBox közös őse: ugyanaz a kód mindkettőn működik (Cut, Copy, Paste, Undo, SelectAll, ReadOnly).
pont.OwnerA menüpont tulajdonosa az a ToolStrip, amelyben áll; a ContextMenuStrip-re átalakítva elérhető a SourceControl. (Almenü pontjánál az Owner a közvetlen almenü; a gyökérhez a OwnerItem láncon át jutsz.)
e.CancelHa az Opening kezelőben Cancel igaz, a menü meg sem nyílik. Hasznos, ha nincs mit mutatni (üres területen kattintás).
Mit ne tegyél az Opening-benNe végezz lassú műveletet (adatbázis, fájl): a menü késve nyílna meg. A menü állapotát egy már ismert adatból számold.

Jósolj!

Két szövegmező (txtA és txtB) osztozik egy cmsSzoveg menün. Az Mindent kijelöl kezelő txtA.SelectAll() sort tartalmaz (fix névvel). Mi történik, ha a txtB-n nyitod a menüt, és azt választod?

Jóslat ellenőrzése

A txtA szövege jelölődik ki, nem a txtB-é: a közös menü kezelője a fix névre hivatkozik, nem arra a vezérlőre, amelyen a menüt előhívták. A közös menü kezelője mindig a SourceControl értékét használja.

A kattintás helyén lévő elem kijelölése

A ListBox és más listák jobb kattintásra nem jelölik ki az elemet. Ha a menü az aktuálisan kijelölt elemre hat, a felhasználó egy másik elemre kattint jobb gombbal, és a menü az előzőn dolgozik: ez a leggyakoribb helyi menü hiba. A megoldás: a MouseDown eseményben a kattintás helye alatti elemet kijelöljük (a menü csak a jobb gomb felengedésekor nyílik meg, tehát ez még előtte fut):

Form1.csC#
private void lstFeladatok_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
int index = lstFeladatok.IndexFromPoint(e.Location); // -1 ('ListBox.NoMatches'), ha nincs elem a helyen

lstFeladatok.SelectedIndex = index; // a -1 megszünteti a kijelölést
}
}

private void cmsFeladat_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
bool vanElem = lstFeladatok.SelectedIndex >= 0;

// üres helyen csak az „Új” parancs értelmes
miSzerkeszt.Visible = vanElem;
miKesz.Visible = vanElem;
miPrioritas.Visible = vanElem;
miTorol.Visible = vanElem;
sepElem.Visible = vanElem;
}
IndexFromPointA listában a megadott pont alatti elem sorszámát adja, vagy ListBox.NoMatches (-1) értéket, ha ott nincs elem (üres terület).
Más vezérlőkA TreeView GetNodeAt(pont), a DataGridView HitTest vagy CellMouseDown, a ListView GetItemAt(x, y) metódusa adja a kattintott elemet.
Visible és EnabledAmi nem értelmezhető (üres terület), azt jobb elrejteni (Visible = false); ami értelmezhető, de most nem végezhető el, azt letiltani (Enabled = false), hogy a felhasználó lássa, létezik a funkció.
BillentyűzetrőlA Menu billentyűvel vagy Shift+F10-gyel előhívott menü nincs egérponthoz kötve, ilyenkor az aktuálisan kijelölt elemre kell hatnia. Ezért a SelectedIndex a kiindulópont.

Jósolj!

A lista három elemből áll, a 3. van kijelölve. A felhasználó a 2. elemre kattint jobb gombbal, és a menüből a Törlés parancsot választja. A MouseDown kezelő nincs megírva. Melyik elem törlődik?

Jóslat ellenőrzése

A 3. elem (a kijelölt): a jobb kattintás nem váltotta át a kijelölést, így a program nem azt törli, amire a felhasználó rákattintott. Ez adatvesztéshez vezethet, ezért a törlést mindig erősítsd meg (7.4 lecke), és a MouseDown kezelő kötelező.

Dinamikus helyi menü

A menü feliratai és állapotai az elem állapotától függenek: egy kész feladatnál „Jelölés nem késznek”, egy nem késznél „Jelölés késznek”; a prioritás almenüben a jelenlegi bejelölve. Ezt az Opening eseményben állítjuk be, a meglévő elemek tulajdonságait módosítva (nem új elemeket építve minden alkalommal):

Form1.csC#
private void cmsFeladat_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

miSzerkeszt.Visible = f != null;
miKesz.Visible = f != null;
miPrioritas.Visible = f != null;
miTorol.Visible = f != null;

if (f == null)
{
return;
}

miKesz.Text = f.Kesz ? "Jelölés &nem késznek" : "Jelölés &késznek";

// a prioritás almenü: a Tag 0, 1, 2; a jelenlegi bejelölve
foreach (ToolStripItem elem in miPrioritas.DropDownItems)
{
ToolStripMenuItem pont = elem as ToolStripMenuItem;

if (pont != null)
{
pont.Checked = int.Parse(pont.Tag.ToString()) == f.Prioritas;
}
}
}

private void Prioritas_Click(object sender, EventArgs e)
{
Feladat f = (Feladat)lstFeladatok.SelectedItem;

f.Prioritas = int.Parse(((ToolStripMenuItem)sender).Tag.ToString());
lstFeladatok.Items[lstFeladatok.SelectedIndex] = f; // ugyanaz az objektum: a lista újrarajzolja a szövegét
}
Tulajdonságok módosításaAz Opening csak a meglévő elemek Text, Enabled, Visible, Checked értékét állítja. Az elemeket nem érdemes minden megnyitásnál létrehozni és törölni (lassabb, és a felszabadítás gondot okoz, 5.7 lecke).
Lista frissítéseA listában tárolt objektum módosítása nem frissíti a megjelenített szöveget. Ha ugyanazt az objektumot visszaírod a helyére (Items[i] = f), a lista újrarajzolja. A ToString() felülírásával az objektum adja a szöveget.
Mnemonikus betű a dinamikus szövegbenA kód által beállított feliratban is lehet &; ügyelj, hogy a két változatban ne ütközzön másik elemmel.
Soha nem az egyetlen útA helyi menü a gyorsítás eszköze: minden funkciónak elérhetőnek kell lennie a főmenüből vagy gombról is (hozzáférhetőség, 6.9 lecke), mert a felhasználó nem feltétlenül tudja, hogy jobb gombbal menü is előhívható.

Jósolj!

Az Opening kezelő minden megnyitáskor új ToolStripMenuItem elemeket ad a menühöz (Items.Add), a régieket nem törli. Mit tapasztalsz a harmadik megnyitásnál?

Jóslat ellenőrzése

A menüben háromszor annyi elem van: minden megnyitás újabb példányokat tesz hozzá. Ha az elemeket mégis dinamikusan akarod építeni, előbb töröld a régieket (Items.Clear(), a kezelőkről leiratkozva és Dispose-zal), vagy a végleges elemeket készítsd el egyszer, és csak a tulajdonságaikat frissítsd.

📷 Képernyőkép helye

Mit tegyél ide: a feladatlista jobb gombbal előhívott menüje (Szerkesztés, Jelölés késznek, Prioritás almenü kinyitva, Törlés).Javasolt fájlnév: winforms-7-2-menu.webp · Alt szöveg: A feladatlista helyi menüje

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 menü rossz elemre hat

A felhasználó a második elemre kattint jobb gombbal, de a menü a kijelölt harmadikra hat.

Ok és javítás

A jobb kattintás nem jelöl ki. A MouseDown eseményben a IndexFromPoint alapján állítsd a SelectedIndex értékét.

2. Az üres területen is megjelenik a teljes menü

Az üres részre kattintva is a „Törlés” és „Szerkesztés” látszik.

Ok és javítás

Az Opening eseményben a kijelölés hiányakor rejtsd el az elemhez kötött pontokat (Visible = false), vagy a teljes menüt (e.Cancel = true).

3. A közös menü mindig ugyanazt a mezőt módosítja

private void miMasol_Click(object sender, EventArgs e)
{
txtA.Copy(); // a menü a txtB-hez is hozzá van rendelve
}
Hiba és javítás

A közös menü kezelője a SourceControl értékét használja: (cmsSzoveg.SourceControl as TextBoxBase).Copy();.

4. NullReferenceException a SourceControl használatakor

A menü kódból jelenik meg (Show(pont)), és a SourceControl üres.

Ok és javítás

A SourceControl csak akkor ismert, ha a menü egy vezérlőhöz kötve jelent meg (jobb kattintás, vagy a Show(vezérlő, pont) hívás). A Show(Point) változat nem állítja be. Mindig ellenőrizd a null értéket (as + null vizsgálat).

5. A menü minden megnyitásnál több elemet tartalmaz

Az Opening kezelő új elemeket ad hozzá a régiek törlése nélkül.

Ok és javítás

A meglévő elemek tulajdonságait módosítsd, ne újakat adj hozzá. Ha mégis dinamikus építés kell, előbb töröld a régieket (Clear és Dispose).

6. A funkció csak jobb gombbal érhető el

A felhasználók egy része nem találja a törlést, vagy a billentyűzettel dolgozók nem érik el.

Ok és javítás

A helyi menü nem lehet az egyetlen út. A funkció legyen a főmenüből és/vagy gombról is elérhető, és a Menu billentyű és a Shift+F10 működjön a helyi menüre.

Mini projekt: feladatlista helyi menüvel

Készíts feladatlistát, amelyet jobb gombbal is lehet kezelni:

  1. Az ablak: lstFeladatok (ListBox), txtUj (új feladat neve, TextBox), btnHozzaad, lblInfo. A Feladat osztály: Nev, Kesz, Prioritas (0: alacsony, 1: közepes, 2: magas); a ToString() adja a lista szövegét ([✔] Nev (!!)).
  2. Feladat helyi menü (cmsFeladat): Szerkesztés, Jelölés késznek / nem késznek (a felirat az állapottól függ), Prioritás almenü (Alacsony, Közepes, Magas, a jelenlegi bejelölve, a Tag 0/1/2, közös kezelő), elválasztó, Törlés (megerősítéssel), Új feladat (az üres terület menüje).
  3. A jobb kattintás a MouseDown eseményben kijelöli a kattintott elemet; üres területen csak az Új feladat látszik.
  4. Szövegmező helyi menü (cmsSzoveg): Visszavonás, Kivágás, Másolás, Beillesztés, Mindent kijelöl, a txtUj és egy txtMegjegyzes mező közösen használja a SourceControl alapján.
  5. A Szerkesztés a feladat nevét a txtUj mezőbe tölti, és a Hozzáad gomb felirata Módosít lesz, amíg a szerkesztés tart.
  6. Minden funkció elérhető gombbal vagy billentyűvel is: Delete (törlés), F2 (szerkesztés), Szóköz (kész-váltás), Enter a txtUj-ban (hozzáadás).
  7. Teszteld: jobb kattintás különböző elemeken és üres helyen, a billentyűzetes Shift+F10, a prioritás bejelölése, a közös szövegmenü két mezőn, a szerkesztés és a törlés megerősítése.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A Feladat osztály egy különálló osztály (a Form1.cs mellett, vagy a fájl végén). A menüpontok a lstFeladatok.SelectedItem objektumon dolgoznak; a változás után az elem visszaírásával frissítjük a listát.

Form1.csC#
public class Feladat
{
public string Nev = "";
public bool Kesz = false;
public int Prioritas = 1;

public override string ToString()
{
string jel = new string('!', Prioritas + 1);
return (Kesz ? "[✔] " : "[ ] ") + Nev + " (" + jel + ")";
}
}

private Feladat szerkesztett = null;

private void Form1_Load(object sender, EventArgs e)
{
miAlacsony.Tag = "0";
miKozepes.Tag = "1";
miMagas.Tag = "2";

miVisszavon.Tag = "visszavon";
miKivag.Tag = "kivag";
miMasol.Tag = "masol";
miBeillesztes.Tag = "beilleszt";
miMindent.Tag = "mindent";

txtUj.ContextMenuStrip = cmsSzoveg;
txtMegjegyzes.ContextMenuStrip = cmsSzoveg;
}

private void Frissit()
{
int kesz = 0;

foreach (object o in lstFeladatok.Items)
{
if (((Feladat)o).Kesz)
{
kesz++;
}
}

lblInfo.Text = $"{lstFeladatok.Items.Count} feladat, ebből kész: {kesz}";
}

private void Mentes()
{
string nev = txtUj.Text.Trim();

if (nev.Length == 0)
{
return;
}

if (szerkesztett != null)
{
szerkesztett.Nev = nev;
lstFeladatok.Items[lstFeladatok.Items.IndexOf(szerkesztett)] = szerkesztett;
szerkesztett = null;
btnHozzaad.Text = "Hozzáad";
}
else
{
Feladat f = new Feladat();
f.Nev = nev;
lstFeladatok.Items.Add(f);
}

txtUj.Clear();
txtUj.Focus();
Frissit();
}

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

private void txtUj_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
Mentes();
}
}

private void lstFeladatok_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
lstFeladatok.SelectedIndex = lstFeladatok.IndexFromPoint(e.Location);
}
}

private void cmsFeladat_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

miSzerkeszt.Visible = f != null;
miKesz.Visible = f != null;
miPrioritas.Visible = f != null;
miTorol.Visible = f != null;
sepElem.Visible = f != null;

if (f == null)
{
return;
}

miKesz.Text = f.Kesz ? "Jelölés &nem késznek" : "Jelölés &késznek";

foreach (ToolStripItem elem in miPrioritas.DropDownItems)
{
ToolStripMenuItem pont = elem as ToolStripMenuItem;

if (pont != null)
{
pont.Checked = int.Parse(pont.Tag.ToString()) == f.Prioritas;
}
}
}

private void miUjFeladat_Click(object sender, EventArgs e)
{
txtUj.Focus();
}

private void miSzerkeszt_Click(object sender, EventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

if (f != null)
{
szerkesztett = f;
txtUj.Text = f.Nev;
btnHozzaad.Text = "Módosít";
txtUj.Focus();
txtUj.SelectAll();
}
}

private void miKesz_Click(object sender, EventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

if (f != null)
{
f.Kesz = !f.Kesz;
lstFeladatok.Items[lstFeladatok.SelectedIndex] = f;
Frissit();
}
}

private void Prioritas_Click(object sender, EventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

if (f != null)
{
f.Prioritas = int.Parse(((ToolStripMenuItem)sender).Tag.ToString());
lstFeladatok.Items[lstFeladatok.SelectedIndex] = f;
}
}

private void miTorol_Click(object sender, EventArgs e)
{
Feladat f = lstFeladatok.SelectedItem as Feladat;

if (f != null && MessageBox.Show("Biztosan törlöd: " + f.Nev + "?", "Törlés", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
{
lstFeladatok.Items.Remove(f);
Frissit();
}
}

private void lstFeladatok_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete) { miTorol.PerformClick(); e.Handled = true; }
else if (e.KeyCode == Keys.F2) { miSzerkeszt.PerformClick(); e.Handled = true; }
else if (e.KeyCode == Keys.Space) { miKesz.PerformClick(); e.SuppressKeyPress = true; }
}

private void cmsSzoveg_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
TextBoxBase mezo = cmsSzoveg.SourceControl as TextBoxBase;

if (mezo == null)
{
e.Cancel = true;
return;
}

bool kijeloles = mezo.SelectionLength > 0;

miVisszavon.Enabled = mezo.CanUndo;
miKivag.Enabled = kijeloles;
miMasol.Enabled = kijeloles;
miBeillesztes.Enabled = Clipboard.ContainsText();
miMindent.Enabled = mezo.TextLength > 0;
}

private void SzovegMuvelet_Click(object sender, EventArgs e)
{
ToolStripMenuItem pont = (ToolStripMenuItem)sender;
TextBoxBase mezo = ((ContextMenuStrip)pont.Owner).SourceControl as TextBoxBase;

if (mezo == null) { return; }

switch (pont.Tag.ToString())
{
case "visszavon": mezo.Undo(); break;
case "kivag": mezo.Cut(); break;
case "masol": mezo.Copy(); break;
case "beilleszt": mezo.Paste(); break;
case "mindent": mezo.SelectAll(); break;
}
}
📷 Képernyőkép helye

Mit tegyél ide: a kész feladatlista: néhány feladat, a jobb gombos menü a Prioritás almenüvel, alatta az információsor.Javasolt fájlnév: winforms-7-2-kesz.webp · Alt szöveg: A feladatlista helyi menüvel

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. Közös menü több gombhoz

Három Button (Piros, Zöld, Kék) közös helyi menüje Háttér: sárga és Háttér: eredeti pontot tartalmaz, és a kattintott gombra hat. Honnan tudod, melyik gomb hívta?

Egy lehetséges megoldás

A menü SourceControl tulajdonságából, a menüpont kezelőjében az Owner láncon át.

Form1.csC#
private void miSarga_Click(object sender, EventArgs e)
{
ToolStripMenuItem pont = (ToolStripMenuItem)sender;
Button gomb = ((ContextMenuStrip)pont.Owner).SourceControl as Button;

if (gomb != null)
{
gomb.BackColor = Color.Gold;
}
}

2. Menü a gomb alatt

A Több… gomb a cmsTobb menüt a gomb alatt jeleníti meg. Hogyan, és mi lesz a SourceControl?

Egy lehetséges megoldás

A Show(gomb, new Point(0, gomb.Height)) hívással. A SourceControl ilyenkor a gomb.

Form1.csC#
private void btnTobb_Click(object sender, EventArgs e)
{
cmsTobb.Show(btnTobb, new Point(0, btnTobb.Height));
}

3. Kép helyi menüje

Egy PictureBox jobb gombos menüje a Kép mentése és Kép másolása a vágólapra pontot kínálja. Melyik metódusokat használod?

Egy lehetséges megoldás

A vágólapra Clipboard.SetImage(pb.Image), a mentésre pb.Image.Save(útvonal) (a SaveFileDialog adja az útvonalat, 7.8 lecke). Ha nincs kép, a menü nem nyílik meg (e.Cancel).

Form1.csC#
private void cmsKep_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = pbKep.Image == null;
}

private void miMasolKep_Click(object sender, EventArgs e)
{
Clipboard.SetImage(pbKep.Image);
}

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. Mikor jelenik meg a helyi menü?

Sorolj fel három módot, ahogyan a felhasználó előhívhat egy helyi menüt.

Megoldás megnyitása

(1) Jobb egérgomb felengedése a vezérlőn; (2) a Menu billentyű; (3) Shift+F10. Kódból a Show(...) hívással.

2. Mire való az e.Cancel az Opening-ben?

Adj egy példát, amikor a menüt nem szabad megjeleníteni.

Megoldás megnyitása

Ha nincs mit mutatni: például üres lista, kép nélküli PictureBox, vagy olyan terület, ahol egyetlen parancs sem értelmezhető. Az e.Cancel = true megakadályozza a megnyitást.

Közepes szint

3. TreeView csomópont jobb kattintásra

Egy TreeView csomópontjára jobb gombbal kattintva a csomópont legyen kijelölve, mielőtt a menü megnyílik. Melyik metódussal?

Megoldás megnyitása

A GetNodeAt(e.Location) metódussal a MouseDown eseményben.

Form1.csC#
private void tvFa_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
tvFa.SelectedNode = tvFa.GetNodeAt(e.Location); // null, ha nincs csomópont a helyen
}
}

4. Menü a billentyűzetről

A Shift+F10 a listában a kijelölt elem helyén nyisson menüt. Mit kell a helyi menüvel tenned, és mit nem?

Megoldás megnyitása

A hozzárendelt ContextMenuStrip a billentyűre magától megnyílik, külön kód nem kell. Ilyenkor nincs egérpont, ezért a menünek a kijelölt elemre kell hatnia (a SelectedIndex alapján), és az Opening ne az egér helyétől függjön.

Kihívás szint

5. Rendszertálca-menü

A programhoz tartozzon egy NotifyIcon a tálca értesítési területén, helyi menüvel (Megnyitás, Kilépés). Mit kell beállítanod?

Megoldás megnyitása

A NotifyIcon.Icon (kötelező, különben nem látszik), a Visible = true és a ContextMenuStrip. Kilépéskor a Visible = false és Dispose() kell, különben az ikon a tálcán marad, amíg az egér rá nem mutat. Az ablak elrejtése a Hide(), megjelenítése a Show() és Activate().

Form1.csC#
private void miMegnyit_Click(object sender, EventArgs e)
{
this.Show();
this.WindowState = FormWindowState.Normal;
this.Activate();
}

private void miKilep_Click(object sender, EventArgs e)
{
ni.Visible = false;
ni.Dispose();
Application.Exit();
}

6. Közös menü és szerkesztő visszahívás

Egy RichTextBox helyi menüje a Félkövér pontot kínálja, a pipa mutatja, hogy a kijelölés félkövér-e. Hogyan állítod a pipát az Opening-ben?

Megoldás megnyitása

A SelectionFont lehet null (vegyes formázású kijelölés), ezért ellenőrizni kell. A kapcsoló kattintáskor új Font példányt állít a stílus módosításával (2.4 lecke).

Form1.csC#
private void cmsRtb_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
Font f = rtb.SelectionFont;

miFelkover.Checked = f != null && f.Bold;
}

Gyors önellenőrzés

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

1. Hogyan rendelsz helyi menüt egy vezérlőhöz?

A vezérlő ContextMenuStrip tulajdonságával (tervezőben vagy kódból).

2. Mikor jelenik meg a menü egérrel?

A jobb gomb felengedésekor, ezért a MouseDown még előtte lefut.

3. Mire való a SourceControl?

Megmondja, melyik vezérlő hívta elő a menüt, így egy menü több vezérlőhöz is használható.

4. Miért kell a MouseDown kezelő a listához?

A jobb kattintás nem jelöli ki az elemet, az IndexFromPoint alapján kell kijelölni, különben a menü rossz elemre hat.

5. Hol igazítod a menüt az állapothoz?

Az Opening eseményben, a meglévő elemek tulajdonságait módosítva.

6. Mi a különbség a Visible és az Enabled között a menüpontnál?

A Visible = false elrejti (nem értelmezhető), az Enabled = false szürkévé teszi (értelmezhető, de most nem végezhető el).

7. Miért ne legyen a helyi menü az egyetlen út?

Mert nem mindenki fedezi fel, és a billentyűzettel dolgozók nehezen érik el. A funkció legyen főmenüből vagy gombról is elérhető.

Mit tudsz már?

  • Helyi menüt rendelsz vezérlőkhöz, és kódból is megjelenítesz.
  • Az Opening eseményben igazítod a menüt, vagy megakadályozod a megnyitását.
  • A SourceControl segítségével közös menüt használsz több vezérlőn.
  • A kattintás helyén lévő elemet kijelölöd (IndexFromPoint).
  • Dinamikus menüt készítesz, és tudod, hogy nem lehet az egyetlen út.

Következő lecke: StatusStrip és alkalmazásállapot

Az ablak alján az állapotsor: üzenetek, sor és oszlop, haladásjelző és ideiglenes értesítések.