views:

3596

answers:

4

I am porting an MFC application to .NET WinForms. In the MFC application, you can right click on a menu or on a context menu item and we show another context menu with diagnostic and configuration items. I am trying to port this functionality to .NET, but I am having trouble.

I have been able to capture the right click, disable the click of the underlying menu and pop up a context menu at the right location, but the original menu disappears as soon as it loses focus.

In MFC, we show the new context menu by calling TrackPopupMenuEx with the TPM_RECURSE flag.

ContextMenu and the newer ContextMenuStrip classes in .NET only have a Show method. Does anyone know how to do this in .NET?

EDIT

I have tried using TrackPopupMenuEx through a p/invoke, but that limits you to using a ContextMenu instead of a ContextMenuStrip which looks out of place in our application. It also still does not work correctly. It doesn't work with the new MenuStrip and ContextMenuStrip.

I have also tried subclassing ToolStripMenuItem to see if I can add a context menu to it. That is working for MenuStrip, but ContextMenuStrip still allows the right click events to pass through as clicks.

A: 

Why not just use a submenu, so that when you hover over the menu item, it shows more items?

Kyle Trauberman
Many of our menus already have sub-menus. We use this feature for administrators to adjust the security of menus. For example, right click on a menu and set it to disabled. That is stored in the database and takes affect for all users.
Rob Prouse
+2  A: 

You'll probably have to p/invoke the method.

[DllImport("user32.dll")]
static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y,
IntPtr hwnd, IntPtr lptpm);

const int TPM_RECURSE = 0x0001;
Jason Diller
So far I haven't managed to get this working.
Rob Prouse
I got this partially working with the old ContextMenu, but not ContextMenuStrip. Even then it was having some problems with focus and highlighting, plus the old ContextMenu classes don't look right with the newer menus.
Rob Prouse
+6  A: 

Edit, due to a comment:

In:
        protected override void OnClick(EventArgs e)
        {
            if (SecondaryContextMenu == null || MouseButtons != MouseButtons.Right)
            {
                base.OnClick(e);
            }
        }

this part

 MouseButtons != MouseButtons.Right

should and does compile as it is a call to Control.MouseButtons. Since the Form inherits Control class, it is sufficient to call MouseButtons property directly.

Hope this helps:

public partial class Form1 : Form
{
    class CustomToolStripMenuItem : ToolStripMenuItem
    {
        private ContextMenuStrip secondaryContextMenu;

        public ContextMenuStrip SecondaryContextMenu
        {
            get
            {
                return secondaryContextMenu;
            }
            set
            {
                secondaryContextMenu = value;
            }
        }

        public CustomToolStripMenuItem(string text)
            : base(text)
        { }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (secondaryContextMenu != null)
                {
                    secondaryContextMenu.Dispose();
                    secondaryContextMenu = null;
                }
            }

            base.Dispose(disposing);
        }

        protected override void OnClick(EventArgs e)
        {
            if (SecondaryContextMenu == null || MouseButtons != MouseButtons.Right)
            {
                base.OnClick(e);
            }
        }
    }

    class CustomContextMenuStrip : ContextMenuStrip
    {
        private bool secondaryContextMenuActive = false;
        private ContextMenuStrip lastShownSecondaryContextMenu = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (lastShownSecondaryContextMenu != null)
                {
                    lastShownSecondaryContextMenu.Close();
                    lastShownSecondaryContextMenu = null;
                }
            }
            base.Dispose(disposing);
        }

        protected override void OnControlAdded(ControlEventArgs e)
        {
            e.Control.MouseClick += new MouseEventHandler(Control_MouseClick);
            base.OnControlAdded(e);
        }

        protected override void OnControlRemoved(ControlEventArgs e)
        {
            e.Control.MouseClick -= new MouseEventHandler(Control_MouseClick);
            base.OnControlRemoved(e);
        }

        private void Control_MouseClick(object sender, MouseEventArgs e)
        {
            ShowSecondaryContextMenu(e);
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            ShowSecondaryContextMenu(e);
            base.OnMouseClick(e);
        }

        private bool ShowSecondaryContextMenu(MouseEventArgs e)
        {
            CustomToolStripMenuItem ctsm = this.GetItemAt(e.Location) as CustomToolStripMenuItem;

            if (ctsm == null || ctsm.SecondaryContextMenu == null || e.Button != MouseButtons.Right)
            {
                return false;
            }

            lastShownSecondaryContextMenu = ctsm.SecondaryContextMenu;
            secondaryContextMenuActive = true;
            ctsm.SecondaryContextMenu.Closed += new ToolStripDropDownClosedEventHandler(SecondaryContextMenu_Closed);
            ctsm.SecondaryContextMenu.Show(Cursor.Position);
            return true;
        }

        void SecondaryContextMenu_Closed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            ((ContextMenuStrip)sender).Closed -= new ToolStripDropDownClosedEventHandler(SecondaryContextMenu_Closed);
            lastShownSecondaryContextMenu = null;
            secondaryContextMenuActive = false;
            Focus();
        }

        protected override void OnClosing(ToolStripDropDownClosingEventArgs e)
        {
            if (secondaryContextMenuActive)
            {
                e.Cancel = true;
            }

            base.OnClosing(e);
        }
    }

    public Form1()
    {
        InitializeComponent();


        CustomToolStripMenuItem itemPrimary1 = new CustomToolStripMenuItem("item primary 1");
        itemPrimary1.SecondaryContextMenu = new ContextMenuStrip();
        itemPrimary1.SecondaryContextMenu.Items.AddRange(new ToolStripMenuItem[] { 
            new ToolStripMenuItem("item primary 1.1"),
            new ToolStripMenuItem("item primary 1.2"),
        });

        CustomToolStripMenuItem itemPrimary2 = new CustomToolStripMenuItem("item primary 2");
        itemPrimary2.DropDownItems.Add("item primary 2, sub 1");
        itemPrimary2.DropDownItems.Add("item primary 2, sub 2");
        itemPrimary2.SecondaryContextMenu = new ContextMenuStrip();
        itemPrimary2.SecondaryContextMenu.Items.AddRange(new ToolStripMenuItem[] { 
            new ToolStripMenuItem("item primary 2.1"),
            new ToolStripMenuItem("item primary 2.2"),
        });

        CustomContextMenuStrip primaryContextMenu = new CustomContextMenuStrip();
        primaryContextMenu.Items.AddRange(new ToolStripItem[]{
            itemPrimary1,
            itemPrimary2
        });

        this.ContextMenuStrip = primaryContextMenu;
    }
}
Eren Aygunes
I will give this a try, thanks.
Rob Prouse
Thanks, this works for ContextMenuStrip and modified for the top level menus of a MenuStrip. I still can't get it to work for the sub-menus of the MenuStrip, but I think that is just a problem with the GetItemAt call.
Rob Prouse
The OnClick override in the CustomToolstripMenuItem class doesn't compile. Can't have (MouseButtons != MouseButtons.Right). What is that line supposed to be? How do you determine which button was pressed, i the click event.
Stuart Helwig
It is a static propert of the Control class. (Control.MouseButtons != MouseButtons.Right). I didn't call [Control.]MouseButtons explicitly as Form inherits Control class.
Eren Aygunes
A: 

This shows how to use multiple ContextMenus as well as different ones with any combination of mouse clicks.

More here: http://code.msdn.microsoft.com/TheNotifyIconExample