仿微信图片查看器`WPF`实现`ListBox` 鼠标滑动批量选中与反选效果

看到微信中,上传图片时的图片查看功能可以通过手指长按并滑动实现多选,于是为解析实现思路,也通过WPF 使用ListBox 实现了一版案例。

参考效果

微信效果如下,支持图片单选和鼠标长按滑动实现批量操作。
959f667467d24f80211ed8a71f031277.gif
WPF模仿效果:
Video_2024-06-24_141507.gif

效果分析

手指从第一项按下,向下拖动,拖动过程中手指位置所在项,就被选中或者反选,第一次点击项的状态决定后续所有覆盖项的状态。
Pasted image 20240624213317.png
手指相关事件:手指按下、手指移动以及手指放开,对应着鼠标操作为:鼠标键下、鼠标移动以及鼠标键起。

代码实现

为了展示效果,案例项目引入了HandyControl 以及 Prism.Core 对应 nuget 包。

	<ItemGroup><PackageReference Include="HandyControl" Version="3.4.0" /><PackageReference Include="Prism.Core" Version="8.1.97" /></ItemGroup>
案例目录
│  App.xaml #应用Application资源
│  App.xaml.cs #应用Application实现类
│  AssemblyInfo.cs
│  BoxItemViewModel.cs #选中项视图实体
│  MainWindow.xaml #案例窗体
│  MainWindow.xaml.cs #窗体后台代码
│  MainWindowViewModel.cs #主视图实体
│  WPFSelectedRange.csproj 
└─Resources #资源目录└─Images  #图片资源
Xaml代码
<Window x:Class="WPFSelectedRange.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:WPFSelectedRange"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"><Window.DataContext><local:MainWindowViewModel></local:MainWindowViewModel></Window.DataContext><!--设置选中模式SelectionMode为多选模式--><ListBox ItemsSource="{Binding BoxItemViewModels}" SelectionMode="Multiple"><ListBox.ItemTemplate><DataTemplate DataType="local:BoxItemViewModel"><!--设置项模板--><Border x:Name="Border" Cursor="Hand" Width="200" Height="240" CornerRadius="3" BorderThickness="1" BorderBrush="LightGray" SnapsToDevicePixels="True"><DockPanel Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"><Grid><Image Margin="10" Source="{Binding ImagePath}" Stretch="UniformToFill"></Image><Border x:Name="Mask" Visibility="Collapsed" Background="#66000000" CornerRadius="{Binding ElementName=Border,Path=CornerRadius}"></Border><CheckBox x:Name="Ck" Margin="0,5,5,0" BorderBrush="LightGray" Background="Transparent" IsChecked="{Binding IsSelected}" DockPanel.Dock="Top" HorizontalAlignment="Right" VerticalAlignment="Top"></CheckBox><TextBlock Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Transparent"></TextBlock></Grid></DockPanel></Border><!--设置数据触发器,选中时改变样式--><DataTemplate.Triggers><DataTrigger Binding="{Binding IsSelected}"  Value="True"><Setter Property="TextElement.Foreground" Value="White"></Setter><Setter TargetName="Border" Property="BorderBrush" Value="LightGray"></Setter><Setter TargetName="Border" Property="BorderThickness" Value="0"></Setter><Setter TargetName="Ck" Property="BorderBrush" Value="Transparent"></Setter><Setter TargetName="Mask" Property="Visibility" Value="Visible"></Setter><Setter TargetName="Ck" Property="Background" Value="{StaticResource SuccessBrush}"></Setter></DataTrigger></DataTemplate.Triggers></DataTemplate></ListBox.ItemTemplate><ListBox.ItemsPanel><ItemsPanelTemplate><!--设置布局容器添加鼠标Preview类事件--><WrapPanel   PreviewMouseLeftButtonDown="UniformGrid_PreviewMouseDown" PreviewMouseMove="UniformGrid_PreviewMouseMove" PreviewMouseLeftButtonUp="UniformGrid_PreviewMouseLeftButtonUp" Orientation="Horizontal"></WrapPanel></ItemsPanelTemplate></ListBox.ItemsPanel><ListBox.ItemContainerStyle><Style TargetType="ListBoxItem"><Setter Property="Padding" Value="5"/><Setter Property="BorderBrush" Value="LightGray"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="ListBoxItem"><Border x:Name="Border" CornerRadius="3"BorderThickness="{TemplateBinding BorderThickness}" Margin="{TemplateBinding Padding}"><ContentPresenter></ContentPresenter></Border></ControlTemplate></Setter.Value></Setter></Style></ListBox.ItemContainerStyle></ListBox>
</Window>
视图实体

主视图实体MainWindowViewModel.cs

   public class MainWindowViewModel{public MainWindowViewModel(){BoxItemViewModels = new List<BoxItemViewModel>();for (int i = 0; i < 6; i++){BoxItemViewModels.Add(new BoxItemViewModel() { Name = "Item" + i,ImagePath=@$"\Resources\Images\{i+1}.png" });}}// BoxItemViewModel集合private List<BoxItemViewModel> _boxItemViewModels;public List<BoxItemViewModel> BoxItemViewModels{get { return _boxItemViewModels; }set{if (_boxItemViewModels != value){_boxItemViewModels = value;}}}}

选中项视图实体BoxItemViewModel.cs

public class BoxItemViewModel:BindableBase
{// 是否选中private bool _isSelected;public bool IsSelected{get => _isSelected;set => SetProperty(ref _isSelected, value);}// 显示名称private string _name;public string Name{get => _name;set => SetProperty(ref _name, value);}// 图片路径属性private string _imagePath;public string ImagePath{get => _imagePath;set => SetProperty(ref _imagePath, value);}
}

核心代码MainWindow.xaml.cs
注意:仅作为案例主要以思路展示为主,如果需要用于实际项目,建议进行附加属性封装和抽象接口封装。

public partial class MainWindow : Window
{private MainWindowViewModel mainWindowViewModel;public MainWindow(){InitializeComponent();this.Loaded += Window_Loaded;}// 当前选中初始状态private bool currentState;// 选中范围起始索引private int startIndex=int.MinValue,endIndex=int.MaxValue;// 临时选中项字典Dictionary<int,BoxItemViewModel> tempSelectItems = new Dictionary<int, BoxItemViewModel>();// 鼠标键下事件private void UniformGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e){tempSelectItems.Clear();// 容器获取ContentPresenter上下文if (e.Source is ContentPresenter content){if (content.Content is BoxItemViewModel vm){vm.IsSelected = !vm.IsSelected;currentState = vm.IsSelected;Debug.WriteLine($"容器键下,当前所在项:{vm.Name}:{currentState}");// 获取当前索引startIndex = mainWindowViewModel.BoxItemViewModels.IndexOf(vm);}}}// 鼠标键起事件private void UniformGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e){// 容器获取ContentPresenter上下文if (e.Source is ContentPresenter content){if (content.Content is BoxItemViewModel vm){Debug.WriteLine($"容器键起,当前所在项:{vm.Name}");// 获取当前索引endIndex = mainWindowViewModel.BoxItemViewModels.IndexOf(vm);foreach (var item in tempSelectItems){item.Value.IsSelected = currentState;}}}// 选中范围Debug.WriteLine($"起始索引:{startIndex}|终止索引:{endIndex}");}// 鼠标移动事件private void UniformGrid_PreviewMouseMove(object sender, MouseEventArgs e){// 容器获取ContentPresenter上下文if (e.LeftButton == MouseButtonState.Pressed && e.Source is ContentPresenter content){if (content.Content is BoxItemViewModel vm){Debug.WriteLine($"容器移动,当前所在项:{vm.Name}");// 获取当前索引endIndex = mainWindowViewModel.BoxItemViewModels.IndexOf(vm);// 移动时,动态缓存临时项// 如果临时项多余目标移动项,则清除多余项if (tempSelectItems.Count() != 0 && Math.Abs(startIndex - endIndex) < tempSelectItems.Count()){// 顺序生成选中项索引集合int[] containerids = Enumerable.Range(Math.Min(startIndex, endIndex), Math.Abs(startIndex - endIndex)).ToArray();// 清除多余项int[] removeids = tempSelectItems.Keys.Except(containerids).ToArray();foreach (var item in removeids){if (item != startIndex || item != endIndex){tempSelectItems[item].IsSelected = !currentState;tempSelectItems.Remove(item);}}}// 起始索引与终止索引不相等咋进行选中项操作if (startIndex != endIndex){int index = startIndex - endIndex;// 选中范围起始索引与终止索引是否顺序操作int start = startIndex < endIndex ? startIndex : endIndex;int end = startIndex < endIndex ? endIndex : startIndex;// 遍历设置选中项状态为当前状态for (int i = start; i <= end; i++){mainWindowViewModel.BoxItemViewModels[i].IsSelected = currentState;// 并判定项是否临时项中,不在则添加if (!tempSelectItems.ContainsKey(i)){tempSelectItems.Add(i, mainWindowViewModel.BoxItemViewModels[i]);}}}}}}private void Window_Loaded(object sender, RoutedEventArgs e){mainWindowViewModel = DataContext as MainWindowViewModel;}
}

归纳

总的来说,核心代码是在集合控件ListBox 的布局容器中添加鼠标事件,以及通过事件的对象参数,获取到子节点对应的VM,进而实现外部操作内部的展示逻辑。案例地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/34559.shtml

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

相关文章

Mysql: SQL-DDL

一.SQL通用语法 1.SQL可以单行或者多行书写,以分号结尾。 2.SQL语句可以使用空格/缩进来增强语句的可读性。 3.MySQL数据库的SQL语句不区分大小写,关键字建议用大写。 4.注释: 单行注释:注释内容或#注释内容(Mysql特有) 多行注释&#xff1a;/*注释内容*/ 二.SQL分类 1.D…

信息学奥赛初赛天天练-34-CSP-J2021完善程序-按位异或、模拟算法、数组模拟环、约瑟夫问题应用

PDF文档公众号回复关键字:20240624 2021 CSP-J 完善程序3 1 完善程序 (单选题 &#xff0c;每小题3分&#xff0c;共30分) &#xff08;Josephus问题&#xff09;有n个人围成一个圈&#xff0c;依次标号0至n-1。从0号开始&#xff0c;依次 0&#xff0c;1&#xff0c;0&#…

0801功率放大问题

3个学时讲一个电路&#xff08;两个共集共集并联&#xff09;&#xff0c;4个问题&#xff0c;发展线索 丙类放大电路用在高频通讯行业&#xff0c;低频功放是甲类&#xff0c;乙类&#xff0c;甲乙类 PT三极管的损耗 Pv电源提供的功率 现代模电通常使用方法b 只有交流…

用Vue3打造一个交互式营养追踪仪表盘

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Vue.js 构建营养仪表盘卡片 应用场景介绍 营养仪表盘卡片是一个可视化工具&#xff0c;用于跟踪个人的营养摄入情况。它通常包含以下信息&#xff1a; 卡路里摄入目标和进度营养成分&#xff08;如脂肪…

2024中国·淮安高端人才精英赛北京分站赛首战告捷

“诚意满淮&#xff0c;创赢未来”&#xff01;6月20-21日&#xff0c;2024中国淮安高端人才精英赛首场分站赛在北京产业创新中心顺利举办。淮安市科技局党组书记、局长胡长青&#xff0c;淮安市委组织部人才处处长沈雪娇&#xff0c;淮安经开区党工委委员、管委会副主任、科技…

C++(part2、3-Linux系统编程+数据库项目):Linux网络云盘

文章目录 一、项目需求分析1.一期&#xff1a;命令行解析(1)cd(用栈管理)、ls、pwd(2)puts、gets(3)mkdir、touch、rmdir、rm 2.二期&#xff1a;密码验证、日志、断点续传、大文件传输(1)密码验证(2)日志(3)断点续传(4)大文件传输 3.三期&#xff1a;用户注册、用户登录、虚拟…

springboot+vue+mybatis穷游管理系统+PPT+论文+讲解+售后

随着现在网络的快速发展&#xff0c;网上管理系统也逐渐快速发展起来&#xff0c;网上管理模式很快融入到了许多企业的之中&#xff0c;随之就产生了“基于vue的穷游管理系统”&#xff0c;这样就让基于vue的穷游管理系统更加方便简单。 对于本基于vue的穷游管理系统的设计来说…

企业文件传输系统只能传输?分享功能同样重要!(下)

上篇我们讲述了企业大文件传输的重要性以及镭速在传输方面的优势&#xff0c;同时企业文件的快速共享显得尤为重要。镭速软件在这方面的表现一样尤为突出&#xff0c;它不仅提供了强大的文件传输功能&#xff0c;而且在文件分享方面也有其独到之处。本文将探讨镭速软件在文件分…

协程: Flow 异步流 /

以异步方式返回多个返回值的方案: 在 Kotlin 协程 Coroutine 中 , 使用 suspend 挂起函数 以异步的方式 返回单个返回值肯定可以实现 , 如果要 以异步的方式 返回多个元素的返回值 , 可以使用如下方案 : 集合序列Suspend 挂起函数Flow 异步流 同步调用返回多个值的弊端&…

江协科技51单片机学习- p17 定时器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Unity面试题 UGUI调整UI与粒子特效的显示层级

首先&#xff0c;必须保证Canvas画布的渲染模式为了相机渲染 方法:一&#xff1a;将需要控制UI显示层级的Image换成Sprite 1.创建一个粒子系统&#xff0c;和两张Sprite. 2.设置Sprite1的Order in Layer为 -1&#xff0c;设置Sprite2的Order in Layer为 1&#xff0c;粒子的Ord…

LONGHEADS:无需训练的多头注意力长文本处理框架

大模型&#xff08;LLMs&#xff09;在处理海量文本数据时展现出了前所未有的能力。然而这些模型在面对超出其训练时所见序列长度的长文本时存在两个主要问题&#xff1a;一是模型对于超出预训练长度的文本难以有效泛化&#xff0c;二是注意力机制的二次方时间复杂度导致计算成…

活动预告|探索 LLM 大模型的小型化 —— 微软 Phi3在 NVIDIA Jetson 与 NIM 平台的最佳实践

在当前高速发展的人工智能领域&#xff0c;如何高效的部署和优化 SLM (小型的大模型) 成为关键。随着微软 Phi-3 系列模型的发布&#xff0c;让 SLM 在 NVIDIA Jetson 边缘计算平台上的部署成为可能。同时 Phi-3 系列模型已在 NVIDIA NIM 平台提供加速的推理服务。 NVIDIA NIM…

解决File协议导致的CORS限制,用Node.js搭建本地服务器

文章目录 一、前言二、分析报错原因三、如何解决四、具体步骤 你是否曾遇到这样的困境&#xff1a;在本地使用file://协议直接打开HTML文件时&#xff0c;由于现代浏览器的安全限制&#xff0c;无法跨源请求&#xff08;CORS&#xff09;本地资源&#xff1f;尤其是当你试图通过…

老电脑焕发第二春,玩转 Stable Diffusion 3

几年前&#xff0c;我头脑一热&#xff0c;配置了一台顶配级消费 PC&#xff08;RTX 2080 Ti GPU i9 CPU&#xff09;&#xff0c;打算用来学习 AI。然而&#xff0c;起初我并没有找到合适的切入点。深度学习早期阶段&#xff0c;消费级显卡根本无法承担训练大模型、微调大模型…

ONLYOFFICE 桌面编辑器8.1---一个高效且强大的办公软件

软件介绍 ONLYOFFICE 桌面编辑器经过不断的更新换代现在迎来了&#xff0c;功能更加强大的ONLYOFFICE 桌面编辑器8.1是一个功能强大的办公套件&#xff0c;专为多平台设计&#xff0c;包括Windows、Linux和macOS。它提供了一套全面的办公工具&#xff0c;包括文档处理、电子表…

elementplus el-table(行列互换)转置

Element Plus v2.4.0, repl v3.4.0 <template> <div><el-table :data"tableData" style"width: 100%"><el-table-column prop"name" label"名字" width"180" /><el-table-column prop"wei…

如何linux 查询进程和杀死进程

在程序开启长链接的时候&#xff0c;有时候会发现端口被占用的情况&#xff0c;但是又没有启动相关的端口&#xff0c;所以我们需要将端口占用结束掉 1.使用指令netstat -ntlp 查看当前有哪些进程&#xff0c;如图&#xff1a; 2.使用指令kill -9 18785 杀死进程&#xff0c…

Charles抓包工具系列文章(一)-- Compose 拼接http请求

一、背景 众所周知&#xff0c;Charles是一款抓包工具&#xff0c;当然是http协议&#xff0c;不支持tcp。&#xff08;如果你想要抓tcp包&#xff0c;请转而使用wireshark&#xff0c;在讲述websocket的相关技术有梳理过wireshark抓包&#xff09; 话说回来&#xff0c;char…

【自然语言处理系列】Python 字符串操作技巧:清理、替换与合并

在编写Python程序时&#xff0c;字符串处理是一项常见的任务。了解如何有效地清理、修改和合并字符串对于数据预处理、文本分析和日常编程都至关重要。本文将引导您通过一系列实用的示例来掌握Python中字符串的核心操作&#xff0c;包括去除不需要的空格和特殊字符、替换文本中…