You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WPF中同时实现控件拖动移动与数据传输?

WPF同时实现UserControl拖动移动与跨控件数据传输方案

核心思路

把拖动视觉反馈(用Adorner显示移动的控件副本)和标准拖放数据传输结合:

  • UserControl自身负责触发拖放、携带数据以及接收数据
  • 窗口层面通过AdornerLayer管理拖动过程中的视觉跟随,实现移动效果
  • 拖放事件中同时处理数据传递逻辑

具体实现示例

1. 可拖动数据传输的UserControl

<UserControl x:Class="WpfDragDropDemo.DraggableItem"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Width="100" Height="80"
             MouseLeftButtonDown="DraggableItem_MouseLeftButtonDown">
    <Border Background="LightBlue" BorderBrush="Gray" BorderThickness="1">
        <StackPanel>
            <TextBlock Text="拖动项" HorizontalAlignment="Center"/>
            <TextBlock x:Name="DataText" Text="示例数据" HorizontalAlignment="Center"/>
        </StackPanel>
    </Border>
</UserControl>

后台代码:

public partial class DraggableItem : UserControl
{
    // 对外暴露的传输数据
    public string ItemData => DataText.Text;

    public DraggableItem()
    {
        InitializeComponent();
        AllowDrop = true;
        Drop += DraggableItem_Drop;
    }

    // 触发拖放,携带自身数据
    private void DraggableItem_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DragDrop.DoDragDrop(this, ItemData, DragDropEffects.Move);
    }

    // 接收拖拽数据并处理
    private void DraggableItem_Drop(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(typeof(string)))
        {
            var receivedData = e.Data.GetData(typeof(string)) as string;
            DataText.Text = $"已接收:{receivedData}";
            e.Effects = DragDropEffects.Move;
        }
    }
}

2. 拖动视觉反馈的Adorner

用于在拖动过程中显示跟随鼠标的控件副本:

public class DragAdorner : Adorner
{
    private readonly Visual _adornerVisual;
    private Point _offset;

    public DragAdorner(UIElement adornedElement, Visual visual) : base(adornedElement)
    {
        _adornerVisual = visual;
        IsHitTestVisible = false; // 不干扰底层控件的拖放检测
    }

    public void SetOffset(Point offset)
    {
        _offset = offset;
        UpdateLayout();
    }

    protected override int VisualChildrenCount => 1;

    protected override Visual GetVisualChild(int index)
    {
        if (index != 0) throw new ArgumentOutOfRangeException();
        return _adornerVisual;
    }

    protected override Size MeasureOverride(Size constraint)
    {
        _adornerVisual.Measure(constraint);
        return _adornerVisual.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        _adornerVisual.Arrange(new Rect(_offset.X, _offset.Y, finalSize.Width, finalSize.Height));
        return finalSize;
    }
}

3. 窗口层面的拖放协调逻辑

处理Adorner的创建、位置更新和清理:

public partial class MainWindow : Window
{
    private DragAdorner _currentAdorner;
    private AdornerLayer _adornerLayer;
    private Point _dragStartPoint;

    public MainWindow()
    {
        InitializeComponent();
        _adornerLayer = AdornerLayer.GetAdornerLayer(MainGrid);
    }

    protected override void OnPreviewDragOver(DragEventArgs e)
    {
        base.OnPreviewDragOver(e);
        if (_currentAdorner != null)
        {
            // 计算鼠标偏移,更新Adorner位置
            var currentPoint = e.GetPosition(MainGrid);
            var offset = new Point(currentPoint.X - _dragStartPoint.X, currentPoint.Y - _dragStartPoint.Y);
            _currentAdorner.SetOffset(offset);
        }
        e.Effects = DragDropEffects.Move;
        e.Handled = true;
    }

    protected override void OnPreviewDragEnter(DragEventArgs e)
    {
        base.OnPreviewDragEnter(e);
        if (e.Source is DraggableItem sourceItem && _currentAdorner == null)
        {
            // 创建拖动控件的副本作为Adorner视觉
            var clone = new DraggableItem { DataText = { Text = sourceItem.ItemData } };
            clone.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            clone.Arrange(new Rect(clone.DesiredSize));

            _dragStartPoint = e.GetPosition(sourceItem);
            _currentAdorner = new DragAdorner(MainGrid, clone);
            _adornerLayer.Add(_currentAdorner);
        }
    }

    protected override void OnPreviewDragLeave(DragEventArgs e)
    {
        base.OnPreviewDragLeave(e);
        // 鼠标离开网格时清理Adorner
        if (!MainGrid.IsMouseOver) CleanupAdorner();
    }

    protected override void OnPreviewDrop(DragEventArgs e)
    {
        base.OnPreviewDrop(e);
        CleanupAdorner();
        e.Handled = true;
    }

    private void CleanupAdorner()
    {
        if (_currentAdorner != null)
        {
            _adornerLayer.Remove(_currentAdorner);
            _currentAdorner = null;
        }
    }
}

窗口XAML:

<Window x:Class="WpfDragDropDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfDragDropDemo"
        Title="拖动移动+数据传输演示" Height="450" Width="800">
    <Grid x:Name="MainGrid">
        <local:DraggableItem HorizontalAlignment="Left" VerticalAlignment="Top" Margin="50,50,0,0"/>
        <local:DraggableItem HorizontalAlignment="Left" VerticalAlignment="Top" Margin="200,150,0,0"/>
        <local:DraggableItem HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,250,0,0"/>
    </Grid>
</Window>

实现效果

  • 拖动任意DraggableItem时,会显示一个跟随鼠标的控件副本,模拟移动效果
  • 将拖动的控件放置到另一个DraggableItem上时,目标控件会接收并显示拖动控件的数据

内容的提问来源于stack exchange,提问作者JosephD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 01:20:56