前面在实现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>