Ask a questionAsk a question
 

General DiscussionCustomising standard context menu items

  • Monday, October 02, 2006 8:40 AMDuncanP - not MSFTModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Another thread (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=749450&SiteID=1) raised the question of how to remove or alter the standard Add / Delete context menu items for the diagram and model explorer. Here are some notes on how you might do this.

     

    Customising diagram menu commands

    Diagram context menu commands are created using the Visual Studio Command Table Compiler (“CTC”), and menu handlers are created and registered via the CommandSet.GetMenuItems. There are some built-in diagram context menu items, such as “Validate” and “Delete”.

     

    Some of the built-in diagram context menu item commands can be customised via virtual methods on the DslPackage.CommandSet class. For example, you can control whether or not the “Delete” context menu appears by overriding the “ProcessOnStatusDeleteCommand” method, and you can control what happens when the item is selected by overriding the “ProcessOnMenuDeleteCommand” method. As an added bonus, the code processing the “Del” key is also routed to these methods, so the same methods allow you control whether items can be deleted via the keyboard.

     

    Unfortunately, you do not appear to be able customize all of the standard menu items in this way. For example, there do not appear be virtual methods that allow you alter the “Add” item menus, or the “Add before” / “AddAfter” commands for swimlanes.

     

    However, you can partially work around this, although the process is more complicated. The command handlers for the standard menu items are added in the base CommandSet from which the target designer CommandSet inherits. You can access the handlers for these standard commands and replace them with your own command handlers. The drawback with this method is you can’t delegate to the standard “OnStatusX” and “OnExecuteX” commands because they are private, so you have to replace the methods entirely with your own versions.

     

    The following code works with a language called “ExampleTaskLanguage1” based on the “Task flow” template, and shows how to remove the “Add before” and “Add After” diagram context menu commands for Actor swimlanes:

     

    using System;
    using System.Collections.Generic;
    using System.Text;

    using Microsoft.VisualStudio.Modeling.Shell;
    using System.ComponentModel.Design;

    namespace DslWorkshop.ExampleTaskLanguage1
    {
        partial class ExampleTaskLanguage1CommandSet
        {

            #region Code to suppress "Delete" for Actor swimlanes

            /// <summary>
            /// Override to stop the "Delete" command appearing for
            /// Actor swim lanes.
            /// </summary>
            protected override void ProcessOnStatusDeleteCommand(System.ComponentModel.Design.MenuCommand command)
            {
                // Check the selected items to see if they contain
                // and Actor swimlane. NB we are looking for the
                // swimlane shape here, not an Actor model element.
                if (CurrentSelectionContainsShape(typeof(ActorSwimLane)))
                {
                    // Disable the menu command
                    command.Enabled = false;
                    command.Visible = false;
                }
                else
                {
                    // Otherwise, delegate to the base method.
                    base.ProcessOnStatusDeleteCommand(command);
                }
            }

            /// <summary>
            /// Returns a flag indicating whether the current
            /// diagram selection includes a shape of the
            /// specified type
            /// </summary>
            private bool CurrentSelectionContainsShape(Type shapeType)
            {
                // Parameter check
                if (shapeType == null) { throw new ArgumentNullException("shapeType"); }

                foreach (object item in this.CurrentDocumentSelection)
                {
                    if (item.GetType() == shapeType) { return true; }
                }
                return false;
            }


            #endregion

     

            #region Code to suppress "AddBefore/After" for Actor swimlanes
           
            /// <summary>
            /// Override to prevent the swimlane "AddBefore/After" menu
            /// items from appearing on the context menu.
            /// </summary>
            /// <returns></returns>
            protected override IList<System.ComponentModel.Design.MenuCommand> GetMenuCommands()
            {
                IList<MenuCommand> commands = base.GetMenuCommands();

                // Remove the default "Swimlane" menu commands and replace them
                // with new verions. NB you can't just remove the commands from
                // the collection, or you will get a package load failure.

                MenuCommand command;

                // Replace the default Swimlane "AddBefore" menu command
                command = GetCommandByID(commands, CommonModelingCommands.SwimlaneAddBefore);
                if (command != null)
                {
                    // Remove the built-in command
                    commands.Remove(command);

                    // Add our own version
                    command = new DynamicStatusMenuCommand(new EventHandler(OnStatusSwimlaneCommand), new EventHandler(OnSwimlaneCommandInvoke), CommonModelingCommands.SwimlaneAddBefore);
                    commands.Add(command);               
                }


                // Replace the default Swimlane "AddAfter" menu command
                command = GetCommandByID(commands, CommonModelingCommands.SwimlaneAddAfter);
                if (command != null)
                {
                    // Remove the built-in command
                    commands.Remove(command);

                    // Add our own version
                    command = new DynamicStatusMenuCommand(new EventHandler(OnStatusSwimlaneCommand), new EventHandler(OnSwimlaneCommandInvoke), CommonModelingCommands.SwimlaneAddAfter);
                    commands.Add(command);

                }
                return commands;
            }

            /// <summary>
            /// Returns the command with the specified id from
            /// the collection, or null if a match isn't found.
            /// </summary>
            private MenuCommand GetCommandByID(IList<MenuCommand> commands, CommandID commandId)
            {
                // Paramter check
                if (commands == null) { throw new ArgumentNullException("commands"); }

                foreach (MenuCommand cmd in commands)
                {
                    if (cmd.CommandID == commandId) { return cmd; }
                }
                return null;
            }


            /// <summary>
            /// New "AddBefore/After" status handler. Always
            /// disables the menu command.
            /// </summary>
            private void OnStatusSwimlaneCommand(object sender, EventArgs e)
            {
                MenuCommand cmd = sender as MenuCommand;
                if (cmd != null)
                {
                    cmd.Visible = false;
                    cmd.Enabled = false;
                }
            }


            private void OnSwimlaneCommandInvoke(object sender, EventArgs e)
            {
                // Do nothing (should never be called)
            }

            #endregion

        }
    }

     

    Note that you cannot simply delete commands that you don’t want in “GetMenuCommands” – the commands are still in the CTC table, so Visual Studio expects a handler to be registered for them. If you just want to remove an unwanted command, you should also edit the generated CTC entries. Search in your project for the string “#define GENERATED_CMDPLACEMENT”. The problem with this approach is that it means you will need to modify one of the standard “include” files. It is probably simpler to leave the command in the CTC file and just register a handler of your own that always makes the command disabled and invisible.

     

     

    Customising ModelExplorer context menu items

    Customising ModelExplorer context menu items is similar to customising diagram context menu items. In both cases there are virtual methods you can override to alter the behaviour of some (but not all) of the standard menu items, and both have a base class that registers handlers for the standard commands. In the case of the model explorer items, the command handlers are registered in the method “[MyLanguageName]Explorer.AddCommandHandlers”.

     

    The following code shows how to alter the context menus for commands to remove the “Add” command, the “Delete” command for Actors, and the “DeleteAll” command for the “Actors” node.

     

    using System;
    using System.Collections.Generic;
    using System.Text;

    using Microsoft.VisualStudio.Modeling.Shell;
    using System.ComponentModel.Design;

    namespace DslWorkshop.ExampleTaskLanguage1
    {
        partial class ExampleTaskLanguage1Explorer
        {

            #region Suppress "Add" context menu item

            /// <summary>
            /// Override to remove the "Add" context menu item from the ModelExplorer.
            /// </summary>
            public override void AddCommandHandlers(System.ComponentModel.Design.IMenuCommandService menuCommandService)
            {
                // NB this removes the Add command for all nodes / domain classes. Haven't
                // worked out how to be more selective than this yet!
               
                // Let the base class set up the command handlers
                base.AddCommandHandlers(menuCommandService);

                // Find and remove the add command
                MenuCommand addCommand = menuCommandService.FindCommand(CommonModelingCommands.ModelExplorerAddModelElement);
                if (addCommand != null) { menuCommandService.RemoveCommand(addCommand); }
            }

            #endregion


            #region Suppress delete for Actors

            /// <summary>
            /// Override to stop Actor elements from being deleted
            /// </summary>
            protected override void ProcessOnStatusDeleteCommand(System.ComponentModel.Design.MenuCommand cmd)
            {
                if (SelectedElement is Actor)
                {
                    cmd.Enabled = false;
                    cmd.Visible = false;
                }
                else
                {
                    base.ProcessOnStatusDeleteCommand(cmd);
                }
            }

     

            /// <summary>
            /// Override to stop the "Delete All" option being available
            /// for the "Actors" node.
            /// </summary>
            protected override void ProcessOnStatusDeleteAllCommand(System.ComponentModel.Design.MenuCommand cmd)
            {
                if (this.SelectedRole != null && this.SelectedRole.Name == "Actor")
                {
                    cmd.Enabled = false;
                    cmd.Visible = false;
                }
                else
                {
                    base.ProcessOnStatusDeleteAllCommand(cmd);
                }
            }


            #endregion
        }
    }
     

     

    NB there *appears* to be a small bug in the behaviour of the context menus – if you don’t select a node before right-clicking on it, then the “ProcessOnStatusDeleteCommand” method isn’t called, which can lead to “Delete All” being visible when you don’t want it to be. One workaround for this is to register an event handler for the treeview’s “AfterSelected” event, and to manually call “OnProcessStatusDeleteCommand” from the handler. I’ll see if I can confirm whether this is a bug or not and will post an update.

     

    Duncan

     

All Replies

  • Thursday, May 15, 2008 12:07 PMbenjamin.s Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hello Duncan

    What happened in the last two years with the maybe-bug you mention at the end of this posting? Is that something I have to worry about in the current release or can I ignore these things today?

    I think it is a little bit bad, that I have to write the same code twice to prevent a user from deleting elements. I have to do nearly the same for the editor command and for the model explorer. I think it would be a good idea to have somewhere in the model a “bool CanRemove()”. (You bring up this idea in 2005 here: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=73606&SiteID=1). Are there any plans to have such a function in the upcoming releases of DSL Tools? I could also imagine having something like the EMD (Element merge directive) for deleting (an EDD).

    I don’t like to raise an exception in the DeleteRule because I don’t want the user to see a Delete menu item if he is never allowed to delete some element.

    Benjamin

  • Thursday, May 15, 2008 7:38 PMbenjamin.s Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    …it’s me again

    I tested it and it seems there is still the same behavior today in the DSL Tools for Orcas.

    I wrote some code to prevent the user from deleting special Model Elements (removing the Delete command in the graphical editor and in the Model Explorer and the Delete All command as well). I also implemented the fix you described.

    All can be found at http://www.ticklishtechs.net/2008/05/15/preventing-model-elements-from-being-deleted/