Bootstrap

ItemsControl 类控件子项拖放

前面在实现TabControl控件的Tab页面拖放查询资料时发现,可以使用装饰器来实现对ItemsControl 类控件子项的拖放。

要实现拖放功能,关键点还是在按下鼠标并移动时,调用 DoDragDrop 方法以触发拖动动作,然后在拖放目标上响应 DropEvent 事件。

在装饰器里面封装这些事件,这里面定一个了一个依赖属性 ItemType,需要在使用装饰器时指定ItemType,应为ItemsSource集合中元素的类型,在拖放响应DropEvent 事件时进行校验,如果拖放的数据与ItemType类型一致,才接收拖放数据。此外,响应DropEvent 事件时,对装饰的ItemsControl 数据源进行检查,如果数据源的ItemsSource 为空,或是已经包含了拖放数据,则将拖放效果更改为 DragDropEffects.None,避免在拖放结束时,源数据被删除导致数据丢失。

注意在响应Load事件时候,应该判断是否是第一次Load,仅在第一次Load时才对Child 添加拖放相关的事件处理方法。因为如果装饰器的Parent如果是一个Tab页,而整个Tab页被拖动到另一个TabControl时,会再次触发装饰器的Load,如果这时候再次为Child 绑定拖放相关事件处理方法,会导致多次重复的绑定。

ItemsControlDragDropDecorator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;
using System.Collections;

namespace WpfLibrary;

public class ItemsControlDragDropDecorator : Decorator
{
    static ItemsControlDragDropDecorator()
    {
        ItemTypeProperty = DependencyProperty.Register("ItemType", typeof(Type), typeof(ItemsControlDragDropDecorator), new FrameworkPropertyMetadata(null));   
    }
    public ItemsControlDragDropDecorator() : base()
    {
        AllowDrop = true;
        IsDraging = false;
        this.Loaded += new RoutedEventHandler(DraggableItemsControl_Loaded);
    }
    public static readonly DependencyProperty ItemTypeProperty;
    public Type ItemType
    {
        get { return (Type)base.GetValue(ItemTypeProperty); }
        set { base.SetValue(ItemTypeProperty, value); }
    }
    private bool IsFirstLoad = true;
    private bool IsDraging = false;
    private Point DragStartPosition;
    private object? DragData;
    void DraggableItemsControl_Loaded(object sender, RoutedEventArgs e)
    {
        if (!IsFirstLoad)
        {
            return;
        }
        IsFirstLoad = false;
        ItemsControl? itemsControl = Child as ItemsControl;
        if (itemsControl == null)
        {
            return;
        }
        itemsControl.AllowDrop = AllowDrop;
        itemsControl.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);
        itemsControl.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp), true);
        itemsControl.AddHandler(MouseMoveEvent, new MouseEventHandler(OnMouseMove), true);
        itemsControl.AddHandler(DropEvent, new DragEventHandler(OnDrop), true);
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var itemsControl = sender as ItemsControl;
        if (itemsControl == null)
        {
            return;
        }
        Point p = e.GetPosition(itemsControl);
        DragData = DragUtility.GetDataObjectFromItemsControl(itemsControl, p);
        if (DragData != null)
        {
            DragStartPosition = p;
        }
        e.Handled = true;
    }
    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var itemsControl = sender as ItemsControl;
        if (itemsControl == null)
        {
            return;
        }
        ResetState(itemsControl);
        e.Handled = true;
    }
    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        var itemsControl = sender as ItemsControl;
        if (itemsControl == null)
        {
            return;
        }
        if (DragData == null || DragData.GetType() != ItemType)
        {
            return;
        }
        Point currentPosition = e.GetPosition(itemsControl);
        if (e.LeftButton == MouseButtonState.Pressed && !IsDraging
            && ((Math.Abs(currentPosition.X - DragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance) || (Math.Abs(currentPosition.Y - DragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance)))
        {
            DataObject dataObject = new DataObject();
            dataObject.SetData("DragData", DragData);
            itemsControl.AllowDrop = false;
            var effects = DragDrop.DoDragDrop(itemsControl, dataObject, DragDropEffects.Copy | DragDropEffects.Move);
            if (effects.HasFlag(DragDropEffects.Move))
            {
                if (itemsControl.ItemsSource != null)
                {
                    IList? list = itemsControl.ItemsSource as IList;
                    if (list != null)
                    {
                        list.Remove(DragData);
                    }
                }
                else
                {
                    itemsControl.Items.Remove(DragData);
                }
            }
            ResetState(itemsControl);
        }
        e.Handled =true;
    }
    private void OnDrop(object sender, DragEventArgs e)
    {
        var itemsControl = sender as ItemsControl;
        if (itemsControl == null)
        {
            return;
        }
        object data = e.Data.GetData("DragData");
        if (data == null || data.GetType() != ItemType)
        {
            return;
        }
        e.Effects = e.KeyStates.HasFlag(DragDropKeyStates.ControlKey) ? DragDropEffects.Copy : DragDropEffects.Move;
        if (itemsControl.ItemsSource != null)
        {
            IList? list = itemsControl.ItemsSource as IList;
            if (list != null && !list.Contains(data))
            {
                list.Add(data);
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }
        else
        {
            if (!itemsControl.Items.Contains(data))
            {
                itemsControl.Items.Add(data);
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }
        e.Handled = true;
    }
    private void ResetState(ItemsControl itemsControl)
    {
        IsDraging = false;
        DragData = null;
        itemsControl.AllowDrop = true;
    }
}

MainWindow2.xaml

<Window x:Class="WpfLibraryTest.MainWindow2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfLibraryTest"
        xmlns:wpfLibrary="clr-namespace:WpfLibrary;assembly=WpfLibrary"
        mc:Ignorable="d"
        Title="MainWindow2" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <wpfLibrary:ItemsControlDragDropDecorator Grid.Row="0" ItemType="{x:Type local:Account}">
            <ListBox x:Name="listBox1" ItemsSource="{Binding Path=Accounts}" DisplayMemberPath="AccountID"/>
        </wpfLibrary:ItemsControlDragDropDecorator>

        <TabControl x:Name="tabControl2" Grid.Row="1" >
            <TabItem Header="tabControl2Item1">
                <wpfLibrary:ItemsControlDragDropDecorator ItemType="{x:Type local:Account}">
                    <DataGrid ItemsSource="{Binding Path=Accounts2}" IsReadOnly="True"></DataGrid>
                </wpfLibrary:ItemsControlDragDropDecorator>
            </TabItem>
            <TabItem Header="tabControl2Item2">
                <wpfLibrary:ItemsControlDragDropDecorator Grid.Row="1" ItemType="{x:Type local:Account}">
                    <DataGrid ItemsSource="{Binding Path=Accounts3}" IsReadOnly="True"></DataGrid>
                </wpfLibrary:ItemsControlDragDropDecorator>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

;