Is there a way to show a “blocking” WinForms Conte

2019-06-24 22:08发布

问题:

Is there a way to show a ContextMenu and block further execution until an item has been selected? In particular, I want to get behavior similar to ShowDialog() but for a ContextMenu.

The straight forward approach doesn't work:

ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);

since the Click callback isn't called directly from Show() but instead at some later point when the message loop processes the click event.

If you are unlucky, menu is garbage collected before the event is processed and in that case the event is just silently lost. (Meaning you can't really use local variables for ContextMenus in this way.)

This seems to work, but feels "unclean":

using (ContextMenu cm = new ContextMenu()) {
    cm.MenuItems.Add("1", (s,e) => {value = 1;});
    cm.Show(control, location);
    Application.DoEvents();
}

Is there a better way?

回答1:

Sorry for the first answer. Here is what I've tried. I made another Form where I put the context menu and a timer.Form2 is displayed as modal from Form1 then the timer shows the context menu on Form2.

Note that Form 2 has some properties set : to not be visible in task bar, not have boarders and the size should be equal with the size of the context menu.

Hope this helps.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right)
        {
            Form2 ctxForm = new Form2();
            ctxForm.Location = this.PointToScreen(e.Location);
            ctxForm.Size = new Size(0, 0);
            ctxForm.ShowDialog();
        }
    }



}


public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
    }

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

    private void timer1_Tick(object sender, EventArgs e)
    {
        //show menu once
        contextMenuStrip1.Show(this, PointToClient(Location));
        contextMenuStrip1.Focus();
        timer1.Enabled = false;
    }

    private void contextMenuStrip1_Closed(object sender, ToolStripDropDownClosedEventArgs e)
    {
        this.Close();
    }
}


回答2:

You can easily prevent the garbage collection of the ContextMenu whilst it is still being shown.

The problem is that you are using a lambda as the event handler for the menu item. This is an anonymous method and so not itself attached to any object instance that would cause the ContextMenu to be referenced and so kept alive. Add a method to the enclosing object and then create a standard EventHandler. That way the existence of the enclosing instance will keep the ContextMenu alive. Not as concise and very C# 1.0 but it will solve the problem.



回答3:

Just wait for the menu to not be visiable.

ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
while (cm.Visible == true) Application.DoEvents();