Overview
This implementation merges two rows by drag-and-drop in DataGrid
using MVVM. The merging logic is implemented in the ViewModel.
1. Introduction
This article shows how to implement drag-and-drop of rows in a DataGrid
. The detailed explanation is available in the section below. All the code related to the drag-and-drop functionality of the DataGrid
is in the code-behind. To merge two rows in a DataGrid
by drag-and-drop, this post tries to put the row merge logic in the ViewModel by using Attached Dependency Properties, which binds the DelegateCommand
in the ViewModel to Dependency Properties in the View. The implementation uses the WPF Toolkit DataGrid
[3] and the WPF Model-View-ViewModel Toolkit [4].
2. Merge two rows by drag and drop in DataGrid
To handle drag and drop in DataGrid
, there are some property bindings in the DataGrid
’s XAML:
<WpfToolkit:DataGrid x:Name="listView" RowHeight="30"
ItemsSource="{Binding Path=Items}"
AutoGenerateColumns="False"
SelectionMode="Single"
AllowDrop="True"
MouseMove="OnMainGridMouseMove"
localControls:MouseMoveInGridSupport.MouseMoveCommand="{Binding MouseMoveCommand}"
localControls:MouseDropInGridSupport.MouseDropCommand="{Binding MouseDropCommand}"
The property AllowDrop
is set to True
, which enables drop in DataGrid
. The code behind handler OnMainGridMouseMove
handles the event MouseMove
, which detects a mouse move in the grid, performs DragDrop
, and passes control to other handlers. There is no business logic or data here related to the ViewModel and Model.
private void OnMainGridMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
{
e.Handled = true;
return;
}
var row = FindVisualParent<DataGridRow>(e.OriginalSource as FrameworkElement);
if ((row != null) && row.IsSelected)
{
var selectedItem = row.Item;
var finalDropEffect = DragDrop.DoDragDrop(row, selectedItem, DragDropEffects.Move);
if (finalDropEffect == DragDropEffects.Move)
{
e.Handled = false;
}
}
}
There are two Attached Dependency Properties in the DataGrid
’s XAML. One is MouseMoveInGridSupport
:
public class MouseMoveInGridSupport : ItemSupportBase
{
public static ICommand GetMouseMoveCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(MouseMoveCommandProperty);
}
public static void SetMouseMoveCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(MouseMoveCommandProperty, value);
}
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand",
typeof(ICommand), typeof(MouseMoveInGridSupport),
new UIPropertyMetadata(null, new PropertyChangedCallback(OnCommandChanged)));
private static void OnCommandChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
((ItemsControl)sender).MouseMove -= new MouseEventHandler(OnMouseMoveClick);
if (e.NewValue != null)
((ItemsControl)sender).MouseMove += new MouseEventHandler(OnMouseMoveClick);
}
private static void OnMouseMoveClick(object sender, MouseEventArgs e)
{
DependencyObject source = (DependencyObject)e.OriginalSource;
var row = TryFindParent<DataGridRow>(source);
var item = row.Item;
if (item == null) return;
var command = GetMouseMoveCommand((DependencyObject)sender);
if (command != null)
{
if (command.CanExecute(item))
command.Execute(item);
}
}
}
MouseMoveInGridSupport
detects the MouseMove
event happening in the DataGrid
, finds the related DataGridRow
, and passes its DataItem
to the DelegateCommand
“MouseMoveCommand
” in the ViewModel.
The second one is MouseDropInGridSupport
:
public class MouseDropInGridSupport : ItemSupportBase
{
public static ICommand GetMouseDropCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(MouseDropCommandProperty);
}
public static void SetMouseDropCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(MouseDropCommandProperty, value);
}
public static readonly DependencyProperty MouseDropCommandProperty =
DependencyProperty.RegisterAttached("MouseDropCommand",
typeof(ICommand), typeof(MouseDropInGridSupport),
new UIPropertyMetadata(null, new PropertyChangedCallback(OnMouseDropCommandChanged)));
private static void OnMouseDropCommandChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
((ItemsControl)sender).Drop -= new DragEventHandler(OnDropClick);
if (e.NewValue != null)
((ItemsControl)sender).Drop += new DragEventHandler(OnDropClick);
}
private static void OnDropClick(object sender, DragEventArgs e)
{
DependencyObject source = (DependencyObject)e.OriginalSource;
var row = TryFindParent<DataGridRow>(source);
var item = row.Item;
if (row == null) return;
var command = GetMouseDropCommand((DependencyObject)sender);
if (command != null)
{
if (command.CanExecute(item))
command.Execute(item);
}
}
}
MouseDropInGridSupport
detects the Drop event that happens in the DataGrid
, finds the related DataGridRow
, and passes its DataItem
to the DelegateCommand
“MouseDropCommand
” in the ViewModel.
The logic to merge two rows is in the ViewModel:
public class MainViewModel : ViewModelBase
{
#region Fields
private Item dragItem;
private Item dropItem;
private DelegateCommand exitCommand;
public DelegateCommand<Item> MouseMoveCommand { get; private set; }
public DelegateCommand<Item> MouseDropCommand { get; private set; }
#endregion
#region Constructor
public MainViewModel()
{
_items = DataAccess.DataAccess.LoadItems();
MouseMoveCommand = new DelegateCommand<Item>(MouseMove, CanMouseMove);
MouseDropCommand = new DelegateCommand<Item>(MouseDrop, CanMouseDrop);
}
#endregion
#region Properties
IList<Item> _items;
public IList<Item> Items
{
get { return _items; }
set
{
_items = value;
base.OnPropertyChanged("Items");
}
}
#endregion
#region Commands
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
#endregion
#region Methods
private void Exit()
{
Application.Current.Shutdown();
}
private void MouseMove(Item item)
{
IList<Item> items = new List<Item>(_items);
dragItem = item;
if (dropItem != null && dragItem != null)
{
Item newItem = new Item();
newItem.Name = "Merge row: " + dragItem.Name + " " + dropItem.Name;
newItem.Data = (dropItem.Data + dragItem.Data) + 100;
newItem.Id = (dropItem.Id + dragItem.Id) + 100;
items.Remove(dragItem);
items.Remove(dropItem);
items.Add(newItem);
Items = items;
}
}
private bool CanMouseMove(Item item)
{
return true;
}
private void MouseDrop(Item item)
{
dropItem = item;
}
private bool CanMouseDrop(Item item)
{
return true;
}
#endregion
}
The DelegateCommand
“MouseDropCommand
” gets the drop item. The DelegateCommand
“MouseDropCommand
” gets the move item, then merges the drop and move items.
3. Conclusion
Merging two rows in a grid by drag and drop is implemented in MVVM. It uses Attached Dependency Properties and DelegateCommand
s to put all the merge logic into the ViewModel.
4. References
- Common DataGrid Add-Ons: http://code.msdn.microsoft.com/Common-DataGrid-Add-Ons-4f64fcee
- MVVM and the WPF DataGrid: MVVM_DataGrid.aspx
- WPF Toolkit DataGrid: http://wpf.codeplex.com/releases/view/40535
- WPF Model-View-ViewModel Toolkit: http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit