Drag Drop Row behavior on WPF DataGrid
attachedbehaviors, c#, drag-and-drop, wpf, wpfdatagrid
Solution
Finally i got the Problem and also made some changes for this to work properly
I used this example to make the `Drag Drop Logic` amd made this behavior may be its use ful to others .... please suggest improvements i would be happy to change ...
Behavior
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Microsoft.Windows.Controls;
using System.Windows.Input;
using System.Collections;
namespace DataGridDragAndDrop
{
public static class DragDropRowBehavior
{
private static DataGrid dataGrid;
private static Popup popup;
private static bool enable;
private static object draggedItem;
public static object DraggedItem
{
get { return DragDropRowBehavior.draggedItem; }
set { DragDropRowBehavior.draggedItem = value; }
}
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
// Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || !(e.NewValue is Popup))
{
throw new ArgumentException("Popup Control should be set", "PopupControl");
}
popup = e.NewValue as Popup;
dataGrid = depObject as DataGrid;
// Check if DataGrid
if (dataGrid == null)
return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove += new MouseEventHandler(OnMouseMove);
}
else
{
dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove);
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
// Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e)
{
//Check if value is a Boolean Type
if (e.NewValue is bool == false)
throw new ArgumentException("Value should be of bool type", "Enabled");
enable = (bool)e.NewValue;
}
public static bool IsEditing { get; set; }
public static bool IsDragging { get; set; }
private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
IsEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
/// <summary>
/// Initiates a drag action if the grid is not in edit mode.
/// </summary>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
IsDragging = true;
DraggedItem = row.Item;
}
/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing)
{
return;
}
//get the target item
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
//get target index
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
//remove the source from the list
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
//move source at the target's location
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
//select the dropped item
dataGrid.SelectedItem = DraggedItem;
}
//reset
ResetDragDrop();
}
/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
/// <summary>
/// Updates the popup's position in case of a drag/drop operation.
/// </summary>
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
popup.DataContext = DraggedItem;
//display the popup if it hasn't been opened yet
if (!popup.IsOpen)
{
//switch to read-only mode
dataGrid.IsReadOnly = true;
//make sure the popup is visible
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
//make sure the row under the grid is being selected
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
}
UIHelper Class
public static class UIHelpers
{
#region find parent
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
//use recursion to proceed with next level
return TryFindParent<T>(parentObject);
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Do note, that for content element,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
//if it's not a ContentElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion
#region update binding sources
/// <summary>
/// Recursively processes a given dependency object and all its
/// children, and updates sources of all objects that use a
/// binding expression on a given property.
/// </summary>
/// <param name="obj">The dependency object that marks a starting
/// point. This could be a dialog window or a panel control that
/// hosts bound controls.</param>
/// <param name="properties">The properties to be updated if
/// <paramref name="obj"/> or one of its childs provide it along
/// with a binding expression.</param>
public static void UpdateBindingSources(DependencyObject obj,
params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
//check whether the submitted object provides a bound property
//that matches the property parameters
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
#endregion
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
DependencyObject element = reference.InputHitTest(point)
as DependencyObject;
if (element == null) return null;
else if (element is T) return (T)element;
else return TryFindParent<T>(element);
}
}
Usage
<!-- Drag and Drop Popup -->
<Popup x:Name="popup1"
AllowsTransparency="True"
IsHitTestVisible="False"
Placement="RelativePoint"
PlacementTarget="{Binding ElementName=shareGrid}">
<!-- Your own Popup construction Use properties of DraggedObject inside for Binding -->
<TextBlock Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold"
<!-- I used name property of in my Dragged row -->
Text="{Binding Path=Name}" />
</Popup>
<DataGrid x:Name="myDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="False"
ItemsSource="{Binding}"
SelectionMode="Single"
this:DragDropRowBehavior.Enabled="True"
this:DragDropRowBehavior.PopupControl="{Binding ElementName=popup1}"></DataGrid >
Problem
I am trying to make an attached behavior to reorder rows bby doing `Drag & Drop` I found some solution (On Stackoverflow and by googling) and using them i am trying to make the behavior... I took the example from Hordcodenet website (i dont have the link now) Code ``` public static class DragDropRowBehavior { private static DataGrid dataGrid; private static Popup popup; private static bool enable; private static object draggedItem; public static object DraggedItem { get { return DragDropRowBehavior.draggedItem; } set { DragDropRowBehavior.draggedItem = value; } } public static Popup GetPopupControl(DependencyObject obj) { return (Popup)obj.GetValue(PopupControlProperty); } public static void SetPopupControl(DependencyObject obj, Popup value) { obj.SetValue(PopupControlProperty, value); } // Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc... public static readonly DependencyProperty PopupControlProperty = DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged)); private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e) { if (e.NewValue == null || !(e.NewValue is Popup)) { throw new ArgumentException("Popup Control should be set", "PopupControl"); } popup = e.NewValue as Popup; dataGrid = depObject as DataGrid; // Check if DataGrid if (dataGrid == null) return; if (enable && popup != null) { dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit); dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit); dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp); dataGrid.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown); dataGrid.MouseMove += new MouseEventHandler(OnMouseMove); } else { dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit); dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit); dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp); dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown); dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove); dataGrid = null; popup = null; draggedItem = null; IsEditing = false; IsDragging = false; } } public static bool GetEnabled(DependencyObject obj) { return (bool)obj.GetValue(EnabledProperty); } public static void SetEnabled(DependencyObject obj, bool value) { obj.SetValue(EnabledProperty, value); } // Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc... public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged)); private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e) { //Check if value is a Boolean Type if (e.NewValue is bool == false) throw new ArgumentException("Value should be of bool type", "Enabled"); enable = (bool)e.NewValue; } public static bool IsEditing { get; set; } public static bool IsDragging { get; set; } private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e) { IsEditing = true; //in case we are in the middle of a drag/drop operation, cancel it... if (IsDragging) ResetDragDrop(); } private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e) { IsEditing = false; } /// <summary> /// Initiates a drag action if the grid is not in edit mode. /// </summary> private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (IsEditing) return; var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid)); if (row == null || row.IsEditing) return; //set flag that indicates we're capturing mouse movements IsDragging = true; DraggedItem = row.Item; } /// <summary> /// Completes a drag/drop operation. /// </summary> private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (!IsDragging || IsEditing) { return; } //get the target item var targetItem = dataGrid.SelectedItem; if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem)) { //remove the source from the list ((dataGrid).ItemsSource as IList).Remove(DraggedItem); //get target index var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem); //move source at the target's location ((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem); //select the dropped item dataGrid.SelectedItem = DraggedItem; } //reset ResetDragDrop(); } /// <summary> /// Closes the popup and resets the /// grid to read-enabled mode. /// </summary> private static void ResetDragDrop() { IsDragging = false; popup.IsOpen = false; dataGrid.IsReadOnly = false; } /// <summary> /// Updates the popup's position in case of a drag/drop operation. /// </summary> private static void OnMouseMove(object sender, MouseEventArgs e) { if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return; //display the popup if it hasn't been opened yet if (!popup.IsOpen) { //switch to read-only mode dataGrid.IsReadOnly = true; //make sure the popup is visible popup.IsOpen = true; } Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight); popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize); //make sure the row under the grid is being selected Point position = e.GetPosition(dataGrid); var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position); if (row != null) dataGrid.SelectedItem = row.Item; } } ``` Helper Class /// /// Common UI related helper methods. /// public static class UIHelpers { ``` #region find parent /// <summary> /// Finds a parent of a given item on the visual tree. /// </summary> /// <typeparam name="T">The type of the queried item.</typeparam> /// <param name="child">A direct or indirect child of the /// queried item.</param> /// <returns>The first parent item that matches the submitted /// type parameter. If not matching item can be found, a null /// reference is being returned.</returns> public static T TryFindParent<T>(DependencyObject child) where T : DependencyObject { //get parent item DependencyObject parentObject = GetParentObject(child); //we've reached the end of the tree if (parentObject == null) return null; //check if the parent matches the type we're looking for T parent = parentObject as T; if (parent != null) { return parent; } else { //use recursion to proceed with next level return TryFindParent<T>(parentObject); } } /// <summary> /// This method is an alternative to WPF's /// <see cref="VisualTreeHelper.GetParent"/> method, which also /// supports content elements. Do note, that for content element, /// this method falls back to the logical tree of the element. /// </summary> /// <param name="child">The item to be processed.</param> /// <returns>The submitted item's parent, if available. Otherwise /// null.</returns> public static DependencyObject GetParentObject(DependencyObject child) { if (child == null) return null; ContentElement contentElement = child as ContentElement; if (contentElement != null) { DependencyObject parent = ContentOperations.GetParent(contentElement); if (parent != null) return parent; FrameworkContentElement fce = contentElement as FrameworkContentElement; return fce != null ? fce.Parent : null; } //if it's not a ContentElement, rely on VisualTreeHelper return VisualTreeHelper.GetParent(child); } #endregion #region update binding sources /// <summary> /// Recursively processes a given dependency object and all its /// children, and updates sources of all objects that use a /// binding expression on a given property. /// </summary> /// <param name="obj">The dependency object that marks a starting /// point. This could be a dialog window or a panel control that /// hosts bound controls.</param> /// <param name="properties">The properties to be updated if /// <paramref name="obj"/> or one of its childs provide it along /// with a binding expression.</param> public static void UpdateBindingSources(DependencyObject obj, params DependencyProperty[] properties) { foreach (DependencyProperty depProperty in properties) { //check whether the submitted object provides a bound property //that matches the property parameters BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty); if (be != null) be.UpdateSource(); } int count = VisualTreeHelper.GetChildrenCount(obj); for (int i = 0; i < count; i++) { //process child items recursively DependencyObject childObject = VisualTreeHelper.GetChild(obj, i); UpdateBindingSources(childObject, properties); } } #endregion /// <summary> /// Tries to locate a given item within the visual tree, /// starting with the dependency object at a given position. /// </summary> /// <typeparam name="T">The type of the element to be found /// on the visual tree of the element at the given location.</typeparam> /// <param name="reference">The main element which is used to perform /// hit testing.</param> /// <param name="point">The position to be evaluated on the origin.</param> public static T TryFindFromPoint<T>(UIElement reference, Point point) where T : DependencyObject { DependencyObject element = reference.InputHitTest(point) as DependencyObject; if (element == null) return null; else if (element is T) return (T)element; else return TryFindParent<T>(element); } ``` } Problem is that the Event `OnMouseLeftButtonDown` is not called when i press it over a row to drag it... but `OnMouseLeftButtonUp` is called after that.... Is there ne way to do this.... I cant seem to find a way