# Tuesday, June 15, 2004

This entry is a little tutorial on how to write your own Reflector Addin. For the past weeks, I have been playing a lot with those and Lutz Roeder was backing me up on MSN for quick questions, so it's my time to return him the favor and write a tutorial about it. So let's get started.A Reflector Addin is basically only a control that gets notified when the browser changes of active items. Of course, the tricky part is to "inject" your Addin into Reflector and to "clean" up when it is unloaded. Once, you're addin is injected, the control you provided takes care of the rest. I will not focus on writing controls here but rather on the procedure to load and unload packages.

Reflector API is rather big, so this tutorial is just the "immerged" part of the iceberg.

Quick Semantics

In Reflector, an Addin is called a Package (IPackage interface). Each package can add multiple Windows (IWindow), which are the control appearing on the right, can add CommandBar items (ICommandBarItem, ICommandBarButton) to the context menus, and attach listeners to the event trigerred when the AssemblyBrowser changes of ActiveItem (IAssemblyBrowser.ActiveItemChanged). The ActiveItem is the item selected in the reflection tree.

Building a simple package (hard way)

The IPackage interface is defined as follows:

public interface IPackage
{
   void Load(IServiceProvider serviceProvider);
   void Unload();
}

As may already know, the Reflector API is exposed through a set of interfaces. The actual implementations are obfuscated. The IServiceProvider instance is a "facade" against the implementations and can be used to retreive IAssemblyBrowser instance (and others). 

The Load method is where you will inject your Addin, while the Unload method is for cleaning. Implementing IPackage is rather boring and bug prone because you need to track all the window, menu item, etc.. that you inject to later remove them. Forgetting to remove a menu item is likely to happen often if you are in a rush. Moreover, using the serviceProvider is verbose in the sense that you access elements through a dictionary:

IAssemblyBrowser ab = (IAssemblyBrowser)serviceProvider.GetService(typeof(IAssemblyBrowser));

Let's implement a small package that displays a text box for assemblies. As mentionned above the steps are:

  • create your control and add it to the IWindowManager instance (retreive from serviceProvider),
  • add menu item and event handler,
  • set control to visible when menu is clicked,
  • add IAssemblyBrowser.ActiveItemChanged handler in your control
  • Don't forget to prepare cleaning up!

The control: this control display the name of the assembly, otherwise nothing.

public class AssemblyNameWindow : TextBox
{
    private IAssemblyBrowser assemblyBrowser=null;
    public AssemblyNameWindow()
    {
        this.Dock = DockStyle.Fill;
        this.Multiline=true; 
    }
    public IAssemblyBrowser AssemblyBrowser
    {
        get
        {
            return this.assemblyBrowser; 
        }
        set
        {
            if (this.assemblyBrowser!=null)
                this.assemblyBrowser.ActiveItemChanged -= new EventHandler(activeItemChanged);
            this.assemblyBrowser = value;
            if (this.assemblyBrowser!=null)
                this.assemblyBrowser.ActiveItemChanged += new EventHandler(activeItemChanged); 
        }
    }
    private void activeItemChanged(Object sender, EventArgs args)
    { 
        // testing if current item is an assembly
        IAssembly assembly = this.assemblyBrowser.ActiveItem as IAssembly;
        if (assembly!=null)
            this.Text=assembly.Name;
        else
            this.Text=null;
    }
}

And now the package, which creates a window, and some menu items:

public class AssemblyNamePackage : IPackage
{
    private IWindowManager windowManager=null;
    private ICommandBar assemblyMenu=null;
    private ICommandBarButton button=null;

    public void Load(IServiceProvider serviceProvider)
    {
        this.windowManager=(IWindowManager)serviceProvider.GetService(typeof(IWindowManager));
        IAssemblyBrowser assemblyBrowser = (IAssemblyBrowser)serviceProvider.GetService(typeof(IAssemblyBrowser ));

        // create window
        AssemblyNameWindow anw = new AssemblyNameWindow();
        anw.AssemblyBrowser=assemblyBrowser;
        // inject window into reflector
        this.windowManager.Windows.Add("AssemblyName",anw,"Assembly Name");

        // create menu item and attach handler
        ICommandBarManager cbm = (ICommandBarManager)serviceProvider.GetService(typeof(ICommandBarManager));
        // get assembly context menu
        this.assemblyMenu = cbm.CommandBars["Browser.Assembly"];
        // add button
        this.button = this.assemblyMenu.Items.AddButton("Assembly name",new EventHandler(button_Click));
    }

    public void Unload()
    {
        // remove window
        this.windowManager.Windows.Remove("AssemblyName");
        // remove button
        this.assemblyMenu.Items.Remove(this.button);
    }
    private void button_Click(Object sender, EventArgs args)
    {
        this.windowManager.Windows["AssemblyName"].Visible=true;
    }
}

That's it. You have built your first Reflector Addin:

Now this is a lot of code to get thigs up and moreover, it is quite errorprone, so let's us another easier method.

Building a simple package (easier way)

This solution uses a few classes that I have written in order to handle all the "load/unload" code. The new way is mainly based on custom attributes. [Update:]Firstly, you need to make your controls implement IServiceComponent where you can register to Reflector events:

public class AssemblyNameWindow : UserControl, IServiceComponent
{
    private ReflectorServices services = null;
    public ReflectorServices Services
    {
       get { return this.services;}
       set
       {
            if (this.services!=null)
            { ... (detach events)}
            this.services = value;
            if (this.services!=null)
            { ... (attach events)}
       }
    }
}

For example, the AssemblyPackage is rewritten as follows:

public class AssemblyPackage2 : BasePackage
{
    [ReflectorWindow(Caption="Assembly Name II")]
    [ReflectorCommandBar(CommandBarTarget.Assembly)]
    private AssemblyNameWindow assemblyName = new AssemblyNameWindow();
[Updated: no need to implement a property]
[Updated: no need to implement Load anymore]
}

This looks much nicer. BasePackage will self inspect and load all properties tag will ReflectorWindow attribute. For each one of those, it will add a menu entry to the corresponding menu using the information in ReflectorCommandBar attribute. A property can have multiple command bar targets and a package can have multiple windows. The example above gives as expected a new menu item:

Where's that package ?

The helper classes are not part of Reflector but you can get them from the MbUnit CVS (look in Samples/Reflector.Graph direcotry).

posted on Tuesday, June 15, 2004 10:12:00 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [6]
Tracked by:
"cowgirls" (online) [Trackback]
"Research for Reflector presentation" (Jason Haley) [Trackback]
"Research for Reflector presentation" (Jason Haley) [Trackback]
"fix satellite card" (online) [Trackback]
"insider stock tip" (online) [Trackback]
"Viagra cialis." (Cialis.) [Trackback]
"Buy xanax without prescription in usa." (Geniric xanax.) [Trackback]