Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF 4 Drag and Drop with visual element as cursor

I have a WPF 4 app which I want to enable drag and drop with, currently I have it working with a basic drag and drop implementation, but I have found that it would be much better if, instead of the mouse cursor changing over to represent the move operation, I could use an image underneath my finger.

My drag and drop operation is initiated inside a custom user control, so I will need to insert a visual element into the visual tree and have it follow my finger around, perhaps I should enable the ManipulationDelta event on my main window, check for a boolean then move the item around?

like image 273
Mark Avatar asked Jun 28 '10 00:06

Mark


1 Answers

From the mentioned article I was able to simplify a little. Basically what you need to do is subscribe in 3 events:

  • PreviewMouseLeftButtonDownEvent: The event that runs when you press the left button, you can start the drag action by invoking DragDrop.DoDragDrop
  • DropEvent: The event that runs when you drop something (control must have AllowDrop set to true in order to accept drops)
  • GiveFeedbackEvent: The event that runs all the time allowing you to give constant feedback

DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move); the first parameter is the element you are dragging, then the second is the data it is carrying and last the mouse effect.

This method locks the thread. So everything after its call will only execute when you stop dragging.

In the drop event you can retrieve the data you sent on the DoDragDrop call.

The source for my tests are located bellow, and the result is:

Sample drag n' drop (gif)

Full Source

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

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

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;
    }
}
like image 121
BrunoLM Avatar answered Sep 28 '22 18:09

BrunoLM