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的一大特點是支持數(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#入門之循環(huán)語句使用詳解(for循環(huán)、do/while)
這篇文章主要介紹了c#入門之循環(huán)語句使用詳解,有for循環(huán)和do/while的示例,需要的朋友可以參考下2014-04-04
C#創(chuàng)建windows系統(tǒng)用戶的方法
這篇文章主要介紹了C#創(chuàng)建windows系統(tǒng)用戶的方法,涉及C#操作用戶名、密碼、顯示名稱、描述、是否強制修改密碼、密碼是否過期等技巧,非常具有實用價值,需要的朋友可以參考下2015-04-04
C#實現(xiàn)求一組數(shù)據(jù)眾數(shù)的方法
這篇文章主要介紹了C#實現(xiàn)求一組數(shù)據(jù)眾數(shù)的方法,這里以浮點型數(shù)組為例分析了C#求眾數(shù)的算法原理與實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08
詳解C#中使用對象或集合的初始值設(shè)定項初始化的操作
這篇文章主要介紹了詳解C#中使用對象或集合的初始值設(shè)定項初始化的操作,文中分別講了對對象和字典的初始化,需要的朋友可以參考下2016-01-01

