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.pswp.cn/diannao/85702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

相机--双目立体相机

教程 链接1 教程汇总 立体匹配算法基础概念 视频讲解摄像机标定和双目立体原理 两个镜头。 双目相机也叫立体相机--Stereo Camera&#xff0c;属于深度相机。 作用 1&#xff0c;获取图像特征&#xff1b; 2&#xff0c;获取图像深度信息&#xff1b; 原理 原理和标定 …

Unity3D仿星露谷物语开发59之定制角色衬衫

1、目标 自定义角色衬衫、裤子、手臂颜色。 2、概念 在Assets -> Sprites -> Output Textures下&#xff0c;Customised_farmer为目前角色所用的精灵表。 如果上面是输出纹理&#xff0c;那么输入纹理是什么呢&#xff1f;它位于Assets/Sprites/Sprite Textures/Chara…

【HarmonyOS 5】游戏开发教程

一、开发环境搭建 ‌工具配置‌ 安装DevEco Studio 5.1&#xff0c;启用CodeGenie AI助手&#xff08;Settings → Tools → AI Assistant&#xff09;配置游戏模板&#xff1a;选择"Game"类型项目&#xff0c;勾选手机/平板/折叠屏多设备支持 二、游戏引擎核心架构…

深度探索:如何用DeepSeek重构你的工作流

前言:AI时代的工作革命 在人工智能浪潮席卷的今天,DeepSeek作为国产大模型的代表之一,正以其强大的自然语言处理能力、代码生成能力和多模态交互特性,重新定义着人类的工作方式。根据IDC报告显示,2024年企业级AI应用市场规模已突破800亿美元,其中智能办公场景占比达32%,…

Linux 进程调度与管理:从内核管理到调度机制的深度解析

文章目录 引言一、进程基础&#xff1a;概念与核心数据结构1.1 进程的本质&#xff1a;程序的动态化身1.2 进程控制块&#xff08;PCB&#xff09;&#xff1a;内核管理的灵魂1.2.1 链表节点嵌入1.2.2 链表操作宏1.2.3 全局链表管理 1.3 进程查看与系统调用1.3.1 通过系统调用获…

信息学奥赛一本通 1570:【例 2】能量项链 | 1843:【06NOIP提高组】能量项链 | 洛谷 P1063 [NOIP 2006 提高组] 能量项链

【题目链接】 ybt 1570&#xff1a;【例 2】能量项链 ybt 1843&#xff1a;【06NOIP提高组】能量项链 洛谷 P1063 [NOIP 2006 提高组] 能量项链 【题目考点】 1. 动态规划&#xff1a;区间动规 2. 环形序列 解决方法&#xff1a;破环为链 模板题&#xff1a;洛谷 P1880 [N…

旅游微信小程序制作指南

想创建旅游微信小程序吗&#xff1f;知道旅游业企业怎么打造自己的小程序吗&#xff1f;这里有零基础小白也能学会的教程&#xff0c;教你快速制作旅游类微信小程序&#xff01; 旅游行业能不能开发微信小程序呢&#xff1f;答案是肯定的。微信小程序对旅游企业来说可是个宝&am…

Vue3+Vite中lodash-es安装与使用指南

在 Vue 3 Vite 项目中安装和使用 lodash-es 的详细指南如下&#xff1a; 一、为什么选择 lodash-es&#xff1f; ES 模块支持&#xff1a;lodash-es 以原生 ES 模块格式发布&#xff0c;支持现代构建工具的 Tree Shaking 按需加载&#xff1a;只引入需要的函数&#xff0c;显…

法律模型选型

当然可以&#xff0c;以下是关于法律法规相关模型的技术选型调研建议&#xff0c;适合算法实习生从0入手&#xff0c;并能交付有深度的调研报告&#xff1a; 一、调研背景与目标 目标&#xff1a;调研用于处理法律法规类任务的大模型与技术方案&#xff0c;明确适合本团队的模…

软件工程专业的本科生应该具备哪些技能

软件工程专业的本科生需要具备扎实的技术基础、良好的开发流程认知和一定的软技能&#xff0c;以适应软件开发行业的需求。以下从技术技能、开发流程与工具、软技能、实践能力等维度整理核心技能清单&#xff0c;供参考&#xff1a; 一、核心技术技能 1. 编程语言 - 必学基础语…

[Java 基础]类,面向对象的蓝图

首先需要区分类和对象都是啥&#xff1f; 类&#xff1a;类是一个模板&#xff0c;它描述一类对象的行为和状态&#xff0c;类这个概念更像是下定义&#xff0c;更像是模板&#xff08;橡皮泥膜具&#xff09;。 对象&#xff1a;对象&#xff08;不是女朋友&#xff09;是类…

selenium-自动更新谷歌浏览器驱动

1、简介 selenium最初是一个自动化测试工具&#xff0c;而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题&#xff0c;因为有些网页数据是通过JavaScript动态加载的。selenium本质是通过驱动浏览器&#xff0c;完全模拟浏览器的操作&#xff0c;比如输入…

java从azure中读取用户信息

以下是用 Java 从 Azure AD 获取用户信息的完整实现方案&#xff0c;使用 Spring Boot 框架和 Microsoft 身份验证库 (MSAL)&#xff1a; 1. 添加 Maven 依赖 <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.…

C# 数据库访问与ORM框架全面指南:从ADO.NET到Entity Framework Core

在现代应用开发中&#xff0c;数据持久化是核心需求之一。作为.NET生态系统中的主力语言&#xff0c;C#提供了丰富多样的数据库访问技术和工具。本文将全面探讨C#中的数据库访问方式&#xff0c;重点介绍三种主流ORM&#xff08;对象关系映射&#xff09;框架&#xff1a;Entit…

day19 leetcode-hot100-37(二叉树2)

104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 1.深度优先遍历&#xff08;递归&#xff09;ps:不好理解&#xff0c;所以我一般不喜欢用递归 思路 典型算法&#xff0c;用递归求出高度&#xff0c;每次都是深度优先。 具体算法 /*** Definition for a bi…

【LLMs篇】13:LLaDA—大型语言扩散模型

栏目内容论文标题大型语言扩散模型 (Large Language Diffusion Models)核心思想提出LLaDA&#xff0c;一种基于扩散模型的LLM&#xff0c;通过前向掩码和反向预测过程建模语言分布&#xff0c;挑战自回归模型&#xff08;ARM&#xff09;在LLM领域的主导地位&#xff0c;并展示…

Deepfashion2 数据集使用笔记

目录 数据类别: 筛选类别数据: 验证筛选前2个类别: Deepfashion2 的解压码 数据类别: 类别含义: Class idx类别名称英文名称0短上衣short sleeve top1长上衣long sleeve top2短外套short sleeve outwear3长外套long sleeve outwear4裙子skirt5裤子trousers6连衣裙dre…

Java并发编程哲学系列汇总

文章目录 并发编程基础并发编程进阶并发编程实践 并发编程基础 Java并发编程基础小结 Java线程池知识点小结 详解JUC包下各种锁的使用 并发编程利器Java CAS原子类全解 深入理解Java中的final关键字 Java并发容器深入解析&#xff1a;HashMap与ArrayList线程安全问题及解…

git 之 stash

一、git stash&#xff1a;临时保存工作区修改 作用 将当前工作目录和暂存区的未提交修改保存到栈中&#xff0c;并恢复工作区到上一次提交的干净状态。 适用场景&#xff1a; 临时切换分支修复紧急 Bug拉取远程代码前清理工作区保存实验性代码避免生成无效提交 常用命令&am…

vxe-grid 双击行,打开expand的内容

1、官网api Vxe Table v4.6&#xff08;根据版本&#xff09; 要调用这个事件&#xff0c;双击单元格&#xff0c;我们打开type"expand"的内容 2、打开的事件toggleRowExpand 3、事件的说明 这个方法&#xff0c;会自动判断当前展开的状态&#xff0c;然后去触发相…