WPF Drag and Drop Between ItemsControls
Posted by: Clarity Blogs: ASP.NET,
on 07 Apr 2009 |
View original | Bookmarked: 0 time(s)
Last week I blogged about a generic Drag and Drop framework. That post focused on the framework and gave some simple implementations of it, but didnt get into more practical examples. So, in honor of Opening Day, heres a quick example of how to use the framework to create the Cincinnati Reds batting order by dragging from a list of players to another list.
Setup
The first thing we need to do is get all of our resources set up. I included the existing DragDropAdornerBase and DragDropHelper classes (discussed in the framework post) into my new project. Next, I set up the images of the Reds starting roster as application level resources.
Model
Now that our resources are in place, lets make a quick object to represent a player. I named this class Player (surprise!). Player.cs has simple public properties to hold the data well show on the screen: Picture, Name and Position.
namespace
Clarity.Demo.ListDragDrop
{
public class Player
{
public string Name { get; set; }
public Position Position { get; set; }
public BitmapImage Img { get; set; }
public Player() { }
public Player(string name, Position position, BitmapImage img)
{
Name = name;
Position = position;
Img = img;
}
}
public enum Position
{
Pitcher = 1,
Catcher = 2,
First = 3,
Second = 4,
ShortStop = 5,
Third = 6,
Outfield = 7
}
}
View
Well also need a UserControl to show the player information. PlayerControl inherits from UserControl and has no code behind. In order to represent the player data visually without code behind PlayerControl takes advantage of DataBinding to the Player model object.
<Canvas Background="Red">
<Image x:Name="playerImage" Source="{Binding Img}" Margin="5 5 0 5" MaxHeight="90" MaxWidth="90"/>
<TextBlock x:Name="name" Text="{Binding Name}" Background="Transparent" Margin="100 15 5 0"/>
<TextBlock x:Name="position" Text="{Binding Position}" Background="Transparent" Margin="100 60 5 0"/>
</Canvas>
And now we have to build the window. Lets make this as simple as possible, so well just put 2 scrollable ItemsControls that use the PlayerControl as a DataTemplate.
<Window x:Class="Clarity.Demo.ListDragDrop.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Clarity.Demo.ListDragDrop"
Title="Window1" Height="600" Width="800" Loaded="Window_Loaded">
<Canvas>
<UniformGrid Rows="1" Columns="2" Height="550">
<ScrollViewer Margin="15 15 30 15" >
<ItemsControl x:Name="playerList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PlayerControl Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<ScrollViewer Grid.Column="1" Margin="30 15 15 15">
<ItemsControl x:Name="lineup">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PlayerControl Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UniformGrid>
</Canvas>
</Window>
And now well populate the playerList ItemsControl in the Window codebehind:
public partial class Window1 : Window
{
ObservableCollection<Player> _players = new ObservableCollection<Player>();
ObservableCollection<Player> _lineup = new ObservableCollection<Player>();
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadPlayers();
playerList.ItemsSource = _players;
lineup.ItemsSource = _lineup;
}
private void LoadPlayers()
{
_players.Add(new Player("Aaron Harang", Position.Pitcher,
(BitmapImage)App.Current.TryFindResource("harang")));
_players.Add(new Player("Alex Gonzalez", Position.ShortStop,
(BitmapImage)App.Current.TryFindResource("gonzalez")));
_players.Add(new Player("Brandon Phillips", Position.Second,
(BitmapImage)App.Current.TryFindResource("phillips")));
_players.Add(new Player("Chris Dickerson", Position.Outfield,
(BitmapImage)App.Current.TryFindResource("dickerson")));
_players.Add(new Player("Edwin Encarnacion", Position.Third,
(BitmapImage)App.Current.TryFindResource("encarnacion")));
_players.Add(new Player("Jay Bruce", Position.Outfield,
(BitmapImage)App.Current.TryFindResource("bruce")));
_players.Add(new Player("Joey Votto", Position.First,
(BitmapImage)App.Current.TryFindResource("votto")));
_players.Add(new Player("Ramon Hernandez", Position.Catcher,
(BitmapImage)App.Current.TryFindResource("hernandez")));
_players.Add(new Player("Willy Taveras", Position.Outfield,
(BitmapImage)App.Current.TryFindResource("taveras")));
}
}
Now, running the app gives us the following window:
Implementation
OK, so at this point weve got the player model and view in place. Now, lets add the fun stuff. First, lets create an adorner for our drag and drop. Inheriting from the DragDropAdornerBase Ive created a simple adorner that just shows the player picture (hiding the RenderTransforms, Animations and Triggers for simplicity).
<local:DragDropAdornerBase x:Class="Clarity.Demo.ListDragDrop.PlayerAdorner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Clarity.Demo.ListDragDrop"
Height="75" Width="100">
<UserControl.RenderTransform...>
<UserControl.Resources...>
<UserControl.Triggers...>
<Grid x:Name="grid">
<Grid.RenderTransform...>
<Image x:Name="playerImage" Source="{Binding Img}" Margin="2"/>
</Grid>
</local:DragDropAdornerBase>
Now that adorner control is created, that means were ready to add it as a resource to Window1.
<Window.Resources>
<local:PlayerAdorner x:Key="adorner"/>
</Window.Resources>
Of course, there are three more pieces necessary to implement the drag and drop. 1) Window1 needs to contain an adorner layer, 2) the PlayerControl DataTemplates need to define the DragDropHelper attached properties to be recognized as Drag Sources, and 3) Window1 needs to subscribe to the DragDropHelper.ItemDropped event.
First the adorner layer gets added at the end of the Window1 xaml:
</UniformGrid>
<Canvas x:Name="adornLayer" Visibility="Collapsed"/>
</Canvas>
Next the playerList and lineup ItemsControls have their DataTemplates redefined:
<local:PlayerControl Margin="5"
local:DragDropHelper.AdornerLayer="adornLayer"
local:DragDropHelper.DragDropControl="{StaticResource adorner}"
local:DragDropHelper.DropTarget="lineup"
local:DragDropHelper.IsDragSource="true"/>
<local:PlayerControl Margin="5"
local:DragDropHelper.AdornerLayer="adornLayer"
local:DragDropHelper.DragDropControl="{StaticResource adorner}"
local:DragDropHelper.DropTarget="playerList"
local:DragDropHelper.IsDragSource="true"/>
And finally, Window1 subscribes to the ItemDropped event to add and remove the players accordingly.
void DragDropHelper_ItemDropped(object sender, DragDropEventArgs e)
{
Player p = e.Content as Player;
if (p == null) return;
if (_players.Contains(p))
{
_players.Remove(p);
_lineup.Add(p);
}
else if (_lineup.Contains(p))
{
_lineup.Remove(p);
_players.Add(p);
}
}
And thats all it takes. Now we can drag and drop players from the left list to the right to set the batting order:
And back right to left if you change your mind

Happy drag and dropping, and go Reds!
Next Steps
Ill be adding a link to the source in the morning. Also, stay tuned for Part 2, including some hot-ification of the dropping.