Developer

Increase code reuse with Visual Studio .NET Toolbox automation

The Common Environment Object Model covers every aspect of the Visual Studio .NET IDE. During a recent engagement, a consultant used it as a tool for building a simple console application to install a new control to the toolbox in Visual Studio .NET.


One of the best things about modern development tools is that they’re extensible. If you have a repetitive coding task, the first step is to wrap up the repeated code into a library or an object that you can reuse. If you’re using a development environment such as Visual Studio .NET, you can make reuse even easier by wrapping your code up as a component or a control. Once you’ve done that, code reuse becomes a matter of drag-and-drop rather than cut-and-paste.

I recently went through this process while helping a client prepare a .NET-based application. We noticed that a common set of menu items was repeated on many forms, and decided to package this set of items as a component. This was a straightforward process: We built a Windows Control Library in C# and added the logic to merge the menus it contained with the parent form. The shared code was all wrapped in the component, and it fired events so that the parent form could also insert its own custom logic into the process. Then came the problem of distributing this control to all of the developers on the project.

The joy of packaging
For the most part, preparing the control for shipment as an independent project was pretty simple. Adding a Setup Project to the same solution that already contained the Control Library gave me a Windows Installer-based installation program for the control. In this context, I didn’t mind that Visual Studio .NET Setup Projects require the .NET Framework on the target computer; anyone wanting to install a custom control in Visual Studio .NET already had the framework by definition.

There was just one piece missing: integrating the control into the Visual Studio .NET user interface. I considered just telling people how to customize their Toolbox in a readme file, but that seemed unprofessional. Surely there was a way to write code to accomplish this task. If I could do the job in code, I could call that code from a custom action in the setup program.

The messy details
My goal led me into the Byzantine pathways of the Common Environment Object Model, a detailed object model that covers every aspect of the Visual Studio .NET IDE, from the task list to the toolbox. There are more than 75 objects in this model; fortunately, for the task at hand I only needed to use the ones shown in Figure A.

Figure A


Let’s look at the steps involved in building a simple console application to install a new control to the toolbox in Visual Studio .NET. I used C# for my code, but of course you can use any managed language. The first step is to set a reference to EnvDTE.dll, the library that contains the Common Environment Object Model. This assembly is installed in the Global Assembly Cache, so you can find it by looking for envdte on the .NET tab of the Add Reference dialog box. Then, you’ll need to tell your C# application to use objects from this assembly:
using EnvDTE;

To use the objects in this assembly, you need to walk down an object hierarchy. This hierarchy starts with a creatable object named DTE (for “design-time extensibility”). Anytime that you want to manipulate the Visual Studio .NET environment, you’ll start with this object:
EnvDTE.DTE env = new EnvDTE.DTE();

The “children” of the DTE object include the Windows collection. This collection has one member for each window within the Visual Studio .NET environment (the Solution Explorer, Properties window, and so on). A helpful set of constants allows you to pull specific windows out of this collection. You can use this to get a reference directly to the Toolbox window:
Window win =
env.Windows.Item(EnvDTE.Constants.vsWindowKindToolbox);


Now, the Window object is only the container for the Toolbox. The Window object lets you deal with things like the position of the window, whether it’s linked to another window, and whether it’s visible. But to work with the actual Toolbox, you need to extract it from the Object property of the Window object:
ToolBox tb = (ToolBox)win.Object;

The Toolbox contains multiple tabs (Windows Forms, Components, and so on). These are all contained in a ToolBoxTabs collection:
ToolBoxTabs tbts = tb.ToolBoxTabs;

Next, you need to decide whether you want to add your component to an existing tab in the Toolbox, or to create your own tab. If you’re shipping a large number of controls, you might prefer to create your own tab to group them all together:
tbt = tbts.Add("MyTabName");

If you’re just adding one or two controls, you’ll probably want to place them in an existing tab. This is a bit trickier, because you can’t be sure that the user hasn’t renamed or deleted the tab that you’re aiming for. One way to handle this problem is to simply iterate through the collection:
int i;
for(i=1; i<=tbts.Count; i++)
{
    if (tbts.Item(i).Name == "Windows Forms")
        {
        tbt = tbts.Item(i);
        break;
    }
}


Either way, once you have a ToolBarTab object, you can proceed to the collection of items on that tab. Not surprisingly, that collection is represented by a ToolBoxItems collection:
ToolBoxItems tbis = tbt.ToolBoxItems;

Now you’re ready to actually add to the Toolbox. One little pitfall to watch out for is that you can only add items to a tab that’s currently active. So, your best bet is to activate it yourself:
tbt.Activate();

Finally, you can call the Add method of the ToolBoxItems collection to actually create a new control in the Toolbox:
ToolboxItem tbi =
 tbt.ToolBoxItems.Add("NewControlName",
 "C:\InstallDir\NewControl.dll",
 vsToolBoxItemFormat.vsToolBoxItemFormatDotNETComponent);


The three parameters to the Add method of the ToolBoxItems collection are as follows:
  • The caption to use when adding the item to the Toolbox
  • The managed assembly that contains the actual component
  • A constant that indicates the type of item to add (here, a .NET component)

The help file says that filenames are assumed to end in “dll,” but I’ve found it necessary to supply the extension. Also, I spent many hours pulling my hair out until I discovered that the full path to the file was a necessity, even if the code adding the file was in the exact same directory as the component. It’s these little quirks that make learning a new API so much fun.

Beyond the toolbox
Even if you don’t have a need for this particular technique, a little thought and research should convince you that the Common Environment Object Model is a powerful tool. The ability to investigate, modify, and control Visual Studio .NET from code makes it possible to write many interesting developer utilities.

For example, you could write a settings-transfer utility that would determine the position and location of windows, menus, and toolbar items within Visual Studio .NET. The next time you need to reinstall, you could save all this information, and then reapply your own settings rather than spending endless hours tweaking everything to be just so.

Another interesting area of the model is the task list. In Visual Studio .NET, you can add arbitrary “to-do” sorts of items to the task list, but right now those items are trapped in an individual project. These items are all available through the TaskItems collection, which means that you can potentially extract, store, and aggregate them. Perhaps an Access database or an Exchange public folder would be an interesting way to share this information between projects?

Final thought
Smart consultants are always on the lookout for ways to leverage their work. Selling your work once is good. Selling your work many times is even better. For example, suppose you’ve tackled a complex development project, like the one featured here, for a client. It’s entirely possible that this project will spin off some reusable code that you can package and sell as an independent product (be sure your contract allows this).

As it turned out, this was a perfect piece for resale, and my client agreed to let me retain ownership of reusable code.

Editor's Picks