WPF Drag and Drop with Adorner using mouse and touch

一世执手 提交于 2019-12-18 13:36:50

问题


I want this to be good question, so I'll write in details what I would like to achieve, what I've found on the internet and I show what I've done so far and what I've tried.

I need to add drag and drop functionality to my application. I have Images (basically controls) that I want to drag to items of listbox.

Here is sample UI:

And here is usage I have now:

As You can see I'm able to drag one of four images and drop it over listbox item. If I move image over correct target (listbox image) image near cursor disappears and everything works fine, but when I don't drop image on list item (I release mouse) that image stays on screen.

I've based my solution on answers to this question, and I'm unable to remove that unwanted window (image near cursor)

My XAML looks like this:

<Window x:Class="DragDrop.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Drag'n'Drop" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <ListBox HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Height="300" Margin="0,10,10,0" VerticalAlignment="Top" Width="234" ItemsSource="{Binding People}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" />
                        <ProgressBar Height="20" Value="{Binding Points}" Margin="0,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <Image HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-60000-icon.png" RenderTransformOrigin="0.5,0.5"
               PreviewMouseLeftButtonDown="OnMouseTouchDown"
               PreviewTouchDown="OnMouseTouchDown"
               PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="10"/>
        <Image HorizontalAlignment="Left" Height="72" Margin="87,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700000-icon.png" RenderTransformOrigin="0.5,0.5"
               PreviewMouseLeftButtonDown="OnMouseTouchDown"
               PreviewTouchDown="OnMouseTouchDown"
               PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="20"/>
        <Image HorizontalAlignment="Left" Height="72" Margin="10,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-7000-icon.png" RenderTransformOrigin="0.5,0.5"
               PreviewMouseLeftButtonDown="OnMouseTouchDown"
               PreviewTouchDown="OnMouseTouchDown"
               PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="30"/>
        <Image HorizontalAlignment="Left" Height="72" Margin="87,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700-icon.png" RenderTransformOrigin="0.5,0.5"
               PreviewMouseLeftButtonDown="OnMouseTouchDown"
               PreviewTouchDown="OnMouseTouchDown"
               PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="40"/>
    </Grid>
</Window>

And code behind:

public partial class MainWindow
{
    private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();

    public ObservableCollection<Person> People
    {
        get { return _people; }
    }

    public MainWindow()
    {
        InitializeComponent();

        _people.Add(new Person() {Name = "Person1", Points = 10});
        _people.Add(new Person() {Name = "Person2", Points = 0});
        _people.Add(new Person() {Name = "Person3", Points = 40});
    }

    private void OnMouseTouchDown(object sender, InputEventArgs e)
    {
        var item = sender as Image;
        if (item == null) return;

        var draggedItem = item;
        var points = Convert.ToInt32(draggedItem.Tag);
        CreateDragDropWindow(draggedItem);
        System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
    }

    private Window _dragdropWindow;

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

        Rectangle r = new Rectangle
        {
            Width = ((FrameworkElement) dragElement).ActualWidth/2,
            Height = ((FrameworkElement) dragElement).ActualHeight/2,
            Fill = new VisualBrush(dragElement)
        };
        _dragdropWindow.Content = r;


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


        _dragdropWindow.Left = w32Mouse.X;
        _dragdropWindow.Top = w32Mouse.Y;
        _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;
    };

    private void UIElement_OnPreviewGiveFeedback(object sender, GiveFeedbackEventArgs e)
    {
        Win32Point w32Mouse = new Win32Point();
        GetCursorPos(ref w32Mouse);

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

    private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
    {
        //var droppedData = e.Data.GetData(typeof(Image)) as Image;
        var droppedData = (Int32) e.Data.GetData(typeof (Int32));
        var stackPanel = sender as StackPanel;
        if (stackPanel != null)
        {
            var student = stackPanel.DataContext as Person;

            //int targetIndex = _people.IndexOf(student);


            if (student != null) student.Points += droppedData;
        }
        if (_dragdropWindow != null)
        {
            _dragdropWindow.Close();
            _dragdropWindow = null;
        }
    }
}

public class Person : INotifyPropertyChanged
{
    private string _name;
    private int _points;

    public string Name
    {
        get { return _name; }
        set
        {
            if (value == _name) return;
            _name = value;
            OnPropertyChanged();
        }
    }

    public int Points
    {
        get { return _points; }
        set
        {
            if (value == _points) return;
            _points = value;
            if (_points >= 100)
            {
                _points -= 100;
                Debug.WriteLine("100!");
            }
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

I found over the internet that I can use class that extends Adorner and I found some examples:

  • http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/ - (WaybackMachine archive link)
  • http://www.zagstudio.com/blog/488#.VfiMSBHtmko
  • http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx
  • https://github.com/punker76/gong-wpf-dragdrop

but all of them show how to drag items from collections (ItemsControls). Third link was promising, but I wasn't able to adopt it to my needs.

So my questions are:

  1. How can I hide that small image window in my example when I cancel drag (MouseUp or incorrect drag target)
  2. Show I use Adorner and how can I use it in my code? I need to show it when I start drag and hide when I drop Image correctly or I cancel drag or drop target is incorrect

I'm starting with WPF so please try to understand my frustration - I've spend last two evenings and night trying to get this working.


回答1:


1) Modify your OnMouseTouchDown handler to include assigning ContinueDragHandler to dragged item before starting the drag, like this

 private void OnMouseTouchDown(object sender, InputEventArgs e)
        {
            var item = sender as FrameworkElement;
            if (item == null) return;

            var draggedItem = item;
            var points = Convert.ToInt32(draggedItem.Tag);
            CreateDragDropWindow(draggedItem);
            System.Windows.DragDrop.AddQueryContinueDragHandler(draggedItem, DragContrinueHandler);
            System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
        }

And the handler itself:

public void DragContrinueHandler(object sender, QueryContinueDragEventArgs e)
        {
            if (e.Action == DragAction.Continue && e.KeyStates != DragDropKeyStates.LeftMouseButton)
            {
                _dragdropWindow.Close();
            }
        }

2) I believe that creating a new window to display image next to a cursor is a dirty dirty hack. There are plenty of various articles around about using adorners with drag'n'drop. Althought your approach works and doesn't require a lot of code. Adorners do, on the other hand. I think you should create another question, if you fail following certain tutorial, with code examples and what steps you took



来源:https://stackoverflow.com/questions/32597184/wpf-drag-and-drop-with-adorner-using-mouse-and-touch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!