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

LearnVSXNow! #24 - Introducing VSXtra

If you have read my posts in the LearnVSXNow series, you can feel that I am not really satisfied with the Managed Package Framework. In a week ago I started a new project on CodePlex called VSXtra. This is an experimental project to create an improved Managed Package Framework. I make it for the VSX community to suggest ideas and receive feedbacks and I hope we can influence the VSX Team when they start to design a new Managed Package Framework.

In this article I give you an overview about the project and I hope you can imagine the opportunities for improving the development experience related to VSPackages. In the beginning and treat the main objectives of the project and give you a few sentences about how I started with the project. The majority of the post analyses a few examples to show you the new style VSXtra allows. If you download the current source code, you can find there the source code for the examples treated here.

VSXtra targets only Visual Studio 2008, I do not plan to focus on compatibility with VS 2005.

 

The VSXtra development approach

 

 

In the last few months I published many articles dealing with patterns and partial solutions to improve the VSX development experience; here is a short list of them:

—  LearnVSXNow! #23 - Coping with GUIDs

—  LVN! Sidebar #4 - Command handlers

—  LVN! Sidebar #3 - Simplifying tool window declaration

—  LVN! Sidebar #1 - Automatically loading packages

—  LearnVSXNow! #17 - Creating a simple custom editor — under pressure

I had many patters in my head like the ones used in the posts above. I always felt that the current MPF implementation is a big constraint if I want to implement my imaginations on its top. So, I decided to start from the MPF source code installed with Visual Studio 2008 SDK and rewrite the MPF in a way supporting my imagination.

No doubt, MPF is full with values! I really like the way as registration attributes are used in conjunction with the related package class and also like the tool window implementation. While analyzing the source code I could find great implementation patterns and even improvement points. I was inspired by them when creating VSXtra. Although I created many new types, also many are based on the original source code with small refactorings.

I decided to create example code first (how the code should look like) and the implementation code only after that. So I developed VSXtra in an example driven way.

In this article you can see the results. The further parts treat examples. I will not go into any implementation details (how VSXtra implements a feature). I only explain how to use VSXtra to create the examples and tell you the differences to the current MPF way.

Example #1: Automatically loaded package with output

The first sample creates a useful less package that is loaded automatically with Visual Studio and writes to the General pane of the output windows. As a result, the output looks like this:

Although the package itself does not provide any function, it is a good example to point out a few values VSXtra adds to MPF. Here is the full source code of the package excluding the resource files and package GUID constant definition:

using System;

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.OutputWindowWithAutoLoad

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

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

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

  [Guid(GuidList.guidOutputWindowWithAutoLoadPkgString)]

  [XtraProvideAutoLoad(typeof(UIContext.NoSolution))]

  public sealed class OutputWindowWithAutoLoadPackage : PackageBase

  {

    protected override void Initialize()

    {

      base.Initialize();

      Console.WriteLine();

      Console.WriteLine("This package demonstrates how to use the Output window.");

     

      Console.Write("*** Boolean values: ");

      Console.Write(true);

      Console.Write("|");

      Console.WriteLine(false);

     

      Console.Write("*** Character values: ");

      Console.Write('H');

      Console.Write("|");

      Console.Write('e');

      Console.Write("|");

      Console.Write('l');

      Console.Write("|");

      Console.Write('l');

      Console.Write("|");

      Console.WriteLine('o');

     

      Console.Write("*** Integral values: ");

      Console.Write((byte)0xab);

      Console.Write("|");

      Console.Write((short)12345);

      Console.Write("|");

      Console.Write(123456789);

      Console.Write("|");

      Console.WriteLine(1234567890123456L);

     

      Console.Write("*** String values: ");

      Console.Write("Hello");

      Console.Write("|");

      Console.WriteLine("World");

 

      Console.Write("*** Floating point values: ");

      Console.Write((Single)Math.PI);

      Console.Write("|");

      Console.WriteLine(Math.E);

 

      Console.Write("*** Char arrays: ");

      var chars = new [] { 'H', 'e', 'l', 'l', 'o' };

      Console.Write(chars);

      Console.Write("|");

      Console.WriteLine(chars);

      Console.WriteLine("End of demonstration.");

      Console.WriteLine();

    }

  }

}

The example was created with the VSPackage wizard of Visual Studio. I used the wizard to create an empty package and then changed the package code. VSXtra packages should be derived from the PackageBase class that can be found in the VSXtra namespace (in the VSXtra.dll assembly). In order our example could compile we must add VSXtra to the list of referenced assemblies.

Building, running and debugging of a VSXtra package happens on the same way as for any other VSPackages created in managed code. Registration attributes are used to register package and related object information with Visual Studio. In the sample above I used the standard registration attributes you are familiar with from VS SDK. Only exception is the XtraProvideAutoLoad attribute that functionally is the same as the ProvideAutoLoad attribute but follows a design patterns described in the “LearnVSXNow! #23 - Coping with GUIDs” blog post.

This package writes messages to the General pane of the output window using the well-known System.Console class. The PackageBase class does all the initialization magic in order the code above could work.

Example #2: Implementing the VS SDK Menus and Commands sample

VS SDK provides a reference sample to demonstrate how to use menu commands with simple handler code. I have written a detailed description about how the sample works; you can download the document from here: Menu and Commands Reference Deep Dive.

The sample implements six commands, four of them can be found in the tool menu, the two others in toolbars:

   


The “VSXtra Command Sample” and the zoom button on toolbar simply write a message to the output window. The graph button displays a message dialog. In the tool menu the “VSXtra Dynamic Visibility 1” button has a hidden pair named “VSXtra Dynamic Visibility 2”, using them alternates their visibility, so once the first then for the second menu button is visible. The “VSXtra Text Changes” button counts the clicks and indicates this number.

The package was created with the VSPackage wizard with a name of DynamicCommands. This time I configured the wizard to create a menu command. Without this step no .vsct file would have been created. I simply copied the .vsct file from the original example and made some cosmetics on it (changed GUIDs and IDs). Here I do not show the file, you can look it up in the downloaded source code.

The package has the following source code:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.DynamicCommands

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

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

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

  [ProvideMenuResource(1000, 1)]

  [Guid(GuidList.guidDynamicCommandsPkgString)]

  public sealed class DynamicCommandsPackage : PackageBase

  {

  }

}

I did not omit any code from the package: the class body is really empty! The original VS SDK sample’s package code contains about 40 lines of code to set up menu command event handlers in the Initialize event of the package. Here we do not have code at all. Where is the trick?

There is now trick: the PackageBase class is smart enough to set up command event handlers from the code automatically. It uses reflection to scan through the assembly implementing the package and recognizes event handler declaration. Here is the full code for event handles:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.DynamicCommands

{

  [Guid(GuidList.guidDynamicCommandsCmdSetString)]

  public sealed class DynamicCommandGroup : CommandGroup<DynamicCommandsPackage>

  {

    [CommandId(CmdIDs.cmdidMyCommand)]

    [WriteMessageAction("Sample Command executed.")]

    public sealed class MyCommand : MenuCommandHandler { }

 

    [CommandId(CmdIDs.cmdidMyGraph)]

    [ShowMessageAction("Graph Command executed.")]

    public sealed class MyGraph : MenuCommandHandler { }

 

    [CommandId(CmdIDs.cmdidMyZoom)]

    [WriteMessageAction("Zoom Command executed.")]

    public sealed class MyZoom : MenuCommandHandler { }

 

    public abstract class DynamicVisibility : MenuCommandHandler

    {

      protected override void OnExecute(OleMenuCommand command)

      {

        var dynCommand1 = GetHandler<DynamicVisibility1>();

        var dynCommand2 = GetHandler<DynamicVisibility2>();

        if (dynCommand1 == null || dynCommand2 == null) return;

        dynCommand1.MenuCommand.Visible = command.CommandID.ID == CmdIDs.cmdidDynVisibility2;

        dynCommand2.MenuCommand.Visible = !dynCommand1.MenuCommand.Visible;

      }

    }

 

    [CommandId(CmdIDs.cmdidDynVisibility1)]

    [CommandVisible(true)]

    public sealed class DynamicVisibility1 : DynamicVisibility { }

 

    [CommandId(CmdIDs.cmdidDynVisibility2)]

    [CommandVisible(false)]

    public sealed class DynamicVisibility2 : DynamicVisibility { }

 

    [CommandId(CmdIDs.cmdidDynamicTxt)]

    public sealed class DynamicText : MenuCommandHandler

    {

      private int _ClickCount;

      protected override void OnExecute(OleMenuCommand command)

      {

        command.Text = "VSXtra Text Changed: " + ++_ClickCount;

      }

    }

  }

}

Each menu command handler is represented by a class derived from MenuCommandHandler. These classes have decorating attributes like CommandId and the others. The handler classes are nested into a class derived from the generic CommandGroup<> class having the DynamicCommandsPackage class as its type parameter. The meaning of the resulted DynamicCommandGroup class is a logical container for commands forming a group. Each command handlers in this group share the same GUID represented by the GuidList.guidDynamicCommandsCmdSetString value. The MenuCommandHandler derived classes do not need to specify this GUID in their CommandId attribute (however, they could do it, or even to specify a different GUID). The containment of the command handler classes in the logical command group helps to guess out the GUID part of the command ID if it is not set explicitly.

Let’s have a look at the command handlers one-by-one. The “VSXtra Sample Command” has the following handler code:

[CommandId(CmdIDs.cmdidMyCommand)]

[WriteMessageAction("Sample Command executed.")]

public sealed class MyCommand : MenuCommandHandler { }

The CommandId attribute sets the ID part of the command within the logical container set by the GUID part of the command identifier. VSXtra provides predefined actions (and also allows developers to create their own custom actions). These actions can be bound to menu handler classes to eliminate the need to write explicit action code. In the code above the WriteMessageAction attribute sends output message to the output window when the command is invoked. The MyZoom command handler works on the same way sending a different message to the output window.

The MyGraph command handler is also simple. Its command action is ShowMessageAction displaying a message box with the specified string as the following code indicates:

[CommandId(CmdIDs.cmdidMyGraph)]

[ShowMessageAction("Graph Command executed.")]

public sealed class MyGraph : MenuCommandHandler { }

There are two buttons, DynamicVisibility1 and DynamicVisibility2 that share their command handler using inheritance: the handler code is defined in an abstract class and concrete classes inherit the behavior:

public abstract class DynamicVisibility : MenuCommandHandler

{

  protected override void OnExecute(OleMenuCommand command)

  {

    var dynCommand1 = GetHandler<DynamicVisibility1>();

    var dynCommand2 = GetHandler<DynamicVisibility2>();

    if (dynCommand1 == null || dynCommand2 == null) return;

    dynCommand1.MenuCommand.Visible = command.CommandID.ID ==

      CmdIDs.cmdidDynVisibility2;

    dynCommand2.MenuCommand.Visible = !dynCommand1.MenuCommand.Visible;

  }

}

 

[CommandId(CmdIDs.cmdidDynVisibility1)]

[CommandVisible(true)]

public sealed class DynamicVisibility1 : DynamicVisibility { }

 

[CommandId(CmdIDs.cmdidDynVisibility2)]

[CommandVisible(false)]

public sealed class DynamicVisibility2 : DynamicVisibility { }

Inside the OnExecute method we can access the command handler instances through the GetHandler method. Through this instance we access the MenuCommand and can set the properties of the menu item. In this case we use it to set the visibility.

The abstract DynamicVisibility class has no attributes; those are attached to the concrete handler classes. The CommandVisible attribute sets the initial state of command items behind the handler class.

The last “tricky” button counts and displays the number of clicks. It uses the following code:

[CommandId(CmdIDs.cmdidDynamicTxt)]

public sealed class DynamicText : MenuCommandHandler

{

  private int _ClickCount;

  protected override void OnExecute(OleMenuCommand command)

  {

    command.Text = "VSXtra Text Changed: " + ++_ClickCount;

  }

}

The code is straightforward. The interesting point is that the owner package always uses the same instance of the command handler class while it is loaded into a Visual Studio instance. So, we can store _ClickCount in an instance field.

Example #3: Dynamic Tool Window

The VS SDK 2008 contains a reference sample called “ToolWindows” sample. That sample implements two kind of tool windows: a “dynamic” tool window demonstrating how to display tool windows and respond to window frame events; a “persisted” tool window to demonstrate tool window persistence and toolbar handling. This VSXtra example demonstrates the dynamic tool window, the next (Example #4) implements the persistent tool window.

The dynamic tool window handles the so-called window frame events: when the tool window is moved, sized, docked, undocked or tabbed its window frame raises events. Our tool window catches the events and writes out a related message into a custom output window pane and also displays the tool window coordinates within the window itself:

 

 

You may not surprise how simple our VSPackage code is:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.DynamicToolWindow

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

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

    IconResourceID = 400)]

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

  [ProvideMenuResource(1000, 1)]

  [ProvideToolWindow(typeof(DynamicWindowPane))]

  [Guid(GuidList.guidDynamicToolWindowPkgString)]

  public sealed class DynamicToolWindowPackage : PackageBase

  {

  }

}

Our tool window has a related menu item in the View|Other windows menu to display the tool window. The related menu handler code uses the same pattern as command items in Example #2:

using System.Runtime.InteropServices;

using VSXtra;

 

namespace DeepDiver.DynamicToolWindow

{

  [Guid(GuidList.guidDynamicToolWindowCmdSetString)]

  public sealed class DynamicToolWindowCommandGroup:

    CommandGroup<DynamicToolWindowPackage>

  {

    [CommandId(CmdIDs.cmdidMyTool)]

    [ShowToolWindowAction(typeof(DynamicWindowPane))]

    public sealed class ShowToolCommand : MenuCommandHandler { }

  }

}

We do not need to write imperative code to display the tool window: the ShowToolWindowAction does it for us. Its parameter declares which tool window should be displayed.

The user interface (code defining the UI behavior) of our dynamic tool window is simple:

using System;

using System.Drawing;

using System.Windows.Forms;

using VSXtra;

 

namespace DeepDiver.DynamicToolWindow

{

  public partial class DynamicWindowControl : UserControl

  {

    public DynamicWindowControl()

    {

      InitializeComponent();

    }

 

    public void RefreshValues(object sender, EventArgs arguments)

    {

      var frame = sender as WindowFrame;

      if (frame == null) return;

      Rectangle rect;

      var framePos = frame.GetWindowPosition(out rect);

      xText.Text = rect.Left.ToString();

      yText.Text = rect.Top.ToString();

      widthText.Text = rect.Width.ToString();

      heightText.Text = rect.Height.ToString();

      dockedCheckBox.Checked = framePos == FramePosition.Docked;

      Invalidate();

    }

  }

}

The RefreshValues method subscribes to window frame events (so the sender argument can be casted to a WindowFrame) and displays the window position in the controls of the user interface. As you know there is no managed type for a window frame in MPF, we have only the IVsWindowFrame and IVsWindoFrameNotify3 interop types. VSXtra defines the WindowFrame type to wrap the functionality of those interfaces into a managed type. WindowFrame plays a crucial role in defining the functionality of the window pane:

using System.ComponentModel;

using System.Drawing;

using System.Runtime.InteropServices;

using VSXtra;

 

namespace DeepDiver.DynamicToolWindow

{

  [Guid("F0E1E9A1-9860-484d-AD5D-367D79AABF55")]

  [InitialCaption("Dynamic Tool Window")]

  [BitmapResourceId(301)]

  class DynamicWindowPane : ToolWindowPane<DynamicToolWindowPackage,

    DynamicWindowControl>

  {

    private OutputWindowPane _OutputPane;

 

    public override void OnToolWindowCreated()

    {

      base.OnToolWindowCreated();

     

      // --- Set up the window pane

      _OutputPane = OutputWindow.GetPane<EventsPane>();

      VsDebug.Assert(_OutputPane != null, "Output pane creation failed.");

     

      // --- Set up window frame events

      Frame.OnShow += OnFrameShow;

      Frame.OnClose += OnFrameClose;

      Frame.OnResize += OnFrameResize;

      Frame.OnMove += OnFrameMove;

      Frame.OnDockChange += OnFrameDockChange;

      Frame.OnStatusChange += UIControl.RefreshValues;

    }

   

    // --- Event handler methods omitted

  }

 

  [AutoActivate(true)]

  [DisplayName("Dynamic window events")]

  class EventsPane : OutputPaneDefinition

  {

  }

}

The window pane class is inherited from the ToolWindowPane<,> generic class that accepts two type parameters. The first is the owner package type (must be a subclass of PackageBase), the second is a control representing the package user interface. The initial appearance of the tool window is set up through attributes like InitialCaption and BitmapResourceID.

After the tool window has been created and sited in its window frame, the OnToolWindowCreated method is called. In this method we set up the output frame where event handler codes write out messages and then subscribe for window frame events. As you see, we can access the hosting window frame through the Frame property of the pane. This example demonstrates all the events the window frame has (see source code comments for detailed description about each event). The OnStatusChange event is raised in parallel with all the other events.

The EventsPane class is a good example how easy is to create and use an output window panel with VSXtra. Using the _OutputPane member field frame event handler methods write directly to this output pane just as windows console applications to the system console:

void OnFrameDockChange(object sender, WindowFrameDockChangedEventArgs e)

{

  _OutputPane.WriteLine("Dock state changed.");

  _OutputPane.WriteLine("  Docked: {0}", e.Docked);

  DisplayPosition(e.Position);

}

 

void DisplayPosition(Rectangle rect)

{

  _OutputPane.WriteLine("  New position: {0}", rect);

}

Example #4: Persisted Tool Window

This example demonstrates the second tool window of the VS SDK’s ToolWindows reference sample called Persisted tool window. This tool window has a tool bar with a refresh button and displays the list of tool windows instantiated within Visual Studio:


This sample uses the same simple package definition as all the examples before:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using VSXtra;

 

namespace DeepDiver.PersistedToolWindow

{

  [PackageRegistration(UseManagedResourcesOnly = true)]

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

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

    IconResourceID = 400)]

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

  [ProvideMenuResource(1000, 1)]

  [ProvideToolWindow(typeof(PersistedWindowPane), Style = VsDockStyle.Tabbed,

    Window = "3ae79031-e1bc-11d0-8f78-00a0c9110057")]

  [Guid(GuidList.guidPersistedToolWindowPkgString)]

  public sealed class PersistedToolWindowPackage : PackageBase

  {

  }

}

As you see, the ProvideToolWindow still uses the “old” form with a literally specified GUID. Soon, it will be changed to the style like XtraProvideAutoLoad attribute in Example #1. The declaration of the tool window pane follows the pattern described in Example #3:

using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Shell;

using Microsoft.VisualStudio.Shell.Interop;

using VSXtra;

 

namespace DeepDiver.PersistedToolWindow

{

  [Guid("0A6F8EDC-5DDB-4aaa-A6B3-2AC1E319693E")]

  [InitialCaption("Persisted Tool Window")]

  [BitmapResourceId(301)]

  [Toolbar(typeof(DynamicToolWindowCommandGroup.PersistedWindowToolbar))]

  class PersistedWindowPane : ToolWindowPane<PersistedToolWindowPackage,

    PersistedWindowControl>

  {

    public override void OnToolWindowCreated()

    {

      base.OnToolWindowCreated();

      UIControl.TrackSelection = GetService<STrackSelection, ITrackSelection>();

      RefreshList(null);

    }

 

    [CommandExecMethod]

    [PromoteCommand]

    [CommandId(CmdIDs.cmdidRefreshWindowsList)]

    private void RefreshList(OleMenuCommand command)

    {

      UIControl.RefreshData();

    }

  }

}

There is a new attribute here, called Toolbar. This attribute attaches a toolbar definition to the tool window (I show later, how to define the toolbar). Our toolbar has only one button we want to handle, it is the Refresh button. To assign a command handler to this button we could use the pattern shown in Example #2 and #3. However, here we want to access the toolbar instance, so it is a better approach to create a command handler method within the toolbar class.

RefreshList is responsible for handling the command. The CommandExecMethod attribute marks this method as one to be called when the command is invoked. The CommandId attribute binds this method to the appropriate command. As you see, it does not set the command GUID explicitly; VSXtra infers it from the GUID of the toolbar attached to this tool window. The PromoteCommand attribute registers the command handler method at the package level in order the command could get the status query requests when the tool window does not have the focus.

How does the tool window pane know how to use the toolbar? In a similar way as we defined menu command handlers, we can define the toolbar:

using System.Runtime.InteropServices;

using VSXtra;

 

namespace DeepDiver.PersistedToolWindow

{

  [Guid(GuidList.guidPersistedToolWindowCmdSetString)]

  public sealed class DynamicToolWindowCommandGroup :

    CommandGroup<PersistedToolWindowPackage>

  {

    [CommandId(CmdIDs.cmdidPersistedWindow)]

    [ShowToolWindowAction(typeof(PersistedWindowPane))]

    public sealed class ShowToolCommand : MenuCommandHandler { }

 

    [CommandId(CmdIDs.IDM_PersistedWindowToolbar)]

    public sealed class PersistedWindowToolbar : ToolbarDefinition {}

  }

}

Here we have a command group—remember, it is a logical container for commands and other UI elements—that defines a menu command handler for the button in the View|Other Windows menu to display our tool window and a type representing the tool window toolbar. This latter one is the PersistedWindowTolbar class deriving from ToolbarDefinition. The CommandId attribute is used to define the ID for the toolbar.

That’s all from this point other bindings are arranged somewhere inside the ToolWindowPane<,> class definition.

The OnToolWindowCreated method of our window pane class initializes the related UI control by setting up the TrackSelection service. This service is used to display information about the selected item in the Properties window.

using System;

using System.Collections;

using System.Collections.Generic;

using System.Windows.Forms;

using Microsoft.VisualStudio.Shell;

using Microsoft.VisualStudio.Shell.Interop;

using VSXtra;

 

namespace DeepDiver.PersistedToolWindow

{

  public partial class PersistedWindowControl : UserControl

  {

    private List<WindowFrame> _ToolWindowList;

    private ITrackSelection _TrackSelection;

    private readonly SelectionContainer _SelectionContainer =

      new SelectionContainer();

    private bool _IgnoreSelectedObjectsChanges;

 

    public PersistedWindowControl()

    {

      InitializeComponent();

    }

 

    internal ITrackSelection TrackSelection

    {

      get { return _TrackSelection; }

      set

      {

        if (value == null)

          throw new ArgumentNullException("value");

        _TrackSelection = value;

        _SelectionContainer.SelectableObjects = null;

        _SelectionContainer.SelectedObjects = null;

        _TrackSelection.OnSelectChange(_SelectionContainer);

        _SelectionContainer.SelectedObjectsChanged += SelectedObjectsChanged;

      }

    }

 

    internal void RefreshData()

    {

      _ToolWindowList = new List<WindowFrame>(WindowFrame.ToolWindowFrames);

      PopulateListView();

    }

 

    private void PopulateListView()

    {

      listView1.Items.Clear();

      foreach (var windowFrame in _ToolWindowList)

      {

        listView1.Items.Add(windowFrame.Caption);

      }

      listView1.SelectedItems.Clear();

      listView1_SelectedIndexChanged(this, null);

      listView1.Columns[0].AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);

      listView1.Invalidate();

    }

 

    private void listView1_SelectedIndexChanged(object sender, EventArgs e)

    {

      if (_IgnoreSelectedObjectsChanges) return;

      var selectedObjects = new ArrayList();

      if (listView1.SelectedItems.Count > 0)

      {

        int index = listView1.SelectedItems[0].Index;

        var frame = _ToolWindowList[index];

        var properties =

          new SelectionProperties(frame.Caption, frame.Guid) { Index = index };

        selectedObjects.Add(properties);

      }

      _SelectionContainer.SelectedObjects = selectedObjects;

      _SelectionContainer.SelectableObjects = WindowsProperties;

      TrackSelection.OnSelectChange(_SelectionContainer);

    }

 

    private void SelectedObjectsChanged(object sender, EventArgs e)

    {

      _IgnoreSelectedObjectsChanges = true;

      try

      {

        listView1.SelectedItems.Clear();

        if (_SelectionContainer.SelectedObjects.Count > 0)

        {

          IEnumerator enumerator =

          _SelectionContainer.SelectedObjects.GetEnumerator();

          if (enumerator.MoveNext())

          {

            var newSelection = (SelectionProperties)enumerator.Current;

            int index = newSelection.Index;

            listView1.Items[index].Selected = true;

          }

        }

      }

      finally

      {

        _IgnoreSelectedObjectsChanges = false;

      }

    }

 

    private ArrayList WindowsProperties

    {

      get

      {

        int index = 0;

        var properties = new ArrayList();

        foreach (WindowFrame frame in _ToolWindowList)

        {

          var property =

            new SelectionProperties(frame.Caption, frame.Guid) { Index = index };

          properties.Add(property);

          ++index;

        }

        return properties;

      }

    }

  }

}

I do not want to tell you all the details how this code work, I suppose you can understand it. I’d rather highlight a few points:

—  The WindowFrame class provides a ToolWindowFrames property to enumerate all the window frames hosting tool windows.

—  The Properties window has a combo box where we can change the selected item. The SelectedObjectsChanged method handles this event.

—  The SelectionProperties helper class is used to show only the “interesting” window frame properties. It inherits from the CustomTypeDescriptorBase class provided by VSXtra.

Where we are?

In this post I gave you a brief overview of the VSXtra project that intends to improve VSX development experiment by providing a new Managed Package Framework. I demonstrated four examples to convince you that VSX experience really can be improved.

By the time you read this post, the first release (v0.1.0.6 alpha) can be downloaded from the projects home page (http://www.codeplex.com/VSXtra). Please, check for the latest releases and examples, and share your imaginations with me!

Main objectives of the project

When I started the project, I treated the following objectives as the top ones:

—  Making it easier for .NET developers to start VSX development. Right now starting with Visual Studio Extensibility is easier than ever before, but could be even better. VSXtra aims to help in the first steps, especially in creating simple startup applications.

—  Creating a layer above the VS SDK COM interop types to provide real objects to be used as easily as the .NET types of the BCL. Accessing the functionality of VS SDK should be as easy as using the essential types of the .NET BCL. Right now developers have to cope with COM types and interfaces, write plumbing code for .NET-COM interoperability. VSXtra aims to remove the majority of this pain.

—  Leveraging on the state-of-the-art features and tools of the .NET framework. The current implementation of Managed Package Framework does not (or poorly) use great .NET technologies like generics, LINQ, metadata techniques. VSXtra leverages on them.

—  Reducing the number of code lines required for basic VSX tasks with about 70%. VSPackages written with MPF are much easier to read and understand than former VSX code written in C++. However, the even the code with MPF contains many “noise” in form of repeated expressions, code lines, explicit casts, etc. VSXtra wants to remove this noise by using declarative approach wherever it’s possible.

—  Making the source code of VSPackages much more readable, straightforward and consistent.  As a result of rethinking the whole MPF, VSXtra definitely intends to enhance the code quality of VSPackages.

 


Posted Jul 08 2008, 03:20 PM by inovak
Filed under:

Comments

buy stendra wrote re: LearnVSXNow! #24 - Introducing VSXtra
on Sun, Feb 24 2013 11:58

k3KqnV I am so grateful for your blog.Really looking forward to read more.