I would like to show how easy in WPF items can be drag and drop between two bound lists in both directions.
The example shows the easiest way without any adorner effects currently.
First of all we start with two list boxed besides, the code is very easy therefore I think I can show it without additional comments.
The user object which is shown in both list and which will be dragged
public class User : INotifyPropertyChanged
{
public User(string name)
{
Name = name;
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private string _name;
public override string ToString()
{
return Name;
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
#endregion PropertyChanged
}
A simply factory for creating some users
public class UserFactory
{
public static List<User> CreateUsers()
{
return new List<User>
{
new User("Willy"),
new User("Jeff"),
new User("Vanessa"),
new User("Chris"),
new User("Michael"),
new User("Steve"),
new User("David")
};
}
}
The main window which display both Lists
<Window x:Class="DragAndDropDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350" MinHeight="350"
Width="525" MinWidth="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding AvailableUsers}" />
<ListBox Grid.Column="1" ItemsSource="{Binding ChoosenUsers}" />
</Grid>
</Window>
And the code for creating the lists
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
AvailableUsers = new EnhancedObservableCollection<User>();
ChoosenUsers = new EnhancedObservableCollection<User>();
CreateUsers();
}
private void CreateUsers()
{
AvailableUsers.AddRange(UserFactory.CreateUsers());
}
public EnhancedObservableCollection<User> AvailableUsers { get; set; }
public EnhancedObservableCollection<User> ChoosenUsers { get; set; }
}
(The EnhancedObservableCollection is a control in Tulipa which provides AddRange, RemoveAll, RemoveRange and Sort)
The target now is to have the possibility to drag the user from the “AvailableUsers” to the “ChoosenUsers” and back.
Dragging with other data except the users object will not allowed.
First we have to starting a dragging, for this we take the PreviewMouseLeftButtonDown and the PreviewMouseMove
In the PreviewMouseLeftButtonDown we define that the user would like to start a drag and drop. And in the PreviewMouseMove we check if a dragging should start and do it.
It is possible to start the drag and drop in the PreviewMouseLeftButtonDown directly, but then the ListBoxes will have problems with the normal selection.
The events in both lists use the same event handler in the code.
The events taken
PreviewMouseLeftButtonDown="Available_PreviewMouseLeftButtonDown"
PreviewMouseMove="Available_PreviewMouseMove"
After pressing on an item the e.OriginalSource should be the element which display the bound object, in my example it’s a TextBlock, there the object is located in the DataContext.
This object will be remembered and the owner of the object too, this is necessary to prevent an dropping in the source list.
private object _dragData;
private object _senderList;
private void Available_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null &&
element.DataContext != null)
{
_dragData = element.DataContext;
_senderList = sender;
}
}
if the mouse is moved over the control, it checks if a dragging action is requested and start the DoDragDrop with the provided object.
The object is reset as soon the DoDragDrop is done.
private void Available_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_dragData != null)
{
DragDrop.DoDragDrop((DependencyObject)sender, _dragData, DragDropEffects.Move);
_dragData = null;
}
}
If we build and test this we see that now we can drag an item, but we have no possible target control, therefore the mouse cursor every time is this “forbidden” icon.
Lets define targets for the items, to allow dropping into the lists the controls need the property AllowDrop enabled
As soon this is set to true the controls will trigger the drag events.
If we let this as is all items can be dropped now, we we won’t that, therefore we take the PreviewDragEnter and PreviewDragOver on both lists.
In the event handler we clarify if the current drag action is allowed on the control or not.
The events taken
PreviewDragEnter="Choosen_PreviewDragEnterOver"
PreviewDragOver="Choosen_PreviewDragEnterOver"
Because only “User” should be possible to drop from one side to another we forbid the action if the data is not a user object and if the mouse is over the source list.
For forbidding or allowing the dragging action we simply have to define the Effects property.
private void Choosen_PreviewDragEnterOver(object sender, DragEventArgs e)
{
User draggedUser = GetDataUser(e.Data);
if (draggedUser != null &&
sender != _senderList)
{
e.Effects = DragDropEffects.All;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
private User GetDataUser(IDataObject dataObject)
{
return dataObject.GetData(typeof(User)) as User;
}
The drag over event normally is enough, but the mouse short flicker on dragging into another control if drag enter isn’t handled.
The event handler have to set Handled on the end otherwise the framework will set all files on allowed and the given Effects will be ignored.
And now the last step, dropping the object from one list to another.
For this we have to take the PreviewDrop on both lists. In the event handler we take the data, check from which list it comes from and move it to the other list.
The event taken
PreviewDrop="Choosen_PreviewDrop"
In the event handler the data is taken and moved from one list to another.
private void Choosen_PreviewDrop(object sender, DragEventArgs e)
{
User draggedUser = GetDataUser(e.Data);
if (draggedUser != null)
{
if (AvailableUsers.Contains(draggedUser))
{
MoveUser(draggedUser, AvailableUsers, ChoosenUsers);
}
else
{
MoveUser(draggedUser, ChoosenUsers, AvailableUsers);
}
}
}
private void MoveUser(User user, EnhancedObservableCollection<User> source, EnhancedObservableCollection<User> target)
{
source.Remove(user);
target.Add(user);
}
Lets see all together
<Window x:Class="DragAndDropDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350" MinHeight="350"
Width="525" MinWidth="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding AvailableUsers}"
AllowDrop="True"
PreviewMouseLeftButtonDown="Available_PreviewMouseLeftButtonDown"
PreviewMouseMove="Available_PreviewMouseMove"
PreviewDragEnter="Choosen_PreviewDragEnterOver"
PreviewDragOver="Choosen_PreviewDragEnterOver"
PreviewDrop="Choosen_PreviewDrop" />
<ListBox Grid.Column="1"
ItemsSource="{Binding ChoosenUsers}"
AllowDrop="True"
PreviewMouseLeftButtonDown="Available_PreviewMouseLeftButtonDown"
PreviewMouseMove="Available_PreviewMouseMove"
PreviewDragEnter="Choosen_PreviewDragEnterOver"
PreviewDragOver="Choosen_PreviewDragEnterOver"
PreviewDrop="Choosen_PreviewDrop" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
AvailableUsers = new EnhancedObservableCollection<User>();
ChoosenUsers = new EnhancedObservableCollection<User>();
CreateUsers();
}
private void CreateUsers()
{
AvailableUsers.AddRange(UserFactory.CreateUsers());
}
public EnhancedObservableCollection<User> AvailableUsers { get; set; }
public EnhancedObservableCollection<User> ChoosenUsers { get; set; }
private object _dragData;
private object _senderList;
private void Available_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null &&
element.DataContext != null)
{
_dragData = element.DataContext;
_senderList = sender;
}
}
private void Available_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_dragData != null)
{
DragDrop.DoDragDrop((DependencyObject)sender, _dragData, DragDropEffects.Move);
_dragData = null;
}
}
private void Choosen_PreviewDragEnterOver(object sender, DragEventArgs e)
{
User draggedUser = GetDataUser(e.Data);
if (draggedUser != null &&
sender != _senderList)
{
e.Effects = DragDropEffects.All;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
private User GetDataUser(IDataObject dataObject)
{
return dataObject.GetData(typeof(User)) as User;
}
private void Choosen_PreviewDrop(object sender, DragEventArgs e)
{
User draggedUser = GetDataUser(e.Data);
if (draggedUser != null)
{
if (AvailableUsers.Contains(draggedUser))
{
MoveUser(draggedUser, AvailableUsers, ChoosenUsers);
}
else
{
MoveUser(draggedUser, ChoosenUsers, AvailableUsers);
}
}
}
private void MoveUser(User user, EnhancedObservableCollection<User> source, EnhancedObservableCollection<User> target)
{
source.Remove(user);
target.Add(user);
}
}
Like I wrote before, this is the easiest way without any better visual feedback for the user and without the possibility to define where exactly the items should be inserted in the target list.
Have fun with it.
David W.