当前位置: 首页 > news >正文

WPF可拖拽ListView

1.控件描述

WPF实现一个ListView控件Item子项可删除也可拖拽排序,效果如下图所示
可拖拽ListView

2.实现代码

配合 WrapPanel 实现水平自动换行,并开启拖拽

<ListViewx: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"><Borderx:Name="border"Height="30"Margin="2,2,3,3"HorizontalAlignment="Stretch"Background="#f7f7f8"CornerRadius="5"Cursor="Hand"><Gridx:Name="innerGrid"MinWidth="20"VerticalAlignment="Center"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><Labelx:Name="label"Margin="2,0,0,0"Content="{Binding DisplayText}"FontSize="13"Foreground="#000" /><ImageGrid.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;}
}

相关文章:

  • Android SharedFlow 详解
  • video-audio-extractor【源码版】
  • 从OSI到TCP/IP:网络协议的演变与作用
  • 设计模式-迪米特法则
  • 3D视觉重构工业智造:解码迁移科技如何用“硬核之眼“重塑生产节拍
  • Doris查询Hive数据:实现高效跨数据源分析的实践指南
  • hive 3集成Iceberg 1.7中的Java版本问题
  • Duix.HeyGem:以“离线+开源”重构数字人创作生态
  • 大数据学习(128)-数据分析实例
  • 【网络安全】漏洞分析:阿帕奇漏洞学习
  • 大数据学习(129)-Hive数据分析
  • 【Web应用】若依框架:基础篇14 源码阅读-后端代码分析-课程管理模块前后端代码分析
  • 设计模式杂谈-模板设计模式
  • 容器化实施:Docker容器构建与优化深度剖析
  • (2025)Windows修改JupyterNotebook的字体,使用JetBrains Mono
  • Python爬虫(48)基于Scrapy-Redis与深度强化学习的智能分布式爬虫架构设计与实践
  • 内网穿透:打破网络限制的利器!深入探索和简单实现方案
  • 解决pycharm同一个文件夹下from *** import***仍显示No module named
  • 在VSCode中开发一个uni-app项目
  • python打卡day44@浙大疏锦行
  • wordpress相关网站/哪家网络推广好
  • 杭州做企业网站/百度贴吧热线客服24小时
  • 济源网站制作/大连seo外包平台
  • 上海沪港建设咨询有限公司网站/惠州优化怎么做seo
  • 网站建设怎么收费/2023年11月新冠高峰
  • 国土资源部网站2014年02月28日全国党风廉政建设视频会/seo交流