WPF 实现可拖拽调整顺序的ListView自定义控件

前言

想实现一个可拖拽调整ListView中各项顺序的自定义控件,方便后面的功能的实现。

网上没有找到完整的自定义控件的实现思路,但是发现了GongSolutions.WPF.DragDrop,这个项目。发现这个项目基本实现了拖拽需要的功能,使用简单并且非常强大,但是我并不像只因为一点拖拽功能就装一个包。

于是尝试自己简单实现,满足我的基本需求即可的——可以拖拽ListView中每一项ListViewItem,以调整其内部顺序。

思路梳理

简单梳理以下思路,会发现,其主要实现以下几点:

  1. 拖拽开始时,获取被拖拽的ListViewItem
  2. 拖拽过程中,拖拽的样式实现
    1. 经过的ListViewItem显示样式,做出一点改动——就仿佛控件在提示用户,你现在拖拽到我这里了,你放手的话被拖拽的对象会插在我这里。
    2. 被拖拽的对象最好能跟随鼠标移动——让用户明确自己拖拽的哪个ListViewItem
  3. 拖拽完成后,将被拖拽的ListViewItem,顺序调整到鼠标所在ListViewItem所在的位置

难点

其中对于我这个WPF小白来讲的难点在于

  1. 如何获取经过的ListViewItem,即鼠标悬浮下对应的ListViewItem

    使用VisualTreeHelper。VisualTreeHelper提供了在可视树中执行常见的节点操作,比如

var hitTestResult = VisualTreeHelper.HitTest(this, position);
var targetItem = FindAncestor<ListViewItem>((DependencyObject)hitTestResult.VisualHit);
// 此处省略 FindAncestor
获取命中的节点对象,再通过查找父节点直到找到对应的ListViewItem,即可找到对应的ListViewItem。
  1. 如何让被拖拽的ListViewItem随着鼠标移动

    基本思路:使用Popup控件,将ListViewItem设置为其Child,并让它跟随鼠标移动

  2. 如何调整ListView的顺序,即如何调整ItemsSource的顺序

    这里参考了GongSolutions.WPF.DragDrop项目中的方法,通过反射使用ObservableCollection中提供的Move方法,所以这里就限制了ItemSource必须为ObservableCollection类型才支持。

开始实践吧!

经过梳理,我们需要实现两个两个控件

  1. DragListView继承自ListView,扩展其拖拽功能
  2. 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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值