WPF 4中使用可视元素作为光标的拖放功能

10

我有一个WPF 4应用程序,希望启用拖放功能。目前,我已经通过基本的拖放实现使其工作正常,但是我发现如果可以使用手指下面的图像代替鼠标光标来表示移动操作,那将会更好。

我的拖放操作是在自定义用户控件内启动的,因此我需要在可视树中插入一个视觉元素,并使其跟随我的手指移动,也许我应该在主窗口上启用ManipulationDelta事件,检查布尔值然后移动物品?

2个回答

33

在提及的文章中,我能够简化一些内容。基本上,您需要订阅三个事件:

  • PreviewMouseLeftButtonDownEvent:按下左键时运行的事件,您可以通过调用 DragDrop.DoDragDrop 开始拖动操作
  • DropEvent:当您放置某物时运行的事件(控件必须将 AllowDrop 设置为 true ,才能接受拖放)
  • GiveFeedbackEvent:始终运行的事件,允许您提供持续反馈

DragDrop.DoDragDrop(draggedItem,draggedItem.DataContext,DragDropEffects.Move); 第一个参数是您正在拖动的元素,然后是它所携带的数据,最后是鼠标效果。

此方法会锁定线程。因此,在其调用之后的所有内容都将在停止拖动时执行。

在放置事件中,您可以检索发送到 DoDragDrop 调用的数据。

我的测试源代码位于下面,并且结果为:

示例拖放(gif)

完整源代码

MainWindow.xaml

<Window x:Class="TestWpfPure.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:uc="clr-namespace:TestWpfPure"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox x:Name="CardListControl" AllowDrop="True" ItemsSource="{Binding Items}" />
    </Grid>
</Window>

Card.xaml

<UserControl x:Class="TestWpfPure.Card"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Border x:Name="CardBorder" BorderBrush="Black" BorderThickness="3" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="246" RenderTransformOrigin="0.5,0.5" CornerRadius="6">
            <TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontFamily="Arial" FontSize="14" />
        </Border>
    </Grid>
</UserControl>

MainWindow.xaml.cs

->

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;

namespace TestWpfPure
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<Card> Items { get; set; }

        private readonly Style listStyle = null;
        private Window _dragdropWindow = null;

        public MainWindow()
        {
            InitializeComponent();

            Items = new ObservableCollection<Card>(new List<Card>
            {
                new Card { Text = "Task #01" },
                new Card { Text = "Task #02" },
                new Card { Text = "Task #03" },
                new Card { Text = "Task #04" },
                new Card { Text = "Task #05" },
            });

            listStyle = new Style(typeof(ListBoxItem));
            listStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
            listStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(CardList_PreviewMouseLeftButtonDown)));
            listStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(CardList_Drop)));
            listStyle.Setters.Add(new EventSetter(ListBoxItem.GiveFeedbackEvent, new GiveFeedbackEventHandler(CardList_GiveFeedback)));

            CardListControl.ItemContainerStyle = listStyle;

            DataContext = this;
        }

        protected void CardList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is ListBoxItem)
            {
                var draggedItem = sender as ListBoxItem;
                var card = draggedItem.DataContext as Card;

                card.Effect = new DropShadowEffect
                {
                    Color = new Color { A = 50, R = 0, G = 0, B = 0 },
                    Direction = 320,
                    ShadowDepth = 0,
                    Opacity = .75,
                };
                card.RenderTransform = new RotateTransform(2.0, 300, 200);

                draggedItem.IsSelected = true;

                // create the visual feedback drag and drop item
                CreateDragDropWindow(card);
                DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
            }
        }

        protected void CardList_Drop(object sender, DragEventArgs e)
        {
            var droppedData = e.Data.GetData(typeof(Card)) as Card;
            var target = (sender as ListBoxItem).DataContext as Card;

            int targetIndex = CardListControl.Items.IndexOf(target);

            droppedData.Effect = null;
            droppedData.RenderTransform = null;

            Items.Remove(droppedData);
            Items.Insert(targetIndex, droppedData);

            // remove the visual feedback drag and drop item
            if (this._dragdropWindow != null)
            {
                this._dragdropWindow.Close();
                this._dragdropWindow = null;
            }
        }

        private void CardList_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
            // update the position of the visual feedback item
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);

            this._dragdropWindow.Left = w32Mouse.X;
            this._dragdropWindow.Top = w32Mouse.Y;
        }

        private void CreateDragDropWindow(Visual dragElement)
        {
            this._dragdropWindow = new Window();
            _dragdropWindow.WindowStyle = WindowStyle.None;
            _dragdropWindow.AllowsTransparency = true;
            _dragdropWindow.AllowDrop = false;
            _dragdropWindow.Background = null;
            _dragdropWindow.IsHitTestVisible = false;
            _dragdropWindow.SizeToContent = SizeToContent.WidthAndHeight;
            _dragdropWindow.Topmost = true;
            _dragdropWindow.ShowInTaskbar = false;

            Rectangle r = new Rectangle();
            r.Width = ((FrameworkElement)dragElement).ActualWidth;
            r.Height = ((FrameworkElement)dragElement).ActualHeight;
            r.Fill = new VisualBrush(dragElement);
            this._dragdropWindow.Content = r;


            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);


            this._dragdropWindow.Left = w32Mouse.X;
            this._dragdropWindow.Top = w32Mouse.Y;
            this._dragdropWindow.Show();
        }


        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

        [StructLayout(LayoutKind.Sequential)]
        internal struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };
    }
}

Card.xaml.cs

:该文件是一个名为“Card”的自定义控件在C#语言下的实现。

using System.ComponentModel;
using System.Windows.Controls;

namespace TestWpfPure
{
    /// <summary>
    /// Interaction logic for Card.xaml
    /// </summary>
    public partial class Card : UserControl, INotifyPropertyChanged
    {
        private string text;
        public string Text
        {
            get
            {
                return this.text;
            }
            set
            {
                this.text = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Text"));
            }
        }

        public Card()
        {
            InitializeComponent();

            DataContext = this;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

我知道这个问题很老了,但也许您可以帮助我。我使用了您的解决方案将图像拖到列表框中,在我释放鼠标到有效元素时非常好用。如果我在拖动时释放鼠标而不是在有效目标上释放,则指针旁边会出现一个小窗口停留在屏幕上。能否看一下我的问题?https://dev59.com/E4_ea4cB1Zd3GeqPKSas - Misiu
1
这是一个绝对史诗般的答案。我添加的唯一一件事就是在 DoDragDrop 之后调用窗口的 Close 方法,这样无论拖放在哪里结束,窗口都会关闭。 - BradleyDotNET
有没有一种方法可以将卡片类作为普通类实现,并在ListBox DataTemplate中定义控件模板? - user1618054
3
您可能需要将屏幕坐标转换为应用程序坐标。 Application.Current.MainWindow.PointFromScreen(new Point(w32Mouse.X, w32Mouse.Y));如果您希望窗口随应用程序最小化: _dragdropWindow.Topmost = false; _dragdropWindow.Owner = Application.Current.MainWindow; - Jacob
我知道这篇文章很古老,但它并没有解决一个主要的问题 - 在控件外部释放将会“冻结”该元素。似乎没有任何处理方式,除了一些低级别的鼠标事件钩子的hacky方法。 - Chebz
显示剩余2条评论

11

我确实阅读了那篇文章,我看到的问题是鼠标光标只能设置为 System.Windows.Input.Cursor,而我不知道如何使用 UIElement...(我想要有动画和更多)。谢谢你的帮助。不过我正在考虑使用这个库的自定义版本:http://dotnetslackers.com/ado_net/re-191632_generic_wpf_drag_and_drop_adorner.aspx - Mark
2
@Mark:我认为文章在“下一个场景:变得花哨并使用我们拖动的视觉元素来进行反馈[而不是光标]”部分解决了这个问题,通过创建一个单独的窗口来跟随鼠标,就像一个光标一样。虽然我还没有尝试过。 - Quartermeister
很酷,我会看一下的。我得停止浏览那么多文章。 - Mark
我不喜欢新窗口解决方案的原因是它会影响操作系统。当你拖动窗口时,任务栏上的图标不再被选中,因为它不是活动窗口。 - ScottFoster1000
博客文章已经移动,链接已经失效。我认为现在的链接地址是:https://learn.microsoft.com/de-de/archive/blogs/jaimer/drag-amp-drop-in-wpf-explained-end-to-end - BerndGit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接