Logo HeaderGraphic
"... A Yankee in the Land of The Long White Cloud, Aotearoa ..."

Dynamic Context Menus for Tree Node items (Routing the Mouse Click Correctly) 

image 

So as you can see above I’m making a UI that looks and feels like an MMC Snap-In (eventually I will make it an actually MMC Snap-In, but that’s a different blog for a different day).  And just like a Snap-In, I want to make Dynamic Menus that change for each type of node selected in the tree (and the Actions Pane should change too).

The problem is that there is really no good way to do unique / dynamic context menu attached to different tree nodes with the built in tools Windows Forms engine (I haven’t looked in to WPF yet).  I’m not saying it can’t be done… just not elegantly.  As an added extra difficulty, you have the issue of making your menu do the “correct” thing for the “correct” node.  That is a Send Sheet Data command on one node IS NOT the same thing as a Send Sheet Data command on a different node.  Different nodes required different database lookups.  So I can’t just make a Context menu item for send sheet that will “know” what to do for all nodes, it has to be custom per node, and the event signature for a ContextMenuItem_Click doesn’t give you enough native information to determine “where” it came from.

Design constraint one; I don’t like duplication if I can avoid it, so each node needs to support the Refresh command, but I don’t want to make up 6 different Refresh commands (one for each type of node), I want one refresh command (and menu item) that can be attached to any node, and then when it is fired it is easy to determine which node needs to be refreshed.  Sure I could just refresh the whole tree / form, but we all know that would be just lazy and in-elegant.

So here was my solution.

First create an enum for each type of command you will want to issue

internal enum MenuItemAction {
    None = 0,
    Refresh = 1,
    AddSatellite = 2,
    SendSheetData = 3,
    RequestConfigData = 4,
    SendConfigData = 5,
    RequestSheetData = 6,
    CheckCommunications = 7,
    EditRinconNode = 8,
    DeleteRinconNode = 9
}

Now create a context menu for each re-usable / assignable grouping of actionsimage 

for example the CommonContextMenuStrip has just one item in my system.  Refreshimage

Now go to the properties window and put in a the enum value for action you want to associate with this menu

image

Ok so now you have all your menus ready to be assembled as you create each node in the tree.

The next step is to create a class where you can information you will need to respond to the menu click.

internal class MenuItemTagInformation {
    public MenuItemAction MenuItemAction { get; set; }
    public TreeNode TreeNode { get; set; }

    public MenuItemTagInformation ( MenuItemAction menuItemAction , TreeNode treeNode ) {
        MenuItemAction = menuItemAction;
        TreeNode = treeNode;
    }
}

You’ll notice that one of the properties of that class is the enumeration, and the other one is the TreeNode, to which you will associate the menuItem.

Ok, next up, you need to find each place you add a TreeNode, and as part of that add, you create the specific menu strip that you want to use for that node, set it’s values and hook up it’s event.  So for example:

var newNode = new TreeNode( controller.Name );
parentNode.Nodes.Add( newNode );

// we now create the new context menu we will attach to this tree node
var newMenu = DuplicateContextMenuStrip( newNode , this.CommonContextMenuStrip );

// and we tack on any other applicable context menus for this node, in this case there are 3
// other menu strips we need to include
DuplicateAndAppendToolStrip( newNode , this.SatellitesControllerContextMenuStrip , newMenu );
DuplicateAndAppendToolStrip( newNode , this.SatelliteSchedulingContextMenuStrip , newMenu );
DuplicateAndAppendToolStrip( newNode , this.NodeSettingsMenuStrip , newMenu );

//and assign the node the newly created strip
newNode.ContextMenuStrip = newMenu;

So le't’s take a look at that DuplicateContextMenuStrip and DuplicateAndAppendToolStrip procedures:

#region ' ContextMenu duplication and Support '

private ContextMenuStrip DuplicateContextMenuStrip ( TreeNode treeNode , ContextMenuStrip origMenu ) {
    var rtrn = new ContextMenuStrip( origMenu.Container );
    DuplicateAndAppendToolStrip( treeNode , origMenu , rtrn );
    return rtrn;
}

private void DuplicateAndAppendToolStrip ( TreeNode treeNode , ContextMenuStrip origMenu , ContextMenuStrip newMenu ) {
    DuplicateAndAddToolStrip( treeNode , origMenu.Items , newMenu.Items );
}

private void DuplicateAndAddToolStrip ( TreeNode treeNode , ToolStripItemCollection origItems , ToolStripItemCollection newItems ) {

    ToolStripItem newItem = new ToolStripSeparator( );

    if ( newItems.Count > 0 ) {
        newItems.Add( newItem );
    }

    foreach ( var item in origItems ) {
        newItem = new ToolStripSeparator( );

        var castItem = item as ToolStripMenuItem ;

        if ( castItem != null ) {

            newItem = new ToolStripMenuItem( castItem.Text , castItem.Image );

            int value;
            MenuItemAction action = MenuItemAction.None;

            //if this menu item has a tag
            if ( castItem.Tag != null ) {

                // if its tag is a menuItemTagInformation object, then the old menu item
                // has come from a context menu associated wiht a node, and is being used
                // to generate the Action Menu
                if ( castItem.Tag is MenuItemTagInformation ) {
                    action = ( ( MenuItemTagInformation )castItem.Tag ).MenuItemAction;

                // The other possiblity is that it comes from template toolstrips on the form
                // in which case the tag will contain the "value" of the enumeration (in string format)
                } else if ( int.TryParse( castItem.Tag.ToString( ) , out value ) ) {

                    action = ( MenuItemAction )value;
                }
                newItem.Tag = new MenuItemTagInformation( action , treeNode );
                newItem.Click += new EventHandler( this.ToolStripMenuItem_ClickSwitchBoard );
            }

			// now if this menu has it's own dropdown items, we need to add them as well
			// and we'll do it thru my favorite way, Recursion
            if ( castItem.DropDownItems != null && castItem.DropDownItems.Count > 0 ) {
                DuplicateAndAddToolStrip( 
						treeNode ,
						castItem.DropDownItems , 
						( ( ToolStripMenuItem )newItem ).DropDownItems
				);
            }

        }

        newItem.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;

        newItems.Add( newItem );

    }
}


#endregion // ToolStrip ContextMenu duplication

So the magic sauce in this is that we duplicate our Context menus using the original as a template, then we use the MenuItem’s tag to store the class that contains the information we need to figure out what we are doing; in this case the Enumeration of the action to take place and the tree node that the action should be preformed upon.

Let’s look at the event handler that each of the menu items was hooked up to (I’ll just give you a sample of the proc)

private void ToolStripMenuItem_ClickSwitchBoard ( object sender , EventArgs e ) {

    var castSender = sender as ToolStripMenuItem ;

    if ( castSender != null
            && ( castSender.Tag as MenuItemTagInformation ) != null ) {

        var nodeInfo = castSender.Tag as MenuItemTagInformation;

        switch ( nodeInfo.MenuItemAction ) {
            case MenuItemAction.None:
                break;
            case MenuItemAction.Refresh:
                refreshToolStripMenuItem_Click( nodeInfo );
                break;
            case MenuItemAction.AddSatellite:
                AddSatelliteMenuItem_Click( nodeInfo );
                break;

...

			case MenuItemAction.PutGetConfigCmdLnOnClpBrd:
				getSynchronizationCommandLineToolStripMenuItem_Click( nodeInfo );
				break;
			default:
				break;
		}

	}

}

And here it’s just a big switch board that takes you where you need to go.

Don’t know if this will be any help to you, but feel free to use it, if it’s any use to you.

Technorati Tags: ,,
Digg This
 
Posted on 15-Apr-09 by Matthew C. Hintzen
Bookmark this post with:        
Tags: