1.控件描述
WPF实现一个ListView控件Item子项可删除也可拖拽排序,效果如下图所示
2.实现代码
配合 WrapPanel 实现水平自动换行,并开启拖拽
<ListView
x:Name="listView"
Grid.Row="1"
Width="300"
AllowDrop="True"
Background="#DCE1E7"
DragEnter="ListView_OnDragEnter"
DragLeave="ListView_OnDragLeave"
DragOver="ListView_OnDragOver"
Drop="ListView_OnDrop"
FocusVisualStyle="{x:Null}"
ItemContainerStyle="{StaticResource NoSelectionListViewItemStyle}"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding FilterItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewKeyDown="ListView_OnPreviewKeyDown"
PreviewMouseLeftButtonDown="ListView_OnPreviewMouseLeftButtonDown"
PreviewMouseMove="ListView_OnPreviewMouseMove"
SelectionChanged="ListView_OnSelectionChanged">
<ListView.Resources>
<Style TargetType="ScrollViewer">
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
</Style>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
模版及样式,引用图片自行替换
<Style x:Key="deleteImgStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.8" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="NoSelectionListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid x:Name="Grid" Background="{TemplateBinding Background}">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Grid" Property="Background" Value="Transparent" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ItemTemplate" DataType="local:FilterItem">
<Border
x:Name="border"
Height="30"
Margin="2,2,3,3"
HorizontalAlignment="Stretch"
Background="#f7f7f8"
CornerRadius="5"
Cursor="Hand">
<Grid
x:Name="innerGrid"
MinWidth="20"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
x:Name="label"
Margin="2,0,0,0"
Content="{Binding DisplayText}"
FontSize="13"
Foreground="#000" />
<Image
Grid.Column="1"
Margin="5,0"
MouseLeftButtonDown="Image_MouseLeftButtonDown"
Source="pack://application:,,,/WPFTest;component/Resources/delete.png"
Stretch="Uniform"
Style="{StaticResource deleteImgStyle}"
Tag="{Binding}" />
</Grid>
</Border>
<DataTemplate.Triggers>
<Trigger SourceName="border" Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#80f7f7f8" />
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="border" Property="Background" Value="#BCC2C9" />
</DataTrigger>
<DataTrigger Binding="{Binding IsDraggedOver}" Value="True">
<Setter TargetName="border" Property="Background" Value="DarkOrange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
后台代码
private ObservableCollection<FilterItem> _filterItems;
/// <summary>
/// 绑定数据源
/// </summary>
public ObservableCollection<FilterItem> FilterItems
{
get => _filterItems;
set { _filterItems = value; OnPropertyChanged(nameof(FilterItems)); }
}
// 删除Item
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
if (sender is Image image)
{
if (image.Tag is FilterItem item)
{
FilterItems.Remove(item);
}
}
}
// 键盘方向键实现Item排序
private void ListView_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (FilterItems?.Count > 1 && listView.SelectedIndex >= 0)
{
var selIndex = listView.SelectedIndex;
var item = FilterItems[selIndex];
if (e.Key == Key.Left && selIndex > 0)
{
FilterItems.RemoveAt(selIndex);
FilterItems.Insert(selIndex - 1, item);
}
else if (e.Key == Key.Right && selIndex < FilterItems.Count - 1)
{
FilterItems.RemoveAt(selIndex);
FilterItems.Insert(selIndex + 1, item);
}
}
e.Handled = true;
}
#region 拖拽排序
private ListViewItem _draggedItem; // 用于存储被拖动的项
private ListViewItem _dropTargetItem; // 用于存储当前的拖拽目标项
// 标记是否处于拖拽状态
private bool _isDragging;
// 鼠标按下时的位置
private Point _mouseDownPosition;
// 最小拖拽距离
private const double DragThreshold = 10.0;
/// <summary>
/// 处理拖动开始的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var item = FindVisualParent<ListViewItem>(e.OriginalSource as DependencyObject);
if (item != null)
{
_draggedItem = item;
_mouseDownPosition = e.GetPosition(null);
}
}
private void ListView_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
if (_draggedItem != null && e.LeftButton == MouseButtonState.Pressed)
{
var currentPosition = e.GetPosition(null);
if (Math.Abs(currentPosition.X - _mouseDownPosition.X) > DragThreshold ||
Math.Abs(currentPosition.Y - _mouseDownPosition.Y) > DragThreshold)
{
_isDragging = true;
// 开始拖动操作
DragDrop.DoDragDrop(_draggedItem, _draggedItem.Content, DragDropEffects.Move);
_isDragging = false;
_draggedItem = null; // 清除拖拽项
}
}
}
/// <summary>
/// 处理拖动过程中的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDragOver(object sender, DragEventArgs e)
{
var listView = sender as ListView;
var point = e.GetPosition(listView);
var hitTestResult = VisualTreeHelper.HitTest(listView, point);
if (hitTestResult != null)
{
var targetItem = FindVisualParent<ListViewItem>(hitTestResult.VisualHit);
if (targetItem != null && targetItem != _draggedItem)
{
// 只有在拖拽目标项发生变化时才进行更新
if (_dropTargetItem != targetItem)
{
// 恢复之前目标项的背景色
if (_dropTargetItem != null)
{
if (_dropTargetItem.DataContext is FilterItem previousViewModel)
{
previousViewModel.IsDraggedOver = false;
}
}
// 高亮显示当前拖拽目标项
if (targetItem.DataContext is FilterItem targetViewModel)
{
targetViewModel.IsDraggedOver = true;
}
_dropTargetItem = targetItem;
}
e.Effects = DragDropEffects.Move; // 允许移动操作
e.Handled = true;
}
}
}
private void ListView_OnDragEnter(object sender, DragEventArgs e)
{
// 设置拖拽目标项的状态为被拖拽
var listView = sender as ListView;
var point = e.GetPosition(listView);
var hitTestResult = VisualTreeHelper.HitTest(listView, point);
if (hitTestResult != null)
{
var targetItem = FindVisualParent<ListViewItem>(hitTestResult.VisualHit);
if (targetItem != null && targetItem != _draggedItem)
{
// 更新之前拖拽目标项的状态
if (_dropTargetItem != null && _dropTargetItem != targetItem)
{
if (_dropTargetItem.DataContext is FilterItem previousViewModel)
{
previousViewModel.IsDraggedOver = false;
}
}
// 更新当前拖拽目标项的状态
if (targetItem.DataContext is FilterItem currentViewModel)
{
currentViewModel.IsDraggedOver = true;
}
_dropTargetItem = targetItem;
}
}
e.Effects = DragDropEffects.Move; // 允许移动操作
e.Handled = true; // 标记事件已处理
}
/// <summary>
/// 处理拖动离开目标区域的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDragLeave(object sender, DragEventArgs e)
{
// 恢复目标项的背景色
if (_dropTargetItem != null)
{
if (_dropTargetItem.DataContext is FilterItem viewModel)
{
viewModel.IsDraggedOver = false;
}
_dropTargetItem = null; // 清除拖拽目标项
}
}
/// <summary>
/// 处理放置操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDrop(object sender, DragEventArgs e)
{
// 检查拖动项和目标项是否有效
if (_draggedItem != null && _dropTargetItem != null && _draggedItem != _dropTargetItem)
{
var items = listView.Items.OfType<object>().ToList();
var draggedIndex = items.IndexOf(_draggedItem.Content);
var dropIndex = items.IndexOf(_dropTargetItem.Content);
if (draggedIndex != -1 && dropIndex != -1)
{
// 移动项的位置
var item = FilterItems[draggedIndex];
FilterItems.RemoveAt(draggedIndex);
FilterItems.Insert(dropIndex, item);
// 恢复目标项的背景色
if (_dropTargetItem.DataContext is FilterItem dropViewModel)
{
dropViewModel.IsDraggedOver = false;
}
}
// 清除拖拽项和目标项的引用
_draggedItem = null;
_dropTargetItem = null;
}
else
{
// 处理无效的放置操作
if (_dropTargetItem != null)
{
if (_dropTargetItem.DataContext is FilterItem dropViewModel)
{
dropViewModel.IsDraggedOver = false;
}
}
_draggedItem = null;
_dropTargetItem = null;
}
}
// 查找可视树中的父级项
private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
{
while (child != null && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
#endregion
绑定项实体类
public class FilterItem : INotifyPropertyChanged
{
private string _displayText;
public string DisplayText
{
get => _displayText;
set { _displayText = value; OnPropertyChanged(nameof(DisplayText)); }
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
}
private bool _isDraggedOver;
public bool IsDraggedOver
{
get => _isDraggedOver;
set
{
if (_isDraggedOver != value)
{
_isDraggedOver = value;
OnPropertyChanged(nameof(IsDraggedOver));
}
}
}
public FilterItem()
{ }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}