Using VS - DevSource
DevSource: Microsoft Developer Resource DevSource Home Sponsored by Microsoft Home Add Ons Architecture Languages Techniques Using VS Forums
Home arrow Using VS arrow Visual Studio Add-Ins: Adding Controls To Forms
Visual Studio Add-Ins: Adding Controls To Forms
By Jon Shemitz

Rate This Article: Add This Article To:

Dragging a control from an add-in to a form is straightforward, but it is not documented at all. Learn a bunch of techniques to make it easier to work with forms.

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.

ADVERTISEMENT

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 here.

Jon Shemitz is an independent developer and author in Santa Cruz, California.




Discuss Visual Studio Add-Ins: Adding Controls To Forms
 
Looks interesting, but no download.
>>> Post your comment now!
 

 
 
>>> More Using VS Articles          >>> More By Jon Shemitz
 



Top Technology Pros One-to-one

All Videos >

Julia explores the Robotics Studio!

Read now >

Messages to Bill Gates!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.