winformstoolstripmenu

How do I filter ToolStripMenuItem mouse events in a Winforms application?


I have a .Net Winforms application with a ToolStrip that contains several ToolStripMenuItems.

I would like to be able to intercept Mouse LButton down events on the ToolStripMenuItems. I have PreFilterMessage defined. I assume I should be able to capture the event in that filter.

bool IMessageFilter.PreFilterMessage(ref Message message)
{
    switch (message.Msg)
    {
    }
}

How would I do that?

Is there a custom message being used?

Once I capture the message, how can I use the Message data to get the actual ToolStripMenuItem that was clicked?


Solution

  • In the IMessageFilter implementation, use the m.HWnd property to get the owner control of the clicked ToolStripItem by calling the Control.FromHandle method and cast it to a proper strip type. To the base ToolStrip class or one of its derives.

    Get the clicked point from the m.LParam property to search the strip's Items collection and find the clicked item whose bounds should contain that point if any.

    // +
    using System.Linq;
    
    public partial class SomeForm : Form, IMessageFilter
    {
        const int WM_LBUTTONDOWN = 0x0201;
    
        public SomeForm()
        {
            InitializeComponent();
            // ...
        }
    
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Application.AddMessageFilter(this);
        }
    
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            Application.RemoveMessageFilter(this);
            base.OnFormClosing(e);
        }
    
        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == WM_LBUTTONDOWN && FromHandle(m.HWnd) is ToolStrip ts)
            {
                var p = PointToClient(PointToScreen(new Point(m.LParam.ToInt32())));
                var item = ts.Items.Cast<ToolStripItem>()
                    .FirstOrDefault(x => x.Bounds.Contains(p));
    
                if (item != null)
                {
                    Console.WriteLine(item.Text);
                    // ...
                }
            }
    
            return false;
        }
    }
    

    If you just need to handle the items of the dropdown menus, then change the type of the owner control from the base ToolStrip to the derived ToolStripDropDownMenu.

    if (m.Msg == WM_LBUTTONDOWN && FromHandle(m.HWnd) is ToolStripDropDownMenu ts)
    {
        // The same ...
    }
    

    Or check the type of the returned item to also skip types like ToolStripSeparator.

    if (item is ToolStripMenuItem)
    {
        Console.WriteLine(item.Text);
        // ...
    }
    

    Find out a different approach here.