On a Windows Form in .Net 3.5 I have created a menu object and populated it with ToolStripMenuItems. One of these items has a DropDown object attached. The DropDown should appear when the mouse hovers over the parent ToolStripMenuItem and disappear when the mouse leaves the ToolStripMenuItem unless it is “leaving” the parent by entering the parent’s DropDown.
Also, I don’t want the DropDown to automatically close when the user makes a selection in it, so I have set its “AutoClose” property to False.
Getting the DropDown to appear was easy. I just set up a handler for a “MouseEnter” event on the parent ToolStripMenuItem. But I’m stuck trying to make the DropDown disappear at the right time. If I set up a handler to close the it when the mouse leaves the parent ToolStripMenuItem, it becomes impossible to use the DropDown, because moving the mouse into the DropDown means “leaving” the parent ToolStripMenuItem, and so the DropDown closes as soon as the user tries to hover over it!
I haven’t been able to figure out how to detect if the mouse has really left the whole ToolStripMenuItem / DropDown assembly (in which case the DropDown should close) or has only “left” the ToolStripMenuItem by entering the DropDown (in which case the DropDown should not close).
This seems like a common design – a drop down that appears / disappears when the mouse hovers over / leaves the parent element – so how is it normally done? Grateful for any suggestions.
Still surprised that this is apparently not a problem that was solved a long time ago, but here’s the solution I came up with:
Quick summary
The class below inherits from ToolStripMenuItem. Use it if you want the item to have a child DropDown menu that appears when the user’s mouse hovers over it.
Terms I use below
ToolStripMenuItem: an item in a ToolStripDropDownMenu. It is both a member of a ToolStripDropDownMenu (the “parent menu”), and it also has access to another ToolStripDropDownMenu via its “DropDown” property (the “child menu”).
Statement of the problem and soltion
The child ToolStripDropDownMenu that appears when you hover over the ToolStripMenuItem should normally close when the mouse leaves that ToolStripMenuItem and/or when it leaves the parent ToolStripDropDownMenu that contains it. However, it should not close if the mouse leaves the parent menu by entering the child menu at the same time. In that case, the “MouseEnter” event on the child menu should cancel the normal behavior of the “MouseLeave” event on the parent menu (i.e., the DropDown should not close).
The problem when you try to set this up in a normal, straightforward way is that the “MouseLeave” event on the parent menu fires before the “MouseEnter” event on the child menu, and the child menu closes before the mouse can enter it.
The solution below shunts the call to DropDown.Close() into a separate thread, where the “Close” action is delayed by a few seconds. In that short window, the “MouseEnter” event on the child DropDown (which is still on the main thread) has a chance to set a globally accessible dictionary value to True. After the delay, the value of this dictionary entry is checked in the separate thread, and the child menu is either closed (by calling the thread-safe “Invoke” method) or not. The program then goes on to check whether the parent menu also needs to be closed, whether that menu’s parent menu needs to be closed, and so on. This code allows floating submenus to be nested as deep as any reasonable person would want.
There are separate handlers for “MouseEnter” and “MouseLeave” events for the individual menu item, its parent menu, and its child menu. They all check on each other to decide on the right course of action.
In conclusion
In posting this I wanted to provide an elegant working solution to this problem for which I had previously been unable to find much help. Still, iIf anyone has any tweaks for it, I’d love to hear them. Until then, please use this class if it helps you. When you instantiate it you need to send it a string for the text that will appear on it, a pointer to the main form, and a pointer to the parent ToolStripDropDownMenu to which you are adding it. After that, just use it as you would a normal ToolStripMenuItem. I also added a flag that can be set to True if you want the child DropDown menu items to behave like radio buttons (only one selectable at a time). — Noel T. Taylor