WPF利用TabControl控件實現(xiàn)拖拽排序功能
在UI交互中,拖拽操作是一種非常簡單友好的交互。尤其是在ListBox,TabControl,ListView這類列表控件中更為常見。通常要實現(xiàn)拖拽排序功能的做法是自定義控件。本文將分享一種在原生控件上設(shè)置附加屬性的方式實現(xiàn)拖拽排序功能。
該方法的使用非常簡單,僅需增加一個附加屬性就行。
<TabControl assist:SelectorDragDropAttach.IsItemsDragDropEnabled="True" AlternationCount="{Binding ClassInfos.Count}" ContentTemplate="{StaticResource contentTemplate}" ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{Binding ClassInfos}" SelectedIndex="0" />
實現(xiàn)效果如下:
主要思路
WPF中核心基類UIElement包含了DragEnter
,DragLeave
,DragEnter
,Drop
等拖拽相關(guān)的事件,因此只需對這幾個事件進行監(jiān)聽并做相應(yīng)的處理就可以實現(xiàn)WPF中的UI元素拖拽操作。
另外,WPF的一大特點是支持?jǐn)?shù)據(jù)驅(qū)動,即由數(shù)據(jù)模型來推動UI的呈現(xiàn)。因此,可以通過通過拖拽事件處理拖拽的源位置以及目標(biāo)位置,并獲取到對應(yīng)位置渲染的數(shù)據(jù),然后操作數(shù)據(jù)集中數(shù)據(jù)的位置,從而實現(xiàn)數(shù)據(jù)和UI界面上的順序更新。
首先定義一個附加屬性類SelectorDragDropAttach
,通過附加屬性IsItemsDragDropEnabled
控制是否允許拖拽排序。
public static class SelectorDragDropAttach { public static bool GetIsItemsDragDropEnabled(Selector scrollViewer) { return (bool)scrollViewer.GetValue(IsItemsDragDropEnabledProperty); } public static void SetIsItemsDragDropEnabled(Selector scrollViewer, bool value) { scrollViewer.SetValue(IsItemsDragDropEnabledProperty, value); } public static readonly DependencyProperty IsItemsDragDropEnabledProperty = DependencyProperty.RegisterAttached("IsItemsDragDropEnabled", typeof(bool), typeof(SelectorDragDropAttach), new PropertyMetadata(false, OnIsItemsDragDropEnabledChanged)); private static readonly DependencyProperty SelectorDragDropProperty = DependencyProperty.RegisterAttached("SelectorDragDrop", typeof(SelectorDragDrop), typeof(SelectorDragDropAttach), new PropertyMetadata(null)); private static void OnIsItemsDragDropEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { bool b = (bool)e.NewValue; Selector selector = d as Selector; var selectorDragDrop = selector?.GetValue(SelectorDragDropProperty) as SelectorDragDrop; if (selectorDragDrop != null) selectorDragDrop.Selector = null; if (b == false) { selector?.SetValue(SelectorDragDropProperty, null); return; } selector?.SetValue(SelectorDragDropProperty, new SelectorDragDrop(selector)); } }
其中SelectorDragDrop
就是處理拖拽排序的對象,接下來看下幾個主要事件的處理邏輯。
通過PreviewMouseLeftButtonDown
確定選中的需要拖拽操作的元素的索引
void selector_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (this.IsMouseOverScrollbar) { //Set the flag to false when cursor is over scrollbar. this.canInitiateDrag = false; return; } int index = this.IndexUnderDragCursor; this.canInitiateDrag = index > -1; if (this.canInitiateDrag) { // Remember the location and index of the SelectorItem the user clicked on for later. this.ptMouseDown = GetMousePosition(this.selector); this.indexToSelect = index; } else { this.ptMouseDown = new Point(-10000, -10000); this.indexToSelect = -1; } }
在PreviewMouseMove
事件中根據(jù)需要拖拽操作的元素創(chuàng)建一個AdornerLayer
,實現(xiàn)鼠標(biāo)拖著元素移動的效果。其實拖拽移動的只是這個AdornerLayer
,真實的元素并未移動。
void selector_PreviewMouseMove(object sender, MouseEventArgs e) { if (!this.CanStartDragOperation) return; // Select the item the user clicked on. if (this.selector.SelectedIndex != this.indexToSelect) this.selector.SelectedIndex = this.indexToSelect; // If the item at the selected index is null, there's nothing // we can do, so just return; if (this.selector.SelectedItem == null) return; UIElement itemToDrag = this.GetSelectorItem(this.selector.SelectedIndex); if (itemToDrag == null) return; AdornerLayer adornerLayer = this.ShowDragAdornerResolved ? this.InitializeAdornerLayer(itemToDrag) : null; this.InitializeDragOperation(itemToDrag); this.PerformDragOperation(); this.FinishDragOperation(itemToDrag, adornerLayer); }
DragEnter
,DragLeave
,DragEnter
事件中處理AdornerLayer
的位置以及是否顯示。
Drop
事件中確定了拖拽操作目標(biāo)位置以及渲染的數(shù)據(jù)元素,然后移動元數(shù)據(jù),通過數(shù)據(jù)順序的變化更新界面的排序。從代碼中可以看到列表控件的ItemsSource
不能為空,否則拖拽無效。這也是后邊將提到的一個缺點。
void selector_Drop(object sender, DragEventArgs e) { if (this.ItemUnderDragCursor != null) this.ItemUnderDragCursor = null; e.Effects = DragDropEffects.None; var itemsSource = this.selector.ItemsSource; if (itemsSource == null) return; int itemsCount = 0; Type type = null; foreach (object obj in itemsSource) { type = obj.GetType(); itemsCount++; } if (itemsCount < 1) return; if (!e.Data.GetDataPresent(type)) return; object data = e.Data.GetData(type); if (data == null) return; int oldIndex = -1; int index = 0; foreach (object obj in itemsSource) { if (obj == data) { oldIndex = index; break; } index++; } int newIndex = this.IndexUnderDragCursor; if (newIndex < 0) { if (itemsCount == 0) newIndex = 0; else if (oldIndex < 0) newIndex = itemsCount; else return; } if (oldIndex == newIndex) return; if (this.ProcessDrop != null) { // Let the client code process the drop. ProcessDropEventArgs args = new ProcessDropEventArgs(itemsSource, data, oldIndex, newIndex, e.AllowedEffects); this.ProcessDrop(this, args); e.Effects = args.Effects; } else { dynamic dItemsSource = itemsSource; if (oldIndex > -1) dItemsSource.Move(oldIndex, newIndex); else dItemsSource.Insert(newIndex, data); e.Effects = DragDropEffects.Move; } }
優(yōu)點與缺點
優(yōu)點:
- 用法簡單,封裝好拖拽操作的附加屬性后,只需一行代碼實現(xiàn)拖拽功能。
- 對現(xiàn)有項目友好,對于已有項目需要擴展拖拽操作排序功能,無需替換控件。
- 支持多種列表控件擴展。派生自
Selector
的ListBox
,TabControl
,ListView
,ComboBox
都可使用該方法。
缺點:
- 僅支持通過數(shù)據(jù)綁定動態(tài)渲染的列表控件,XAML硬編碼或者后臺代碼循環(huán)添加列表元素創(chuàng)建的列表控件不適用該方法。
- 僅支持列表控件內(nèi)的元素拖拽,不支持穿梭框拖拽效果。
- 不支持同時拖拽多個元素。
小結(jié)
本文介紹列表拖拽操作的解決方案不算完美,功能簡單但輕量,并且很好的體現(xiàn)了WPF的數(shù)據(jù)驅(qū)動的思想。個人非常喜歡這種方式,它能讓我們輕松的實現(xiàn)列表數(shù)據(jù)的增刪以及排序操作,而不是耗費時間和精力去自定義可增刪數(shù)據(jù)的控件。
到此這篇關(guān)于WPF利用TabControl控件實現(xiàn)拖拽排序功能的文章就介紹到這了,更多相關(guān)WPF TabControl拖拽排序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實現(xiàn)按照指定長度在數(shù)字前補0方法小結(jié)
這篇文章主要介紹了C#實現(xiàn)按照指定長度在數(shù)字前補0方法,實例總結(jié)了兩個常用的數(shù)字補0的技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04C#調(diào)用WinAPI部分命令的方法實現(xiàn)
本文主要介紹了C#調(diào)用WinAPI部分命令的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01