Thursday, February 4, 2010

Drag and drop in Visual Studio 2010


Let me explain first how drag and drop works in a WPF application:

1) the Win32 window hosting the WPF controls needs to be registered as a drop target with Windows by calling RegisterDragDrop. When some object is dragged, Windows selects the “closest” Win32 window registered for drag drop under the mouse, and then it calls functions like DragEnter/Drop/etc on the IDropTarget interface provided at registration. WPF does this registration for HwndSource windows, and when an object is dragged over a WPF window, WPF will be called.

2) the WPF control that wants to intercept the drop needs to specify UIElement.AllowDrop=true. When called by Windows, WPF will select the innermost WPF control under the mouse that allows drop and will route to it WPF-style drag and drop events (e.g. PreviewDragEnter events will be tunneled to that control, then DragEnter events will bubble up). The controls and its parents have a chance of intercepting these events.

3) the control or one of the parents needs to recognize one of the formats of the data being dragged, and allow the drop. If no control recognizes the format, the DropEffect will be None and the drop will not be possible.

Now back to Visual Studio.

Visual Studio 2010 and later are WPF applications. The main window and floating windows (toolwindows or documents) are HwndSource, thus are registered for drag and drop by WPF. The shell sets AllowDrop=true on the root WPF elements in these windows, and since this is an inherited property, all child controls (e.g. the toolwindows contents) will automatically allow drop as well.

If the toolwindows host WPF content but they don’t intercept the drag & drop events, the shell will eventually intercept the bubbling WPF events, and will attempt to extract from the dropped data file names to be opened either as solutions/project or in editor windows. Similarly, if the toolwindows host Win32 elements but don’t register them as droptargets with Windows, Windows will pick the shell’s main window as drop target and again the shell will check the dropped data if it contains filenames that can be opened in editor.

A) Drag and drop over Shell-owned elements

The shell owns some elements in the Visual Studio UI, e.g. the frames of the toolwindows and document windows, document tabs in the document well, the main window’s background area, main window’s toolbars, toolbars in toolwindows hosted by the shell, etc., thus it controls dragging and dropping over these elements.

The shell understands only the DataFormat.FileDrop (CF_HDROP) format. This format is used for instance when one or more files are dragged from Windows Explorer. When dropped over Visual Studio’s main window, VS will use the dropped file names to open the files in a Visual Studio editor.

This works when dropping over most shell elements, with a couple of exceptions:
- dropping over tabbed toolwindows tabs or document tabs is disallowed: instead this is used to switch the active toolwindow or document in the tab group.
- dropping over comboboxes in toolbars (if editable, these allow drop with Text format)
- dropping over the main window’s title is not possible (this is a WPF issue)

B) Drag and drop over toolwindows’ content

A tool window implementer has the option of using Win32-based content or using a WPF element as content. When using the VsSDK/MPF classes like ToolWindowPane, the content can be exposed using either the Window or Content properties from the base class.

Win32 content

Whenever a Win32 window is returned from ToolWindowPane.Window, from IVsWindowPane.CreatePaneWindow, or indirectly from IVsUIElementPane.CreateUIElementPane via IVsUIWin32Element, that toolwindow will have a Win32 content. To intercept drop operations on the toolwindow content, the toolwindow’s implementer will need to register the Win32 window with Windows by calling RegisterDragDrop, and providing an IDropTarget, then handling the Windows-style drag and drop calls.

An example of such toolwindow is SolutionExplorer, which registers its content to allow D&D of solution and project items between the projects in the tree.

WPF content

Whenever a FrameworkElement is returned from the ToolWindowPane.Content property, from IVsUIElementPane.CreateUIElementPane (or indirectly via IVsUIWpfElement), the shell will host that toolwindow’s content in a pure-WPF hierarchy, without intervening HWnd. WPF-style events will then flow freely up/down the visual tree starting with the main window (or the floating window root, when the toolwindow is floating).  Conditions 1) and 2) are automatically satisfied, and all you need to do is satisfy 3) – intercept the WPF events for the dropped format you understand. Again, if the toolwindow does not intercept the dropped data and mark the WPF events handled, the shell will intercepts  them and will either deny the drop or will open the files in editors (if the data is in the FileDrop format accepted by the shell).

See the sample below for an example.

C) Drag and drop over toolwindows’ toolbars

To use a shell-implemented toolbars (WPF), a toolwindow can host it in two modes:

a) Letting the shell to host the toolbar. E.g. get an IVsToolWindowToolbarHost from the window frame by calling IVsWindowFrame.GetProperty for VSFPROPID_ToolbarHost, call AddToolbar on the toolbar host. The shell will host the toolbar directly in a WPF tree without intervening hwnds. Any drops over the toolbar will be handled by the shell. To intercept drops over the toolbar area, the toolbar has to be added using a different function: instead of using IVsToolWindowToobar.AddToobar, query the IVsToolWindowToolbarHost2 interface from the toolbar host and call AddToolbar2 method. This allows providing an object implementing IDropTarget interface that will be called by the shell when the drop happens over the toolbar area.

b) Hosting the toolbar yourself. E.g. If your toolwindow content is Win32-based, create a Win32 window child of the toolwindow’s content, and an object implementing IVsToolWindowToolbarHost interface, call  IVsUIShell.SetupToolbar to associate the Win32 child with your toolbar host, then call IVsToolWindowToobar.AddToobar to add the toolbar into the host. This way you can place a toolbar anywhere in your toolwindow’s area. If the toolwindow’s content  is already registered as drop target by calling RegisterDragDrop, Windows will select it as the “closest” drop target under mouse when the drop happens under the toolbar area, too, and you’ll receive the Windows-style calls on the drop target interface. You can also register the toolbar host child window as a separate drop target if you so wish.


The following sample demonstrates drag and drag of a custom data format, and intercepting the drop in a WPF control. It also demonstrate dragging of data in formats understood by editors and VS shell.

You need VS2010 and VSSDK2010 installed in order to build the sample and run it under the experimental hive. To view the toolwindow, use the View/OtherWindow/DragAndDropToolwindow command, then drag the labels over the indicated areas.