WPF拖拽交互全攻略及实现自定义拖拽控件及数据交换技巧解析
目录
- 1. 基本概念
- 2 . 实现拖拽功能
- 概述
- 需要要实现基本的拖放,完成以下任务:
- 其他操作
- 示例
- 3.1 设置拖拽源,拖拽开始
- 3.2 设置拖拽效果
- DragDropEffects
- 3.3 设置放置目标,处理拖拽数据
- 拖拽输入DragEnter事件
- DragOver事件
- 拖拽离开DragLeave事件
- 拖拽结束Drop事件
- 3. 其他
- 实际使用中遇到的问题
- 实现拖拽交换数据的自定义控件
- 效果
- 思路解析
- 具体实现
- 参考
1. 基本概念
拖拽(Drag and Drop)是一种常见的用户交互方式、数据传输方法。允许用户通过拖动鼠标来移动或复制数据。
在WPF 中的拖拽操作主要涉及以下几个概念:
- 拖动源(Drag Source):用户开始拖动的控件。
- 拖动目标(Drop Target):用户释放拖动的控件。
- 数据对象(Data Object):封装拖动过程中传递的数据。
- 可以通过拖放操作的对象的类型和数量是完全任意的。
例如,文件、文件夹和内容选择是通过拖放操作操作的一些更常见的对象。 - 拖拽源和放置目标可以是同一应用程序或不同应用程序中的UI元素。
- 拖放支持在单个应用程序内或不同应用程序之间操作对象。
- 还完全支持 WPF 应用程序和其他 Windows 应用程序之间的拖放。
- 在 WPF 中,任何 UIElement 或 ContentElement 都可以参与拖放。
UIElement 和 ContentElement 类包含 DragDrop 附加事件的别名,以便当 UIElement 或 ContentElement 作为基本元素继承时,这些事件会出现在类成员列表中。
2 . 实现拖拽功能
概述
需要要实现基本的拖放,完成以下任务:
- 确定将成为拖动源的元素。拖动源可以是 UIElement 或 ContentElement。
- 在将启动拖放操作的拖动源上创建一个事件处理程序。该事件通常是 MouseMove 事件。
- 在拖动源事件处理程序中,调用 DoDragDrop 方法来启动拖放操作。在 DoDragDrop 调用中,指定拖动源、要传输的数据以及允许的效果。
- 确定将成为放置目标的元素。放置目标可以是 UIElement 或 ContentElement。
- 在放置目标上,将AllowDrop 属性设置为 。
- 在放置目标中,创建一个 Drop 事件处理程序来处理放置的数据。
- 在 Drop 事件处理程序中,使用 GetDataPresent 和 GetData 方法从 DragEventArgs 中提取数据。
- 在 Drop 事件处理程序中,使用数据执行所需的拖放操作。
其他操作
要在拖动期间执行其他操作,请处理放置目标上的 DragEnter、DragOver 和 DragLeave 事件。
要更改鼠标指针的外观,请处理拖动源上的 GiveFeedback 事件。
要更改取消拖放操作的方式,请处理拖动源上的 QueryContinueDrag 事件。
示例
本节介绍如何实现椭圆元素的拖放。椭圆既是拖动源又是放置目标。传输的数据是椭圆的 Fill 属性的字符串表示形式。
<Ellipse Height="50" Width="50" Fill="Green"
MouseMove="ellipse_MouseMove"
GiveFeedback="ellipse_GiveFeedback"
AllowDrop="True"
DragEnter="ellipse_DragEnter"
DragLeave="ellipse_DragLeave"
DragOver="ellipse_DragOver"
Drop="ellipse_Drop" />
3.1 设置拖拽源,拖拽开始
示例
private void ellipse_MouseMove(object sender, MouseEventArgs e)
{
Ellipse ellipse = sender as Ellipse;
if (ellipse != null && e.LeftButton == MouseButtonState.Pressed)
{
DragDrop.DoDragDrop(ellipse,
ellipse.Fill.ToString(),
DragDropEffects.Copy);
}
}
在 MouseMove 事件处理程序内部,调用 DoDragDrop 方法来启动拖放操作。 DoDragDrop 方法采用三个参数:
dragSource
– 对作为传输数据源的依赖对象的引用;这通常是 MouseMove 事件的来源。data
- - 包含传输数据的对象,包装在 DataObject 中。
任何可序列化的对象都可以在参数中传递。如果数据尚未包装在 DataObject 中,它将自动包装在新的 DataObject 中。要传递多个数据项,您必须自己创建 DataObject,并将其传递给 DoDragDrop 方法。
// 创建一个新的 DataObject
DataObject dataObject = new DataObject();
// 添加多个数据项
dataObject.SetData("Text", "这是一个文本数据");
dataObject.SetData("Number", 12345);
dataObject.SetData("Date", DateTime.Now);
// 启动拖放操作
DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, dataObject, DragDropEffects.Copy);
allowedEffects
- DragDropEffects 枚举值之一,指定允许的拖放操作效果。
3.2 设置拖拽效果
向用户提供有关允许的操作(移动、复制、无)的反馈,并且可以基于附加的用户输入(例如在拖动期间按 ESC 键)取消拖放操作。可以选择处理拖动源上的 GiveFeedback 和 QueryContinueDrag 事件。
DragDropEffects
WPF 定义了一个 DragDropEffects
枚举用于指定拖放操作的效果。它支持按位组合其成员值,以便在拖放操作中表示不同的效果。
DragDropEffects
枚举包含以下成员:
-
None: 值为0,表示放置目标不接受数据。
-
Copy: 值为1,将拖动源中的数据复制到放置目标。
-
Move: 值为2,将拖动源中的数据移动到放置目标。
-
Link: 值为4,将拖动源中的数据链接到放置目标。
-
Scroll: 值为-2147483648,拖动时可以滚动目标,以定位在目标中当前不可见的某个放置位置。
-
All: 值为-2147483645,表示
Copy
、Move
和Scroll
效果的组合。 -
GiveFeedback
-
GiveFeedback具有默认处理程序,通常可这些事件,除非有特定需要更改它们的默认行为。
-
拖动拖动源时,会连续引发 GiveFeedback 事件。
-
此事件的默认处理程序检查拖动源是否位于有效的放置目标上方。
如果是,它会检查放置目标允许的效果。然后,它向最终用户提供有关允许的放置效果的反馈。
例如:将鼠标光标更改为不可放置、复制或移动光标来完成的。
-
如果处理此事件,请务必将其标记为已处理,以便默认处理程序不会覆盖您的处理程序。
private void Element_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (e.Effects == DragDropEffects.Copy)
{
// 更改鼠标光标为复制光标
Mouse.SetCursor(Cursors.Cross);
}
else
{
// 更改鼠标光标为不可放置光标
Mouse.SetCursor(Cursors.No);
}
// 标记事件为已处理
e.Handled = true;
}
- QueryContinueDrag
拖动拖动源时会连续引发 QueryContinueDrag 事件。
可以处理此事件,以根据 ESC、SHIFT、CTRL 和 ALT 键的状态以及鼠标按钮的状态确定结束拖放操作的操作。
如果按下 ESC 键,此事件的默认处理程序将取消拖放操作;
如果释放鼠标按钮,则删除数据。
这些事件在拖放操作期间不断引发。因此,您应该避免在事件处理程序中执行资源密集型任务。 例如,使用缓存的游标而不是每次引发 GiveFeedback 事件时创建新游标。
private void Element_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
// 如果按下 ESC 键,则取消拖放操作
if (e.EscapePressed)
{
e.Action = DragAction.Cancel;
}
// 如果释放鼠标按钮,则删除数据
else if (e.KeyStates == DragDropKeyStates.None)
{
e.Action = DragAction.Drop;
else
{
e.Action = DragAction.Continue;
}
Mouse.SetCursor(Cursors.Arrow);
}
3.3 设置放置目标,处理拖拽数据
- 指定它是有效的放置目标
AllowDrop 属性设置为true
- 当拖动源拖过目标时响应拖动源
拖拽输入DragEnter事件
当数据被拖入放置目标的边界时;
不要在 DragEnter 事件中设置 DragEventArgs.Effects 属性,因为它会在 DragOver 事件中被覆盖。
示例
// 定义一个私有变量来保存椭圆的原始填充画笔
private Brush _previousFill = null;
// 椭圆的 DragEnter 事件处理程序
private void ellipse_DragEnter(object sender, DragEventArgs e)
{
// 将发送者转换为 Ellipse 类型
Ellipse ellipse = sender as Ellipse;
// 如果转换成功,即发送者确实是一个椭圆
if (ellipse != null)
{
// 保存当前的填充画笔以便在拖放操作结束后恢复
_previousFill = ellipse.Fill;
// 检查拖动的数据对象是否包含可转换为 Brush 的字符串数据
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
// 提取拖动的数据对象中的字符串数据
string dataString = (string)e.Data.GetData(DataFormats.StringFormat);
// 创建一个 BrushConverter 对象用于转换字符串到 Brush
BrushConverter converter = new BrushConverter();
// 检查字符串是否可以转换为有效的 Brush
if (converter.IsValid(dataString))
{
// 将字符串转换为 Brush 并应用于椭圆的填充
Brush newFill = (Brush)converter.ConvertFromString(dataString);
ellipse.Fill = newFill;
}
}
}
}
GetDataPresent讲解
GetDataPresent
方法用于检查拖动的数据中是否包含指定格式的数据。
它有多个重载,常用的入参类型包括字符串和类型。
以下是对这些入参的详细介绍:
- 字符串格式
bool GetDataPresent(string format)
- 参数:
format
是一个字符串,表示要检查的数据格式。 - 常用值: 可以使用
DataFormats
类中的预定义格式,如DataFormats.Text
、DataFormats.Bitmap
、DataFormats.FileDrop
等。 - 示例:
if (e.Data.GetDataPresent(DataFormats.Text))
{
// 数据中包含文本格式的数据
}
- 类型格式
bool GetDataPresent(Type format)
- 参数:
format
是一个Type
对象,表示要检查的数据类型。 - 常用值: 可以使用
typeof
关键字来指定类型,如typeof(string)
、typeof(Button)
等。 - 示例:
if (e.Data.GetDataPresent(typeof(Button)))
{
// 数据中包含 Button 类型的数据
}
- 带有自动转换选项的字符串格式
bool GetDataPresent(string format, bool autoConvert)
- 参数:
format
: 一个字符串,表示要检查的数据格式。autoConvert
: 一个布尔值,指示是否允许自动转换数据格式。
- 示例:
if (e.Data.GetDataPresent(DataFormats.Text, true))
{
// 数据中包含文本格式的数据,允许自动转换
}
- 总结
GetDataPresent
方法通过检查拖动的数据中是否包含指定格式的数据,帮助确定拖放操作的有效性。根据不同的需求,可以使用字符串或类型作为参数,甚至可以指定是否允许自动转换数据格式。
DragOver事件
- 当数据被拖动到放置目标上时,DragOver 事件会连续发生。
- 此事件与拖动源上的 GiveFeedback 事件配对。
- 在 DragOver 事件处理程序中,通常使用 GetDataPresent 和 GetData 方法来检查传输的数据是否采用放置目标可以处理的格式。
- 可以检查是否按下了任何修饰键,这通常表明用户是否打算执行移动或复制操作。
示例
private void ellipse_DragOver(object sender, DragEventArgs e)
{
// 默认不允许拖放操作
e.Effects = DragDropEffects.None;
// 如果 DataObject 包含字符串数据,则提取它
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string dataString = (string)e.Data.GetData(DataFormats.StringFormat);
// 如果字符串可以转换为 Brush,则允许复制或移动操作
BrushConverter converter = new BrushConverter();
if (converter.IsValid(dataString))
{
e.Effects = DragDropEffects.Copy | DragDropEffects.Move;
}
}
}
- 拖拽离开或结束时,检查传输的数据是否采用其可以接收的格式,处理丢弃的数据。
拖拽离开DragLeave事件
private void ellipse_DragLeave(object sender, DragEventArgs e)
{
// 将 sender 转换为 Ellipse 对象
Ellipse ellipse = sender as Ellipse;
// 如果转换成功
if (ellipse != null)
{
// 将 Ellipse 的填充色恢复为之前的颜色
ellipse.Fill = _previousFill;
}
}
拖拽结束Drop事件
private void ellipse_Drop(object sender, DragEventArgs e)
{
// 将 sender 转换为 Ellipse 对象
Ellipse ellipse = sender as Ellipse;
if (ellipse != null)
{
// 如果 DataObject 包含字符串数据,则提取它
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string dataString = (string)e.Data.GetData(DataFormats.StringFormat);
// 如果字符串可以转换为 Brush,
// 则进行转换并将其应用于 ellipse
BrushConverter converter = new BrushConverter();
if (converter.IsValid(dataString))
{
Brush newFill = (Brush)converter.ConvertFromString(dataString);
ellipse.Fill = newFill;
}
}
}
}
3. 其他
实际使用中遇到的问题
DragDrop.DoDragDrop
后不触发鼠标事件
在使用拖住的过程中,也常常用到拖拽并移动控件位置。在我的尝试中,发现**DragDrop.DoDragDrop(_dragDropBorder, dataobject, DragDropEffects.Move);
会阻碍_dragDropBorder
**的其他鼠标事件。但是我并没找到直接明确的说法。
如以下代码,我平常事在PreviewouseLeftButtonDown
中设置拖拽源,拖拽开始。
在MouseMove
中通过TranslateTransform
移动控件位置,发现并不起效,因为没有触发MouseMove
事件。
private Point _startPoint;
private TranslateTransform _translateTransform;
public Point _initialTransformPosition;
private void ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(Application.Current.MainWindow);
if (_translateTransform != null)
{
_initialTransformPosition = new Point(_translateTransform.X, _translateTransform.Y);
}
else
{
_translateTransform = PART_Border?.RenderTransform as TranslateTransform;
_initialTransformPosition = new Point(0, 0); // 如果没有Transform,则初始位置为(0,0)
}
// 以下代码如果不注释,将影响ellipse_MouseMove执行
DataObject dataObject = new DataObject();
dataObject.SetData("Brush", PART_Ellipse.Fill);
dataObject.SetData("Source", PART_Ellipse);
DragDrop.DoDragDrop(PART_Ellipse, dataObject, DragDropEffects.All);
}
private void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed) return;
if (_translateTransform == null) return;
Point currentPoint = e.GetPosition(Application.Current.MainWindow);
double offsetX = currentPoint.X - _startPoint.X;
double offsetY = currentPoint.Y - _startPoint.Y;
double newX = _initialTransformPosition.X + offsetX;
double newY = _initialTransformPosition.Y + offsetY;
if (newX == 0 && newY == 0) return;
_translateTransform.X = newX;
_translateTransform.Y = newY;
}
我尝试在ellipse_QueryContinueDrag
中处理位置移动,效果不理想——椭圆移动轨迹诡异。我暂时没有理清为什么。代码如下:
private void ellipse_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.KeyStates != DragDropKeyStates.LeftMouseButton) return;
if (_translateTransform == null) return;
Point currentPoint = Mouse.GetPosition(Application.Current.MainWindow);
double offsetX = currentPoint.X - _startPoint.X;
double offsetY = currentPoint.Y - _startPoint.Y;
double newX = _initialTransformPosition.X + offsetX;
double newY = _initialTransformPosition.Y + offsetY;
if (newX == 0 && newY == 0) return;
_translateTransform.X = newX;
_translateTransform.Y = newY;
}
实现拖拽交换数据的自定义控件
鉴于在DragDrop中使用遇到的问题,我思考如果自己实现该如何编写?于是我自己简陋的实现了一个自定义控件DragBorder
。只要用来实现一盒可拖拽移动控件位置,并且拖拽到另一个DragBorder
的同时实现数据的合并,合并数据后将拖拽源从父控件中删除的功能。这里实现的比价简陋,只做抛砖引玉石。
在我的文章WPF 实现可拖拽调整顺序的ListView自定义控件_wpf 重写itemscontrol 可以手动拖动元素 任意位置停靠-CSDN博客中,提到了一个项目GongSolutions.WPF.DragDrop。我的实现思路也是来源于这个项目的启发。另外一个题外话是,我自己蛮喜欢的RPA软件影刀也是用了该项目的dll。
效果
思路解析
- 区分控件是拖拽源还是拖拽目标
- 当拖拽源拖拽到拖拽目标时,根据鼠标当前位置,获取到拖拽目标。
- 并在合理的时机触发拖拽源和拖拽目标的Drag事件、拖拽目标的DragLeave事件、DragOver事件
具体实现
使用MVVM模式,依赖CommunityToolkit.Mvvm、Microsoft.Xaml.Behaviors.Wpf
- 实现自定义控件,在PreviewMouseLeftDown事件中确定该控件为拖拽源
[RelayCommand]
private void DragStart(MouseEventArgs e)
{
IsDragSource = true;
//记录移动前的位置和变换数据
mouseDownPosition = e.GetPosition(Application.Current.MainWindow);
if (_translateTransform != null)
{
initialTransformPosition = new Point(_translateTransform.X, _translateTransform.Y);
}
else
{
_translateTransform = _source.PART_Border?.RenderTransform as TranslateTransform;
initialTransformPosition = new Point(0, 0); // 如果没有Transform,则初始位置为(0,0)
}
_source.CaptureMouse();//_source为View,初始化ViewModel时赋值
}
- 在
MouseMove
事件中处理控件变换移动,通过命中测试(Hit Test)在视觉树中查找特定类型的控件(DragBorderControl)来确定拖拽目标,并触发拖拽目标的DragLeave事件、DragOver事件
internal class HitTestHelper
{
public static DragBorderControl GetHitDropControls(Visual visual, Point elementPosition)
{
DragBorderControl hitControl = null;
// 定义命中测试回调方法
HitTestResultCallback resultCallback = new HitTestResultCallback(result =>
{
// 检查命中的控件是否为 DragBorderControl 类型
if (result.VisualHit is FrameworkElement frameworkElement)
{
var parent = frameworkElement;
while (parent != null)
{
if (parent is DragBorderControl hitItem && !hitItem.ViewModel.IsDragSource)
{
hitControl = hitItem;
return HitTestResultBehavior.Stop;
}
parent = VisualTreeHelper.GetParent(parent) as FrameworkElement;
}
}
return HitTestResultBehavior.Continue;
});
// 执行命中测试
VisualTreeHelper.HitTest(visual, null, resultCallback, new PointHitTestParameters(elementPosition));
return hitControl;
}
}
[RelayCommand]
public void DragMove(MouseEventArgs e)
{
if (_source.IsMouseCaptured)
{
IsDragging = true;
if (_translateTransform == null)
{
ReleaseMouseCapture(_source);
return;
}
//计算偏移量,移动控件
Point currentPoint = Mouse.GetPosition(Application.Current.MainWindow);
double offsetX = currentPoint.X - mouseDownPosition.X;
double offsetY = currentPoint.Y - mouseDownPosition.Y;
double newX = initialTransformPosition.X + offsetX;
double newY = initialTransformPosition.Y + offsetY;
if (newX == 0 && newY == 0) return;
_translateTransform.X = newX;
_translateTransform.Y = newY;
//是否进入目标区域
var hitTarget = HitTestHelper.GetHitDropControls(this.ParentControl, Mouse.GetPosition(null));
if (hitTarget != null && hitTarget != TargetControl)
{
//如果目标区域发生变化,则触发DragLeave事件
if (TargetControl != null && TargetControl.ViewModel.IsDragEnter)
{
TargetControl.ViewModel.IsDragEnter = false;
DragLeaveEvent?.Invoke(this, new DragEventArgs(DragData, _source, TargetControl));
}
//更新目标区域,触发DragEnter事件
TargetControl = hitTarget;
hitTarget.ViewModel.IsDragEnter = true;
DragEnterEvent?.Invoke(this, new DragEventArgs(DragData,_source, TargetControl));
}
else if (hitTarget == null)
{
//如果没有进入目标区域,原目标区域触发DragLeave事件
if (TargetControl != null)
{
TargetControl.ViewModel.IsDragEnter = false;
TargetControl.ViewModel.DragLeaveEvent?.Invoke(this, new DragEventArgs(DragData, _source, TargetControl));
TargetControl = null;
}
}
}
}
- 在
PreviewMouseLeftButtonUp
事件中处理拖拽源完成拖拽,处理数据合并、合并后源控件从父类中移除的功能。数据合并部分的功能我实现的比较粗糙,还是WPF的DragDrop实现的好。
[RelayCommand]
public void Drag(MouseButtonEventArgs e)
{
if (TargetControl != null)
{
TargetControl.ViewModel.DragEvent?.Invoke(this, new DragEventArgs(DragData, _source, TargetControl));
}
if (_source.IsMouseCaptured)
{
ReleaseMouseCapture(_source);
}
}
private void ReleaseMouseCapture(UIElement element = null)
{
IsDragging = false;
IsDragSource = false;
IsDragEnter = false;
if (TargetControl != null && TargetControl.ViewModel.IsDragEnter)
{
IsDragEnter = false;
}
TargetControl = null;
if (element != null)
element.ReleaseMouseCapture();
}
#region Event
private void Drag(object? sender, DragEventArgs e)
{
if (e.Target != null && this._source == e.Target && e.Target.ViewModel.IsDragEnter)
{
this.DragData = MerageDragData(e);
if (e.Source.ViewModel.IsRemoveAfterMerage)
{
RemoveAfterMerage(e.Source);
}
e.Target.ViewModel.IsDragEnter = false;
}
}
#endregion
private IEnumerable<DataItem> MerageDragData(DragEventArgs e)
{
var thisDragDataList = EnsureList(this.DragData).Cast<DataItem>().ToList();
var sourceDragDataList = EnsureList(e.DragData).Cast<DataItem>().ToList();
thisDragDataList.AddRange(sourceDragDataList);
return thisDragDataList;
}
public List<DataItem> EnsureList(object dragData)
{
if (dragData is List<DataItem> existingList)
{
return existingList;
}
else if (dragData is DataItem singleItem)
{
return new List<DataItem> { singleItem };
}
else if (dragData is String singleString)
{
return new List<DataItem> { new DataItem { Value = singleString, Type = "String" } };
}
else if (dragData is List<string> singleStringList)
{
return singleStringList.Select(s => new DataItem { Value = s, Type = "String" }).ToList();
}
else if (dragData is null)
{
return new List<DataItem>();
}
else
{
// 处理其他类型的数据,可能需要转换
throw new ArgumentException("Unsupported drag data type");
}
}
private void RemoveAfterMerage(DragBorderControl source)
{
if(source.ParentControl == null)
{
return;
}
if (source.ParentControl is Panel panel)
{
panel.Children.Remove(source);
}
else if (source.ParentControl is Grid grid)
{
grid.Children.Remove(source);
}
else if (source.ParentControl is Canvas canvas)
{
canvas.Children.Remove(source);
}
}
- 控件代码
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:DragBorder">
<Style TargetType="{x:Type local:DragBorderControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DragBorderControl}">
<Border
x:Name="PART_Border"
Grid.Column="1"
Height="Auto"
Padding="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
AllowDrop="False"
Background="{TemplateBinding Background}"
BorderThickness="0"
CornerRadius="5"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
<Grid HorizontalAlignment="Stretch">
<ContentPresenter
x:Name="contentPresenter"
Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
</Grid>
<Border.RenderTransform>
<TranslateTransform x:Name="PART_BorderTransform" />
</Border.RenderTransform>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction
Command="{Binding ViewModel.DragStartCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:DragBorderControl}}}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
- 使用Demo
<Window
x:Class="DragBorder.MainWindow"
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:local="clr-namespace:DragBorder"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<local:DragBorderDataTemplateSelector x:Key="DataTemplateSelector">
<local:DragBorderDataTemplateSelector.StringTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Value,
StringFormat='String: {0}'}"
TextWrapping="NoWrap" />
</DataTemplate>
</local:DragBorderDataTemplateSelector.StringTemplate>
<local:DragBorderDataTemplateSelector.IntTemplate>
<DataTemplate>
<TextBlock
Width="200"
Height="200"
Text="{Binding Value,
StringFormat='Int: {0}'}"
TextWrapping="NoWrap" />
</DataTemplate>
</local:DragBorderDataTemplateSelector.IntTemplate>
</local:DragBorderDataTemplateSelector>
</Window.Resources>
<StackPanel
x:Name="Root"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<local:DragBorderControl
x:Name="DragBorder1"
Background="Red"
DragData="{Binding Data1}"
IsRemoveAfterMerage="True"
ParentControl="{Binding ElementName=Root}">
<ListView
ItemTemplateSelector="{StaticResource DataTemplateSelector}"
ItemsSource="{Binding ElementName=DragBorder1,
Path=DragData}" />
</local:DragBorderControl>
<local:DragBorderControl
x:Name="DragBorder2"
Background="Green"
DragData="{Binding Data2}"
IsRemoveAfterMerage="True"
ParentControl="{Binding ElementName=Root}">
<ListView
ItemTemplateSelector="{StaticResource DataTemplateSelector}"
ItemsSource="{Binding ElementName=DragBorder2,
Path=DragData}" />
</local:DragBorderControl>
</StackPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
public List<string> Data1 { get; set; } = new List<string>() { "Data from DragBorder1" };
public List<string> Data2 { get; set; } = new List<string>() { "DragBorder2" };
}
参考
- Drag and Drop Overview - WPF .NET Framework | Microsoft Learn
- (19) Setting Up Drag Drop - WPF DRAG DROP TUTORIAL #1 - YouTube
- 主 wpf-tutorials/DragDropDemo ·SingletonSean/wpf-tutorials — wpf-tutorials/DragDropDemo at master · SingletonSean/wpf-tutorials
- 本文自定义实现控件源码
备注
- 链接2分了5期视频详细讲解了DragDrop并由提供源码,感兴趣的同学可以前去查看。