Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Drag and Drop in WPF

0.00/5 (No votes)
7 Nov 2009 2  
An article showing how to add drag and drop to a WPF application using the GongSolutions.Wpf.DragDrop library.

Introduction

For a while, I've been lamenting the lack of decent drag and drop support in WPF. While WPF has advanced desktop GUI programming considerably from the days of WinForms, drag and drop hasn't changed since I started programming on Windows with Visual Basic 3.0.

In particular, one must litter code-behinds with drag and drop logic, something that is anathema to any self-respecting WPF developer in the days of MVVM.

After putting up with this for a while, I came across an article by Bea Stollnitz on using attached properties to add drag and drop logic to controls in XAML. However, Bea's solution did not go as far as I needed for my purposes. What I needed was:

  • MVVM support: Anything more than basic drag drop operations need logic, and code-behind isn't the right place for that logic. Decisions about what can be dragged and what can be dropped where should be delegated to ViewModels.
  • Insert/Drop Into: Sometimes when you drag an item from one place to another, you're re-ordering the items in a list. At other times, you're dropping one item onto another, like dropping a file into a folder in a file manager.
  • TreeView support
  • Multiple selections
  • Automatic scrolling

Looking around, I couldn't see anything that satisfied my needs, so I decided to write one. You can find the latest source code with examples at the Google Code project. It's licensed under the BSD license, so you're free to use it pretty much however you like.

Using the Code

To demonstrate the use of the drag drop framework, let's use a simple Schools example. In this example, we have three ViewModels:

  • MainViewModel: This simply contains the collection of schools.
  • SchoolViewModel: A school has a name and a collection of pupils.
  • PupilViewModel: A pupil has a name.

In our UI, we'll display two listboxes. The first to display the list of schools, and the second to display the list of pupils belonging to that school. Our XAML looks like this:

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    
    <ListBox Grid.Column="0" 
       ItemsSource="{Binding Schools}" 
       DisplayMemberPath="Name" 
       IsSynchronizedWithCurrentItem="True"/>
    <ListBox Grid.Column="1" 
       ItemsSource="{Binding Schools.CurrentItem.Pupils}" 
       DisplayMemberPath="FullName"/>
</Grid>

And the resulting window looks like this:

gong-wpf-dragdrop1.png

Firstly, we want to be able to re-order the pupils in the second ListBox. This is done by setting the IsDragSource and IsDropTarget attached properties on the ListBox:

<ListBox Grid.Column="1" 
  ItemsSource="{Binding Schools.CurrentItem.Pupils}" 
  DisplayMemberPath="FullName"
  dd:DragDrop.IsDragSource="True" 
  dd:DragDrop.IsDropTarget="True"/>

You will now be able to drag and drop items within the ListBox to re-order them. Now, you might suppose that if you were to set the IsDropTarget property on the first ListBox, you'd be able to drag a pupil into a school. However, try doing this, and you won't be allowed. That's because the first ListBox is bound to a collection of SchoolViewModel objects whereas the second ListBox is bound to a collection of PupilViewModels. What should happen here? The framework doesn't know, so we're going to have to tell it.

Enter DropHandlers

To add a drop handler to the ListBox, we set the DropHandler attached property on the first ListBox, and bind it to a ViewModel. In this case, we'll just bind it to the MainViewModel, like so:

<ListBox Grid.Column="0" 
  ItemsSource="{Binding Schools}" DisplayMemberPath="Name" 
  IsSynchronizedWithCurrentItem="True"
  dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{Binding}"/>

Now we need to add the handling code to MainViewModel. To do this, we implement the IDropTarget interface. This interface defines two methods: DragOver and Drop. DragOver is called whilst the drag is underway, and is used to determine whether the current drop target is valid:

void IDropTarget.DragOver(DropInfo dropInfo)
{
    if (dropInfo.Data is PupilViewModel && 
        dropInfo.TargetItem is SchoolViewModel)
    {
        dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
        dropInfo.Effects = DragDropEffects.Move;
    }
}

Here, we're checking that the dragged data is a PupilViewModel, and the item that is currently the target of the drag is a SchoolViewModel.

The dropInfo.Data property contains the data being dragged. If the control that was the source of the drag is a bound control (which it is), this will be the object that the dragged item was bound to. The dropInfo.TargetItem property contains the object that the item currently under the mouse cursor is bound to.

If the data types are correct, we go ahead and set the drop target adorner. Because dropping a pupil into a school causes the pupil to be added to the school, we choose the Highlight adorner which highlights the target item. The other available drop target adorner is the Insert adorner, which draws a caret at the point in the list the dropped item will be inserted, and is what you see when re-ordering pupils in the second ListBox.

Finally, we set the drag effect to DragDropEffects.Move. This tells the framework that the dragged data can be dropped here and to display a Move mouse pointer. If we don't set this property, the default value of DragDropEffects.None is used, which indicates that a drop is not allowed at this position.

Next, the Drop method:

void IDropTarget.Drop(DropInfo dropInfo)
{
    SchoolViewModel school = (SchoolViewModel)dropInfo.TargetItem;
    PupilViewModel pupil = (PupilViewModel)dropInfo.Data;
    school.Pupils.Add(pupil);
}

Here, we simply get the SchoolViewModel and the PupilViewModel that are involved in the drop from the DropInfo parameter, and add the pupil to the school. We can be sure that the data is going to be of the expected type because the DragOver method has already filtered out any other situation.

Oh! But hold on, when we drop the pupil into the new school, we're not removing him from the previous school, i.e., we're copying instead of moving. To remove the pupil from the previous school, we need to be able to get hold of the collection from which the drag was initiated. This is provided by the DropInfo.DragInfo object. This object holds information relating to the source of the drag. In particular, we're interested in the SourceCollection property:

((IList)dropInfo.DragInfo.SourceCollection).Remove(pupil);

That's All For Now Folks

That completes this introductory article. You can find more examples in the Google Code project. Some features to look out for:

  • Drag handlers
  • Drop adorners
  • Multiple selections

If this article interested you, please join in! Post to the Google Groups, or contribute code to advance the project, and hopefully, we can make something that can be drag and dropped screaming out of the 20th century.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here