WPF中實(shí)現(xiàn)DataGrid行拖拽功能的完整方案
前言
在WPF應(yīng)用開發(fā)中,DataGrid是一個(gè)常用的數(shù)據(jù)展示控件。然而,原生的DataGrid并不支持行拖拽功能,這在需要調(diào)整行順序的場(chǎng)景中顯得尤為不便。
本文將介紹如何實(shí)現(xiàn)一個(gè)優(yōu)雅的DataGrid行拖拽功能,使用戶可以通過拖拽操作輕松調(diào)整行順序。該方案基于開源項(xiàng)目實(shí)現(xiàn),使用MIT協(xié)議的HandyControl樣式庫,具有良好的可擴(kuò)展性和實(shí)用性。
實(shí)現(xiàn)效果

核心實(shí)現(xiàn)
1、定義拖拽行為類
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; }
}
// 定義PopupControl附加屬性
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup),
typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
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;
if (dataGrid == null) return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += OnBeginEdit;
dataGrid.CellEditEnding += OnEndEdit;
dataGrid.MouseLeftButtonUp += OnMouseLeftButtonUp;
dataGrid.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
dataGrid.MouseMove += OnMouseMove;
}
else
{
dataGrid.BeginningEdit -= OnBeginEdit;
dataGrid.CellEditEnding -= OnEndEdit;
dataGrid.MouseLeftButtonUp -= OnMouseLeftButtonUp;
dataGrid.MouseLeftButtonDown -= OnMouseLeftButtonDown;
dataGrid.MouseMove -= OnMouseMove;
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
// 定義Enabled附加屬性
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool),
typeof(DragDropRowBehavior), new UIPropertyMetadata(false, OnEnabledChanged));
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
private static void OnEnabledChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
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;
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
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;
IsDragging = true;
DraggedItem = row.Item;
}
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing) return;
dataGrid.Cursor = Cursors.Arrow;
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
dataGrid.SelectedItem = DraggedItem;
}
ResetDragDrop();
}
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
if (dataGrid.Cursor != Cursors.SizeAll) dataGrid.Cursor = Cursors.SizeAll;
popup.DataContext = DraggedItem;
if (!popup.IsOpen)
{
dataGrid.IsReadOnly = true;
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
2、UI工具類
public static class UIHelpers
{
// 查找父元素
public static T TryFindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject parentObject = GetParentObject(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null) return parent;
else return TryFindParent<T>(parentObject);
}
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
if (child is ContentElement contentElement)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
if (contentElement is FrameworkContentElement fce)
return fce.Parent;
}
return VisualTreeHelper.GetParent(child);
}
// 更新綁定源
public static void UpdateBindingSources(DependencyObject obj, params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
// 從指定點(diǎn)查找元素
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);
}
}
3、XAML中使用
<Grid Grid.Row="1" Margin="5">
<!-- 拖拽提示Popup -->
<Popup x:Name="popup1" AllowsTransparency="True"
IsHitTestVisible="False" Placement="RelativePoint"
PlacementTarget="{Binding ElementName=dataGrid1}">
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center"
FontSize="14" FontWeight="Bold" Text="Dragging..." />
</Popup>
<!-- 啟用拖拽行為的DataGrid -->
<DataGrid x:Name="dataGrid1"
controlEx:DragDropRowBehavior.Enabled="True"
controlEx:DragDropRowBehavior.PopupControl="{Binding ElementName=popup1}"
AutoGenerateColumns="False"
ItemsSource="{Binding ListItem}"
RowHeaderWidth="60">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Column1}" Header="column1" />
<DataGridTextColumn Binding="{Binding Column2}" Header="column2" />
<DataGridTextColumn Binding="{Binding Column3}" Header="column3" />
</DataGrid.Columns>
</DataGrid>
</Grid>
實(shí)現(xiàn)原理
1、拖拽開始:用戶按下鼠標(biāo)左鍵時(shí),記錄被拖拽的行(OnMouseLeftButtonDown)
2、拖拽過程:鼠標(biāo)移動(dòng)時(shí)顯示提示Popup并更新位置(OnMouseMove)
3、拖拽結(jié)束:釋放鼠標(biāo)時(shí)交換行位置(OnMouseLeftButtonUp)
4、狀態(tài)管理:使用IsEditing和IsDragging標(biāo)志管理編輯與拖拽狀態(tài)
5、UI輔助:通過UIHelpers類在可視化樹中查找元素
關(guān)鍵特性
可視化反饋:拖拽時(shí)顯示"Dragging..."提示
編輯支持:編輯狀態(tài)下自動(dòng)禁用拖拽功能
平滑交互:拖拽過程中光標(biāo)自動(dòng)切換為"SizeAll"樣式
行定位:自動(dòng)定位鼠標(biāo)懸停位置的行
總結(jié)
本文介紹了WPF DataGrid行拖拽功能的完整實(shí)現(xiàn)方案。該方案通過附加行為的方式擴(kuò)展了DataGrid的功能,使其支持行拖拽操作。
關(guān)鍵點(diǎn)包括:
1、使用附加屬性實(shí)現(xiàn)功能解耦
2、通過Popup提供拖拽視覺反饋
3、利用UIHelpers工具類簡化可視化樹操作
4、正確處理編輯與拖拽的沖突
該實(shí)現(xiàn)方案具有較好的可復(fù)用性,只需在DataGrid上設(shè)置附加屬性即可啟用拖拽功能,無需修改原有業(yè)務(wù)邏輯。
以上就是WPF中實(shí)現(xiàn)DataGrid行拖拽功能的完整方案的詳細(xì)內(nèi)容,更多關(guān)于WPF DataGrid行拖拽功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
快速解決owin返回json字符串多帶了雙引號(hào)"多了重string轉(zhuǎn)義字符串
下面小編就為大家?guī)硪黄焖俳鉀Qowin返回json字符串多帶了雙引號(hào)"多了重string轉(zhuǎn)義字符串。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08
C#借助Spire.Doc for .NET實(shí)現(xiàn)調(diào)整Word文檔頁眉和頁腳的高度
在 Microsoft Word 文檔中手動(dòng)調(diào)整頁眉和頁腳高度往往令人頭疼,本文將介紹一種使用 Spire.Doc for .NET 在 C# 中精確調(diào)整 Word 頁眉和頁腳高度的解決方案,有需要的小伙伴可以了解下2026-03-03
C# 撒列實(shí)現(xiàn)關(guān)鍵字過濾的實(shí)例
C# 撒列實(shí)現(xiàn)關(guān)鍵字過濾的實(shí)例,需要的朋友可以參考一下2013-04-04
解析從源碼分析常見的基于Array的數(shù)據(jù)結(jié)構(gòu)動(dòng)態(tài)擴(kuò)容機(jī)制的詳解
本篇文章是對(duì)從源碼分析常見的基于Array的數(shù)據(jù)結(jié)構(gòu)動(dòng)態(tài)擴(kuò)容機(jī)制進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

