Recently I had my first opportunity to add drag/drop functionality to a WPF application and I learned that WPF doesn't change the recipe for drag/drop much from previous Windows client technologies. It's not too surprising, really, since most of us will want our WPF applications to "play nice" with Windows and other applications your user is running. And mostly, that means sharing data in a standard way between applications.
The data exchange mechanism between the "drag source" application and the "drop target" application is an object that implements IDataObject. (The drag source and drop target can be the same application, of course.) In WPF the IDataObject is passed from the drag source by the System.Windows.DragDrop.DoDragDrop() method, and received by the drop target as the Data member of DragEventArgs. The IDataObject also provides some methods for inspecting what data is available and which UX gesture was should be applied to the data (copy, move, etc.).
In WPF, as with earlier technologies, you'll encounter the "three bears" of drag/drop implementation complexity. Here are some simple examples that illustrate the differences.
Baby Bear: Your app is a drop target for another app's drag data
The simplest drag/drop implementation only has to read the data passed by another application (or Windows) when a user drops and object on your application. This example lets a user drag a file from the desktop and drop it on a StackPanel in your application. The creation of the IDataObject is the job of the drag source, so Windows has already taken care of that part for you. Your app just handles the WPF Drop event and queries e.Data for the file that was dragged.
XAML
<Page x:Class="SimpleDropTarget"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<StackPanel AllowDrop="True" Drop="FileDropHandler">
<Label x:Name="myLabel" >Drop a file here</Label>
</StackPanel>
</Page>
Code
public partial class SimpleDropTarget : System.Windows.Controls.Page
{
+ default constructor
private void FileDropHandler(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
//Do work. Can special-case logic based on Copy, Move, etc.
string[] fileNames = e.Data.GetData(DataFormats.FileDrop, true) as string[];
myLabel.Content = fileNames[0] as string;
}
e.Handled = true;
}
}
Mama Bear: Your application is a drag source
When your application wants to act as the source of some dragged data, things start to get a bit more involved. Now, you must handle mouse events to detect when dragging is happening and figure out what UIElement is being dragged. And your code must create an IDataObject with the data you'd like to pass to the drop target.
Note that when your application is both the drag source on the drop target it's often useful to pass data in a custom format that you define, rather than using one of the canned ones defined by the DataFormats object.
Here's a bare-bones code example that passes a file name:
XAML
<Page x:Class="WindowsApplication1.SimpleDragSource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Canvas>
<ListBox x:Name ="fileList"
PreviewMouseDown="ListBoxItemDragBegin"
PreviewMouseMove="ListBoxItemDragMove"
/>
</Canvas>
</Page>
Code
public partial class SimpleDragSource : System.Windows.Controls.Page
{
bool dragStarted;
public SimpleDragSource()
{
InitializeComponent();
//bind a list of files to the ListBox
DirectoryInfo windowsDir = new DirectoryInfo("c:\\windows");
fileList.ItemsSource = windowsDir.GetFiles();
}
private void ListBoxItemDragBegin(object sender, MouseButtonEventArgs e)
{
dragStarted = true;
base.OnPreviewMouseDown(e);
}
private void ListBoxItemDragMove(object sender, MouseEventArgs e)
{
if (dragStarted && sender is ListBox)
{
//create data object
ListBox theList = sender as ListBox;
DataObject data = CreateDataObjectFromList(theList);
//trap mouse events for the list, and perform drag/drop
Mouse.Capture(sender as UIElement);
System.Windows.DragDrop.DoDragDrop(theList, data, DragDropEffects.Copy);
Mouse.Capture(null);
dragStarted = false;
base.OnPreviewMouseMove(e);
}
}
private DataObject CreateDataObjectFromList(ListBox list)
{
DataObject data = new DataObject();
FileSystemInfo current = list.SelectedItem as FileSystemInfo;
if (current != null)
data.SetData(DataFormats.FileDrop, new string[] { current.FullName as string });
return data;
}
}
Papa Bear: Full Blown Drag/Drop
The examples above are instructive, but the approaches they show are effective for only the most basic drag/drop scenarios. For applications that make more extensive use of drag/drop you'll want to consider:
- Refactoring the event handling and DataObject creation logic so it can be used for multiple drag sources and drop targets in your application. Otherwise, you'll find yourself writing a lot of redundant code.
- Adding user feedback for dragging. At a minimum change the cursor to something that indicates "drag in progress" or – since this is WPF and it's easy to do spiffy visualizations – show a preview of the item to be dropped under the cursor.
Pavan Podila's library tackles both of these requirements in a nice, reuseable way. Lester Lobo cleaned it up a little and shares his version here.
Enjoy ~ Susan