Best Practice for Adding Commands to Visual Studio .NET

 

One of the most important considerations to increase the performance of Visual Studio .NET is to delay the loading of VSPackages. Loading a VSPackage DLL is expensive from a file I/O point of view, adding extra memory pressure to the working set, and execution time of running more code. There are many delay loading techniques employed by VSPackages depending on the features that the VSPackage provides. For VSPackages that provide global commands, the goal is that the commands are displayed in the UI (on menus, toolbars, and context menus) without loading the VSPackage DLL. Only when the user executes one of the commands is the VSPackage DLL loaded. In many cases the commands are always available statically. This case is easy. The commands are defined in the .CTC file of the VSPackage without specifying the DYNAMICVISIBILITY flag. The following example shows an always available “View.HelloWorldTool” command (guidToolWnCmdSet:icmdHelloWorld ) that is owned by a VSPackage (guidToolWnPkg).

           

Using CMDS_SECTION

CMDS_SECTION guidToolWnPkg

    BUTTONS_BEGIN

      // GUID:CMDID GROUP:ID PRIORITY ICON TYPE FLAGS

// BUTTON_TEXT MENU_TEXT TOOLTIP_TEXT COMMAND_NAME

      guidToolWnCmdSet:icmdHelloWorld, guidSHLMainMenu:IDG_VS_VIEW_BROWSER, 0x0100, guidOfficeIcon:msotcidNoIcon, BUTTON, , "&Hello World Tool";

            // This icmdHelloWorld is not defined as a shared

// command, so it uses a custom guidToolWnCmdSet

            // as the command set GUID. Also, by specifying

// blank for the FLAGS, this command is

            // default visible and enabled. Other valid values

// for FLAGS are the following:

            // DEFAULTDISABLED, DEFAULTINVISIBLE,

// DYNAMICVISIBILITY, TEXTCHANGES

            // These values for FLAGS can be or'ed together,

// e.g. "DEFAULTINVISIBLE | DYNAMICVISIBILITY"

    BUTTONS_END

CMDS_END

 

 

 

In many cases, the visibility of the command needs to be dynamic; it depends on the current context/selection of the UI. The ideal visibility of a command may be tied to many different factors. The perfect showing of the command is most easily managed by implementing IOleCommandTarget::QueryStatus, but this requires that the VSPackage DLL is loaded in order to get a chance to execute code. The preferred strategy is to allow the IDE to manage the visibility of the VSPackage commands without loading the VSPackage. In order to do this, the visibility of a command can be declared in the .CTC file as being tied to one or more special UI Contexts that are identified by a GUID called a “CmdUI Context GUID”. The IDE automatically tracks some UI Contexts:

  • Active Project Type UI Context. (By convention, for most project types this GUID value is the same as the GUID of the VSPackage that implements the project. VCProjects on the other hand use the Project Type GUID as the value.)
  • Active Window UI Context. (Normally this will be the last active document window that establishes the current UI Context for key bindings but in special cases this might be a tool window that has a key binding table like the internal web browser. For multi-tabbed document windows like the HTML editor there will be a different CmdUI GUID for each tab.)
  • Active Language Service associated with a Text Editor

 

In addition there are other modes that are identified by active CmdUI GUIDS:

  • GUID UICONTEXT_SolutionBuilding
  • GUID UICONTEXT_Debugging
  • GUID UICONTEXT_Dragging
  • GUID UICONTEXT_FullScreenMode
  • GUID UICONTEXT_DesignMode
  • GUID UICONTEXT_NoSolution
  • GUID UICONTEXT_SolutionExists
  • GUID UICONTEXT_EmptySolution
  • GUID UICONTEXT_SolutionHasSingleProject
  • GUID UICONTEXT_SolutionHasMultipleProjects
  • GUID UICONTEXT_CodeWindow

 

A VSPackage can arrange to have a command visibility automatically managed by the IDE without loading their VSPackage DLL by defining the command in its CTC file with “DEFAULTDISABLED | DEFAULTINVISIBLE | DYNAMICVISIBILITY” flags and also adding one or more entries in the VISIBILITY_SECTION. When one of the specified CmdUI Context GUID’s is turned on then the command will be made visible and enabled automatically without actually loading the VSPackage and calling its QueryStatus implementation. In many cases, it is not possible to get the perfect visibility/enabling of a command in the manner. It is still better tradeoff to have reasonably good dynamic enabling of the command that may sometimes show the command when it should not be, then to force the early loading of the VSPackage so that it can handle QueryStatus. In some cases the use may get an error message saying that the command really did not apply. But from then on, the command enabling will be perfect because the VSPackage will then be loaded. The overall performance increase by only loading VSPackages when the user really needs them is more important than sometimes showing a command when it really does not apply. In some cases if an appropriate CmdUI Context is not already defined, it is better to add code to an already loaded VSPackage to turn on a new CmdUI Context GUID so that other VSPackages can avoid being loaded early. The SID_SVsShellMonitorSelection/IID_IVsMonitorSelection service can be used to turn CmdUIContext GUID’s on and off (see GetCmdUIContextCookie, IsCmdUIContextActive, and SetCmdUIContext methods).

 

The following is an example of a VSPackage command taken from the SlnExt sample that has dynamic visibility of a command managed by CmdUI Contexts without loading the VSPackage. In this case the command is the “Project.AddNewSolutionNote command that is set to be enabled and visible whenever a Solution exits (i.e. when one of the following CmdUI Context GUID’s is on: UICONTEXT_EmptySolution, UICONTEXT_SolutionHasMultipleProjects, or UICONTEXT_SolutionHasSingleProject).

 

Using VISIBILITY Section

CMDS_SECTION guidSlnExtPkg

  BUTTONS_BEGIN

      // GUID:CMDID GROUP:ID PRIORITY ICON TYPE FLAGS

// BUTTON_TEXT MENU_TEXT TOOLTIP_TEXT COMMAND_NAME

guidSlnExtCmdSet:icmdAddNote, guidSlnExtMenuGrp:IDG_SLNNOTES,

0x0100, guidOfficeIcon:msotcidNoIcon, BUTTON,

DEFAULTDISABLED | DEFAULTINVISIBLE | DYNAMICVISIBILITY,

"Add New &Solution Note";

  BUTTONS_END

CMDS_END

VISIBILITY_SECTION

    // Command GUID when visible

    guidSlnExtCmdSet:icmdAddNote, UICONTEXT_EmptySolution;

    guidSlnExtCmdSet:icmdAddNote, UICONTEXT_SolutionHasMultipleProjects;

    guidSlnExtCmdSet:icmdAddNote, UICONTEXT_SolutionHasSingleProject;

VISIBILITY_END

 

 

The following is an excerpt from the VSIP documentation on the VISIBILITY_SECTION:

The VISIBILITY_SECTION – VISIBILITY_END section determines the dynamic visibility of commands. Each entry identifies a command and an associated command UI context. A command listed in the VISIBILITY_SECTION – VISIBILITY_END section will appear only with the associated context. This section only applies to commands and toolbars, not to groups. Commands not included in this section are visible whenever their parent menu is active. A command may be associated with multiple command UI contexts by including an entry for each command/context combination.

Each entry in the VISIBILITY_SECTION – VISIBILITY_END section contains two fields. They are, in order:

  • GUID:CmdId

The GUID:CmdId field identifies the command or menu for which you want to control static visibility.

  • GUID

The GUID field identifies the command UI context to associate with the GUID:CmdId field. When this command UI context has focus, the command or menu is visible.

Commands must have the DEFAULT_INVISIBLE and DYNAMIC VISIBILITY flags set in order for entries in the VISIBILITY_SECTION – VISIBILITY_END section to take effect. Toolbars have DYNAMIC_VISIBILITY by default.

Comments