How to toggle a ContextMenuStrip display using a button click but also allowing it to close normally (menu item clicks, lost focus etc)

StackOverflow https://stackoverflow.com/questions/17326200

Question

I created a simple UserControl consisting of a Label and ContextMenuStrip. I made it function like a ComboBox but instead of a dropdown I'm displaying a ContextMenuStrip.

I have it working but there's some trickiness that I can't figure out.

I'm trying to make the label ComboButton work the same way a ComboBox does. Click the button, the dropdown appears. Click the button a second time and it'll retract. The problem is, the ContextMenu disappears on any mouse click. So when I click the button a second time to retract the menu, the menu disappears first, and then the click event fires, displaying the menu again.

I still want the Menu to disappear when a user selects a menuitem and when they just click anywhere on the form like a normal context menu does.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Windows.Forms.VisualStyles;
using System.Diagnostics;

namespace Controls
{
    public partial class CMenu : UserControl
    {
        ButtonState _buttonState = ButtonState.Normal;

        public CMenu()
        {
            InitializeComponent();
        }

        private void lblSelect_Paint(object sender, PaintEventArgs e)
        {
            base.OnPaint(e);
            ControlPaint.DrawComboButton(e.Graphics, getLabelRect(), _buttonState);
        }

        private bool IsDropdownHit(MouseEventArgs e)
        {
            Rectangle cursor = new Rectangle(e.X, e.Y, 1, 1);
            if (e.Button == MouseButtons.Left && cursor.IntersectsWith(getLabelRect()))
            {
                return true;
            }
            return false;
        }

        private void lblSelect_MouseUp(object sender, MouseEventArgs e)
        {
            if (!IsDropdownHit(e))
                return;

            if (!cmsItems.Visible)
                lblSelect.ContextMenuStrip = cmsItems;
                cmsItems.Width = lblSelect.Width;
                cmsItems.Show(lblSelect, 0, lblSelect.Height);
        }

        private Rectangle getLabelRect()
        {
            return new Rectangle(lblSelect.Width - 20, 0, 20, lblSelect.Height);
        }
    }
}
Was it helpful?

Solution

So, I've rewritten your mouseUp a little:

private void lblSelect_MouseUp(object sender, MouseEventArgs e)
    {
        if (IsDropdownHit(e) && cmsItems.Tag == null)
        {
            cmsItems.Width = lblSelect.Width;
            cmsItems.Show(lblSelect, 0, lblSelect.Height);
            cmsItems.Tag = "Shown";
        }
        else
        {
            cmsItems.Hide();
            cmsItems.Tag = null;
        }
    }

Now, it'll close. BUT - if you dont use the button to close, you will have to click twice to open it, next time.

Workaround for this "bug":

void cmsItems_Closed(object sender, ToolStripDropDownClosedEventArgs e)
    {
        Point c = PointToClient(Cursor.Position);
        if (!IsDropdownHit(new MouseEventArgs(MouseButtons.Left, 1, c.X - lblSelect.Location.X, c.Y - lblSelect.Location.Y, 0)))
            cmsItems.Tag = null;
    }

Depending on how you designed your form, you might have to adjust the MouseEventArgs-Coordinates, to successfully determine Dropdownhits. I just added the CMenu Control to an empty form.

For the workaround, don't forget to add the handler into the constructor of your CMenu.cs:

cmsItems.Closed += cmsItems_Closed;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top