Ways to use .Net Reflector #2.1: Creating your own add-ins

by Jason Haley 25. August 2007 21:36

In this entry I walk through creating a Reflector add-in that exposes its own UI using a user control and works with the Reflector code model on a simple level.  This add-in will expose itself on the context menu of the assembly browser (enabled only in certain conditions) when clicked it will show the user control that uses the Reflector code model of the currently selected type to provide functionality to the user.

Download the code

Download Word Version

clip_image001[1][1]Background
Before getting into how to build this add-in, I want to give a little background about it and the functionality it is to provide.  In my common everyday developer life (ie. work), I often have the need to convert a bit flag enum to a decimal value and vice-versa. I have notice there are a couple of these enums that I have to convert quite frequently … so I have a big interest in automating this. In my case the db stores a decimal representation of the C# enum and I often need to turn a bit on or off or know what a specific decimal value translates to. For demonstration purposes I’ll use a enum that you all have on your machines (System.IO.NotifyFilters) to illustrate. Reflector defines the enum as the shown in the image on the right.

As you can see, there are 8 fields and they are listed in alpha order of the field names.  This helps, but when the numbers start getting larger figuring out what bits are on for a given value takes longer than it should ... besides shouldn't a utility do that for you?  For example: if you have decimal value of 29, what bits are turned on? ... FileName, Attributes, Size, LastWrite.  That one isn't too hard but when you get enums that have decimal values over 100,000 mistakes could happen.   The add-in that I’m going to walk through is designed to provide the following functionality:

  • Display a Checkbox list of the enum fields (displaying the name and value) – for input and display
  • Display a Textbox that will provide 2 pieces of functionality:
    • Shows the decimal number of the items checked in the check box list
    • Takes a decimal value as input from the user, which when tabbed out of will update the check box list showing the item (bits) that are turned on for the given decimal value
  • Copy button that will put the value in the text box on the clipboard
  • Provide a context menu item in the assembly browser (that is only enabled on enums with the flags attribute) to show the UI

The final look of the control and context menu item is to be something like this:

BitFlagConverter ContextMenu

Now that you have an idea of what functionality I wanted to add to Reflector, let's walk through the steps to build it.

Building a Reflector Add-in that Shows a User Control
There are two pieces to this add-in: the add-in package (ties the functionality into Reflector) and the user control.  Let's walk through creating the package first. 

The package is very close to the package created in Ways to use .Net Reflector #2: Create your own add-ins.  You need a Load(), Unload() (both required by the IPackage interface) and a button click event handler.  This time I put it on the Browser.TypeDeclaration command bar instead of the Tools command bar.  One thing you might be wondering is how I knew about the Browser.TypeDeclaration command bar ... I found it by looking through code of other Reflector add-ins from CodePlex Reflector Add-ins Home, but you can also find it in the document Lutz has created - Introduction to the .NET Reflector Add-In Model.

Create the Package
First create a dll project and name it something like EnumAddin and set a reference to the Reflector.exe

Once the project is created rename the default class to something like EnumPackage, make the class implement the IPackage interface and add the following using statements:

using Reflector;

using Reflector.CodeModel;

using Reflector.CodeModel.Memory;

Next create the member level variables needed to access services provided by Reflector and tie the UserControl to the add-in package:

private IWindowManager windowManager;

private ICommandBarManager commandBarManager;

private ICommandBarSeparator separator;

private ICommandBarButton button;

private BitFlagConverter bitFlagConverter;

The WindowManager manages the application window and panes in Reflector and is accessible through the Reflector.IWindowManager interface.  If you want your add-in to show a window in the Reflector application, you'll need to add your controls to the IWindowManager.Windows collection [wrapped in a UserControl].  One note on adding your own controls to the windows collection- Reflector doesn't want you to add Form objects (you'll be notified of this at runtime if you try it), so make your UI components Controls or UserControls and you'll be fine.  Here is the complete listing of the package class contents:

/// <summary>

/// Loads the specified service provider.

/// </summary>

/// <param name="serviceProvider">The service provider.</param>

public void Load(IServiceProvider serviceProvider)

{

    // Create a command button, separator on the TypeDeclaration context menu and wire up the button click event

    this.commandBarManager = (ICommandBarManager)serviceProvider.GetService(typeof(ICommandBarManager));

    this.separator = this.commandBarManager.CommandBars["Browser.TypeDeclaration"].Items.AddSeparator();

    this.button = this.commandBarManager.CommandBars["Browser.TypeDeclaration"].Items.AddButton("Show Bit Flag Converter",

        new EventHandler(this.BitFlagButton_Click));

    // Create the user control and give it a reference to the context menu button for enabling/disabling

    bitFlagConverter = new BitFlagConverter(serviceProvider);

    bitFlagConverter.ContextMenuButton = this.button;

    // Get the window manager service and add the UserControl

    this.windowManager = (IWindowManager)serviceProvider.GetService(typeof(IWindowManager));

    this.windowManager.Windows.Add("BitFlagWindow", bitFlagConverter, "Bit Flag Converter");

}

/// <summary>

/// Unloads this instance.

/// </summary>

public void Unload()

{

    this.commandBarManager.CommandBars["Browser.TypeDeclaration"].Items.Remove(this.button);

    this.commandBarManager.CommandBars["Browser.TypeDeclaration"].Items.Remove(this.separator);

}

/// <summary>

/// Handles the Click event of the button control.

/// </summary>

/// <param name="sender">The source of the event.</param>

/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>

private void BitFlagButton_Click(object sender, EventArgs e)

{

    this.windowManager.Windows["BitFlagWindow"].Visible = true;

}

The code is pretty self explanatory so I won't bore you with a detailed walkthrough of it.

Create the UserControl
Now that the Package class is done, we need to create the user control that implements the add-in functionality discussed above in the background section.

Add a new UserControl to the project named BitFlagConverter.  You can also add a Form and then change the base class to UserControl - that is what I did until I found out Reflector wouldn't allow a Form to be loaded... if you forget to change it to UserControl - you'll be reminded when you test it the first time.  Since I originally created the UI using a Form, the behavior might be different when starting with a UserControl.

clip_image001[2]The UserControl needs to have four controls, which is shown in the image to the left.  The controls are a label in front of the Textbox used to remind me what the TextBox if for :), the TextBox to show the decimal value (and take one as input), a Button for copying the value to the clipboard quickly and a CheckBoxList for showing the enum fields.  When creating Reflector add-ins that have UI controls, you'll want make sure you test what happens when the font in Reflector changes (View menu -> Options -> Appearance - Browser Font), this can have adverse effects on your controls.

Once the controls are laid out the way you want them, you'll need to setup the event handlers.  As I'm sure you know, when it comes to the TextBox you could choose either the TextChanged event or Leave event for handling updating after a user inputs a value. I chose the Leave event since I thought it would be more efficient for my use case - this means the list won't be updated with the selected values until I tab out of it.

Besides handling the events for the check box list SelectedValueChanged, TextBox Leave and the Button Click, you'll also want to add an event handler for the IAssemblyBrowser.ActiveItemChanged event.  The AcitiveItemChanged event fires when the user selects something in the assembly browser (tree view portion of Reflector).  When this event fires, you can check the IAssemblyBrowser.ActiveItem property to see what the user selected. To wire into this event you need to pass either an IAssemblyBrowser or an IServiceProvider to the UserControl (in case you didn't notice in the code earlier - the constructor to BitFlagConverter has an IServiceProvider parameter).

Since the majority of the UserControl code is somewhat standard window forms programming and not specific to writing Reflector add-ins, I'll try and discuss only the add-in code.  So let’s look at the UserControl code, starting with the member level variables:

IAssemblyBrowser assemblyBrowser;

ICommandBarButton contextMenuButton;

As mentioned above, the UserControl needs to keep a reference to the IAssemblyBrowser and a reference to the context command bar button in order to enable/disable it.  The contextMenuButton is wrapped in a write only property and was set in the Package.Load() detailed above.  The assemblyBrowser gets set in the constructor after it is retrieved from the passed in IServiceProvider object.  Here is the implementation of the constructor:

public BitFlagConverter(IServiceProvider serviceProvider)

{

    this.assemblyBrowser = (IAssemblyBrowser)serviceProvider.GetService(typeof(IAssemblyBrowser));

    this.assemblyBrowser.ActiveItemChanged += new EventHandler(AssemblyBrowser_ActiveItemChanged);

    InitializeComponent();

}

IType The context of this add-in (whether it should be enabled and populated) depends on what the user has selected in the assembly browser.  If the user has selected a type that is not an enum or is an enum but doesn’t have the flags attribute, then the add-in control should clear the list and textbox and disable itself (both the control and the context menu).  If it is an enum that has the flags attribute, then populate the list and enable the UI components.  The event handler of the ActiveItemChanged event (shown below), only provides the condition logic – the interesting work is done in the IsFlagsEnum utility method.

public void AssemblyBrowser_ActiveItemChanged(object sender, EventArgs e)

{

    if (IsFlagsEnum(this.assemblyBrowser.ActiveItem))

    {

        this.UpdateList(this.assemblyBrowser.ActiveItem);

        SetEnabled(true);

    }

    else

    {

        ClearUI();

        SetEnabled(false);

    }

}

In order to find out whether or not the active item is an enum with the flags attribute, you must remember the basics: an enum is a type that inherits from System.Enum and attributes are stored on the type itself. Now is when we have to start working with the interfaces exposed by Reflector code model. The interfaces that are relevant to finding out if a current ITypeDeclaration is an enum that has the flags attribute on it can be seen on the left and right: ITypeDeclaration, ITypeReference, IType, ICustomAttribute, IMethodReference and IMemberReference. You can also see the usage of these interfaces in the IsFlagsEnum() method shown below.

The IsFlagsEnum method checks if the active item implements the ITypeDeclaration interface, if it doesn’t then there is no nICustomAttribute eed to go any further. If the active item is a type declaration, then we need to check its base type to see if it is an enum. When checking the name on a type reference (the BaseType property on the ITypeDeclaration expose an ITypeReference interface) make sure you break the namespace and name apart – because they are separate properties. In my case, I only checked for the name “Enum”, but you might want to also check the namespace is “System” to ensure the type is the System.Enum type.

Once we know the type is an enum, we need to look through all the attributes on the type to see if the “FlagsAttribute” is one of them. If you look at the diagram of the ICustomAttribute (to the right), you’ll notice there isn’t a relation to any type or name – which means you can’t simply check the name of the attribute. The trick I used (there may be a better way that I’m not aware of), is to go through the Constructor property – which is an IMethodReference interface. As shown in the diagram, the IMethodReference inherits the IMemberReference interface, which has the DeclaringType property of the IType interface. In my (limited) experience of using Reflector code model classes, I’ve noticed that the majority of the situations where you have an IType interface, it is usually either an ITypeReference or an ITypeDeclaration (which inherits from ITypeReference) – either way you can get the name of the type through the type reference. So at this point we need to cast the DeclaringType property to an ITypeReference interface to get the name of the type the attribute’s constructor belongs to – in essence the attribute type name. Here is the code that does that:

private bool IsFlagsEnum(object activeItem)

{

    ITypeDeclaration value = activeItem as ITypeDeclaration;

    if (value != null && value.BaseType.Name == "Enum")

    {

        foreach (ICustomAttribute attribute in value.Attributes)

        {

            if (((ITypeReference)attribute.Constructor.DeclaringType).Name == "FlagsAttribute")

            {

                return true;

            }

        }

    }

    return false;

}

CropperCapture[2] Now that we know when the user selects an enum with the flags attribute, all we need to do is get the field names of the CropperCapture[3] enum and their values so they can be added to the checkbox list. In order to do this, we need to loop through the ITypeDeclaration.Fields property. As you can see in the diagram, the IFieldDeclaration interface inherits from the IFieldReference interface which inherits from the IMemberReference interface – which shows the name property in on the IMemberReference interface. In the Reflector disassembler window, when you see an enum declaration and its values, there are two things involved in building that output: the field name and its initializer expression. The name is easy enough to get from the IMemberReference, but you need to do some poking around the code model to find that the field Initializer is usually a LiteralExpression. Once you know this, you can cast the Initializer property to an ILiteralExpression to get the value and you are all set. Now you have the name and the value of the fields, which can then be used to populate the check box list, and all that fun UI stuff. Here is the code to the UpdateList method, which populates the check box list:

private void UpdateList(object activeItem)

{

    valueTable.Clear();

    ClearUI();

    ITypeDeclaration value = activeItem as ITypeDeclaration;

    foreach (IFieldDeclaration fieldDef in value.Fields)

    {

        // Always ignore the "value__" field, since it is a special system field.

        if (fieldDef.Name != "value__")

        {

            long val = Convert.ToInt64(((ILiteralExpression)fieldDef.Initializer).Value);

            string text = fieldDef.Name + " {" + val.ToString() + "}";

            this.lstEnumItems.Items.Add(text);

            valueTable.Add(text, val);

        }

    }

}

If you are interested in all the pieces of the UserControl, you should download the code and check it out. I’ve only covered the add-in related methods. Here is a screen shot of the final add-in looking at the System.IO.NotifyFilters enum:

FinalBitFlagConverter

Summary
In this entry I’ve walked through how to create a Reflector add-in that provides its own UI via a UserControl and how to tie it into the context menu of the assembly browser. I’ve also walked through the more interesting parts of the logic that listens to a user’s selection in the assembly browser and some of how to determine what the current type selected is and its properties. We also looked at the field declarations on a type and touched on expressions with the field’s Initializer property. All these pieces take the development of a Reflector add-in just a little bit further than in the original entry on creating a Reflector add-in with no UI. Next time we’ll get even further into the Reflector.CodeModel interfaces.

Comments (1) | Post RSSRSS comment feed |

Categories:
Tags:

Comments

Comments are closed