Отключение удаления, если перетаскивание было инициализировано из элемента управления внутри ListBoxItem - PullRequest
0 голосов
/ 24 января 2019

Мне нужна функция перетаскивания элементов ListBox, но я столкнулся с проблемой. Когда я щелкаю и перетаскиваю фон ListBoxItem, перетаскивание работает нормально, но когда я щелкаю текст или любой элемент управления в шаблоне элемента, я получаю значок «отключен», и после отпускания кнопки мыши событие выпадения не выполняется.

Вот мой XAML:

<Window x:Class="TestingGround.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestingGround"
        xmlns:viewModels="clr-namespace:TestingGround.ViewModels"
        xmlns:models="clr-namespace:TestingGround.Models"
        mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <viewModels:CustomersViewModel />
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate 
            x:Key="CustomerTemplate"
            DataType="{x:Type models:Customer}">
            <Grid Height="64">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="40" />
                    <ColumnDefinition />
                    <ColumnDefinition Width="64" />
                </Grid.ColumnDefinitions>

                <TextBlock 
                    Grid.Column="0" Text="{Binding Id}"
                    HorizontalAlignment="Center" VerticalAlignment="Center" />

                <TextBlock 
                    Grid.Column="1" Text="{Binding Name}"
                    HorizontalAlignment="Center" VerticalAlignment="Center" />

                <Rectangle
                    Grid.Column="2"
                    Width="48" Height="48" Fill="DarkSlateGray"
                    HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Grid>
        </DataTemplate>

        <Style 
            x:Key="ListBoxItemStyle"
            TargetType="ListBoxItem">
            <Setter Property="AllowDrop" Value="True" />
            <EventSetter 
                Event="PreviewMouseLeftButtonDown" 
                Handler="EventSetter_PreviewMouseLeftButtonDown" />
            <EventSetter 
                Event="Drop" 
                Handler="EventSetter_Drop" />
            <EventSetter
                Event="PreviewGiveFeedback"
                Handler="EventSetter_PreviewGiveFeedback" />
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.Style>
            <Style TargetType="Grid">
                <Setter Property="TextBlock.FontSize" Value="24" />
                <Setter Property="TextBlock.FontWeight" Value="Bold"></Setter>
            </Style>
        </Grid.Style>

        <ListBox 
            Name="ListBox"
            ItemsSource="{Binding Customers}" 
            ItemTemplate="{DynamicResource CustomerTemplate}"
            ItemContainerStyle="{DynamicResource ListBoxItemStyle}"
            HorizontalContentAlignment="Stretch">

        </ListBox>
    </Grid>
</Window>

И код позади:

using System.Collections;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using ListBoxItem = System.Windows.Controls.ListBoxItem;

namespace TestingGround
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

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

        private Window _adorner;
        private Point _offset;
        private object _dragContext;

        public MainWindow()
        {
            InitializeComponent();
        }

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

            var r = new Rectangle
            {
                Width = ((FrameworkElement) dragElement).ActualWidth,
                Height = ((FrameworkElement) dragElement).ActualHeight,
                Fill = new VisualBrush(dragElement),
                Opacity = 0.5
            };

            _adorner.Content = r;

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

            _offset = dragElement.PointFromScreen(new Point(w32Mouse.X, w32Mouse.Y));

            _adorner.Left = w32Mouse.X - _offset.X;
            _adorner.Top = w32Mouse.Y - _offset.Y;
            _adorner.Show();
        }

        private void EventSetter_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!(sender is ListBoxItem listBoxItem)) return;

            _dragContext = listBoxItem.DataContext;
            CreateAdorner(listBoxItem);
            DragDrop.AddQueryContinueDragHandler(listBoxItem, DragContinueHandler);
            DragDrop.DoDragDrop(listBoxItem, listBoxItem.DataContext, DragDropEffects.Move);
            listBoxItem.IsSelected = true;
        }

        private void EventSetter_Drop(object sender, DragEventArgs e)
        {
            if (!(sender is ListBoxItem listBoxItem)) return;

            var target = listBoxItem.DataContext;

            var removedIdx = ListBox.Items.IndexOf(_dragContext);
            var targetIdx = ListBox.Items.IndexOf(target);

            if (removedIdx < 0 || targetIdx < 0) return;

            if (removedIdx < targetIdx)
            {
                ((IList)ListBox.ItemsSource).Insert(targetIdx + 1, _dragContext);
                ((IList)ListBox.ItemsSource).RemoveAt(removedIdx);
            }
            else
            {
                if (((IList)ListBox.ItemsSource).Count <= removedIdx) return;

                ((IList)ListBox.ItemsSource).Insert(targetIdx, _dragContext);
                ((IList)ListBox.ItemsSource).RemoveAt(removedIdx + 1);
            }

            ListBox.Items.Refresh();
        }

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

            _adorner.Left = w32Mouse.X - _offset.X;
            _adorner.Top = w32Mouse.Y - _offset.Y;
        }

        private void DragContinueHandler(object sender, QueryContinueDragEventArgs e)
        {
            if (e.EscapePressed)
                _adorner.Close();

            if (e.Action == DragAction.Continue && e.KeyStates != DragDropKeyStates.LeftMouseButton)
                _adorner.Close();
        }
    }
}

DataContext довольно прост, но я все равно предоставляю его здесь:

using System.Collections.Generic;
using TestingGround.Models;

namespace TestingGround.ViewModels
{
    public class CustomersViewModel
    {
        public List<Customer> Customers { get; set; } = new List<Customer>
        {
            new Customer {Id = 1, Name = "John Doe"},
            new Customer {Id = 2, Name = "Merry Sue"},
            new Customer {Id = 3, Name = "Cruz Campo"},
            new Customer {Id = 4, Name = "Mounty Dew"},
            new Customer {Id = 5, Name = "Ben Dover"},
        };
    }
}

namespace TestingGround.Models
{
    public class Customer
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }
}

Я проверил обработчики, и тип отправителя всегда ListBoxItem, поэтому я понятия не имею, что происходит.

...