Autor Beitrag
hydemarie
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: So 07.10.18 16:13 
Mahlzeit,

ich bastle mal wieder (bzw. sehr langsam: immer noch) um Windows Forms herum.

Aktuelles Problem: Ich habe eine Art anwendungsweites Kontextmenü. Mein Programm - immer noch das hier - ist so aufgeteilt, dass ich ein MDI-Fenster mit beliebig vielen Unterfenstern habe. Jedes dieser Unterfenster besteht eigentlich nur aus einer TextBox und ein paar Metadaten.

Im MainWindow habe ich folgendes Kontextmenü erstellt:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
private void MDI_MouseUp(object sender, MouseEventArgs e)
{
    // ... diverse Bedingungen ...
    CreateContextMenu();
}


Die Methode sieht ungefähr so aus (hat bei mir aber noch ein paar OnClicks):

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
public void CreateContextMenu()
{
    var mi = new MenuItem[3];

    mi[0] = new MenuItem("item1");
    mi[1] = new MenuItem("item2");
    mi[2] = new MenuItem("item3");

    var cm = new ContextMenu(mi);
    ContextMenu = cm;
    ContextMenu.Show(thisnew Point(Cursor.Position.X, Cursor.Position.Y));
}


Die Positionierung des Menüs (es sollte am Mauszeiger erscheinen) funktioniert leider noch nicht so, wie ich das gern hätte, aber das ist gerade nicht mein Hauptproblem. Mein Hauptproblem ist vielmehr, dass ich gern das Kontextmenü auch an die TextBoxes in den Unterfenstern (die ja nicht im MainWindow definiert sind, sondern bei mir EditorWindow heißen...) anheften würde. Dafür müsste ich das Kontextmenü, das Windows standardmäßig vorgibt, aber erst mal entfernen.

Erster Versuch:

ausblenden C#-Quelltext
1:
2:
3:
4:
private void EditorWindow_Load(object sender, EventArgs e)
{
    txtContents.ContextMenu = null;
}


Klappt aber nicht, das Kontextmenü ist dann immer noch da...

Ideen?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 07.10.18 17:02 
Du solltest ContextMenu nicht auf null setzen sondern auf dein ContextMenu.
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: So 07.10.18 17:04 
Das erstelle ich aber programmatisch (eben per CreateContextMenu), weil ein paar Elemente darin sich je nach aktuellem Zustand unterschiedlich verhalten. Es gibt daher zur Kompilierzeit gar kein ContextMenu. Mein Plan war: Standardkontextmenü entfernen, dann zur Laufzeit das dynamisch erstellte per .Click anhängen, wie ich das im MainWindow auch mache.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 07.10.18 17:10 
Dynamisch erstellen kannst du ja immer noch tun. Es reicht ja die passenden MenuItems zu erzeugen/einzublenden und nicht das Contextmenü.
Einfach eine leeres Contextmenü am Anfang erstellen und allen Controls zuweisen die eins anzeigen wollen und im Popup Event das Contextmenu so vorbereiten wie man es in diesem Context angezeigt haben will.

Für diesen Beitrag haben gedankt: hydemarie
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: So 07.10.18 17:12 
So weit hatte ich wie immer nicht gedacht. :oops:

Wo erstelle ich das am besten so, dass MainWindow und die Unterfenster darauf zugreifen können?
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: So 07.10.18 18:30 
- Nachträglich durch die Entwickler-Ecke gelöscht -
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: So 07.10.18 20:30 
Beide leiten sich von Form ab, ich habe keine eigene Basisklasse... das grafische "ContextMenu"-Control kann ich dann auch nicht einfach nutzen, oder?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 07.10.18 22:49 
Doch das geht. Wenn sich deine beiden Formtypen nicht gegenseitig kennen sollen kann auch einfach eine 3.te Klasse benutzt werden die ein ContextMenu liefert.

Ich verstehe aber noch nicht ganz denn Sinn das zu zentralisieren. Ein Contextmenu, wie der Name ja schon impliziert, ist Kontextabhängig. Warum nicht einfach an Ort und Stelle das Contextmenu erzeugen da man an dieser Stelle braucht? Möchtest du einen Standard etablieren für alle gleichartigen Controls?
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: So 07.10.18 22:52 
So was in der Art, ja. Ich möchte ein programmweites Kontextmenü haben (eigentlich sogar zwei, beim Mittelklick soll ein anderes erscheinen - aber das ist leichter, da muss ich nichts überschreiben), das keine Sonderbehandlung für Textfelder vorsieht. :)

Aber wenn ich das in einer Klasse definiere, dann kann ich es doch nicht als ContextMenu zuweisen (weil es dann kein Control ist)?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: So 07.10.18 23:01 
Die 3.te Klasse kann ja auch einfach ein ContextMenü veröffentlichen bzw. man wählt eine Oberfläche für die 3.te Klasse die das Contextmenü versteckt. Moment.

Edit:

z.B.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
public static class ContextMenuProvider
{
    private static ContextMenu contextMenu = null;    
    private static Dictionary<Control, Action<ContextMenu>> handlers;

    static ContextMenuProvider()
    {
        contextMenu = new ContextMenu();
        handlers = new Dictionary<Control, Action<ContextMenu>>();
        contextMenu.Popup += ContextMenu_Popup;
    }

    public static void Attach(Control ctrl, Action<ContextMenu> action)
    {
        ctrl.ContextMenu = contextMenu;
        handlers.Add(ctrl, action);           
    }

    public static bool Detach(Control ctrl)
    {
        ctrl.ContextMenu = null;
        return handlers.Remove(ctrl);
    }

    private static void ContextMenu_Popup(object sender, EventArgs e)
    {
        if (sender is ContextMenu menu &&
            handlers.TryGetValue(menu.SourceControl, out var action))
        {
            contextMenu.MenuItems.Clear();
            action(contextMenu);
        }
    }
}


Setup z.b. im Form Konstruktor oder Load/Shown Event der Form

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
ContextMenuProvider.Attach(myLovelyTextBox, menu =>
{
    menu.MenuItems.Add("MyLovelyMenuItem1", (s, a) => MessageBox.Show("Item1 clicked!"));
    if (DateTime.Now.Second % 2 == 0
        menu.MenuItems.Add("MyLovelyMenuItem2", (s, a) => MessageBox.Show("Item2 clicked!"));
});

Für diesen Beitrag haben gedankt: hydemarie
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: Mo 08.10.18 00:04 
Das übersteigt meine C#-Fähigkeiten mal wieder um ein Vielfaches... danke! :oops:
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: Mo 08.10.18 00:23 
Hmm:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
        private void TxtContents_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                MainForm.CreateContextMenu(this);
            }
        }


Ich sehe aber immer noch das normale Kontextmenü (mit "Ausschneiden", "Kopieren" und so weiter).
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 08.10.18 09:33 
Der Code macht ja auch so keinen Sinn.

Aber warum bedankst du dich erst bei Ralf und benutzt dann nicht den Code? Du mußt ihn ja (ersteinmal) nicht verstehen, sondern nur die Attach-Methode benutzen.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 08.10.18 12:36 
MouseClick wird für die rechte Maustaste nicht gefeuert da das Contextmenu Handling vorher passiert.
Wenn du die rechte Maustaste am Control abfangen willst solltest du auf den MouseDown Event ausweichen.
Sei dir aber bewusst das Contextmenu Handling passiert dann immer noch (nur halt danach).
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: Mo 08.10.18 20:28 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Aber warum bedankst du dich erst bei Ralf und benutzt dann nicht den Code?


Tu' ich doch ... habe nur mal wieder die Hälfte hier reinkopiert. (Verzeihung - zu viele Dinge gleichzeitig... ich brauche Urlaub.)

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
        public static void CreateContextMenu(Control elem)
        {
            ContextMenuProvider.Attach(elem, menu =>
            {
                menu.MenuItems.Add("item1", (s, a) => { doSomething(1); ContextMenuProvider.Detach(elem); });
                menu.MenuItems.Add("item2", (s, a) => { doSomething(2); ContextMenuProvider.Detach(elem); });
                // ...
            });

            elem.ContextMenu.Show(elem, MousePosition);
        }


Also eigentlich sollte das doch ca. das Gewünschte tun?

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du die rechte Maustaste am Control abfangen willst solltest du auf den MouseDown Event ausweichen.
Sei dir aber bewusst das Contextmenu Handling passiert dann immer noch (nur halt danach).


Und wie kann ich das Standardhandling unterbinden?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 08.10.18 20:44 
Attach ist ein Setup Methode die rufst du nur einmal auf. Z.B. im Konstruktor der Form für jedes Control auf der Form das ein Menü bekommen soll. Danach würde der ContextMenuProvider selbst dann das Contextmenü öffnen wenn ein Rechts Click auf dem Control ausgeführt wird für das man Attach aufgerufen hat. Keine Notwendigkeit selbst Show am Contextmenü aufzurufen. Dann brauchst du Detach auch nicht im übergeben Delegaten aufzurufen. Das kannst du wenn die Form zerstört wird.

Edit:

Zitat:
Und wie kann ich das Standardhandling unterbinden?


Nun wenn du das ohne den ContextMenuProvider(oder ähnliches) machen willst und selbst ein explizites eigenes Show an einem eigenen ContextMenu benutzen willst dann kannst du

a.) der ContextMenu Property des Controls ein leeres Contextmenü zuweisen. Ein leeres Contextmenü wird nicht angezeigt verhindert aber das das Standardcontextmenu angezeigt wird. Zumindest bei Textboxen. Bei anderen Controls könnte das Verhalten etwas anders sein.

b.) bei einer Textbox kannst du auch ShortcutsEnabled auf false setzen. Dann ist das Menu weg aber auch alle Shortcuts (z.B. das ganze Copy&Paste Zeug)
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: Mo 08.10.18 21:01 
Unter bestimmten Umständen möchte ich beim Rechtsklick aber kein Kontextmenü aufrufen, nämlich, wenn ich gerade ein Rectangle zeichnen will... :D

Ich gehe mal ein bisschen ins Detail, weil mir scheint, das würde Verständigungsproblemen vorbeugen:
Ich möchte - mit leichten Usabilityverbesserungen ... - dieses Programm nachbauen. Es kennt im Wesentlichen zwei Modi: Zeichenmodus (v.a. "neues Fenster") und Editiermodus.

Im Editiermodus habe ich ein Rechtsklickmenü in Haupt- und Unterfenster(n), jedenfalls, wenn das alles klappt... dort kann ich z.B. "neues Fenster" auswählen. Das startet den Zeichenmodus. Dann kann ich durch Rechtsklick - Ziehen - Rechtsklick ein neues Fenster "malen". Danach wird der Zeichenmodus wieder verlassen. Das funktioniert auch schon ganz gut so weit, ein Großteil des Codes ist ja immerhin von dir. :wink: Ich habe aber deshalb ein "bedingtes" Kontextmenü, das nur angezeigt werden soll, wenn ich gerade kein Fenster male. Das .Show() ist also durchaus notwendig - oder?

Zitat:
a.) der ContextMenu Property des Controls ein leeres Contextmenü zuweisen. Ein leeres Contextmenü wird nicht angezeigt verhindert aber das das Standardcontextmenu angezeigt wird. Zumindest bei Textboxen.


Das würde vermutlich gehen, aber würde "Attach" das dann nicht einfach überschreiben und danach wäre es quasi "weg"?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 08.10.18 21:19 
Zitat:
Das würde vermutlich gehen, aber würde "Attach" das dann nicht einfach überschreiben und danach wäre es quasi "weg"?


Nun darum habe ich extra geschrieben "wenn du das ohne ContextMenuProvider machen willst" ;)
Du wolltest direkt einen Mouse Event verdrahten um da was drin zu tun. Das macht nur Sinn wenn du das da komplett selbst machst inklusive anzeigen.
So wie ich es gezeigt hatte mit dem ContextMenuProvider macht es nur Sinn wenn du nicht auch noch selbst an den ContextMenus und wann die angezeigt werden rumspielst.


Zitat:
Ich habe aber deshalb ein "bedingtes" Kontextmenü, das nur angezeigt werden soll, wenn ich gerade kein Fenster male. Das .Show() ist also durchaus notwendig - oder?


In meinem Codebeispiel hatte ich im Delegaten das blödsinnige Beispiel das der 2.te Menupunkt nur beim Click zu einer geradzahligen Sekunde angezeigt wird. Da kannst du natürlich auch jede andere Bedingung die nötig ist einbauen. Der Delegat der da ausgeführt wird und ich da Inline definiert habe ist ja auch nur irgendeine Methode die natürlich alles tun kann was eine Methode tun kann. In diesem Fall wird der Delegat bei jedem Rechtsklick ausgeführt und der Scope dieses Delegaten ist die Form. Du solltest also auf alles Zugriff haben was du für deine Logik brauchst.

Wenn du es lesbarer findest kannst du da ja auch einfach eine normale Methode an Attach übergeben und das mit dem Inline-Delegaten lassen. Das war eh eher der Einfachheit im Bespiel geschuldet.
hydemarie Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 475
Erhaltene Danke: 51



BeitragVerfasst: Mo 08.10.18 23:55 
user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Du wolltest direkt einen Mouse Event verdrahten um da was drin zu tun. Das macht nur Sinn wenn du das da komplett selbst machst inklusive anzeigen.


Ja - ich dachte, das hätte ich vor? Ich will nach zwei verschiedenen Mausklicks (rechts bzw. mittig irgendwo bzw. im Textfenster) 0 bis 2 (je nach Applikationszustand) verschiedene Kontextmenüs anzeigen. :)
Ohne Provider hätte ich doch wieder das Problem mit den sehr starren Menüs, oder?

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du es lesbarer findest kannst du da ja auch einfach eine normale Methode an Attach übergeben und das mit dem Inline-Delegaten lassen.


Ich finde es so eigentlich schon sehr schön nutzbar, aber wahrscheinlich muss ich was umbauen. Vor Mittwoch komme ich aber nicht mehr dazu. Au Backe...
Danke für deine Geduld bis hierhin!