前言
想实现一个可拖拽调整ListView中各项顺序的自定义控件,方便后面的功能的实现。
网上没有找到完整的自定义控件的实现思路,但是发现了GongSolutions.WPF.DragDrop,这个项目。发现这个项目基本实现了拖拽需要的功能,使用简单并且非常强大,但是我并不像只因为一点拖拽功能就装一个包。
于是尝试自己简单实现,满足我的基本需求即可的——可以拖拽ListView中每一项ListViewItem,以调整其内部顺序。
思路梳理
简单梳理以下思路,会发现,其主要实现以下几点:
- 拖拽开始时,获取被拖拽的ListViewItem
- 拖拽过程中,拖拽的样式实现
- 经过的ListViewItem显示样式,做出一点改动——就仿佛控件在提示用户,你现在拖拽到我这里了,你放手的话被拖拽的对象会插在我这里。
- 被拖拽的对象最好能跟随鼠标移动——让用户明确自己拖拽的哪个ListViewItem
- 拖拽完成后,将被拖拽的ListViewItem,顺序调整到鼠标所在ListViewItem所在的位置
难点
其中对于我这个WPF小白来讲的难点在于
-
如何获取经过的ListViewItem,即鼠标悬浮下对应的ListViewItem
使用VisualTreeHelper。VisualTreeHelper提供了在可视树中执行常见的节点操作,比如
var hitTestResult = VisualTreeHelper.HitTest(this, position);
var targetItem = FindAncestor<ListViewItem>((DependencyObject)hitTestResult.VisualHit);
// 此处省略 FindAncestor
获取命中的节点对象,再通过查找父节点直到找到对应的ListViewItem,即可找到对应的ListViewItem。
-
如何让被拖拽的ListViewItem随着鼠标移动
基本思路:使用Popup控件,将ListViewItem设置为其Child,并让它跟随鼠标移动
-
如何调整ListView的顺序,即如何调整ItemsSource的顺序
这里参考了
GongSolutions.WPF.DragDrop
项目中的方法,通过反射使用ObservableCollection中提供的Move方法,所以这里就限制了ItemSource必须为ObservableCollection类型才支持。
开始实践吧!
经过梳理,我们需要实现两个两个控件
- DragListView继承自ListView,扩展其拖拽功能
- DragDropPreview继承自Popup,扩展其跟随鼠标移动功能
那么开始实践吧!
DragListView
1. 鼠标点击时,记录开始拖拽的索引、被拖拽的ListViewItem
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ResetDragDrop();
var position = e.GetPosition(this);
var hitTestResult = VisualTreeHelper.HitTest(this, position);
if (hitTestResult == null) return;
_draggedItem = FindAncestor<ListViewItem>((DependencyObject)hitTestResult.VisualHit);
if (_draggedItem == null)
{
ResetDragDrop();
return;
}
_oldPosition = GetInsertIndex(position);
}
private int GetInsertIndex(Point position)
{
var hitTestResult = VisualTreeHelper.HitTest(this, position);
if (hitTestResult == null) return -1;
var targetItem = FindAncestor<ListViewItem>((DependencyObject)hitTestResult.VisualHit);
if (targetItem == null) return -1;
return Items.IndexOf(targetItem.DataContext);
}
private static T FindAncestor<T>(DependencyObject current) where T : DependencyObject
{
do
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
2. 鼠标开始移动时,触发拖拽事件,创建DragDropPreview
private void OnPreviewDragMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed
&& _draggedItem != null && _draggedItemPreview == null)
{
CreatePopupWithControl(_draggedItem, _draggedItem.DataContext);
DragDrop.DoDragDrop(_draggedItem, _draggedItem.DataContext, DragDropEffects.Move);
}
}
private void CreatePopupWithControl(UIElement control, object data)
{
var newListItem = new ListViewItem();
newListItem.Content = data;
newListItem.Style = this.ItemContainerStyle;
newListItem.ContentTemplate = this.ItemTemplate;
_draggedItemPreview = new DragDropPreview();
_draggedItemPreview.PlacementTarget = this;
_draggedItemPreview.Child