当前位置: 首页 > 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;}
}
http://www.xdnf.cn/news/867547.html

相关文章:

  • 质检 LIMS 系统数据防护指南 三级等保认证与金融级加密方案设计
  • 英国2025年战略防御评估报告:网络与电磁域成现代战争核心
  • Axios 取消请求的演进:CancelToken vs. AbortController
  • 【读代码】从预训练到后训练:解锁语言模型推理潜能——Xiaomi MiMo项目深度解析
  • 【android bluetooth 协议分析 12】【A2DP详解 2】【开启ble扫描-蓝牙音乐卡顿分析】
  • 光伏防逆流控制方案
  • .NET Core接口IServiceProvider
  • Spring Boot MVC自动配置与Web应用开发详解
  • Asp.net Core 通过依赖注入的方式获取用户
  • 全志A40i android7.1 调试信息打印串口由uart0改为uart3
  • 六种高阶微分方程的特解(原创:daode3056)
  • Java观察者模式深度解析:构建松耦合事件驱动系统的艺术
  • NC28 最小覆盖子串【牛客网】
  • 基于Axure+墨刀设计的电梯管理系统云台ERP的中保真原型图
  • Apache APISIX
  • CMake入门:3、变量操作 set 和 list
  • 深度学习项目之RT-DETR训练自己数据集
  • 通过模型文件估算模型参数量大小
  • Flask框架详解:轻量高效的Python Web开发利器
  • 深入解析Oracle SQL调优健康检查工具(SQLHC):从原理到实战优化
  • intense-rp-api开源程序是一个具有直观可视化界面的 API,可以将 DeepSeek 非正式地集成到 SillyTavern 中
  • Windows系统工具:WinToolsPlus 之 SQL Server Suspect/质疑/置疑/可疑/单用户等 修复
  • stress 服务器压力测试的工具学习
  • linux操作系统---网络协议
  • LeetCode 3370.仅含置位位的最小整数
  • 二维 根据矩阵变换计算镜像旋转角度
  • 短剧+小说网盘搜索系统(支持全网网盘转存拉新)
  • 《T/CI 404-2024 医疗大数据智能采集及管理技术规范》全面解读与实施分析
  • [ Qt ] | 与系统相关的操作(二):键盘、定时器、窗口移动和大小
  • 虚拟机CentOS 7 网络连接显示“以太网(ens33,被拔出)“、有线已拔出、CentOS7不显示网络图标