Context Menu Check Box will not stay open

ImDaFrEaK

Well-known member
Joined
Jan 7, 2006
Messages
416
Location
California
Programming Experience
5-10
Hey guys, I hope this isn't a stupid question. I have a context menu on my NotifyIcon and for easier use I added four check box options. Basically, I want the user to check the options they want then hit the search. However, each time the item is clicked to check it the menu closes and it has to be re-opened to then search or check another box. This is annoying. Am I missing something simple? I can't find a way to make it stay open. I even tried to enable the double click option and it still closes after the first click. Still, I knew that wasn't the right answer. How can I add check boxes to my menu list and have them checked without it closing?
 
Yes you are missing something. That is how menus work. They close when you make a selection. They are not supposed to work any differently so to make them do so would require a lot of low-level work, and you'd be going against the standard Windows UI. The thing is, why make the user select search anyway? If you know that you want to search after they make a selection then just search. There's no need to make them click twice if you already know exactly what they want to do after the first click.
 
Actually, here's the thing. Its a mp3 program and on the actual mp3 there are is a search bar. Along with that search bar are 4 checkboxes (default checked) that change the specifics of what the user is searching for. The context menu and the menu for the player are pretty much the same, so anytime you want to find a certain song or artist or genre or folder you just un-check the options you don't want to search for. It's a handy search option and I know so oinly because I use it. I would not have programed all the extra if it wasn't worth it. Anyways. My contexst menu, among other thigns, has a textbox in it and a label above it that says Seach for... and the label has a arrow over to 4 check boxes, the ones I previously mentioned. Now you can go ahead and type in the textbox and press enter and start a search but it searches for the settings currently existing in the player search options. It's really comfortable to be able to right click anywhere on the player and search or do the same with notify icon, and it would just be that much simpler that if at that point you wanted to change the search options you could, and you can; but like I said you have to re-open the menu.
 
I agree with jmcilhinney about the nature of menus, a search dialog is what you want to make.

Btw, I managed to subclass menustrips and stay-menuitems in a way that stopped menu from disappear on click but could at the same time not check/uncheck, could be possible but I'd go for a dialog.
 
Thank you guys so much for helping. However; the nature of how this program is performing is right on target. The Search Dialog is basically just a text box so no need for a whole new window window ect. If I can't over come the problem with having the context menu closing I will just keep it the way it is. If your interested though in seeing what I am accomplishing with this program I will send you a copy via e-mail. it's a quick install (if you have the 2.0 framework installed) if not it will re-direct you to microsoft downloads in which will give you the free 2.0 update. Either way, the player isn't a normal media player by all means and I'm setting up a new easier interface and search criteria. There are many ways to accomplish one task and is the same for song searching. This was just the quicky way from right clicking. I think if you are curious sampling what I have so far will help you realize what I am doing.
 
Here's the cure.............

To cancel a context menu from closing is actually all too simple. I feel honestly stupid for even asking before I tried this.

Basically there are a few ways that I figured out. The way I went with was to build a Private Property.
VB.NET:
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] m_Cancel_Context_Menu [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean[/COLOR][/SIZE][SIZE=2] = [/SIZE][SIZE=2][COLOR=#0000ff]True
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE][SIZE=2] Cancel_Context_Menu() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Return[/COLOR][/SIZE][SIZE=2] m_Cancel_Context_Menu
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Set[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] value [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean[/COLOR][/SIZE][SIZE=2])
m_Cancel_Context_Menu = value
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Set
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Property
[/COLOR][/SIZE]

Then I caught the Closing Event for the Context menu.

VB.NET:
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1_Closing([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Object[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Windows.Forms.ToolStripDropDownClosingEventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1.Closing
e.Cancel = [/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

Then the rest is history. I just make sure to set the Cancel_Context_Menu property on the clicks that I want to close and the ones that I don't. However, as a default the menu item closes then runs the click event, so I call the close event as well.

VB.NET:
[SIZE=2][COLOR=#0000ff]
Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ByTitleToolStripMenuItem_Click([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.EventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] ByTitleToolStripMenuItem.Click
[/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu = [/SIZE][SIZE=2][COLOR=#0000ff]True
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].CheckBoxTitleSearch.Checked = sender.checked
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff][/COLOR][/SIZE] 
[SIZE=2][COLOR=#0000ff][/COLOR][/SIZE] 
[SIZE=2][COLOR=#0000ff][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#000000] [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2][COLOR=#000000] CancelToolStripMenuItem_Click([/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2][COLOR=#000000] sender [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#000000] System.Object, [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2][COLOR=#000000] e [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#000000] System.EventArgs) [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2][COLOR=#000000] CancelToolStripMenuItem.Click[/COLOR]
[/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu = [/SIZE][SIZE=2][COLOR=#0000ff]False
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].ContextMenuStrip1.Close()
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE][/COLOR][/SIZE]


So now it works perfectly and with much less clutter than a search dialog. I appreciate all the help and hopefully someone can use this information as well. Please note that there are many options you can take to prevent the menu from closing by catching this event. I just wanted to take this route. I wish I would have figured this out sooner but I was having a brain fart. Thanks again.
 
Oh yes, I almost forgot. Make sure that you set the Context_Menu_Cancel option to true each time you open the menu because like I said, the initial click will close the menu then run the routine.

VB.NET:
[SIZE=2][COLOR=#0000ff]
Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1_Opening([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.ComponentModel.CancelEventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1.Opening
[/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu = [/SIZE][SIZE=2][COLOR=#0000ff]True
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

Well, it wasn't nothing spectacular but I am glad I wasn't the only one that learned something from it....
 
ok, closing event good, I tried every combination of mouse down/click/up events last night for strip and button, forgot to go for closure.
 
The entire process I used to prevent the Context Menu from Closing

Since I have recieved some request on this I will post the entire process I used and I will annotate what is going on.

First, Add this property to the form with the context menu.
VB.NET:
[SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] m_Cancel_Context_Menu [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean[/COLOR][/SIZE][SIZE=2] = [/SIZE][SIZE=2][COLOR=#0000ff]True[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff] 
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE][SIZE=2] Cancel_Context_Menu() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]     Get
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]          Return[/COLOR][/SIZE][SIZE=2] m_Cancel_Context_Menu
[/SIZE][SIZE=2][COLOR=#0000ff]     End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]     Set[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] value [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Boolean[/COLOR][/SIZE][SIZE=2])
          m_Cancel_Context_Menu = value
[/SIZE][SIZE=2][COLOR=#0000ff]     End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Set
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Property
[/COLOR][/SIZE]

Then, in the opening event of the Context Menu place this code along with any other code you might want.
VB.NET:
[SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1_Opening([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.ComponentModel.CancelEventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1.Opening
[/SIZE][SIZE=2][COLOR=#0000ff]     Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu = [/SIZE][SIZE=2][COLOR=#0000ff]True
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

Also, place this code in your the closing event of the Context Menu...
VB.NET:
[SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1_Closing([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Object[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Windows.Forms.ToolStripDropDownClosingEventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] ContextMenuStrip1.Closing
     e.Cancel = [/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

Now any button you click will keep the context menu open. So you need to add this code to any button you click that you want it to close on.
VB.NET:
[SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] CancelToolStripMenuItem_Click([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.EventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] CancelToolStripMenuItem.Click
[/SIZE][SIZE=2][COLOR=#0000ff]     Me[/COLOR][/SIZE][SIZE=2].Cancel_Context_Menu = [/SIZE][SIZE=2][COLOR=#0000ff]False
[/COLOR][/SIZE][SIZE=2][/SIZE][SIZE=2][COLOR=#0000ff]     Me[/COLOR][/SIZE][SIZE=2].ContextMenuStrip1.Close()
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2] [/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

Now, to explain this process here's what's going on.
First we built a property and added it to the form. This property is preset to "True" even though it dosn't have to be.

Anytime the form opens we set the property to "True". This property is assigned to the Cancel property in the closing event. So if the context menu attempts to close, then it's e.Cancel will be set to the Cancel_Context_Menu property which it "True" and will not close.

This is why you have to specify in the buttons that you wish to close on or programmatically however you choose when to close the context menu by setting the property to "False".

The only drawback is that when you click the button it first attempts to close the Context Menu then run the code, so even on the buttons that we set the property to "False" we don't close on first click. To make this happen wse simply set the Property to False the run the ContextMenu Close method.

You will see that there are many other options for making this happen and probably some more efficient ones but this is the one I chose for now and it works just great.

Another draw back is that the ContextMenu will never close unless you click one of the buttons you assigned to close it or do it else where programmatically. So what I also did was build a default Click event and assign a handle from every components' click event to it with the same code with the buttons that you choose to close the context menu with. So if you click anywhere on the form it will close the context menu. This still leaves you with the context menu showing if you click on another window or the desktop ect.....
 
ImDaFrEaK said:
Since I have recieved some request on this I will post the entire process I used and I will annotate what is going on.

I found a really nice solution to this that i think covers all the bases. Im posting it so you can check it out, maybe use it and possibly also pass it on or for others to use.

I managed to get a context menu whereby you can easily specify which items are checkboxes and hence:
Should not make the menu go away when interacted
Should toggle the state of their click when interacted

Interacted is defined as:
Clicked with the mouse
Moved to with the cursor keys and return pressed
The inline shortcut (underlined letter) key is pressed, e.g. F X for File.. Exit
The Ctrl/Alt/Shift key shortcut is pressed


After a few hours of messing and messing, I've got to that stage. Here's the annotated code in pretty colours (but not syntax colored cause the forum software makes a MESS when it does):

VB.NET:
Public Class frmContextMenuExample
 
    [COLOR="seagreen"]'these need copy/pasting too
    'these key list must be in uppercase
    'for every item on your menu put the keys in the relevant list
    'i.e. if you checkbox is shortcutted by Ctrl+G put a G in the Ctrl key list
 
    'if youre thinking of using numbers.. dont! things get messy when numpad
    'numbers are used. stick to letters![/COLOR]
    Private ctxMenuChkBoxInlineShorts As String = "CHE"
    Private ctxMenuChkBoxCtrlKeyShorts As String = "QWE"
    Private ctxMenuChkBoxAltKeyShorts As String = ""
    Private ctxMenuChkBoxShiftKeyShorts As String = ""
    Private ctxMenuChkBoxShortKeyPressed As Boolean = False
    Private ctxMenuClosingEvtMustCancel As Boolean = False
 
 
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        RegisterMenuCheckbox(Check1ToolStripMenuItem, ContextMenuStrip1)
        RegisterMenuCheckbox(Check2ToolStripMenuItem, ContextMenuStrip1)
        RegisterMenuCheckbox(Check3ToolStripMenuItem, ContextMenuStrip1)
    End Sub
 
 
 
 
    Private Sub TextBoxToolStripMenuItem_KeyPress _
    (ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBoxToolStripMenuItem.KeyPress
 
        [COLOR=seagreen]'here's how we make a menu go away and do something when we have a textbox and press return[/COLOR]
        If e.KeyChar = Convert.ToChar(Keys.Return) Then
            DirectCast(sender, ToolStripTextBox).PerformClick() 'close the context menu
            MessageBox.Show("Running search")
        End If
 
    End Sub
 
 
    Private Sub Form1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Click
 
        [COLOR="seagreen"]'heres how we show a context menu at the point of the mouse[/COLOR]
        If DirectCast(e, MouseEventArgs).Button = Windows.Forms.MouseButtons.Right Then
            ContextMenuStrip1.Show(Me, DirectCast(e, MouseEventArgs).Location)
        End If
 
 
    End Sub
 
 
 
 
 
 
 
   [COLOR="seagreen"] '!!!!!!!!everything after this line needs copy/paste into another form if you have a contextmenu to affect
    'as described[/COLOR]
 
    Private Sub chkBoxMenuItemPaint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs)
 
       [COLOR="seagreen"] 'checkbox items to which this handler is attached should not close the menu.
        'one of the ways we can tell if such an item is being interacted with is when
        'it gets painted - the menu items typically changed colour when being interacted
        'with and paint is fired as a result. at this time we can see if the item is
        'selected - the highlight that occurs when you roll the mouse over or use the
        'cursor keys to select it. there is a problem in that when we press a shortut key
        'i.e. the menu item text is &MenuItem and we press M - the problem is that the item
        'doesnt actually become selected but it experiences a paint. in this case, pressing
        'a key clicks our item and the menu closes. when we find a key has been pressed we
        'indicate this with a boolean so that when the paint occurs, whether the item is
        'selected or whether the keypress happened. [/COLOR][COLOR="seagreen"]ctxMenuChkBoxKeyShortPressed will be
        'switched to false by the itemclick event that will be happening soon[/COLOR]
        ctxMenuClosingEvtMustCancel = DirectCast(sender, ToolStripMenuItem).Selected Or ctxMenuChkBoxShortKeyPressed
    End Sub
 
 
    Private Sub contextMenuStripClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ToolStripDropDownClosingEventArgs)
 
       [COLOR="seagreen"] 'how did the menu come to want to close? if it was an itemclick such as click
        'or highlight+return or programmatic PerformClick() then we should check if it
        'needs cancelling
        'if we could detect here, which item it was that was clicked, our life would be
        'simple, but this event occurs before the ItemClick and sender is the context menu
        'itself so holds no clues :( [/COLOR]
        If e.CloseReason = ToolStripDropDownCloseReason.ItemClicked Then
            e.Cancel = ctxMenuClosingEvtMustCancel
        End If
 
        [COLOR="SeaGreen"]'make doubly sure we set to false here - if we dont do this then theoretically it can 
        'become set to true somehow, the user clicks outside the menu, the menu goes away but 
        'mustcancel remains true.. so the next time the menu is shown, it wont go away first time!
        'its also a catch-all: if the menu tries to close and there's an error in our logic
        'that we havent catered for, then the mustcancel will become false so the user can click
        'again and the menu will definitely go[/COLOR]
        ctxMenuClosingEvtMustCancel = False
 
    End Sub
 
 
 
    Private Sub chkBoxMenuItemClick(ByVal sender As System.Object, ByVal e As System.EventArgs)
        [COLOR="seagreen"]'this toggles the state of the checkbox[/COLOR]
        DirectCast(sender, ToolStripMenuItem).Checked = Not DirectCast(sender, ToolStripMenuItem).Checked
 
        [COLOR="seagreen"]'if the statetoggle was arrived by by keypress then switch off the keypress
        'this avoids the final problem i found whereby if the most recent key the user
        'pressed was a shortkey, then mustcancel would remain true, so the menu would remain
        'when the next item was clicked. it also good practice because it makes the menuitems
        'that are click-but-dont-hide-menu responsible for cleaning up "their own mess"[/COLOR]
        If ctxMenuChkBoxShortKeyPressed Then ctxMenuChkBoxShortKeyPressed = False
    End Sub
 
    [COLOR="seagreen"]'the previewkeydown event doesnt fire if we are typing into a textbox on the menu. this is useful[/COLOR]
    Private Sub contextMenuStripPreviewKeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PreviewKeyDownEventArgs)
 
       [COLOR="seagreen"] 'we code in at the top, which of our context menu items have shortcut key presses in
        'underlined style, which are ctrl or shift or altkey activated. it has a bug in that
        'if you make a menu item fire from TWO control keys (Ctrl+Alt+A for example) then it 
        'will assume set shortkeypressed to true needlessly. of course, you can adjust the logic
        'yourself, or just stick to not combining control keys[/COLOR]
        If ((Not (e.Alt Or e.Shift Or e.Control)) AndAlso ctxMenuChkBoxInlineShorts Like String.Format("*{0}*", Convert.ToChar(e.KeyCode))) OrElse _
           (e.Alt AndAlso ctxMenuChkBoxAltKeyShorts Like String.Format("*{0}*", Convert.ToChar(e.KeyCode))) OrElse _
           (e.Shift AndAlso ctxMenuChkBoxShiftKeyShorts Like String.Format("*{0}*", Convert.ToChar(e.KeyCode))) OrElse _
           (e.Control AndAlso ctxMenuChkBoxCtrlKeyShorts Like String.Format("*{0}*", Convert.ToChar(e.KeyCode))) Then
 
            [COLOR="seagreen"]'one of our watched keys was pressed[/COLOR]
            ctxMenuChkBoxShortKeyPressed = True
        End If
 
    End Sub
 
 
 
    Private Sub RegisterMenuCheckbox(ByVal tsmi As ToolStripMenuItem, ByVal onMenu As ContextMenuStrip)
        'handle paint events
        AddHandler tsmi.Paint, AddressOf chkBoxMenuItemPaint
        AddHandler tsmi.Click, AddressOf chkBoxMenuItemClick
 
        [COLOR="seagreen"]'we remove first to ensure only one menu handler for this event. if add is called multiple times
        'then the subs run multiple times, which destroys the logic[/COLOR]
        RemoveHandler onMenu.PreviewKeyDown, AddressOf contextMenuStripPreviewKeyDown
        AddHandler onMenu.PreviewKeyDown, AddressOf contextMenuStripPreviewKeyDown
        RemoveHandler onMenu.Closing, AddressOf contextMenuStripClosing
        AddHandler onMenu.Closing, AddressOf contextMenuStripClosing
    End Sub
 
 
End Class

it is also available as the attached zip file.


To use this in your own project:
Copy and paste the variables declarations area into your form
Copy and paste every sub after the comment "!!!!!!!!everything after this line"

In your form's load, repeatedly call RegisterMenuCheckbox for every toolstripmenuitem that is to be a checkbox behaviour, and the context menu it is on - this will dynamically bind the event handlers.. makes it easier for you to setup :)

Have a play with your new menu. The xample project contains a menu with several checkbox items, a click item, a combo item and a textbox item jsut for testing..
 

Attachments

  • Desktop.zip
    5.4 KB · Views: 39
great discussion in this thread, I thought I saw something about id3 tags somewhere here - I love those, so great for determination. Basically I use them to play music randomly based on genres (related genres actually :)). Well, there are lots of ways to skin a cat, about "context menu check box will not stay open" here is the easiest solution, you just gotta know those events and in what order they occur. My solution use two events from ContextMenuStrip, the ItemClicked and the Closing plus the Tag property of any menu item. The ItemClicked happen before Closing (where you may cancel the menu from closing). If you set the Tag property of some menu item to for instance "dontclose" and use the code below the context menu will cancel the closing on only those menu items that specify this simple instruction. (You may set the Tag property in Designer view, very convenient!)
VB.NET:
Dim dontclose As Boolean
 
Private Sub ContextMenuStrip1_Closing(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripDropDownClosingEventArgs) _
Handles ContextMenuStrip1.Closing
  If dontclose = True Then
    e.Cancel = True
    dontclose = False
  End If
End Sub
 
Private Sub ContextMenuStrip1_ItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) _
Handles ContextMenuStrip1.ItemClicked
  If e.ClickedItem.Tag = "dontclose" Then dontclose = True
End Sub
Minding the clue here - the CheckOnClick property that will check/uncheck the menu item when clicked - the click event will happen even if closing is cancelled and the automatic checkbox will occur as normal.
 
JohnH said:
VB.NET:
Private Sub ContextMenuStrip1_ItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) _
Handles [B]ContextMenuStrip1.ItemClicked[/B]
  If [B]e.ClickedItem[/B].Tag = "dontclose" Then dontclose = True
End Sub



Noooooooo....... *thud* *thud* *thud*

I cant believe i missed this one - i wrote in my long code this:

Private Sub contextMenuStripClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ToolStripDropDownClosingEventArgs)


'if we could detect here, which item it was that was clicked, our life would be
'simple, but this event occurs before the ItemClick and sender is the context menu
'itself so holds no clues :(



Which is, of course, not correct - we CAN detect which item was clicked through use of ItemClicked of the context menu rather than the menu item


I'll go back to banging my head on something else now, but thats a cool heads up.. cheers johnH (and why you gotte be right all the time dammit? :D)


PS;if anyone knows a reference source whereby the order of events for everything, is mapped out, i'd love to know.. i get so tired of adding a listbox to a form, and attaching a different event handler to every event i think might be useful, which makes a list entry, then reading the list...

i'd love for msdn to do this.. e.g. have a page for "ContextMenu Events"
"here are the events.." blahblah
"and here are the order they occur in when action X is performed"...

does it exist?
 
If you check the events of Control class in documentation some of them does a good job at listing the order of relevant events.
Control events: http://msdn2.microsoft.com/en-us/library/1dk48x94.aspx
see especially the Enter, Key.. and Mouse.. ranges of events.

As for the above ItemClicked-Closing it is not difficult to find when you know of the events, and these are only for one single control, but it is difficult to make general documentation of all kinds of inter-control events that could happen.
 
Great use of tags and yet another easier way to accomplish the goal. But hey, I put a lot of work into figuring this out to start with so don't make me look so bad with these simpler versions, lol. JK. Thanks for the info!
 
Back
Top