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

news2024/11/17 21:18:42

看到微信中,上传图片时的图片查看功能可以通过手指长按并滑动实现多选,于是为解析实现思路,也通过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.coloradmin.cn/o/1858870.html

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

相关文章

【代码仓库提交大文件,用Git LFS!】

开始前 Git LFS&#xff1a;请注意&#xff0c;你的远程仓库需要支持Git LFS。GitHub、GitLab和Bitbucket都支持Git LFS&#xff0c;但可能需要额外的配置或开启特定的支持选项。 介绍 Git LFS (Large File Storage) 是一个 Git 扩展&#xff0c;用于处理和存储大文件。通常…

【ai】tx2 nx: jetson Triton Inference Server 运行YOLOv4

【ai】tx2 nx: jetson Triton Inference Server 部署YOLOv4 部署了服务端。需要对其测试【ai】tx2-nx 查看 jetpack 版本信息及对应的tritonserver【ai】tx2-nx:配置tritonserver2.17.0-jetpack4.6 环境并运行例子C++ Triton YoloV4 client 是基于 r21.05的 服务端的tensort 的…

离散数学-再次复习

1.先找到e&#xff0c;这里是0 2.对每个元素求它的阶 3.根据拉格朗日定理&#xff0c;子群的阶必须是群 G 的阶的因数。群 G 的阶为 10&#xff0c;它的因数有 1、2、5 和 10。这意味着子群的阶可能是 1、2、5 或者 10。 4.相同阶的就放为一组&#xff0c;也就是它的一个子群…

Java面试复习思路

Java面试复习思路路线一&#xff1a; 准备Java面试时&#xff0c;可以从以下几个方面着手&#xff0c;以确保你能够自信且全面地展示你的技能和知识&#xff1a; 基础与核心概念&#xff1a; 确保你对Java基础有深入理解&#xff0c;包括但不限于&#xff1a;面向对象编程原则…

自从用了这个 69k star 的项目,前端小姐姐再也不催我了

一般在开发前后端分离的项目时&#xff0c;双方会定义好前后端交互的 http 接口&#xff0c;根据接口文档各自进行开发。这样并行开发互不耽误&#xff0c;开发好后做个联调就可以提测了。 不过最近也不知道怎么回事&#xff0c;公司新来的前端小姐姐总是在刚开始开发的时候就…

项目中eventbus和rabbitmq配置后,不起作用

如下&#xff1a;配置了baseService层和SupplyDemand层得RabbitMQ和EventBus 但是在执行订阅事件时&#xff0c;发送得消息在base项目中没有执行&#xff0c;后来发现是虚拟机使用得不是一个&#xff0c;即上图中得EventBus下得VirtualHost&#xff0c;修改成一直就可以了

Java-内部类成员内部类

类的五大成员 属性 方法 构造方法 代码块 内部类 什么是内部类&#xff1f; 在一个类的里面&#xff0c;再定义一个类。 举例&#xff1a;在A类的内部定义B类&#xff0c;B类就被称为内部类 内部类表示的事物是外部类的一部分 内部类单独出现没有任何意义 内部类的访问特点 1.…

lsopsed 安装与工程创建

Xposed与lsposed异同点 Xposed支持到安卓7.1 Xposed会将激活的模块注入到每个进程&#xff0c;需要自已在模块内根据包名过滤 Xposed激活模块后重启系统生效 lsposed支持安卓8.1-14 1sposed激活模块后需要勾选要作用于哪些app lsposed激活模块后重启对应app生效 lsposed模块…

Redis(超详细)

Redis Redis概念&#xff1a; Redis是开源的&#xff0c;遵循BSD的&#xff0c;基于内存数据存储&#xff0c;被用于作为数据库、缓存机制、消息中间件&#xff1b; Redis的特点&#xff1a; 1.高性能key/valu内存xing数据库&#xff1b; 2.支持丰富的数据类型 3.支持持久化&am…

Information security in DLMS/COSEM(Green-Book)—认证机制

Information security in DLMS/COSEM 9.2.1 概述9.2.2 DLMS/COSEM安全概念9.2.2.1 概述 9.2.2.1 概述9.2.2.2 身份识别和认证9.2.2.2.1 身份识别9.2.2.2.2 认证机制9.2.2.2.2.1 概述 无安全认证&#xff08;Lowest Level Security&#xff09;&#xff1a;低级别安全认证&#…

python中数据的作用域

一、命名空间 在 Python 中&#xff0c;命名空间是一个系统&#xff0c;它用于确保名字的唯一性&#xff0c;并防止命名冲突。命名空间是一个存储变量名称&#xff08;或者更广泛地说&#xff0c;标识符&#xff09;与对象之间映射的抽象概念。每个变量名你在程序中创建&#x…

Redis-数据类型-zset

文章目录 1、查看redis是否启动2、通过客户端连接redis3、切换到db4数据库4、将一个或多个member元素及其score值加入到有序集key当中5、升序返回有序集key6、升序返回有序集key&#xff0c;让分数一起和值返回的结果集7、降序返回有序集key&#xff0c;让分数一起和值返回到结…

Android上编译和使用curl

1 概述 Android系统编译的时候默认是没有带curl工具的&#xff0c;但是在aosp源码中&#xff0c;却是有curl的源码包含。所以只需要编译curl&#xff0c;然后将其push到Android设备中&#xff0c;就可以使用curl命令了。 2 编译curl 这里编译curl是在整机代码环境下进行编译…

机器人控制系列教程之雅可比矩阵和奇异性

简介 在机器人学和控制理论中&#xff0c;雅可比矩阵扮演着至关重要的角色。它是一个描述机器人末端执行器速度与关节速度之间关系的数学工具。通过雅可比矩阵&#xff0c;我们可以更好地理解和控制机器人的运动&#xff0c;特别是在执行精确操作时。雅可比矩阵的计算通常基于…

python turtle 画帕恰狗

先上个图给大家看看 代码 ##作者V w1933423 import turtle turtle.bgcolor("#ece8dc") turtle.setup(600,900) p turtle.Pen() p.pensize(14) p.speed(5) p.color("black")p.penup() p.goto(-54,-44) p.pendown() p.goto(-37,-39) p.goto(-27,-24) p.go…

swp添加池子addLiquidity失败

案发现场 首次添加交易对、一直失败、但是也没提示具体的原因。到这一步就没了、由下图可知、也没看到log、由此可见第一步就失败了。 解决方案 工厂KywFactory 添加如下 bytes32 public constant INIT_CODE_PAIR_HASH keccak256(abi.encodePacked(type(KywPair).creatio…

0620所学——环境变量、CMake等

https://www.cnblogs.com/bravesunforever/p/10939078.html CMake&#xff1a; https://zhuanlan.zhihu.com/p/659412062 0621: 学会了在Github里创建组织&#xff0c;把本地仓库“同步”&#xff0c;就可以上传到Github&#xff0c;然后学会了把自己的Repos转移到组织里。G…

前端中的深拷贝

第1部分&#xff1a;引言 深拷贝&#xff1a;前端开发的隐形守护者 在前端开发的世界里&#xff0c;数据的传递和状态的管理是构建用户界面的基础。然而&#xff0c;数据的复制常常被忽视&#xff0c;直到它引发bug&#xff0c;我们才意识到它的重要性。深拷贝&#xff0c;这…

NC--介绍-未加密加密后-流量抓包对比

免责声明:本节仅做技术交流与学习... 目录 介绍: 用法: 未加密--流量抓包 加密: 攻击端 靶机 抓包分析: 介绍: nc 是一个Linux环境下常用的工具命令&#xff0c;可以用来帮助开发者查询和解决网路问题&#xff0c;通常被认为是 NetCat 工具的缩写&#xff0c;在网络工具…

arduino按钮

Arduino - Button Arduino - 按钮 参考&#xff1a; ezButton-按钮库从按钮开关看上拉pull-up电阻、下拉电阻按键的防抖动处理 The button is also called pushbutton, tactile button or momentary switch. It is a basic component and widely used in many Arduino projec…