WPF Drag Adorner 修复运动并在 Adorner 中显示反馈

WPF Drag Adorner fix movement and show feedback in Adorner

我正在尝试为我正在创建的项目构建类似于 Inventory 的界面。 想法是拥有可以拖动到播放器的图像列表,如下所示:

图片从目录加载并显示在ListView中,玩家列表显示在ListBox中。

我的 XAML 看起来像这样:

<Window x:Class="DynamicImagesDrag.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dynamicImagesDrag="clr-namespace:DynamicImagesDrag"
        Title="MainWindow"
        Height="405"
        Width="719.162"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <dynamicImagesDrag:StringToImageConverter x:Key="StringToImageConverter" />
    </Window.Resources>
    <Grid>
        <ListView Name="MyList"
                  ItemsSource="{Binding Images}"
                  PreviewMouseLeftButtonDown="UIElement_OnPreviewMouseLeftButtonDown"
                  PreviewMouseMove="UIElement_OnPreviewMouseMove"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  VerticalAlignment="Stretch"
                  HorizontalAlignment="Left"
                  Margin="10" Width="250">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>

            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ListViewItem">
                                <ContentPresenter/>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListView.ItemContainerStyle>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <DockPanel Width="50" Height="50">
                        <DockPanel.Background>
                            <ImageBrush ImageSource="BG1.png"/>
                        </DockPanel.Background>
                        <Image Source="{Binding Path, Converter={StaticResource StringToImageConverter} }"
                               Height="32"
                               Width="32" />
                    </DockPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <ListBox ItemsSource="{Binding People}" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"
                 Width="200">
            <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" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

后面的代码:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace DynamicImagesDrag
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        private readonly ObservableCollection<MyImage> _images = new ObservableCollection<MyImage>();

        public ObservableCollection<MyImage> Images
        {
            get { return _images; }
        }

        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 });

            string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            if (appPath != null)
            {
                string imagePath = Path.Combine(appPath, "Images");
                if (Directory.Exists(imagePath))
                {
                    var images = Directory
                        .EnumerateFiles(imagePath)
                        .Where(file => file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("png"))
                        .ToList();

                    foreach (string image in images)
                    {
                        _images.Add(new MyImage
                        {
                            Name = Path.GetFileName(image),
                            Path = image,
                            Points = Convert.ToInt32(Path.GetFileNameWithoutExtension(image))
                        });
                    }
                }
            }
        }

        [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;
        };

        public static Point GetMousePosition()
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }


        private void UIElement_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _startPoint = e.GetPosition(null);
        }

        #region Field and Properties

        private bool _dragHasLeftScope;
        private Point _startPoint;
        public bool IsDragging { get; set; }

        DragAdorner _adorner;
        AdornerLayer _layer;
        public FrameworkElement DragScope { get; set; }

        #endregion // Field and Properties



        private void UIElement_OnPreviewMouseMove(object sender, MouseEventArgs e)
        {
            // Ensure that the user does not drag by accident
            if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
            {
                Point position = e.GetPosition(null);

                if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    StartDragInProcAdorner(e);
                }
            }
        }

        void DragScope_DragLeave(object sender, DragEventArgs e)
        {
            if (e.OriginalSource == DragScope)
            {
                Point p = e.GetPosition(DragScope);
                Rect r = VisualTreeHelper.GetContentBounds(DragScope);
                if (!r.Contains(p))
                {
                    _dragHasLeftScope = true;
                    e.Handled = true;
                }
            }
        }

        void Window1_DragOver(object sender, DragEventArgs args)
        {
            if (_adorner == null) return;
            _adorner.LeftOffset = args.GetPosition(DragScope).X /* - _startPoint.X */ ;
            _adorner.TopOffset = args.GetPosition(DragScope).Y /* - _startPoint.Y */ ;
        }

        void DragScope_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            if (_dragHasLeftScope)
            {
                e.Action = DragAction.Cancel;
                e.Handled = true;
            }
        }

        private void StartDragInProcAdorner(MouseEventArgs e)
        {
            DragScope = Application.Current.MainWindow.Content as FrameworkElement;

            bool previousDrop = DragScope.AllowDrop;
            DragScope.AllowDrop = true;
            try
            {
                DragEventHandler draghandler = Window1_DragOver;
                DragScope.PreviewDragOver += draghandler;
                DragEventHandler dragleavehandler = DragScope_DragLeave;
                DragScope.DragLeave += dragleavehandler;
                QueryContinueDragEventHandler queryhandler = DragScope_QueryContinueDrag;
                DragScope.QueryContinueDrag += queryhandler;

                DragScope.GiveFeedback+=DragScope_GiveFeedback;

                FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;

                if (dr == null)
                    return;
                _adorner = new DragAdorner(DragScope, dr, true, 0.5);
                _layer = AdornerLayer.GetAdornerLayer(DragScope);
                _layer.Add(_adorner);


                IsDragging = true;
                _dragHasLeftScope = false;

                DataObject data = new DataObject(MyList.SelectedItem as MyImage);
                DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move);

                DragScope.AllowDrop = previousDrop;
                AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
                _adorner = null;

                DragScope.DragLeave -= dragleavehandler;
                DragScope.QueryContinueDrag -= queryhandler;
                DragScope.PreviewDragOver -= draghandler;

                IsDragging = false;
            }
            catch
            {
                DragScope.AllowDrop = previousDrop;
                AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
                _adorner = null;
                IsDragging = false;

            }
        }

        private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {


        }

        private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
        {
            var stackPanel = sender as StackPanel;
            if (stackPanel == null) return;
            var student = stackPanel.DataContext as Person;

            MyImage myImage = e.Data.GetData(typeof(MyImage)) as MyImage;
            if (student != null) student.Points += myImage.Points;
        }
    }

    public class StringToImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                return new BitmapImage(new Uri((string)value));
            }
            catch
            {
                return new BitmapImage();
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    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));
        }
    }

    public class MyImage
    {
        public string Path { get; set; }
        public string Name { get; set; }

        public int Points { get; set; }
    }
}

和 DragAdorner(取自 http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx

class DragAdorner : Adorner
{
    public DragAdorner(UIElement owner) : base(owner) { }

    public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
        : base(owner)
    {
        _owner = owner;
        VisualBrush _brush = new VisualBrush
        {
            Opacity = opacity,
            Visual = adornElement
        };

        DropShadowEffect dropShadowEffect = new DropShadowEffect
        {
            Color = Colors.Black,
            BlurRadius = 15,
            Opacity = opacity
        };

        Rectangle r = new Rectangle
        {
            RadiusX = 3,
            RadiusY = 3,
            Fill = _brush,
            Effect = dropShadowEffect,
            Width = adornElement.DesiredSize.Width,
            Height = adornElement.DesiredSize.Height
        };


        XCenter = adornElement.DesiredSize.Width / 2;
        YCenter = adornElement.DesiredSize.Height / 2;

        _child = r;
    }

    private void UpdatePosition()
    {
        AdornerLayer adorner = (AdornerLayer)Parent;
        if (adorner != null)
        {
            adorner.Update(AdornedElement);
        }
    }

    #region Overrides

    protected override Visual GetVisualChild(int index)
    {
        return _child;
    }
    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }
    protected override Size MeasureOverride(Size finalSize)
    {
        _child.Measure(finalSize);
        return _child.DesiredSize;
    }
    protected override Size ArrangeOverride(Size finalSize)
    {

        _child.Arrange(new Rect(_child.DesiredSize));
        return finalSize;
    }
    public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
    {
        GeneralTransformGroup result = new GeneralTransformGroup();

        result.Children.Add(base.GetDesiredTransform(transform));
        result.Children.Add(new TranslateTransform(_leftOffset, _topOffset));
        return result;
    }

    #endregion

    #region Field & Properties

    public double scale = 1.0;
    protected UIElement _child;
    protected VisualBrush _brush;
    protected UIElement _owner;
    protected double XCenter;
    protected double YCenter;
    private double _leftOffset;
    public double LeftOffset
    {
        get { return _leftOffset; }
        set
        {
            _leftOffset = value - XCenter;
            UpdatePosition();
        }
    }
    private double _topOffset;
    public double TopOffset
    {
        get { return _topOffset; }
        set
        {
            _topOffset = value - YCenter;

            UpdatePosition();
        }
    }

    #endregion
}

拖动几乎可以正常工作:

除装饰器仅在源列表和目标列表中可见外,在整个拖动过程中不显示。

我的问题是:

我在互联网(包括 SO)上搜索了解决方案,但找不到适合我需要的任何东西。

我发现了一些很棒的文章:
http://www.codeproject.com/Articles/37161/WPF-Drag-and-Drop-Smorgasbord
http://www.zagstudio.com/blog/488#.VgHPyxHtmkp
http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/
https://blogs.claritycon.com/blog/2009/03/generic-wpf-drag-and-drop-adorner/

最后一个非常有趣,但我无法修改为向玩家添加点数而不是移动物品。在我的例子中,我希望左侧的项目保留,我只想根据拖动的项目更新右侧的列表。

我建议使用 Gong WPF dragdrop

这个库对我来说一直很好用,而且比内置支持要好得多。它还增加了对 MVVM 设计的强大支持。

祝你好运!

我还没有找到所有答案,但它可能会引导您实现目标。

  1. How can I fix drag and drop to see adorner whole time?

    你的DragLayer是透明的,只需为你的Grid

  2. Background属性设置一个值
  3. How can I display image instead selectedItem inside adorner? Right now inside adorner is that brown background, I'd like to get only transparent image.

我最好的办法是探索 VisualTree 以获取模板内的 Image :

//Get the `ListViewItem`
FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;
//Explore the VisualTree to get the grand-child
//This should be refactored to a Func<UIElement,UIElement> to accord to templates changes
UIElement el = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(dr, 0), 0) as UIElement;
//Create the DragAdorner using the found UIElement
_adorner = new DragAdorner(DragScope, el, true, 1d);
  1. How can I show if dragtarget is correct inside adorner instead of changing cursor? I'd like to change opacity of adorner if target is correct.

要显示一些反馈,您需要... GiveFeedBack

这是您的处理程序:

private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
    if (_adorner == null) return;
    if (e.Effects == DragDropEffects.Copy)
    {
        _adorner.Opacity = 1d;
        e.Handled = true;
    }
    else
    {
        _adorner.Opacity = 0.5d;
        e.Handled = true;
    }
}

现在,您必须在 2 个地方设置所需的效果:Person 模板和 DragLayer

private void StackPanel_DragOver(object sender, DragEventArgs e)
{
    e.Effects = DragDropEffects.Copy;
    e.Handled = true;
}
void Window1_DragOver(object sender, DragEventArgs args)
{
    if (_adorner == null) return;
    _adorner.LeftOffset = args.GetPosition(DragScope).X;
    _adorner.TopOffset = args.GetPosition(DragScope).Y;
    if (!args.Handled)
        args.Effects = DragDropEffects.Move;
}

为了使这些起作用,您必须在启动 DragDrop 时允许这 2 个效果:

DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move | DragDropEffects.Copy);

此外,为避免累积不透明度降低,请在构造函数中将 DragAdorner 不透明度设为 1。

  1. I'd like to get drag and drop working with touch events, @KOTIX suggested using Gong WPF dragdrop, will it work fine on touch enabled screens?

这个解决方案是完全原生的,你应该让它与触摸设备一起工作。

  1. Currently I'm setting AllowDrop on StackPanel inside ListBox ItemTemplate, should it stay there or maybe I should set in on ListBox?

如果您的目标是将 MyImage.Points 添加到放置目标,则必须在 StackPanel 上设置 AllowDrop