Context menu strip in Klasse auslagern
-
Hallo,
mein Programm wird langsam etwas umfangreicher, so dass ich es etwas aufräumen möchte. Anfangen wollte ich mit meinem TreeView Steuerelement. Dieses hat ein dynamisches Context Menu Strip. Wenn der Benutzer also mit der rechten Maustaste auf einen TreeNode klickt wird erstmal ausgewertet, welcher node angeklickt wurde und anschließend das richtige Context Menu Strip geladen.
Das ganze würde ich jetzt gerne auslagern, denn ich habe schon jetzt neun verschiedene Menüs - und es werden noch viele mehr werden.
Ich hatte mir das so vorgestellt: In der Klasse meines Hauptformulars möchte ich nurnoch folgenden Code stehen haben:
private void MainTree_NodeMouseClick(object sender, MouseEventArgs e) { switch (e.Button) { case System.Windows.Forms.MouseButtons.Right: TreeViewHandle.HandleTreeMouseRightClick(sender, e, MainTree.SelectedNode, MainTree); break; default: break; } } private void MainTree_MouseUp(object sender, MouseEventArgs e) { switch (e.Button) { case System.Windows.Forms.MouseButtons.Right: if (MainTree.ContextMenuStrip!=null) { MainTree.ContextMenuStrip.Show(new Point(e.X, e.Y)); } break; default: break; } }
Also, wenn der Benutzer mit der rechten Maustaste auf den TreeNode klickt, wird
das jeweilige Menü an den TreeView als ContextMenuStrip übergeben.
TreeViewHandle ist eine statische Klasse, welche mir das korrekte Context Menu Strip zurückliefern soll - in dem Code ist das bisher nur für die TreeNodes "node", "line" und "shell" eingebaut.
Lässt der User die Maustaste los wird das korrekte ContextMenuStrip angezeigt.unsafe static class TreeViewHandle { public static void HandleTreeMouseRightClick(object sender, MouseEventArgs e, TreeNode STNode, TreeView MainTV) { if (e.Button == System.Windows.Forms.MouseButtons.Right && STNode != null) { List<string> TPath = new List<string>(STNode.FullPath.Split('\\')); if (TPath.Count() < 1) { return; } switch (TPath.Last().ToString()) { case "node": MainTV.ContextMenuStrip = TreeViewContextMenu.C_NodeMenu(); break; case "line": MainTV.ContextMenuStrip = TreeViewContextMenu.C_LineMenu(); break; case "shell": MainTV.ContextMenuStrip = TreeViewContextMenu.C_ShellMenu(); break; default: break; } } } }
static class TreeViewContextMenu { public static ContextMenuStrip C_NodeMenu() { ContextMenuStrip CM = new ContextMenuStrip(); CM.Items.Add("External node number"); CM.Items.Add("Internal node number"); return CM; } public static ContextMenuStrip C_LineMenu() { ContextMenuStrip CM = new ContextMenuStrip(); CM.Items.Add("External line number"); CM.Items.Add("Internal line number"); return CM; } public static ContextMenuStrip C_ShellMenu() { ContextMenuStrip CM = new ContextMenuStrip(); CM.Items.Add("External shell number"); CM.Items.Add("Internal shell number"); return CM; } }
So, bis hierhin funktioniert alles. Bei einem Rechtsklick wird beim Klicken das korrekte Menü an den TreeView übergeben. Wenn man die Maustaste loslässt, wird dieses angezeigt. Jetzt möchte ich aber noch die EventHandler definieren und hier wirds schwierig, denn folgende Anweisung ist in einer statischen Klasse nicht erlaubt:
static class TreeViewContextMenu { public static ContextMenuStrip C_ShellMenu() { ToolStripItem ti; ContextMenuStrip CM = new ContextMenuStrip(); ti = CM.Items.Add("External shell number"); ti.Click += new EventHandler(externalShell_click); /*ERROR*/ ti.CM.Items.Add("Internal shell number"); ti.Click += new EventHandler(internalShell_Click); /*ERROR*/ return CM; } private void externalShell_Click(object sender, EventArgs e) { /*Do something*/ } private void internalShell_Click(object sender, EventArgs e) { /*Do something*/ } }
Das ist nicht zulässig. Der Fehler lautet:
Für das nicht statische Feld, die Methode oder die Eigenschaft .TreeViewContextMenu.externalNodeNumber_Click(object sender, EventArgs e) ist ein Objektverweis erforderlich.Meine Frage nun: Wie kann ich das machen? Es funktioniert, wenn ich den kompletten Code in meine Main.cs Klasse packe, aber das geht einfach nicht - das würde so unübersichtlich werden - ich habe noch viele weitere Steuerelemente, die alle noch dynamische Kontextmenüs bekommen sollen. Wie würde ihr das angehen?
-
Na, die Lösung erscheint doch wohl ganz einfach: die Ereignismethoden auch statisch machen
Generell würde ich aber von statischen Klassen eher abraten (außer es handelt sich um reine Utility-Klassen).
Es gibt designtechnisch mehrere Möglichkeiten der Entkopplung:
- jedes Steuerelement + Ereignismethoden als eigenes (User)Control (dadurch wird die Form-Klasse schon mal um einiges aufgeräumter)
- Nutzung von "partial", d.h. Methoden einer Klasse auf mehrere Dateien verteilen (wie bei form.designer.cs)Generell ist dein Ansatz aber in Ordnung so, die Funktionalität auf verschiedene (kleinere) Klassen zu verteilen.
PS: Das Abonnieren geht auch kürzer mittels
ti.Click += ExternalShell_Click; // <- außerdem Namensanpassung gemäß Konvention
-
Th69 schrieb:
Na, die Lösung erscheint doch wohl ganz einfach: die Ereignismethoden auch statisch machen
Ich dreh durch. Danke erstmal, ich hatte das gestern (dachte ich) auch so versucht und immer Fehler bekommen.... aber so funktioniert es, Du hast recht.
Noch eine Frage zu den (User)Controls. Ist das nicht sehr kompliziert, wenn man so ein Steuerelement noch macht, oder kann man das Vererben und eigene Funktionen hinzufügen / überschreiben?
-
Ja, das geht genau so einfach. Erstelle einfach eine neue Klasse und leite dann von dem Steuerelement ab:
public class MyTreeView : TreeView { }
Nun kannst du dort alle Methoden, Eigenschaften und Ereignisse hinzufügen, die du haben möchtest.
Bei den Ereignissen sollte man dann jedoch einfach die virtuellen On...-Methoden überschreiben (und dadrin dann die base.On...()-Methode aufrufen), anstatt sich an das öffentliche Ereignis zu hängen.Wenn du diese Klasse (d.h. das zugehörige Projekt) einmal kompiliert hast, dann steht es auch im Forms-Designer zur Verfügung.
Dann kannst du einfach im Form dann dieses neue Steuerelement benutzen.Bei zusammengesetzen Controls würde man aber besser ein UserControl (Benutzersteuerelement) erstellen (z.B. ein Label mit TextBox und ein Button daneben).