Мне нужна функция перетаскивания элементов 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, поэтому я понятия не имею, что происходит.