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

LearnVSXNow! Part #32 - VSXtra at DevCon - Part 1

After I had held my presentation on September 16 on the VSX Developer Conference in Redmond I downloaded the presentation file just to check it again. Going through these slides are recognized that they do not tell too much without commentary. So I decided to publish it in commented form.

This blog post is the first part from the two, the second is coming soon.

Objectives

In this presentation my main objective is to show you how I think about the possibilities to improve the Managed Package Framework. I will demonstrate a bunch of implementation patterns I created in the frame of the VSXtra project.

Of course, I am hungry for your feedback, and I hope I can influence Microsoft guys responsible for the next major version of MPF.

My Personal Story

However, I deal with .NET framework from the first beta of the 1.0 version and Visual Studio from its first public release, Visual Studio Extensibility is a new field for me. To be honest I tried to create VSPackages since I have the first SDK for VS 2005 but I always got frustrated mainly because of the relatively high initial cost of getting into the world of VSX.

With the release of VS 2008 SDK I decided to pay that high cost and started to learn how VSX works and what is behind the scenes. I also decided to share the experience learned and created the LearnVSXNow project just at the beginning of this year.

I know I am relatively new in the community of seniors dealing with VSX and do not have so much knowledge and experience about extensibility details than the old guys from all around the world. However, being new in this field maybe allows me looking at the whole extensibility story with a fresh eye not dimmed by my former implementation experiences.

About Developer Experience

In order to treat why I think the current MPF experience can be improved, let me tell you a few thoughts about what I mean by DX.

“User Experience” and “Developer Experience” are among the most frequently used buzzwords today. We have heard very much about what user experience is, but a bit less about developer experience. I guess, developer experience is a kind of user experience where the “user” itself is a developer. I think there are many factors determining what the something we call developer experience is.

—  The Development Environment makes the first impression about DX. What kind of editors and designers we have there? What are the tools we can use in the environment? Can we customize or extend it?

—  Majority of developers opts for the Programming Model as a primary DX factor. What kind of object model is used there? Does is mirror the real world in a natural or intuitive way? What kind of programming paradigm is used? Imperative, functional or something else? Is the code using the model compact or verbose?

—  There is another important factor that best can be described with the word Guidance. What is the learning curve of a specific development product or technology? What is the quality of documentation and help provided? Are there any FAQs or samples to help developers’ perception?

I think, when we are talking about developer experience related to Visual Studio Extensibility, we face these primary factors.

The Role of Managed Package Framework

Let’s put the Managed Package Framework into the picture through the layered architecture of VSX components!

At the back we have the core services of the Visual Studio IDE. We can take it into account just like the Win32 API for the Windows operating systems. At the top we have applications for Visual Studio as Macros, Add-ins and Packages. There is a stack of components between the core services and the application layer. In my presentation I focus on the managed VSPackage development. In this area our VSPackages actually use COM interop assemblies over the Package and Automation APIs. The Managed Package Framework provides a great help to use an object model over the COM interop assemblies.

By its position we can say MPF is a primary factor for DX! Developers wanting to create extensions for Visual Studio qualify their experience according to how they feel when using MPF.

Demo: Feelings about MPF

To point out why I think the current MPF can be improved, let us see a few VSPackages created by the VSPackage wizard of Visual Studio. The first package is created with a simple menu command displaying a message box on the screen. Here I show the essential source code of the package, I omitted all comments to improve the readability:

using System;

using System.Diagnostics;

using System.Globalization;

using System.Runtime.InteropServices;

using System.ComponentModel.Design;

using Microsoft.VisualStudio.Shell.Interop;

using Microsoft.VisualStudio.Shell;

 

namespace MyCompany.SimpleMenuCommand

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

  [InstalledProductRegistration(false, "#110", "#112", "1.0",

    IconResourceID = 400)]

  [ProvideLoadKey("Standard", "1.0", "SimpleMenuCommand", "MyCompany", 1)]

  [ProvideMenuResource(1000, 1)]

  [Guid(GuidList.guidSimpleMenuCommandPkgString)]

  public sealed class SimpleMenuCommandPackage : Package

  {

    public SimpleMenuCommandPackage()

    {

      Trace.WriteLine(string.Format(CultureInfo.CurrentCulture,

      "Entering constructor for: {0}", this.ToString()));

    }

 

    protected override void Initialize()

    {

      Trace.WriteLine(string.Format(CultureInfo.CurrentCulture,

        "Entering Initialize() of: {0}", this.ToString()));

      base.Initialize();

      OleMenuCommandService mcs = GetService(typeof(IMenuCommandService))

        as OleMenuCommandService;

      if (null != mcs)

      {

        CommandID menuCommandID =

          new CommandID(GuidList.guidSimpleMenuCommandCmdSet,

          (int)PkgCmdIDList.cmdidMyCommand);

        MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID);

        mcs.AddCommand(menuItem);

      }

    }

 

    private void MenuItemCallback(object sender, EventArgs e)

    {

      IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));

      Guid clsid = Guid.Empty;

      int result;

      Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(

                 0,

                 ref clsid,

                 "SimpleMenuCommand",

                 string.Format(CultureInfo.CurrentCulture,

                   "Inside {0}.MenuItemCallback()", this.ToString()),

                 string.Empty,

                 0,

                 OLEMSGBUTTON.OLEMSGBUTTON_OK,

                 OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,

                 OLEMSGICON.OLEMSGICON_INFO,

                 0,

                 out result));

    }

  }

}

This source code does not seem too complicated but for the functionality it offers, it seems too long. The Initialize method contains more than ten lines dealing with preparing a handler method that responds to the event when the command behind our menu item is executed (the user click on the menu item). The MenuItemCallback method simple shows up a message box on the screen, however, it seems to do some more complicated task by the number of lines.

Let’s see another example with a VSPackage implementing a simple tool window! Here is the source code for the package body:

using System;

using System.Diagnostics;

using System.Globalization;

using System.Runtime.InteropServices;

using System.ComponentModel.Design;

using Microsoft.VisualStudio.Shell.Interop;

using Microsoft.VisualStudio.Shell;

 

namespace MyCompany.SimpleToolWindow

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

  [InstalledProductRegistration(false, "#110", "#112", "1.0",

    IconResourceID = 400)]

  [ProvideLoadKey("Standard", "1.0", "SimpleToolWindow", "MyCompany", 1)]

  [ProvideMenuResource(1000, 1)]

  [ProvideToolWindow(typeof(MyToolWindow))]

  [Guid(GuidList.guidSimpleToolWindowPkgString)]

  public sealed class SimpleToolWindowPackage : Package

  {

    public SimpleToolWindowPackage()

    {

      Trace.WriteLine(string.Format(CultureInfo.CurrentCulture,

        "Entering constructor for: {0}", this.ToString()));

    }

 

    private void ShowToolWindow(object sender, EventArgs e)

    {

      ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true);

      if ((null == window) || (null == window.Frame))

      {

        throw new NotSupportedException(Resources.CanNotCreateWindow);

      }

      IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;

      Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());

    }

 

    protected override void Initialize()

    {

      Trace.WriteLine(string.Format(CultureInfo.CurrentCulture,

        "Entering Initialize() of: {0}", this.ToString()));

      base.Initialize();

 

      OleMenuCommandService mcs = GetService(typeof(IMenuCommandService))

        as OleMenuCommandService;

      if (null != mcs)

      {

        CommandID toolwndCommandID =

          new CommandID(GuidList.guidSimpleToolWindowCmdSet,

          (int)PkgCmdIDList.cmdidMyTool);

        MenuCommand menuToolWin =

          new MenuCommand(ShowToolWindow, toolwndCommandID);

        mcs.AddCommand(menuToolWin);

      }

    }

  }

}

The situation is very similar to the package we have seen before. The ShowToolWindow method has about 8 lines to display the tool window. The tool window declaration looks like this:

using System.Windows.Forms;

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

 

namespace MyCompany.SimpleToolWindow

{

  [Guid("5f180713-6959-402b-a2f7-63f85b925675")]

  public class MyToolWindow : ToolWindowPane

  {

    private MyControl control;

 

    public MyToolWindow() :

      base(null)

    {

      this.Caption = Resources.ToolWindowTitle;

      this.BitmapResourceID = 301;

      this.BitmapIndex = 1;

      control = new MyControl();

    }

 

    override public IWin32Window Window

    {

      get

      {

        return (IWin32Window)control;

      }

    }

  }

}

This declaration does not do anything else but sets up the user control representing the user interface of the tool window. None of the codes above is complex, but all of them seem “noisy”; we have to type much more then we feel optimal.

When looking at the source codes above, we can ask the following question from ourselves: Should the code be so noisy?

As a last sample, create a custom editor with the VSPackage wizard and look for the code! You’ll see the code generated is quite long, about 4,500 lines containing about 3,000 code lines when omitting comments. Even if we know it contains complex functionality, this code seems too complex for the first sight!

Major Issues with MPF

Let me summarize the major issues I have with MPF, the demo before highlighted only a few of them.

The current MPF adds value only to a small portion of the core IDE services. As you remember for my demo, actually there is no real value added for developers of custom editors. MPF does not use the newest .NET technologies like generics and LINQ. If you looked at VSPackage source codes from Pablo Galiano’s (PowerCommands is one of them), you can see good examples how these technologies can use developers perception about VSX.

I feel and I suppose you could feel the same after the demos: We must still write too much plumbing code in our programs to access and use majority of core services.

If you have created WinForms applications before, you can see that it is much easier to create an event handler method for responding a menu item click than doing the same for a VSPackage. Would not it be nice to use the same approach for VSPackage command handlers?

I guess the right position for MPF would be the same for VSX as the Base Class Library for the .NET Framework.

VSXtra in a NutShell

At the end of May I started an experimental project called VSXtra and published it on CodePlex at the end of June. My main objective was to demonstrate many ways how DX can be improved by a new MPF. I wanted to create something that helps in making the first steps in creating a VSPackage. I handle VSXtra like if it were a new Base Class Library for VSX and hope it will provide more readable and straightforward VSPackages. The current release of this runtime library provides a few dozen types and about 15 samples.

VSXtra Component Stack

To have you understand how VSXtra does its work, let me show you where it is located in the stack of service components between the core IDE services and a Managed VSPackage.

Right now VSXtra is build over the Managed Package Framework; however, it changes many MPF core types in the Microsoft.VisualStudio.Shell namespace. Examples of these types are the most-known Package class, but WindowPane, ToolWindowPane and many others belong to this category. In my imagination VSXtra is going to change the whole MPF.

The current VSXtra implementation uses all the lower layers. My aim is that Managed VSPackages use only the VSXtra objects to implement their functionality, but of course VSPackages can consume all the lower layers.

Demo: Creating a Simple Menu Command with VSXtra

First I show you how to create a simple menu command with VSXtra. The Package (VSXtraMenuCommand) I am going to create implements the same functionality as the package created by the VSPackage wizard. To spare a couple of minutes I have already created the package and removed comments and whitespaces to improve readability. I did not make any changes in the package semantics.

I run the package, and as you can see, on the Tools menu there is the VSXtra Menu Command function that pops up a message box when activated.

OK, now let us change this package to use VSXtra. As the first step I add the VSXtra runtime to the referenced assemblies. I comment out the old package code and then I change the package class to derive from the PackageBase class. PackageBase in VSXtra replaces the Package class in Microsoft.VisualStudio.Shell namespace and adds a lot of power to the package.

As the next step I create an instance method of the package to respond to the menu command execution:

[CommandExecMethod]

[CommandId(GuidList.guidVSXtraMenuCommandCmdSetString,

  CmdIDs.cmdidVSXtraMenuCommand)]

private void MyMenuCommand()

{

  VsMessageBox.Show("Inside VSXtra Menu Command");

}

The CommandExecMethod attribute signs this method defines a menu command execution method. The CommandId attribute defines the GUID and ID pair to bind this method to the corresponding command. As you see VSXtra provides an easy way to pop up a Visual Studio message box.

We can even display the message box using the CommandAction attribute feature of VSXtra:

[CommandExecMethod]

[CommandId(GuidList.guidVSXtraMenuCommandCmdSetString,

  CmdIDs.cmdidVSXtraMenuCommand)]

[ShowMessageAction("Inside VSXtra Menu Command")]

private void MyMenuCommand()

{

}

The ShowMessageAction attribute does exactly what its name suggests.

I suppose, you agree that this code is compact enough and expresses the developer’s intention.

Demo: Implementing a Simple Tool Window with VSXtra

Now, let’s see an example with a tool window. As before I have pre-created a package with the VSPackage wizard, removed to comments and also added the VSXtra reference to the project. The wizard created a very simple tool window for me, now I refactor it using VSXtra features. I do not change the user interface; I only build up the so-called window frame responsible for hosting the UI of the tool window:

[InitialCaption("$ToolWindowTitle")]

[BitmapResourceId(301)]

public class MyToolWindow: ToolWindowPane<VSXtraToolWindowPackage, MyControl>

{

}

As you see, the definition of the tool window pane is very simple using the generic ToolWindowPane<> generic class. The first type parameter sets the type of the package owning the tool window frame; the second type defines the type responsible for the UI of the window. The initial window properties can be set through attributes.

Now, let’s change the package class definition. I create a package leaving the default registration attributes:

public sealed class VSXtraToolWindowPackage : PackageBase

{

}

I add a menu command handler method to the package just like I did it in the previous demonstration sample:

[CommandExecMethod]

[CommandId(GuidList.guidVSXtraToolWindowCmdSetString, CmdIDs.cmdidShowToolWindow)]

private void ShowToolWindow()

{

}

Our menu command handler should display the tool window pane I have created. I can tell it to the method by simply declaring a command action using the ShowToolWindowAction attribute:

[ShowToolWindowAction(typeof(MyToolWindow))]

This attribute specifies the type of the tool window pane to display. In order VSXtra could support all tool window features, we must change the ProvideToolWindow attribute to XtraProvideToolWindow attribute:

[XtraProvideToolWindow(typeof(MyToolWindow))]

Now, I can run the VSPackage. When I click on the appropriate menu item the tool window pops up. By clicking on the button I can check that it works.

I hope these two samples convinced you that there are great opportunities to improve the DX with some good implementation patterns.

Coping with GUIDs

In this slide I show you a simple VSPackage that automatically loads into the VS IDE at startup time and is intended to display a simple message into the Output window:

[PackageRegistration(UseManagedResourcesOnly = true)]

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

[InstalledProductRegistration(false, "#110", "#112", "1.0",

  IconResourceID = 400)]

[ProvideLoadKey("Standard", "1.0", "SimpleMenuCommand", "MyCompany", 1)]

[ProvideMenuResource(1000, 1)]

[ProvideAutoLoad("ADFC4E64-0397-11D1-9F4E-00A0C911004E")]

[Guid(GuidList.guidSimpleMenuCommandPkgString)]

public sealed class SimpleMenuCommandPackage : Package

{

  protected override void Initialize()

  {

    // --- Here we write a message to the Output window

  }

}

This package compiles without any warnings or errors but does not work. Can you guess what is wrong with it? I tell you, as you can guess it from the title of the slide it is because of the highlighted GUID of the ProvideAutoLoad attribute is wrong. How can a GUID be wrong? It has no syntax error, it got compiled! The GUID is wrong because it has no meaning in the specified context, VS IDE cannot interpret it as a UI context GUID. Eventually, the last “E” of the GUID should be “F”.

With this sample I wanted to demonstrate that we have many fields where we have to struggle with GUIDs. Using constants instead of literal values would not help in this situation. Nobody will prevent me to use a wrong constant (a constant in a wrong place)!

“Typed GUID” Pattern

VSXtra uses a simple pattern to avoid the issues with GUIDs. I call it “Typed GUID” pattern, because the base idea is to wrap a GUID into a type as you can see the definition in the slide:

[Guid("The GUID value")]

public sealed class IdeaBehindTheGUID: ISemantics { }

We assign the GUID to the class through the Guid attribute. The class implements a markup interface (an empty “name-only” interface). We can use this interface to add semantics to to GUID. Let me show you concrete example:

[Guid("ADFC4E64-0397-11D1-9F4E-00A0C911004F")]

public sealed class NoSolution: IUIContextGuidType { }

This class defines a GUID representing the UI context where no solution is loaded into Visual Studio. The IUIContextGuidType markup interface has the meaning “Hey guys, this GUID represents a UI context; use only where a UI Context is expected”. How can we live with it?

public sealed class XtraProvideAutoLoadAttribute : RegistrationAttribute

{

  public XtraProvideAutoLoadAttribute(Type type)

  {

    if (!typeof(IUIContextGuidType).IsAssignableFrom(type))

      throw new ArgumentException();

    // ...

  }

}

We use the typed GUID, for example here in the definition of XtraProvideAutoLoad attribute we use a Type in the constructor instead of a GUID. In the body we can check if the type provided represents a UI context or not. When we use this attribute to decorate a package, we use it like this:

[XtraProvideAutoLoad(typeof(NoSolution))]

Demo: Using Typed GUIDs

Let me demo this concept with a small package that automatically loads a package when Visual Studio is started and writes a message to the Output window:

[PackageRegistration(UseManagedResourcesOnly = true)]

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

[InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]

[ProvideLoadKey("Standard", "1.0", "TypedGuid", "DeepDiver", 1)]

[XtraProvideAutoLoad(typeof(UIContext.NoSolution))]

[Guid(GuidList.guidTypedGuidPkgString)]

public sealed class TypedGuidPackage : PackageBase

{

  protected override void Initialize()

  {

    Console.WriteLine("This package uses a Typed GUID.");

  }

}

Here we use the XtraProvideAutoLoad attribute on the same way as I presented in the previous slide, passing a typed GUID as an attribute. By the way, look at just another feature of VSXtra: you can use the System.Console class to write to the Output window.

When I run the application, it works as expected.

Now let’s make a wrong decision and change the typed GUID to one invalid in this context, for example let’s use System.Int32:

[XtraProvideAutoLoad(typeof(int))]

Now, when we want to run our application, we get a build time message telling we are using an invalid typed GUID. Actually this is not a compile-time message but a message coming from the RegPkg.exe, but it is a sign that we did something wrong! Should we use a simple GUID, we would never get such kind of message. We could only perceive that out package does not do what we expect!

Packages

The most important change VSXtra introduces is a completely rewritten form of the original Package class of the Microsoft.VisualStudio.Shell namespace. VSXtra names that new class PackageBase. I have rewritten the original Package class to add more power to the new class. My aim was to create a smart type that uses the declarative model to avoid unnecessary and long initialization code. So, with the new PackageBase type no explicit code should be written for services, command handler and factories. I added new properties and methods to the class, most of them leverages on the values of generics. Of course, the new package abstraction uses the typed GUID pattern.

Package-Scoped Objects

I’d like to show you another pattern used widely in VSXtra, this is related to the so-called package-scoped objects. Just to introduce you what am I talking about, let me show you a brief overview of objects used within Visual Studio packages. This time I classify objects according to their service access opportunities.

As all of us know VSPackages are loaded into the memory space of the VS IDE through on on-demand mechanism. They are sited and so can access a service provider to request objects for various services. VS loads packages into the memory the first when we want to use one of its objects.

So, there are objects owned by the package, I call them package-scoped objects. Commands, services and tool windows are good examples. These objects can directly access the packages’ service providers to access package level and global services.

Packages can be related to other objects. A part of them like tool window UIs can sited in the VS IDE and so these objects can access the IDE’s global services. Some of them are just in relation with the package but because they are not package-scoped and not sited in the IDE they cannot access any services directly.

In VSXtra I treat all package-scoped objects with generic types having a package type as their first type parameter.

Package Instance Propagation

In the current implementation of MPF when you want to use a package-scoped object you must pass explicitly pass the package instance to this object in order to access package services from within the object. You can do it either in the constructor or through a property.

VSXtra has a very simple mechanism to avoid the need to explicitly pass a package instance. The mechanism is based on fact that we have exactly one instance of a certain package type sited within one Visual Studio process.

Because the PackageBase class keeps track of loaded package instances we can access a particular instance by its type. The next source code extract shows how it works:

public class MyPackageScopedClass<TPackage>

{

  public MyPackageScopedClass() // --- No need to pass package instance

  {

    // ...

  }

 

  public TPackage Package

  {

    get { return PackageBase.GetPackageInstance<TPackage>(); }

  }

}

Using the GetPackageInstance<> generic method of the PackageBase class we can access the specified package instance by its type. We can close it into a property (of course you’d better cache the property value). By doing this we do not need to pass the package or service provider instance to the constructor. I suppose, I do not have to tell that it makes the code simpler.

Demo: Listing Sited VSXtra Packages

I show you a short demo about package features. In later demos I will illustrate many PackageBase features; here I only show you how to access package metadata with VSXtra.

[CommandExecMethod]

[CommandId(GuidList.guidDisplayLoadedPackagesCmdSetString,

  CmdIDs.cmdidDisplayPackages)]

private static void DisplayPackageInfo()

{

  var sitedPackages = SitedVSXtraPackages;

  Console.WriteLine("There are currently {0} sited VSXtra packages:",

    sitedPackages.Count);

  foreach (var package in sitedPackages)

  {

    Console.WriteLine("  {0}", package.GetType().FullName);

    var registeredHandlers = GetCommandHandlerInstances(package.GetType());

    if (registeredHandlers.Count() > 0)

    {

      Console.WriteLine("    Registered command handlers:");

      foreach (var command in registeredHandlers)

      {

        Console.WriteLine("      {0}", command.GetType().FullName);

      }

    }

  }

}

The essence of the code is encapsulated into the DisplayPackageInfo method. This method responds to a menu command and as you can see there is no explicit code binding this method to the corresponding command. What you can see is a few attributes expressing the intention of the developer. All the work is done behind the scenes by the PackageBase class. The SitedVSXtraPackages property retrieves a collection of packages sited. We can even retrieve the menu command classes registered by a VSXtra package using the GetCommandHandlerInstances method.

What’s Next

In the next part I will treat the part of the presentation treating menus, commands, services, tool windows and my imaginations about the future of VSXtra.


Posted Sep 18 2008, 03:32 PM by inovak
Filed under:

Comments

DiveDeeper's blog wrote LearnVSXNow! Part #33 - VSXtra at DevCon - Part 2
on Tue, Sep 23 2008 11:37

This post is the second part of the VSXtra presentation on September 15, 2008 at VSX Developer Conference

Lars wrote re: LearnVSXNow! Part #32 - VSXtra at DevCon - Part 1
on Wed, Sep 24 2008 9:04

Very nice! Really a proper way to go. I'm really interested in the project and custom editor/designer stuff. Will the project simplify hierarchy implementations as well ?

inovak wrote re: LearnVSXNow! Part #32 - VSXtra at DevCon - Part 1
on Wed, Sep 24 2008 9:19

Hi Lars, if you download the VSXtra source code, you'll find the starting points for custom editors. I started working on simplifying the hierarchies. I suppose the first results can be expected in about 6-8 weeks.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?