//
you're reading...
.NET 2.0

ContextMenuStrip redux

Earlier I asked why people were Googling for “ContextMenuStrip” so much, since it is the number 1 search term to hit my site. Colin Charnley saw my plea and posted an interesting question in the comments to that post:

I’m searching for help on using .NET 2.0’s ContextMenuStrip. I’ve assigned it to a ListView and it pops up fine when I right click on the ListView. Problem is, there seems no easy way to detect if the user clicked on a item in the listview (and which one) or just on the background of the ListView.

At its heart this basically boils down to how do you go about doing some preprocessing involving some Controls, the ContextMenuStrip, and it’s ToolStripMenuItem’s.

There are quite a few ways you can accomplish this and I’ll detail two different ways to do it. The rest are just variations on what I’ll show here.

Let the control do the work

The first is to use the ContextMenuStrip property as Colin is already doing. But also handle the Opened event on the ContextMenuStrip that is assigned to that control. Unfortunately this means you need to either have one ContextMenuStrip per control or figure out a way to determine which control was clicked and I don’t see a real obvious way of doing that.

Add the following code the Opened event handler:

ListViewItem item = null;

if( myListView.SelectedItems.Count > 0 )
    item = myListView.SelectedItems[ 0 ];

if( item != null )
{
    // Do something with the item selected
}
else
{
    // The user clicked in the background
}

This basically is all you need to find the item that was clicked on and now you can do what you want with the menu.

Like I said this is really only feasible if you are tying one ContextMenuStrip to one ListView control. If you want to use the same ContextMenuStrip across multiple controls (ListView or otherwise) then you need to do something a bit different.

It feels better when you do it yourself

In order to use the same ContextMenuStrip with multiple controls you’ll have to stop using the ContextMenuStrip property* on the control and handle everything yourself.

Its not all that hard actually, and it makes it pretty obvious when you can make your changes as well as what control you are working on.

For each of the controls that you want to display the context menu on, add a handler to the MouseUp event.

if( e.Button == MouseButtons.Right )
{
    Control c = sender as Control;

    // Sanity check to ensure that a Control called this method
    if( c != null )
    {
        ListView lv = sender as ListView;
        ListBox lb = sender as ListBox;
        // etc...

        // See which of the control types had the mouse-up event fire
        // if you want you could also compare sender to one of your existing 
        // controls rather than do it this way.
        if( lv != null )
        {
            // Work with the ListView
        }
        else if( lb != null )
        {
            // Work with the ListBox
        }
        // etc...

        // Finally show the menu, but we must pass in a point
        // relative to the screen.
        myContextMenuStrip.Show( c.PointToScreen( e.Location ) );
    }
}

If you want to, you could skip the call to show the ContextMenuStrip and just have the ContextMenuStrip property assigned on the control as in the first case, but if you change item text or even add/remove items you could see some display problems. This is caused because the context menu is already beginning to show by the time the MouseUp handler runs. If you add some trace statements to both the Opened and MouseUp events you will see that first Opened fires followed right behind MouseUp. And if you throw an exception from the MouseUp code you can see that it has already drawn the shadow for the menu and looks like it was just about to draw the items in the menu itself. At least that is what I saw on my notebook.

Considerations

If you use the second technique with a ListView control you may notice something odd when you dismiss the menu by clicking outside of its borders. It appears that if you dismiss the menu by left-clicking outside of the menu and your click is inside of the same control it fires another MouseUp event but it incorrectly reports that it was the right mouse button that was released so the code to show the menu runs yet again.

I am not aware of a work around, but this did not happen when I substituted a ListBox for the ListView control so I can only assume it is a problem with the combination of a ListView and the ContextMenuStrip, because I don’t remember hearing about this bug in v1.x of the framework.

I certainly hope this helps Colin out, if not I’ll post a more specific example than the ones I’ve given here.

Advertisements

About James

I am a Senior Developer/Consultant for InfoPlanIT, LLC. I previously spent over 7 years as a Product Manager for what eventually became ComponentOne, a division of GrapeCity. While there, I helped to create ActiveReports 7, GrapeCity ActiveAnalysis, and Data Dynamics Reports.

Discussion

5 thoughts on “ContextMenuStrip redux

  1. Thanks, that works for me. Went with your solution of leaving the ConextMenuStrip assigned to the ListView, but also handled the ListView’s MouseUp event so I could enable\disable menuItems. This got around any display problems which you mentioned.

    Also, after speaking to microsoft personnel on their newsgroup, they tell me there will be an “Opening” event in the VS2005 release verion which will make this simpler.

    Thanks for you help and lets hope they improve the control in the release version.

    Posted by Colin Charnley | September 23, 2004, 7:26 am
  2. Thanks, just what I was looking for!!

    Posted by Russ | November 21, 2006, 11:34 am
  3. ok ,fine ur comment

    Posted by raja | October 4, 2007, 1:39 am
  4. I realize that this page is a bit long in the tooth, but the information here is still valid and I found it to be useful.

    In that vein, I thought I would share something that I found that should add to what has been provided here.

    Starting in .Net 2.0 there is a property of the contextmenustrip called SourceControl. This property will provide you the last control that the menustrip was activated on. So, if you handle one of the events that fire such as Opening you can use the SourceControl property of the contextmenustrip to determine which control the menu was activated from.

    This will allow you to have a single menu that can service a number of controls and the code can have a way to determine which control was the target.

    I have the following code in my event handler for Opening:

    private void _context_Opening(object sender, CancelEventArgs e)
    {
    Object o = (Object)(sender as ContextMenuStrip).SourceControl;

    if (o is ToolStripRichTextBox.ToolStripRichTextBoxControl)
    {
    _currentObject = o;
    }
    else
    _currentObject = null;
    }

    And then I have this in the menu click event:

    private void _mnuClear_Click(object sender, EventArgs e)
    {
    if (_currentObject != null && _currentObject is ToolStripRichTextBox.ToolStripRichTextBoxControl)
    {
    ToolStripRichTextBox rtb = ((ToolStripRichTextBox.ToolStripRichTextBoxControl)_currentObject).Owner;
    rtb.Text = “”;
    }
    }

    A bit obscure, but it works nicely…

    Cheers,

    creo

    Posted by creo | April 6, 2009, 3:07 am
  5. Thanks for the info, found it to be great.

    Had one note to make that would have given Colin the ability to achieve what he wanted using less code.

    You Said “Unfortunately this means you need to either have one ContextMenuStrip per control or figure out a way to determine which control was clicked and I don’t see a real obvious way of doing that.”

    This is incorrect. Assuming the controls are similar you can do something similar to this in the toolStripMenuItem1_click handler:

    private void toolStripMenuItem1_click(object sender, EventArgs e)
    {
     CheckedListBox clb =  contextMenuStrip1.SourceControl as  CheckListBox; // you can even do this to a few different types and see which works, as your code does
     Point p = clb.PointToClient(new  Point(contextMenuStrip1.Left, contextMenuStrip1.Top));
    }
    So now point p gives you the coordinates in the actual Control that was clicked on. Which is where your code left us.

    Not sure if this is an addition since VS2008 but I can’t be bothered finding a project in VS2005 to test it against.

    Cheers, Chris.

    Posted by dwarfsoft | November 17, 2009, 1:39 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archive

%d bloggers like this: