2004-08-31
| Rate This Article: | Add This Article To: |
This is the second of a two-part series. Part 1 was a step-by-step guide to hosting a C# UserControl in a Visual Studio [VS] tool window. This article will show you how to create controls on an open form, in two different ways.
The sample add-in (you'll find a link at the end of this article) is a standard ListView with a couple of entries. (When you build and install it, it will show in the Tools / Add-in Manager dialog as the "DevSource Control Browser.") When you have a form designer open and you double-click on an add-in entry, the add-in creates a PictureBox with the icon from the add-in at position 0,0 on the form. Alternatively, when you drag an entry from the add-in to a form, the add-in creates a PictureBox at the drop point.
Both these operations need a copy of the DTE interface that was passed to the Connect object when the tool window was created. Accordingly, the browser project's Connect class saves the UserControl reference that HostUserControl returns:
Browser Hosted = (Browser) shimControl.HostUserControl()
and passes the browser the DTE interface:
Hosted.SetDTE((EnvDTE.DTE) application);
Double Clicking
Creating a control via DTE is pretty straightforward and well documented. After some rather routine code to check that you double-clicked on a list view item, the first important step is to detect whether VS has an active form designer. Form designers support an IDesignerHost interface, which we'll use in several different ways, so Browser.cs scans the ActiveDocument for a window that supports IDesignerHost:
private IDesignerHost GetActiveDesigner()
{
if (DTE.ActiveDocument != null)
foreach (Window W in DTE.ActiveDocument.Windows)
{
IDesignerHost Host = W.Object as IDesignerHost;
if (Host != null)
return Host;
}
return null;
}
The double click event handler calls this, indirectly (after checking that you are clicking on an item) within the AddControlToForm(typeof(PictureBox)) method call. The very first thing the AddControlToForm method does is to look for an active designer, and return null if it can't find one:
IDesignerHost Host = GetActiveDesigner(); if (Host == null) return null;
The next four things the add-control method does are more interesting. First, it creates a VS transaction, so that the whole double-click control creation is a single Undo item, instead of an add component, set parent, set text triplet:
using (DesignerTransaction Transaction =
Host.CreateTransaction("DevSource 'control browser' " +
"add-in - add control to form"))
Second, the add-control method uses the IDesignerHost to create a control, given its System.Type:
IComponent NewComponent = Host.CreateComponent(ControlType);
Third, the add-control method casts the IComponent to a Control; sets the new control's Parent and Text properties; and does a BringToFront:
Control NewControl = NewComponent as Control;
if (NewControl != null)
{
NewControl.Parent = Host.RootComponent as Control;
NewControl.Text = Text != null ? Text : NewControl.Name;
NewControl.BringToFront();
}
Finally, the add-control method commits the transaction (the transaction's Dispose method only cleans up resources and does not commit) and returns the new control (or null):
Transaction.Commit(); return NewControl;
Drag and Drop
Dragging a control from an add-in to a form is almost as straightforward, but it is not documented at all. After literally days of chasing false leads, I found that you can't DoDragDrop() either a System.Type or a ToolboxItem. You have to use IToolboxService to serialize a ToolboxItem, and you can then drag the serialized tool box item. Ultimately, it turned out that you can use the IDesignerHost interface to get an IToolboxService interface.
The drag and drop region in the Browser.cs file contains some perfectly ordinary code to cache the ListViewItem that the user clicked on (if any) and to check whether the mouse has moved more than DragSize pixels away with the mouse down. This is encapsulated in the Drag field, whose Triggered method returns true when you should initiate a drag operation.
Since the drag operation needs an IDesignerHost, just like the double click operation, the first step is again to call GetActiveDesigner, and return if it returns null. If there is an active designer, we will use this to get an IToolboxService:
IToolboxService ToolboxService =
(IToolboxService) Host.RootComponent.Site.GetService(
typeof(IToolboxService));
Host.RootComponent is the Form (or UserControl) in the active form designer. The form "knows" if it is hosted in a form designer, and RootComponent.Site is an ISite that represents services the form designer provides. Crucially, ISite descends from IServiceProvider, and so can be used to get the IToolboxService that we need to serialize a ToolboxItem.
The second step is to create a ToolboxItem wrapper for the System.Type of the control we wish to drag — a PictureBox, in this case — and to add a ComponentsCreated event handler to take care of setting up the control once it's dropped onto a form:
ToolboxItem Item = new ToolboxItem(typeof(PictureBox));
OnCreateCallback Callback = new OnCreateCallback(Icon);
Item.ComponentsCreated += new
ToolboxComponentsCreatedEventHandler(Callback.OnCreate);
The third step is to use the toolbox service interface to serialize the toolbox and then to initiate a drag and drop operation with the serialized toolbox item:
object Serialized = ToolboxService.SerializeToolboxItem(Item); DoDragDrop(Serialized, DragDropEffects.Copy|DragDropEffects.Move);
The critical piece, here, is that the object that SerializeToolboxItem returns supports the IDataObject interface.
When you drop a serialized ToolboxItem onto a form, VS deserializes the ToolboxItem and creates the component(s) it wraps. (Although the standard ToolboxItem can only wrap a single component, derived types can wrap multiple components, and the component creation methods reflect this, returning an IComponent[] instead of a single Component or IComponent.) The callback gets this IComponent[] as part of its ToolboxComponentsCreatedEventArgs parameter, and retrieves the new control as, e.g.
PictureBox NewControl = e.Components[0] as PictureBox;
The sample callback only sets the picture box's Size and Image to match the list view's icon, so I don't show it here.
Modal Clicking
The most common way to use the VS Toolbox (at least for me) is modal clicking: selecting a Toolbox item by clicking on it, then adding it to a form by clicking on the form. (Or, perhaps, clicking and dragging to give the new control a non-default size.)
Unfortunately, I have not been able to find out how to emulate this from a custom add-in. Perhaps modal clicking, like dragging and dropping, requires a few undocumented tricks, but I'm inclined to suspect that it can't be done. It looks like checking the Toolbox for a selected item when you click on a designer is built into VS, yet there is no API for outside developers to hook into. It's famously hard to prove a negative, of course, and I'd love to hear that it can be done. You can contact me through my Web site, or post a talkback message here.
Download the code
Jon Shemitz is an independent developer and author in Santa Cruz, California.
![]() |
|


