Drag and Drop in UWP

Windows 10 introduced a new way to drag and drop between the Windows Shell and also within your UWP app. I am going to show you how these work using a simple example.

The application is a simple organizer of images.

Fig 1. Screenshot of application loaded

The idea is that you load your images into the application by dragging and dropping them into the Unorganized column from Windows Explorer.

Fig 2. Dragging from Pictures folder to the Unorganized column, the thumbnails appear with a Copy tooltip

If you try dragging them into the wrong column, it should validate and not allow the user to do this.

Fig 3. Dragging from Pictures folder to the Like column,  the thumbnail shows a Not Allowed tooltop

Once they are loaded in the application you can drag items from Unorganized to either Like, or, Dislike. 

Fig 4. Successfully dropped from the Pictures folder to the Unorganized column

Fig 5. Dragging from the Unorganized column to the Like column - they can move multiple items at once if they like

Fig 6. Once the user has dropped the images in the like column, the move is complete from one ListView to another.

Let's walk through the two main scenarios, drag and dropping an image from Windows Explorer into the UWP app and drag and dropping an image from the Unorganized to the Like or Dislike column.


Drag and dropping an image from Windows Explorer to my UWP app

There are some properties and events that I've set on the Unorganized ListView to make sure that it is drag and drop ready from Explorer.

<ListView AllowDrop="True" SelectionMode="Extended"
          DragOver="UnorganizedListView_OnDragOver" 
          Drop="UnorganizedListView_OnDrop" 
          CanDragItems="True" 
          DragItemsStarting="UnorganizedListView_OnDragItemsStarting"
          ItemsSource="{x:Bind MyItems}" 
          ItemTemplate="{StaticResource MyItemDataTemplate}" 
          Grid.Column="0" Grid.Row="1" />

The first property is AllowDrop which is a pretty self explanatory, it enables items to be dropped onto this UIElement, make sure you set this to true.

The second property is DragOver, when dragging from Windows Explorer I need to make sure that the ListView tells the user it's intention when they drag over it - in this case it's to copy.

private void UnorganizedListView_OnDragOver(object sender, DragEventArgs e)
{
    e.AcceptedOperation = DataPackageOperation.Copy;
}

The final property to get dragging from explorer working, is the Drop event, this occurs when the user releases the mouse button. 

private async void UnorganizedListView_OnDrop(object sender, DragEventArgs e)
{
    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var storageItems = await e.DataView.GetStorageItemsAsync();

        foreach (StorageFile storageItem in storageItems)
        {
            var bitmapImage = new BitmapImage();
            await bitmapImage.SetSourceAsync(await storageItem.OpenReadAsync());

            var myItem = new MyItem
            {
                Id = Guid.NewGuid(),
                Image = bitmapImage
            };

            this.MyItems.Add(myItem);
        }
    }
}

Let's walk through this code a little

If the source is coming from Windows Explorer, I should be expecting a StorageItem, checking if the e.DataView contains StorageItems confirms this.

Now that I know the type is correct, I fetch the storage items by calling the e.DataView.GetStorageItemsAsync();  this will return a list of IStorageItems - as the user could drop multiple items.

I iterate though the list and then load the content in to a BitmapImage. As the ListView is data bound to the MyItems observable collection of MyItem - a basic object that has a BitmapImage and Id - I create a MyItem object and add it to the MyItems collection

That's it!


Drag and drop between ListViews in my UWP app

Now that we have items loading into the application, it's time to organize them.

Let's take another look at XAML for the Unorganized ListView.

<ListView AllowDrop="True" SelectionMode="Extended"
          DragOver="UnorganizedListView_OnDragOver" 
          Drop="UnorganizedListView_OnDrop" 
          CanDragItems="True" 
          DragItemsStarting="UnorganizedListView_OnDragItemsStarting"
          ItemsSource="{x:Bind MyItems}" 
          ItemTemplate="{StaticResource MyItemDataTemplate}" 
          Grid.Column="0" Grid.Row="1" />

To ensure that items can be dragged from the Unorganized ListView to the other ListViews we need to set the CanDragItems property to true.

Finally, we need to wire up the DragItemsStarting event. When the user is dragging items from the Unorganized ListView we need to pass some metadata to help the destination ListView identify which item has been dropped onto it.

private void UnorganizedListView_OnDragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
    var items = string.Join(",", e.Items.Cast<MyItem>().Select(i => i.Id));
    e.Data.SetText(items);
    e.Data.RequestedOperation = DataPackageOperation.Move;
}

The DragItemsStartingEventArgs contains an Items property which contains the data context of all the items being dragged from the ListView - in this case MyItem.

Using Linq I select all the Ids of the items - as I want to just send a reference to the object - I then join them by separating them with a comma.

I set the payload of the drag and drop with this string and set the request of this operation to Move.

Now that we have set up the source, let's look at setting up the destination.

<ListView x:Name="LikesListView" 
          AllowDrop="True"
          DragOver="ListView_DragOver" 
          Drop="ListView_Drop"  
          ItemsSource="{x:Bind Likes}" 
          ItemTemplate="{StaticResource MyItemDataTemplate}"
          Grid.Column="1" Grid.Row="1" />

<ListView x:Name="DislikesListView"
          AllowDrop="True"
          DragOver="ListView_DragOver" 
          Drop="ListView_Drop"  
          ItemsSource="{x:Bind Dislikes}" 
          ItemTemplate="{StaticResource MyItemDataTemplate}"
          Grid.Column="2" Grid.Row="1" />

As you can see, they have very similar properties to the Unorganized ListView, we still set the AllowDrop to true.

The DragOver code is slightly different as I want to check that items being dropped are Text based.

private void ListView_DragOver(object sender, DragEventArgs e)
{
	if (e.DataView.Contains(StandardDataFormats.Text))
	{
		e.AcceptedOperation = DataPackageOperation.Move;
	}
}

If the user tries to drop from Windows Explorer, they will get the following:

Fig 7. Dragging from Windows Explorer to the Like ListView is not allowed based on DragOver code.

The final event we are interested in, is the Drop event. 

private async void ListView_Drop(object sender, DragEventArgs e)
{
    if (e.DataView.Contains(StandardDataFormats.Text))
    {
        var id = await e.DataView.GetTextAsync();
        var itemIdsToMove = id.Split(',');

        var destinationListView = sender as ListView;
        var listViewItemsSource = destinationListView?.ItemsSource as ObservableCollection<MyItem>;

        if (listViewItemsSource != null)
        {
            foreach (var itemId in itemIdsToMove)
            {
                var itemToMove = this.MyItems.First(i => i.Id.ToString() == itemId);

                listViewItemsSource.Add(itemToMove);
                this.MyItems.Remove(itemToMove);
            }
        }
    }
}

Similar to before, we check the data being dropped, in this case it's Text - as we are looking for Ids.

We then split the string that was passed through, this is to identify the various items being moved. 

As this method is being shared by both Like and Dislike ListViews, I have to determine which collection I need to update.

Once I have determine the right ListView, I simply search for the Id in the MyItems - the source - collection, and, then add that item to the listViewItemSource - the destination - collection. Finally, I remove the item from MyItems.

That concludes this post, you will now have a complete drag and drop from Windows Explorer to your UWP, and, drag and drop inside your application too using various data types.

You can find the source for this code at:
https://github.com/shenchauhan/blog/tree/master/DragAndDrop

Channel 9 video: https://channel9.msdn.com/Shows/Inside-Windows-Platform/Using-Drag-and-Drop-in-your-Universal-Windows-App

Happy coding!