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.

http://www.alinconstantin.net/Download/Example.SimpleDragDropInVS.zip

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.

Capture

12 comments:

Andrei said...

Hi Alin,

What pisses me off in VS 2010 is that I can't drop files anymore in the editor to open them - only on the document well. Is it possible to revert to the VS2008 behavior (dropping anywhere opens the files) ?

Thanks

Alin Constantin said...

Hi Andrei,

You should be able to drop files on the editor surface, too, and have VS open them as new documents.
The default editor (e.g. the one for txt files) intercepts the WPF-style drag and drop events, and if the format dropped is not something it recognizes and that it can paste directly, it then delegates the drop to the shell's drop target, which opens them as new documents.

I can see two ways when drop may not work:
- if VS is started as an elevated process and you attempt to drag an item from a non-elevated process and drop it onto VS. In this case it's Windows denying the drop anywhere on the application's surface. Since you mention being able to drop on the document well, I guess this is not your case
- if you're using an editor that installs its own handler for WPF events, or it's a Win32 editor that registers the window as drop target and doesn't call the shell for what it doesn't process.

I'll contact on private so you can tell me more about your scenario.

Alin

Alin Constantin said...

Andrei, nu dau de adresa ta de mail pe hardcode.ro. Daca vrei, contacteaza-ma tu direct pe alincATmicrosoftDOTcom sau daca preferi raporteaza problema la http://connect.microsoft.com/VisualStudio/

Alin

Nam said...

Hi Alin,

In VS2010, is it possible to drag and drop something into the WPF/Silverlight Design Surface from custom tool window pane? This tool window pane is something likes DataSources explorer in VS.

Thanks and Regards,
Nam

Alin Constantin said...

@Nam: Yes, the Silverlight/WPF Designer window accepts drop operations (e.g. notice you can drag items from Toolbox and drop them on the surface), but to have D&D from your custom toolwindow to the designer surface working you must initiate the Drag operation with a data format that the designer will accept. I'm not sure what are the formats accepted by that designer. You can probably ask on this forum what those formats are: http://social.msdn.microsoft.com/Forums/en-US/vswpfdesigner/threads

Nam said...

Hi Alin,

Thanks for your comments. I can see a guy also had a question like me but it's not resolved yet. http://social.msdn.microsoft.com/Forums/vi-VN/vswpfdesigner/thread/3571c485-71b9-4072-b3e0-47e6726a36dc

I tried serialize a toolbox item (ex: Button) as data object and put it in DragDrop.DoDragDrop but not any luck.
Please see the below:

IToolboxService toolbox = (IToolboxService)GetService(typeof(IToolboxService));

foreach (ToolboxItem toolBoxItem in toolbox.GetToolboxItems())
{
if (toolBoxItem.DisplayName == "Button")
{
_buttonDataObject = toolbox.SerializeToolboxItem(toolBoxItem);
break;
}
}
DragDrop.DoDragDrop((DependencyObject)sender, _dataObject, DragDropEffects.All);

Thanks and Best Regards,

Alin Constantin said...

@Nam: I'm not familiar with the toolbox D&D, but from what I read the ToolboxItem class is the way to go. http://social.msdn.microsoft.com/forums/en-us/winformsdesigner/thread/48E2B80A-D068-444F-A9CA-5A295DC9D9B0
You may want to talk with Josh Stevens (joshsATmicrosoftDOTcom) as he knows best about toolbox objects.

As for the drop on the Cider designer surface, I'd recommend contacting Mark Wilson-Thomas (mwthomasATmicrosoftDOTcom) or Brian Pepin (brianpeATmicrosoftDOTcom) - the Cider devs should know best what are the clipboard formats accepted by their surface on drop. If I understans correctly Mark's reply on the forums' link you gave in previous reply, Cider uses a private format, so you may be out of luck there. See if Brian/Mark can describe you that format...

Alin

Nam said...

Thank you, Alin. I sent emails to them and will let you know when I resolve this issue.

Notre said...

Thanks so much Alin! I had the problem where I couldn't drop within my VS editor package hosting a WPF control. Once I explicitly set the format and handled the drop events, everything started working. I would've never figured it out without this post!

Mike said...

Apart from the removal of the MDI environment what hacks me most is that I can no longer select and drag a section of code, it simply refuses to do it - just displays a "can't do that" icon (circle with diagonal line through it).

Stupid, just stupid.

Alin Constantin said...

@Mike: Drag and drop in the text editor is still possible.

Perhaps you have turned off by mistake the following setting? Tools/Options dialog, select the TextEditor node in the options tree, make sure "Drag and drop text editing" is checked.

If that doesn't fix the problem for you, please open a bug on http://connect.microsoft.com/visualstudio - D&D in editor is owned by the Editor team, opening a bug on Connect will allow you to get in touch with the right people owning the scenario.

Anonymous said...

Great Post

It looks to be the only available project that describes how to do drag/drop in VS. I wish this project was on MSDN.