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行拖拽功能,使用戶可以通過(guò)拖拽操作輕松調(diào)整行順序。該方案基于開源項(xiàng)目實(shí)現(xiàn),使用MIT協(xié)議的HandyControl樣式庫(kù),具有良好的可擴(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、拖拽過(guò)程:鼠標(biāo)移動(dòng)時(shí)顯示提示Popup并更新位置(OnMouseMove
)
3、拖拽結(jié)束:釋放鼠標(biāo)時(shí)交換行位置(OnMouseLeftButtonUp
)
4、狀態(tài)管理:使用IsEditing
和IsDragging
標(biāo)志管理編輯與拖拽狀態(tài)
5、UI輔助:通過(guò)UIHelpers
類在可視化樹中查找元素
關(guān)鍵特性
可視化反饋:拖拽時(shí)顯示"Dragging..."提示
編輯支持:編輯狀態(tài)下自動(dòng)禁用拖拽功能
平滑交互:拖拽過(guò)程中光標(biāo)自動(dòng)切換為"SizeAll"樣式
行定位:自動(dòng)定位鼠標(biāo)懸停位置的行
總結(jié)
本文介紹了WPF DataGrid行拖拽功能的完整實(shí)現(xiàn)方案。該方案通過(guò)附加行為的方式擴(kuò)展了DataGrid的功能,使其支持行拖拽操作。
關(guān)鍵點(diǎn)包括:
1、使用附加屬性實(shí)現(xiàn)功能解耦
2、通過(guò)Popup提供拖拽視覺(jué)反饋
3、利用UIHelpers工具類簡(jiǎn)化可視化樹操作
4、正確處理編輯與拖拽的沖突
該實(shí)現(xiàn)方案具有較好的可復(fù)用性,只需在DataGrid上設(shè)置附加屬性即可啟用拖拽功能,無(wú)需修改原有業(yè)務(wù)邏輯。
以上就是WPF中實(shí)現(xiàn)DataGrid行拖拽功能的完整方案的詳細(xì)內(nèi)容,更多關(guān)于WPF DataGrid行拖拽功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實(shí)現(xiàn)給Word每一頁(yè)設(shè)置不同文字水印的方法詳解
Word中設(shè)置水印時(shí),可使用預(yù)設(shè)的文字或自定義文字設(shè)置為水印效果,但通常添加水印效果時(shí),會(huì)對(duì)所有頁(yè)面都設(shè)置成統(tǒng)一效果。本文以C#?代碼為例,對(duì)Word每一頁(yè)設(shè)置不同的文字水印效果作詳細(xì)介紹,感興趣的可以了解一下2022-07-07.NET/C#實(shí)現(xiàn)識(shí)別用戶訪問(wèn)設(shè)備的方法
這篇文章主要介紹了.NET/C#實(shí)現(xiàn)識(shí)別用戶訪問(wèn)設(shè)備的方法,結(jié)合實(shí)例形式分析了C#識(shí)別用戶訪問(wèn)設(shè)備的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02C#使用Socket實(shí)現(xiàn)分布式事件總線的示例代碼
這篇文章主要介紹了C#使用Socket實(shí)現(xiàn)分布式事件總線,文中通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-10-10C#中使用FilleStream實(shí)現(xiàn)視頻文件的復(fù)制功能
這篇文章主要介紹了C#中使用FilleStream實(shí)現(xiàn)視頻文件的復(fù)制功能,本文通過(guò)實(shí)例代碼給大家介紹的非常想詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09