Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building a Refactoring Plug-in for VS.NET - the Sequel

0.00/5 (No votes)
31 Aug 2004 1  
Describes how to extend the original refactor add-in with additional features.

Sample Image

Introduction

Based on an existing add-in, this article shows a simple framework for creating add-ins that provide multiple refactoring functions. It shows how to interact with the source code model, automating editing functions in the source, and generating new classes.

Background

A year ago, I wrote an article that provided a Visual Studio add-in to refactor a variable into a property. Since then this add-in has been in general use in my work and I have added several other refactoring functions. To this purpose, I implemented a little framework for my add-ins to make it easier to add other refactoring functions.

The add-in now sports the following features:

  • Make Property - point to a variable and turn it into a property with setter and getter methods.
  • Make Function - select a piece of code, turn it into a function, and insert a call to the function at the original location.
  • Make Collection Class - select a class and generate a typed collection class (based on CollectionBase).
  • Make Mock Object - select a class (even framework classes) and generate a collection class that overrides all available functions and properties to allow you to simulate this class.
  • Move To Super Class - take an element and move it to the superclass.
  • What is This - a simple demonstration to show how to pick up the edit point in the code and work out what it is.

The Plug-in Framework

When you create a new project to build a VS plug-in, VS.NET generates a class called connect. This class contains all the necessary interfaces that will be called by VS.NET. There are three kinds of calls:

  1. initialization and cleanup calls: these happen at startup, when add-ins are loaded or when VS.NET shuts down.
  2. the Query call: this call occurs often, whenever VS.NET needs to know whether to enable your command in its popup menus. Your code has to determine the circumstance and then can respond with a status.
  3. the Exec call: your add-in command has been invoked and you need to run it.

You do not need to have one connect class per command, however if you make multiple ones available in this class (as is done here), the connect call has to work out which command is affected in the Query and Exec calls.

The plug-in framework solves this by creating a base class called VS-Plugin. Each kind of command listed above is implemented as a sub class of VS-Plugin. The connect class is now only aware of a collection of these plug-ins, and when a Query or Exec call comes, it simply iterates through its collection and passes the request on to the appropriate class.

Sample Image

The VSPlugIn class also contains several utility functions to do things such as:

  • what is currently highlighted, a class, a function, etc.
  • the ability to link into the necessary command structures into VS.NET.
  • putting up a dialog to ask the user for a target project.
  • displaying messages in the Output window of VS.NET.
  • etc.

Most plug-ins also have a separate class to do the actual work - e.g., the MakeMockObject plug-in class has a class called MockObjectBuilder. This is simple common sense of separating concerns, the plug-in class' purpose is to understand the request's detail and to deliver the resulting code into the right place, the MockObjectBuilder class' purpose is to generate the Mock class.

How to add another Plug-in

There are only a few steps that need to be done:

1. Create a new VSPlugIn subclass

VSPlugIn has several abstract properties and methods which you must implement:

  • cmdName: the short command, e.g.: "MakeProperty".
  • qualifiedName: the fully qualified command, e.g.: "RefactorAddIn.Connect.MakeProperty".
  • IconId: the ID of the icon to show in the menu, e.g.: 54.
  • position: where in the menu to position the command (1= first).
  • shortDescription: the text to show in the menu.
  • long Description: the text for the tool help.
  • doQueryStatus(): implement the logic to determine if the method currently makes sense and should be active or be inactive (or invisible).
  • doExec(): implement the actual action of the command.

2. Extend the Connect class

The connect class has a method called OnStartupComplete. You must add your plug-in to the other plug-in's collection and call the methods to have it show up in the right tool bars.

CommandBar popup = InsertSubMenu("Code Window", "Refactoring");
CommandBar classViewCommandBar = applicationObject.CommandBars["Class View Item"];

VSPlugIn plugin = new PropertyMaker(applicationObject, addInInstance);
Plugins.Add(plugin);
plugin.InsertCommand(classViewCommandBar,false);
plugin.InsertCommand(popup, false);

The first two lines are already given, they provide two commandbars, one a separate popup menu that is part of the Code Window menu (the one that pops up when you right click in the code window), and the other is the menu that pops up when you right click in the Class Explorer.

You need to add code in similar to the last four lines shown above:

  1. create an instance of your plug-in.
  2. add the plug-in into the plug-ins collection.
  3. if you want the plug-in be available for each commandbar, call the plug-in's InsertCommand method. The boolean should only be true for special circumstances when developing - see below in Points of Interest.

The above example makes the property maker available on both menus. You can target other menubars or leave them out altogether. In this instance, you can still bind to your command via the keyboard mapping available in the Tools->Options dialog (select Folder Environment and item Keyboard) - or you can call your command from macros.

Points of Interest

Programming Visual Studio.NET is not for the faint hearted. Good documentation is not easy to come by. However, I found the book "Inside Microsoft Visual Studio. NET" (Brian Johnson, Craig Skibo, Marc Young, Microsoft Press, ISBN 0-7356-1874-7) to be an excellent help. Still you need to read it several times.

Visual Studio .NET is written in COM, which makes life less pleasant. Here are a few pointers:

  1. Collections start with index 1.
  2. If you ask a collection for something it doesn't have, you won't get a null but an exception. So if you want to check if something is in the collection, you have to write something like this:
    protected bool HasCommand(string commName,out Command command)
    {
        command = null;
        try
        {
            Commands commands = _applicationObject.Commands;
            command = commands.Item(commName,-1);
            return true;
        }
        catch (System.Exception){}
        return false;
    }
  3. Not all commands will work under all circumstances. For instance, the command InsertClass, which inserts a new class into the code model, only works with C++, not for C# or VB.NET. However, InsertMethod does.
  4. Not all windows give their content out. While the documentation makes a lot of effort telling us how to access the tree controls in some of their windows, you need to almost read between the lines to work out that this is not available for some windows, e.g.: the Class View window.
  5. It is highly recommended to peruse and understand other examples of plug-ins. Graceful acknowledgement to Erick Sgarbi and his article on a VS Plug-in where I borrowed several techniques including the one above.
  6. If you are interested in CodeDom (defining code constructs and then generating source code), look at my CollectionBuilder class for an example.
  7. When you publish your add-in to Visual Studio, you create an object called a command. This command is the structure by which other parties in VS.NET can recognize your plug-in and invoke it. Be aware that on shutdown, VS.NET saves this command and reloads it on restart. Hence you do not have to recreate a command every time your add-in is loaded. In fact, you shouldn't, because this gums up the works. The VSPlugIn::InsertCommand method has a boolean parameter called deleteIfExists; if true, it will delete existing commands before creating a new one. This parameter should only be true during developing and debugging your add-in. In default mode (=false), it will try to find and use the existing command and create one only if it doesn't exist yet.
  8. It is also possible to create during debugging multiple instances of controls (the things that make a command visible in a pop up menu). The InsertCommand method tries to flush these out by calling flushControls(). If you don't do this, all kinds of weird things can happen, sending you around the bend.

History

  • Version 1.1 - Based upon feedback I have added several changes
    • Make Property is now configurable in the way the private variable is named.
    • The setup adds a registry entry for HKEY_CURRENTUSER\ Software\ MethodsConsulting\ RefactorAddIn\ PrivatevarCase. Possible values are:
      • underscore - prepends an underscore to the variable
      • m_underscore - prepends an 'm_' to the variable name
      • camel - use camel casing
    • Move To SuperClass now works with Variables, Properties and functions
    • Plus various fixes to smaller annoyances.

Enjoy

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here