Dotneteers.net
All for .net, .net for all!

LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture

In the last week I was at MVG Global Summit 2008 held in Seattle and Redmond. I had fun there, I got many information about future directions on Microsoft development products and met with other MVPs. I also had a dinner with Ken Levy, the PM of the VSX Team and had a great chat about community and VS futures.

Due to the jet lag of nine hours I got awaken every morning—well, if we can call it morning at all—at about 4 o’clock. To spend my time till the hotel bar opens for breakfast I looked at the new release of PowerCommands made by Pablo Galiano. I found this free tool great not just because of its functionality but also due to the high quality source code that is also downloadable.

I think this tool is a good candidate for a deep dive since community members can learn a lot from the patterns and constructs used within. I have searched for deep dive articles on PowerCommands but I did not find any. So I decided to dig into the source code and tell you all important things that—as I guess—can be useful when learning VSX programming.

However, this article is intended to be a deep dive I summarize many information that can help even beginners to understand what is behind at certain concepts.

Values in PowerCommands source

There are many great patterns in the PowerCommands source code that can help to understand many aspects of VSPackage development and also patterns that offer architectural guidelines for packages. Without the need of completeness, here is a list:

—  VSPackage programming basics

—  Adding custom branding to packages

—  Declaring and consuming custom services

—  Consuming Visual Studio events

—  VSPackage patterns

—  Binding commands with menu items

—  Creating and using option pages

—  Other useful patterns

—  Using C# 3.0 syntax features (extension methods, lambda expressions, etc.)

—  Good examples of how LINQ queries make expressing intentions easy.

Overview of PowerCommands architecture and solution structure

Let us start the deep dive at the architecture of PowerCommands that is quite simple, as you can see in the following figure:

The VSPackage owns a few object types that provide the functionality. Each Command providing the functionality of PowerCommands is independent from the others, they do not interact directly with each other. A few Command objects have an associated UI. For manageability reasons commands are collected into a container named CommandSet. The package contains few Services to manage commands and related functionality. PowerCommands can be customized through options that can be set on Option Pages. A few commands require responding to VS IDE events. These events are watched and handled by Event Listeners.

The downloadable source code contains a solution with one Visual Studio Integration Package project. The source files within this project are very well structured; the following table gives you an overview about what can be found in a specific source folder:

Source folder Content
Commands

Each command is represented by a separate class. This folder contains these classes.

Commands\Base

The folder has only one file, DynamicCommand.cs that holds the common root class of command classes.

Commands\UI

This folder holds the user interfaces associated with commands. These UIs are implemented as WPF forms.

Common

A few general helper classes.

Extensions

Helper classes containing extension methods.

Linq

PowerCommands intensively uses LINQ. This folder contains helper classes handling the project hierarchy with iterators and LINQ queries.

Listeners

Classes in this folder implement listener functions.

Mvp

The UI of PowerComands follows the Model-View-Presenter pattern. This folder provides basic implementation types representing that design pattern.

OptionPages

The folder holds the types representing the option pages.

Resources

This folder contains the bitmap and icon files used in the package.

Services

Services used within this VSPackage

Shell

Helper classes for interacting with a few shell-owned object types.

ToolWindows

A folder for tool windows used within PowerCommands.

The PowerCommands VSPackage

To understand how PowerCommands work the best start is to have a look at the structure of its package file named PowerCommandsPackage.cs. The decorating attributes tell us a lot about what the package provides when integrating into Visual Studio:

[PackageRegistration(UseManagedResourcesOnly = true)]

[ProvideLoadKey("Standard", "1.0", "PowerCommands for Visual Studio 2008",

  "Microsoft PLK", 1)]

[InstalledProductRegistration(true, "#9394", "#25288", "1.0",

  IconResourceID = 57077,

  LanguageIndependentName = "PowerCommands for Visual Studio 2008")]

[DefaultRegistryRoot(@"Software\Microsoft\VisualStudio\9.0")]

[ProvideMenuResource(1000, 1)]

[ProvideAutoLoad("{ADFC4E64-0397-11D1-9F4E-00A0C911004F}")]

[ProvideService(typeof(SCommandManagerService),

  ServiceName = "CommandManagerService")]

[ProvideService(typeof(SUndoCloseManagerService),

  ServiceName = "UndoCloseManagerService")]

[ProvideProfileAttribute(typeof(CommandsPage), "PowerCommands", "Commands", 15600,

  1912, true, DescriptionResourceID = 197)]

[ProvideOptionPageAttribute(typeof(CommandsPage), "PowerCommands", "Commands",

  15600, 1912, true)]

[ProvideProfileAttribute(typeof(GeneralPage), "PowerCommands", "General", 15600,

  4606, true, DescriptionResourceID = 2891)]

[ProvideOptionPageAttribute(typeof(GeneralPage), "PowerCommands", "General", 

  15600, 4606, true)]

[ProvideToolWindowVisibility(typeof(UndoCloseToolWindow),

  "{F1536EF8-92EC-443C-9ED7-FDADF150DA82}")]

[ProvideToolWindow(typeof(UndoCloseToolWindow), MultiInstances = false,

  Style = VsDockStyle.Tabbed,

  Orientation = ToolWindowOrientation.Top,

  Transient = true,

  Window = "{D78612C7-9962-4B83-95D9-268046DAD23A}")]

[Guid("24E33DBF-CADF-4DA8-ACFE-566366FC8468")]

public sealed class PowerCommandsPackage : Package, IVsInstalledProduct

{

  // --- Package body

}

PowerCommandsPackage implements the IVsInstalledProduct interface to add branding information about the package to the splash screen and to the Help|About dialog. The class is decorated with many package attributes. I treat the highlighted attributes in a few details.

Attribute Description
PackageRegistration

The regpkg.exe utility scans types for this attribute to recognize that the type should be registered as a package. Adding this attribute to our class, regpkg.exe will handle it as a package and looks for other attributes to register the class according to our intention. In our example this attribute sets the UseManagedResourcesOnly flag to tell that all resources used by our package are described in the managed package and not in a satellite .dll.

ProvideLoadKey

This attribute defines the Package Load Key and the base information used to generate the PLK. The first four parameter of the attribute defines the following elements:

—  Minimum VS edition expected

—  Version of the product (package)

—  Name of the product (package)

—  Company name (owner/developer).

The fifth parameter is the ID of the resource holding the PLK.

InstalledProductRegistration

This attribute is responsible to provide information to be displayed by the Help|About function in the VS IDE. The constructor of this attribute requires four parameters with the following meanings:

—  The first false indicate that out package does not provide its own UI to display information about the package.

—  The second and third strings provide the name and the description of the package. The “#” characters indicate that these values should be looked up in the package resources with the IDs following the # character.

—  The fourth “1.0” parameter is the product ID (version number).

—  IconResourceID tells which icon to use for the package.

—  LanguageIndependentName holds the non-localized name of this package.

The resources (name, description and icon) should be defined in the VSPackage.resx file.

DefaultRegistryRoot

To provide an easy way to develop and debug Visual Studio components, VS provides a quite simple method: it allows naming a registry root in the startup parameters of the devenv.exe (that is the VS IDE). When we run a VS component in debug mode, our component actually runs in an IDE using the so-called experimental hive. This hive uses different settings for our debug environment.

When we use the “Start Debugging” function of VS, it uses regpkg.exe with a command line parameter to register out package for the experimental hive.

The DefaultRegistryRoot attribute names the registry root to be used for the package registration if the corresponding regpkg.exe command line parameter is not used. In our example this is the registry root of the “normal” VS 2008 IDE.

ProvideMenuResource

This attribute has two parameters. The first is the so-called resourceID. This value should be set to 1000 as the VSCT compiler uses the value of 1000 by default when adding the binary representation of the .vsct file to the VSPackage resource.  The second parameter is called versionID and it plays important role in the caching mechanism of resources. Instead going into details right now, please keep in mind that ProvideMenuResource attribute allows the package to register its menu resources.

ProvideAutoLoad

VSPackages arte on-demand loaded: VS loads them when first the package is about to be used. This attribute explicitly tells the VS Shell to load the package automatically when it enters into a specific context. The GUID parameter of the attribute is the so-called NoSolution context; this is the default context after starting VS.

This attribute is required since PowerCommands listens to solution events. The listeners can only be created if the package is initialized. If we did not use this attribute, the events related to the first solution opened might not be caught by the package. You can read more about this mechanism in LVN! Sidebar #1.

ProvideService

This attribute is used to proffer services implemented by this package to be consumed by other VSPackages. PowerCommands proffers two services. The attribute specifies the type that can be used to request the service and also sets the name of the service.

ProvideOptionPage

This attribute is used by regpkg.exe to register an option page for the package. The parameters are the followings:

—  Class representing the option page UI

—  Non-localized option page category name

—  Non-localized option page name under the category

—  Resource ID for the category name

—  Resource ID for the page name

—  Flag indicating that the option page can be accessed through the VS automation mechanism.

In the package we use two option pages to customize the behavior of commands.

ProvideProfile

The parameters set on the option pages can be saved to VS settings. This attribute sets up which settings should be saved. The first five parameters have the same meaning as in case of ProvideOptionPage attribute. The closing true parameter value describes that a corresponding option page belongs to the setting. The DescriptionResourceID sets the resource ID used for the setting group description.

Both option pages used by PowerCommands have a corresponding ProvideProfile attribute.

ProvideToolWindow

This attribute is used by regpkg.exe to register a tool window for the package. The first parameter defines the type implementing the tool window. Multinstances is set to false, so we have only a singleton instance of this tool window. The Style parameter sets that the tool window is to be tabbed by default. Orientation sets that the tool window is tabbed at the top of the window identified by the Window parameter.

The Transient property is set to true telling the IDE it should not show tool window if VS is opened (even if it was displayed before closing the IDE).

The GUID used for the Window parameter refers to the Error List window.

ProvideToolWindowVisibility

This attribute sets the context in which the specified tool window is visible. The first parameter is the type of the tool window; the second is the GUID of the context. In this case the specified GUID refers to the so-called SolutionExists context.

It means that our tool window is automatically hidden when the current solution is closed.

The internal package structure is simple as it can be seen from its prototype:

public sealed class PowerCommandsPackage : Package, IVsInstalledProduct

{

  // --- Properties

  public CommandsPage CommandsPage { get; }

  public GeneralPage GeneralPage { get; }

  public IUndoCloseManagerService UndoCloseManager { get; }

  public DTE Dte { get; }

  public UndoCloseToolWindow UndoCloseToolWindow { get; }

 

  // --- IVsInstalledProduct implementation

  public int IdBmpSplash(out uint pIdBmp);

  public int IdIcoLogoForAboutbox(out uint pIdIco);

  public int OfficialName(out string pbstrName);

  public int ProductDetails(out string pbstrProductDetails);

  public int ProductID(out string pbstrPID);

 

  // --- Lifecycle methods

  protected override void Initialize();

  protected override void Dispose(bool disposing);

 

  // --- Event Handlers

  int RDTListener_AfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame);

  int RDTListener_BeforeSave(uint docCookie);

  void documentListener_DocumentClosing(Document Document);

  int solutionListener_AfterOpenSolution(object pUnkReserved, int fNewSolution);

  int solutionListener_AfterCloseSolution(object pUnkReserved);

 

  // --- Private implementation methods

  private Document GetDocumentToBeSaved(uint docCookie);

  private object CreateCommandManagerService(IServiceContainer container,

    Type serviceType);

  private object CreateUndoCloseManagerService(IServiceContainer container,

    Type serviceType);

  private string GetResourceString(string resourceName);

  private string GetViewKind(Document document);

}

Here I do not go into the implementation details of all members above, I will treat them later at the corresponding topic. For example, when I detail how the Undo Close File command works I describe the related event handler methods. However, to understand how the package works, I show the code for the Initialize method:

protected override void Initialize()

{

  base.Initialize();

 

  // --- Initialize services defined within the package

  (this as IServiceContainer).AddService(

      typeof(SCommandManagerService),

      new ServiceCreatorCallback(CreateCommandManagerService),

      true);

 

  (this as IServiceContainer).AddService(

      typeof(SUndoCloseManagerService),

      new ServiceCreatorCallback(CreateUndoCloseManagerService),

      true);

 

  // --- Init the set of commands available within PowerCommands

  CommandSet commandSet = new CommandSet(this);

  commandSet.Initialize();

 

  // --- Initialize an RTDListener

  RDTListener = new RDTListener(this);

  RDTListener.Initialize();

  RDTListener.BeforeSave +=

    new RDTListener.OnBeforeSaveHandler(RDTListener_BeforeSave);

  RDTListener.AfterDocumentWindowHide +=

    new DTListener.OnAfterDocumentWindowHideEventHandler(

    RDTListener_AfterDocumentWindowHide);

 

  // --- Initialize an SolutionListener

  solutionListener = new SolutionListener(this);

  solutionListener.Initialize();

  solutionListener.AfterCloseSolution +=

    new SolutionListener.OnAfterCloseSolutionHandler(

    solutionListener_AfterCloseSolution);

  solutionListener.AfterOpenSolution +=

    new SolutionListener.OnAfterOpenSolutionHandler(

    solutionListener_AfterOpenSolution);

 

  // --- DocumentListener is responsible for handling document related events.

  documentListener = new DocumentListener(this);

}

First the services are initialized later when I treat them, I explain, how initialization works. The initialization of commands is done by the Initialize method of the CommandSet class that uses the SCommandManagerService. Before the service can be used, its initialization should be done.

Then the listener types are initialized. Due to the ProvideAutoLoad attribute, the package is loaded into the memory after Visual Studio is started and then the Initialize method is called. It is important in order the SolutionListener can work correctly, since it subscribes to the event representing the solution is opened. If PowerCommands were not loaded into the memory by Visual Studio startup, the Initialize method would not run unless one of the available commands or object within the package is requested. In this case we could “lose” a few AfterOpenSolution events and that would prevent a few commands work appropriately.

Command handling

The individual commands within PowerCommands are independent. PowerCommands provides a simple infrastructure to keep track of available commands. This helps in a few common tasks, like enabling or disabling each command separately, enumerating commands, etc. The architecture of command handling is summarized in the following figure:

 

The heart of the command handling architecture is the CommandManagerService class that provides the ICommandManagerService to keep track of registered commands. Through this service commands can be registered. The PowerCommandsPackage class uses the CommandSet type to interact with the CommandManagerService. For each command an appropriate instance class is created. When the CommandSet class registers a command instance, it also binds that instance with ID of the commands used by the VS Shell. Through this ID the VS Shell can invoke the command.

The CommandManagerService

The implementation of this service follows the service design pattern recommended by the VS SDK. The elements of this pattern can be found in the files of the Service folder:

// --- ICommandManagerService.cs

[Guid("31A6E058-A531-41CC-B385-B6FAB536066A")]

[ComVisible(true)]

public interface ICommandManagerService

{

  void RegisterCommand(OleMenuCommand command);

  void UnRegisterCommand(OleMenuCommand command);

  IEnumerable<OleMenuCommand> GetRegisteredCommands();

}

 

// --- SCommandManagerService.cs

[Guid("357C77BD-7F09-47E6-82E7-2E847D73204C")]

public interface SCommandManagerService

{

}

 

// --- CommandManagerService.cs

internal class CommandManagerService :

  ICommandManagerService,

  SCommandManagerService

{

  // --- Implementation details

}

In this pattern ICommandManagerService is the interface defining the service contract. This interface is COM visible in order to be available by external packages. The SCommandManagerService type is actually the “address” of the service. We can use this interface type as the parameter of the GetService method when requesting a service object.

The service contract is implemented in the CommandManagerService class. It also must implement the SCommandManagerService since without it the GetService method will not retrieve the service instance.

In order the service can be used the package must initialize it:

[ProvideService(typeof(SCommandManagerService),

  ServiceName = "CommandManagerService")]

// --- Other attributes omitted

public sealed class PowerCommandsPackage : Package, IVsInstalledProduct

{

  // --- Only relevant members are listed

  protected override void Initialize()

  {

    base.Initialize();

 

    // --- Initialize services defined within the package

    (this as IServiceContainer).AddService(

        typeof(SCommandManagerService),

        new ServiceCreatorCallback(CreateCommandManagerService),

        true);

   // --- Other initialization tasks

  }         

 

  // --- This method creates the service instance

  private object CreateCommandManagerService(IServiceContainer container,

    Type serviceType)

  {

    if (container != this)

    {

      return null;

    }

    if (typeof(SCommandManagerService) == serviceType)

    {

      return new CommandManagerService();

    }

    return null;

  }

}

The ProvideService attribute ensures that our service class is registered. The Initialize method adds our service to the service container of the package using the AddService method. The service object is instantiated only when the service is first requested. This is done by the private CreateCommandManagerService method that we also pass to AddService.

The CreateCommandManagerService implements simple operations:

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Linq;

using Microsoft.VisualStudio.Shell;

 

namespace Microsoft.PowerCommands.Services

{

  internal class CommandManagerService :

    ICommandManagerService,

    SCommandManagerService

  {

    private IList<OleMenuCommand> registeredCommands;

 

    public CommandManagerService()

    {

      registeredCommands = new List<OleMenuCommand>();

    }

 

    public void RegisterCommand(OleMenuCommand command)

    {

      if (registeredCommands.SingleOrDefault(

          cmd => cmd.CommandID.Guid.Equals(command.CommandID.Guid) &&

              cmd.CommandID.ID.Equals(command.CommandID.ID)) == null)

      {

        registeredCommands.Add(command);

      }

    }

 

    public void UnRegisterCommand(OleMenuCommand command)

    {

      if (registeredCommands.SingleOrDefault(

          cmd => cmd.CommandID.Guid.Equals(command.CommandID.Guid) &&

              cmd.CommandID.ID.Equals(command.CommandID.ID)) != null)

      {

        registeredCommands.Remove(command);

      }

    }

 

    public IEnumerable<OleMenuCommand> GetRegisteredCommands()

    {

      return new ReadOnlyCollection<OleMenuCommand>(registeredCommands);

    }

  }

}

Both RegisterCommand and UnregisterCommand use a LINQ query to look if the list of registered commands contains a specific command or not. This query uses the standard query operator SingleOrDefault which returns null if the command is not on the list.

The CommandSet class

CommandSet is a thin wrapper around CommandManagerService to register the built-in PowerComands. The only place where it is used in the initialization code of the package:

public sealed class PowerCommandsPackage : Package, IVsInstalledProduct

{

  // --- Only relevant members are listed

  protected override void Initialize()

  {

    base.Initialize();

 

    // --- Service initialization

    // --- CommandSet initialization

    CommandSet commandSet = new CommandSet(this);

    commandSet.Initialize();

    // --- Other initialization code

  }         

}

The code of the CommandSet class is really straightforward:

internal class CommandSet

{

  private IServiceProvider serviceProvider;

  OleMenuCommandService menuCommandService;

 

  public CommandSet(IServiceProvider provider)

  {

    this.serviceProvider = provider;

    menuCommandService = this.serviceProvider.GetService<IMenuCommandService,

      OleMenuCommandService>();

  }

 

  public void Initialize()

  {

    RegisterMenuCommands();

  }

 

  private void RegisterMenuCommands()

  {

    ICommandManagerService commandManager =

      this.serviceProvider.GetService<SCommandManagerService,

      ICommandManagerService>();

 

    OleMenuCommand openVSCommandPromptCommand =

      new OpenVSCommandPromptCommand(this.serviceProvider);

    menuCommandService.AddCommand(openVSCommandPromptCommand);

    commandManager.RegisterCommand(openVSCommandPromptCommand);

 

    // --- Add the other commands with the same pattern

    // ...

 

    OleMenuCommand showUndoCloseToolWindowCommand =

      new ShowUndoCloseToolWindowCommand(this.serviceProvider);

    menuCommandService.AddCommand(showUndoCloseToolWindowCommand);

  }

}

Each command is represented by an OleMenuCommand derived class, like openVSCommandPromptCommand in the code above. The function behind a specific command is handled by an instance of the corresponding command class. That instance is created here and the command is registered with the CommandManagerService and is bound to the shell.

The DynamicCommand class

The classes representing the commands’ functionality are derived from OleMenuCommand through the DynamicCommand class that extends OleMenuCommand with common functionality.

public abstract class DynamicCommand : OleMenuCommand

{

  // --- Private fields

  private static DTE dte;

  private static IServiceProvider serviceProvider;

  private static PowerCommandsPackage powerCommandsPackage;

 

  // --- Properties

  protected static IServiceProvider ServiceProvider

  {

    get { return serviceProvider; }

  }

 

  protected static DTE Dte

  {

    get

    {

      if (dte == null)

      {

        dte = CollapseProjectsCommand.ServiceProvider.GetService<DTE>();

      }

      return dte;

    }

  }

 

  protected static PowerCommandsPackage PowerCommandsPackage

  {

    get

    {

      if (powerCommandsPackage == null)

      {

        powerCommandsPackage = ServiceProvider.GetService<PowerCommandsPackage>();

      }

      return powerCommandsPackage;

    }

  }

 

  // --- Command instantiation

  public DynamicCommand(IServiceProvider provider, EventHandler onExecute,

    CommandID id) : base(onExecute, id)

  {

    this.BeforeQueryStatus += new EventHandler(OnBeforeQueryStatus);

    CollapseProjectsCommand.serviceProvider = provider;

  }

 

  // --- Command status query

  protected void OnBeforeQueryStatus(object sender, EventArgs e)

  {

    OleMenuCommand command = sender as OleMenuCommand;

    command.Enabled = command.Visible = command.Supported = CanExecute(command);

  }

 

  // --- Execution status query

  protected virtual bool CanExecute(OleMenuCommand command)

  {

    return PowerCommandsPackage.CommandsPage.DisabledCommands.SingleOrDefault(

        cmd => cmd.Guid.Equals(command.CommandID.Guid) &&

            cmd.ID.Equals(command.CommandID.ID)) == null;

  }

}

The base OleMenuCommand class provides events to query the status of a command and execute the command. In the DynamicCommand constructor we pass the command ID and the event handler method for the command execution. DynamicCommand implements the event for the command status query. This query simply calls the CanExecute virtual method that retrieves a bool value indicating if the command is enabled or not.

By default CanExecute uses a LINQ query to check if the command is enabled or disabled on the “Commands” option page. Derived classes can override this method by calling the base implementation and applying additional filtering for the command status.

The class has three static properties to access IServiceProvider, DTE and PowerCommandsPackage instances within the command classes. These instances are frequently used during command execution. Setting the static serviceProvider field in every instance constructor is an annoyance; however, it works since we use the same service provider in every instantiation.

Menu bindings

The command table describing the menu item bindings can be found in the PowerCommands.vsct file. This file is relatively long due to the number of commands defined there. However, the same pattern is repeated for the all commands. Here I show an extract from the .vsct file containing all nodes representing the Copy Path Command:

<?xml version="1.0" encoding="utf-8"?>

<CommandTable xmlns=

  "http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable"

  xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <Extern href="stdidcmd.h"/>

  <Extern href="vsshlids.h"/>

  <Extern href="msobtnid.h"/>

  <Commands package="guidPowerCommandsPkg">

    <Groups>

      <Group guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup"

        priority="0x0100">

        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_SOLNNODE" />

      </Group>

      <Group guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup1"

        priority="0x0100">

        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" />

      </Group>

      <Group guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup2"

        priority="0x0100">

        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_FOLDERNODE" />

      </Group>

      <Group guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup3"

        priority="0x0100">

        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE" />

      </Group>

      <!-- Other groups -->

    </Groups>

    <Buttons>

      <Button guid="guidCopyPathCommand" id="cmdidCopyPathCommand"

        priority="0x0100" type="Button">

        <Icon guid="guidCopyPathCommandBitmap" id="bmpPic1" />

        <CommandFlag>DynamicVisibility</CommandFlag>

        <CommandFlag>DefaultInvisible</CommandFlag>

        <Strings>

          <ButtonText>Copy Path</ButtonText>

        </Strings>

      </Button>

      <!-- Other button definitions -->

    </Buttons>

    <Bitmaps>

      <Bitmap guid="guidCopyPathCommandBitmap" href="Resources\Empty16.bmp"

        usedList="bmpPic1" />

      <!-- Other bitmap definitions -->

    </Bitmaps>

  </Commands>

  <CommandPlacements>

    <CommandPlacement guid="guidCopyPathCommand" id="cmdidCopyPathCommand"

      priority="0x0100">

      <Parent guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup"/>

    </CommandPlacement>

    <CommandPlacement guid="guidCopyPathCommand" id="cmdidCopyPathCommand"

      priority="0x0100">

      <Parent guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup1"/>

    </CommandPlacement>

    <CommandPlacement guid="guidCopyPathCommand" id="cmdidCopyPathCommand"    

      priority="0x0100">

      <Parent guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup2"/>

    </CommandPlacement>

    <CommandPlacement guid="guidCopyPathCommand" id="cmdidCopyPathCommand"

      priority="0x0100">

      <Parent guid="guidCopyPathCommand" id="grpidCopyPathCommandGroup3"/>

    </CommandPlacement>

    <!-- Other command placement definitions -->

  </CommandPlacements>

  <Symbols>

    <GuidSymbol name="guidCopyPathCommand"

      value="{7F95D8FB-4996-4763-AF41-A2154A831F77}">

      <IDSymbol name="cmdidCopyPathCommand" value="0x5A09" />

      <IDSymbol name="grpidCopyPathCommandGroup" value="0x67DB" />

      <IDSymbol name="grpidCopyPathCommandGroup1" value="0x67DC" />

      <IDSymbol name="grpidCopyPathCommandGroup2" value="0x67DE" />

      <IDSymbol name="grpidCopyPathCommandGroup3" value="0x67DD" />

    </GuidSymbol>

    <GuidSymbol name="guidCopyPathCommandBitmap"

      value="{841DCDEE-63A0-4D75-A489-B5B7F40D5C0F}">

      <IDSymbol name="bmpPic1" value="1" />

    </GuidSymbol>

  </Symbols>

</CommandTable>

The Copy Path Command is displayed for the solution, project, folder and item levels in the Solution Explorer’s project hierarchy on the context menu. For each location there is a related Group element that binds the group to its corresponding Parent. The command itself is represented by a Button element that does not contain an explicit Parent definition. Since the button is tied to four Group elements, CommandPlacement items are used to define these relations.

There is an icon belonging to the command that is assigned to the button. Definition of Symbols matches with the structure of groups and buttons.

The GuidSymbol used for guidCopyPathCommand matches exactly with the one used as the class GUID of CopyPathCommand:

// --- PowerCommandPackage.vsct

<GuidSymbol name="guidCopyPathCommand"

   value="{7F95D8FB-4996-4763-AF41-A2154A831F77}">

...

// --- CopyPathCommand.cs

[Guid("7F95D8FB-4996-4763-AF41-A2154A831F77")]

internal class CopyPathCommand : DynamicCommand { ... }

This match is used in the implementation of CopyPathCommand.

Implementing commands

Now we have seen the key elements and infrastructure of handling commands. Let’s see how a concrete command leverages on this infrastructure! I have chosen the Copy Path Command to explain the important details.

This commands simply copies the full path name of the item selected in Solution Explorer to the clipboard. The command is represented by the CopyPathCommand class inherited from DynamicCommand. Here is the full source code of the class (I changed the comments for clarity):

[Guid("7F95D8FB-4996-4763-AF41-A2154A831F77")]

[DisplayName("Copy Path")]

internal class CopyPathCommand : DynamicCommand

{

  // --- Command ID used for this command (look it in the VSCT file)

  public const uint cmdidCopyPathCommand = 0x5A09;

 

  // --- Constructs the command instance

  public CopyPathCommand(IServiceProvider serviceProvider): base(

    serviceProvider,

    OnExecute,

    new CommandID(

      typeof(CopyPathCommand).GUID,

      (int)CopyPathCommand.cmdidCopyPathCommand))

  {

  }

 

  // --- Enable this command only if there is an open solution

  protected override bool CanExecute(OleMenuCommand command)

  {

    if (base.CanExecute(command))

    {

      return DynamicCommand.Dte.Solution.IsOpen;

    }

    return false;

  }

 

  // --- Execute the command

  private static void OnExecute(object sender, EventArgs e)

  {

    string path = string.Empty;

    if (DynamicCommand.Dte.SelectedItems.Item(1).Project != null)

    {

      // --- Executed at the project level

      path = DynamicCommand.Dte.SelectedItems.Item(1).Project.FullName;

    }

    else if (DynamicCommand.Dte.SelectedItems.Item(1).ProjectItem != null)

    {

      // --- Executed at the folder level / item level

      path =             

        DynamicCommand.Dte.SelectedItems.Item(1).ProjectItem.get_FileNames(0);

    }

    else

    {

      // --- Executed at the solution level

      path = DynamicCommand.Dte.Solution.FullName;

    }

    Clipboard.SetDataObject(IOHelper.SanitizePath(path), true);

  }

}

This command is bound to the menu item passing a CommandID in the base constructor that combines the class’s GUID and the cmdidCopyPathCommand ID. The static OnExecute method is responsible for executing the command (it is also passed as an argument in the base constructor).

The overridden CanExecute method first calls the base implementation. Remember, it checks if the command is enabled at all on the “Commands” option pages. If the command is enabled, it checks if there is an open solution, otherwise it disables the command.

All command class implementation use common patterns found in the code of CopyPathCommand:

—  Binding the command class to the menu items

—  The static OnExecute method

—  The CanExecute method implementation

Where we are?

In this post we took a look at the architecture of the PowerCommands package and went into details on how commands are defined and executed. In the next post we examine some UI related to PowerCommands’ options and start to analyze commands one-by-one.


Posted Apr 17 2008, 08:06 AM by inovak
Filed under:

Comments

Gunnar wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sun, Jul 20 2008 1:43

I wonder why they used IDSymbol = 0x5A09, 0x67DE, etc. Why not 1,2,3,etc.?

DiveDeeper's blog wrote PowerCommands with VSXtra
on Thu, Jul 24 2008 8:26

You could see from my blogs and LearnVSXNow articles that I am a fan PowerCommands ( PowerCommand Deep

DiveDeeper's blog wrote LearnVSXNow! #28 - VSXtraCommands Part 1 — Command handling patterns
on Fri, Aug 1 2008 10:20

About a week ago I announced that I started to create an alternative implementation of PowerCommands

Charley wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Mon, Aug 23 2010 17:15

Nice article, but where can I download the source code of the powercommands?

Alek wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Thu, Nov 15 2012 22:01

Dan, excellent potins;I see your valid point on using GUID for the sync purpose. Many years ago, I actually had to invent my own  unique  field, so I could compare two rows from two databases (as you pointed out, the INT were different) and I had created a secondary index based on this unique field, in order to find them. So, I can see the guid use here. I wonder if that's what AdventureWorks uses it for.On the Modified, I thought they might be either using for row concurrency or log of  last changed . I had done so before by also saving the user who changed them.I appreciate your feedback and hope to see you on LS forum;Take care and thank you again...Ben

visit site wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sun, Feb 10 2013 23:38

I think this is a real great blog.Really thank you! Really Great.

Spingbridge topsoil supplier wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Feb 16 2013 14:08

Very good post.

buy stendra online wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sun, Feb 24 2013 19:09

oTN7Lb Major thanks for the blog article.Thanks Again. Keep writing.

creative ways get more twitter followers wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Tue, Feb 26 2013 19:44

Thank you for your article.Really looking forward to read more. Great.

make your hair grow faster wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Tue, Feb 26 2013 21:27

I loved your blog article. Really Great.

stay at home parents wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Tue, Feb 26 2013 23:05

I really like and appreciate your blog post.Much thanks again. Much obliged.

Security Cameras wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 0:40

Really enjoyed this blog.Much thanks again. Fantastic.

instagram followers train wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 0:40

Really appreciate you sharing this blog.Much thanks again. Much obliged.

colonie de vacances wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 1:23

Thank you for your blog post.Really looking forward to read more.

riovagas wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 2:15

Very neat article post.Thanks Again. Great.

Toronto Businesses for sale wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 3:52

Great, thanks for sharing this article.Really looking forward to read more. Keep writing.

photography effects wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 5:31

Im obliged for the article post.Really thank you! Great.

buy instagram followers wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 6:18

Looking forward to reading more. Great blog post.Really looking forward to read more. Awesome.

singles chat room wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 7:04

Thanks again for the article post.Really looking forward to read more.

email marketing wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 7:10

This is one awesome blog.Really thank you! Really Great.

how to get more likes on instagram wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 8:13

I really liked your blog post.Really looking forward to read more. Awesome.

nikon d5200 cheapest price in bhubneshwar wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 8:49

Great article post. Really Cool.

woodworking bench plans wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 9:01

I appreciate you sharing this blog post.Much thanks again.

resort wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 10:12

I loved your blog.Really thank you! Will read on...

Grigores sintages wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 10:29

wow, awesome blog post. Will read on...

hotel booking kerala wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 12:07

Im grateful for the blog.Really thank you! Cool.

usa facebook fans wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 12:09

I think this is a real great blog post. Awesome.

Cell phone service wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 12:53

Major thanks for the blog article.Much thanks again. Fantastic.

oil paintings sale online wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 19:49

I really enjoy the blog.Much thanks again. Cool.

thoroughbred horse racing partnerships wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Wed, Feb 27 2013 20:39

Fantastic article. Keep writing.

wooden hot tubs wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Thu, Feb 28 2013 7:52

wow, awesome post. Really Great.

Vpn wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Thu, Feb 28 2013 20:42

Thanks a lot for the blog post.Thanks Again. Really Great.

ourmeds wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Thu, Feb 28 2013 22:18

Hey, thanks for the article post.Really thank you! Much obliged.

thy ucak bileti|thy ucak bileti wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 12:29

Thank you for your article.Thanks Again. Keep writing.

GeForce GTX 690 wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 12:43

Thanks-a-mundo for the article.Really thank you! Really Cool.

escort agency wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 14:19

Awesome blog post.Really thank you! Great.

adjustable dumbbells wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 15:54

Im obliged for the blog article.Really looking forward to read more. Will read on...

web content writer wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 17:24

Major thankies for the blog article.Really looking forward to read more. Will read on...

social bookmarking wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 17:27

Looking forward to reading more. Great blog post.Really thank you! Want more.

steals and deals wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 17:30

Im obliged for the blog post.Really looking forward to read more. Keep writing.

affiliate marketing|make 100 dollars a day wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 19:05

Major thankies for the blog.Thanks Again. Much obliged.

guinness storehouse discount wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 19:10

Major thanks for the blog post. Want more.

cardiology blog wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 20:54

Very neat article post.Really looking forward to read more. Much obliged.

Network marketing iniziativa wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 21:38

Hey, thanks for the post. Much obliged.

youtube wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 22:19

This is one awesome blog article.Much thanks again. Great.

Ferienwohnung cuxhaven wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Fri, Mar 1 2013 22:37

Very neat post.Thanks Again. Want more.

halovar wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 0:20

Thanks so much for the article post.Really looking forward to read more.

click here wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 1:49

Awesome blog article.Really thank you! Keep writing.

oxyelite usplabs wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 2:04

Thank you for your article post.Really thank you!

lipo 6 wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 3:47

I am so grateful for your post. Fantastic.

create a blog wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 5:31

Im thankful for the article.Really looking forward to read more. Fantastic.

nulled scripts wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 5:58

Thanks-a-mundo for the blog post.

car insurance wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 7:16

I really enjoy the blog.Really looking forward to read more.

Fast UK Payday Loans wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 8:59

Great, thanks for sharing this blog.Really looking forward to read more. Great.

seo services adelaide wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 10:47

I am so grateful for your article.Really looking forward to read more. Great.

kim kardashian sex tape wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 12:16

Hey, thanks for the post. Really Cool.

gold buyers wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 12:34

Thank you ever so for you post. Keep writing.

Get Twitter followers wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 13:14

Thank you ever so for you blog.Much thanks again. Fantastic.

best green coffee bean extract wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 14:20

Thank you for your article post.Really looking forward to read more. Fantastic.

What is a Domain Name wrote re: LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture
on Sat, Mar 2 2013 14:52

Very neat article post. Cool.

Read Full Report wrote Read Full Report
on Mon, Oct 13 2014 21:09

LearnVSXNow! #19 - PowerCommands Deep Dive — Command Architecture - DiveDeeper's blog - Dotneteers.net