RSS Feed


Drag/Drop in WPF

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:

  1. 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.
  2. 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

 
Posted by Susan Warren | 0 Comments | Trackback Url | Bookmark with:        
Tags:

Links to this Post

Comments

Name:
URL:
Email:
Comments:

CAPTCHA Image Validation