前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[WPF 自定义控件]创建包含CheckBox的ListBoxItem

[WPF 自定义控件]创建包含CheckBox的ListBoxItem

作者头像
dino.c
发布于 2020-02-21 03:44:45
发布于 2020-02-21 03:44:45
3.1K00
代码可运行
举报
文章被收录于专栏:dino.c的专栏dino.c的专栏
运行总次数:0
代码可运行

1. 前言

Xceed wpftoolkit提供了一个CheckListBox,效果如下:

不过它用起来不怎么样,与其这样还不如参考UWP的ListView实现,而且动画效果也很好看:

它的样式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<ListViewItemPresenter ContentTransitions="{TemplateBinding ContentTransitions}"
    x:Name="Root"
    Control.IsTemplateFocusTarget="True"
    FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
    SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}"
    CheckBrush="{ThemeResource ListViewItemCheckBrush}"
    CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
    DragBackground="{ThemeResource ListViewItemDragBackground}"
    DragForeground="{ThemeResource ListViewItemDragForeground}"
    FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
    FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
    PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
    PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
    PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
    SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"
    SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
    SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}"
    PressedBackground="{ThemeResource ListViewItemBackgroundPressed}"
    SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}"
    DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
    DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
    ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
    ContentMargin="{TemplateBinding Padding}"
    CheckMode="{ThemeResource ListViewItemCheckMode}"
    RevealBackground="{ThemeResource ListViewItemRevealBackground}"
    RevealBorderThickness="{ThemeResource ListViewItemRevealBorderThemeThickness}"
    RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}">

属性是很多了,但这里没有自定义CheckBox样式的方法,而且也没法参考它的动画如何实现。幸好UWP还提供了一个ListViewItemExpanded样式,里面有完整的布局、VisualState等,不过总共有差不多500行,只拿其中MultiSelectStates的部分也将近100行,这太过复杂了,这还是有些麻烦,在WPF中实现起来反而简单很多。

2. 实现

微软的文档中有介绍如何Create ListViewItems with a CheckBox,原理十分简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<DataTemplate x:Key="FirstCell">
  <StackPanel Orientation="Horizontal">
    <CheckBox IsChecked="{Binding Path=IsSelected, 
      RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}"/>
  </StackPanel>
</DataTemplate>

就是在控件模板中添加一个CheckBox并且这个CheckBox通过FindAncestor的Binding方式绑定到ListViewItem的IsSelected属性。虽然是ListView的方法,但它同样适用于ListBox。所以我使用这个方式封装了一个ListBox控件,目前基本上没什么功能,就只是在每个ListBoxItem前面加上一个CheckBox。以前介绍过如何自定义ItemsControl,要自定义一个ListBox控件,同样需要三部:

  1. 定义ListBox
  2. 关联ListBoxItem和ListBox
  3. 实现ListBox的逻辑
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ExtendedListBox : ListBox
{
    public static readonly DependencyProperty IsMultiSelectCheckBoxEnabledProperty =
        DependencyProperty.Register(nameof(IsMultiSelectCheckBoxEnabled), typeof(bool), typeof(ExtendedListBox), new PropertyMetadata(true));

    public bool IsMultiSelectCheckBoxEnabled
    {
        get { return (bool)GetValue(IsMultiSelectCheckBoxEnabledProperty); }
        set { SetValue(IsMultiSelectCheckBoxEnabledProperty, value); }
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ExtendedListBoxItem();
    }
}


public class ExtendedListBoxItem : ListBoxItem
{
    public ExtendedListBoxItem()
    {
        DefaultStyleKey = typeof(ExtendedListBoxItem);
    }
}

上面就是全部代码。定义了ExtendedListBoxExtendedListBoxItem两个类,然后重写GetContainerForItemOverride关联这两个类,最后在ExtendedListBox的代码里模仿UWP的ListView提供了IsMultiSelectCheckBoxEnabled属性,其他功能主要由XAML提供:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition/>
</Grid.ColumnDefinitions>
<Primitives:KinoResizer>
    <CheckBox Margin="{TemplateBinding Padding}"
              IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
              IsTabStop="False"
              x:Name="SelectionCheckMark"/>
</Primitives:KinoResizer>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Grid.Column="1"
                  Margin="{TemplateBinding Padding}"/>

ControlTemplate使用Resizer包装CheckBox,这是为了CheckBox隐藏或显示时有过渡动画。然后在ControlTemplate.Triggers里添加两个DataTrigger,根据所属的ListBox的IsMultiSelectCheckBoxEnabledSelectionMode显示或隐藏SelectionCheckMark:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBox},Path=SelectionMode}"
             Value="Single">
    <Setter Property="Visibility"
            TargetName="SelectionCheckMark"
            Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBox},Path=IsMultiSelectCheckBoxEnabled}"
             Value="False">
    <Setter Property="Visibility"
            TargetName="SelectionCheckMark"
            Value="Collapsed" />
</DataTrigger>

最终效果如下:

3. 添加VisualState

WPF的Button的ControlTemplate没有使用VisualState,但Button支持VisualState,用户可以自定义使用VisualState的ControlTemplate。ExtendedListBoxItem也模仿UWP提供了MultiSelectEnabled和MultiSelectDisabled两个VisualState,因为ListBoxItem需要知道承载它的ListBox的IsMultiSelectCheckBoxEnabled和SelectionMode,所以需要给ListBoxItem添加一个Owner属性,并重载ListBox的PrepareContainerForItemOverride函数,在这个函数中为ListBoxItem的Owner赋值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
    base.PrepareContainerForItemOverride(element, item);
    if (element is ExtendedListBoxItem listBoxItem)
        listBoxItem.Owner = this;
}

ListBoxItem中使用监视Owner的IsMultiSelectCheckBoxEnabled和SelectionMode的改变,并在这两个值改变时更新VisualState:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected virtual void OnOwnerChanged(ExtendedListBox oldValue, ExtendedListBox newValue)
{
    if (oldValue != null)
    {
        var descriptor = DependencyPropertyDescriptor.FromProperty(ListBox.SelectionModeProperty, typeof(ExtendedListBox));
        descriptor.RemoveValueChanged(newValue, OnSelectionModeChanged);

        descriptor = DependencyPropertyDescriptor.FromProperty(ExtendedListBox.IsMultiSelectCheckBoxEnabledProperty, typeof(ExtendedListBox));
        descriptor.RemoveValueChanged(newValue, OnIsMultiSelectCheckBoxEnabledChanged);
    }
    if (newValue != null)
    {
        var descriptor = DependencyPropertyDescriptor.FromProperty(ListBox.SelectionModeProperty, typeof(ExtendedListBox));
        descriptor.AddValueChanged(newValue, OnSelectionModeChanged);

        descriptor = DependencyPropertyDescriptor.FromProperty(ExtendedListBox.IsMultiSelectCheckBoxEnabledProperty, typeof(ExtendedListBox));
        descriptor.AddValueChanged(newValue, OnIsMultiSelectCheckBoxEnabledChanged);
    }
}

private void OnSelectionModeChanged(object sender, EventArgs args)
{
    UpdateVisualStates(true);
}

private void OnIsMultiSelectCheckBoxEnabledChanged(object sender, EventArgs args)
{
    UpdateVisualStates(true);
}

为了使用VisualState我在ControlTemplate多写了80行代码,因为没有用上VisualTransition所以这个ControlTemplate有一些Bug,反正只是用来验证添加的两个VisualState是否有效。在ListBoxItem里用Trigger比使用VisualState更简洁有效。

4. 使用同样的原理为DataGrid的行添加ChechBox

DataGrid也可以用同样的原理为每一行添加CheckBox,只不过DataGrid的Template会负责很多。

首先自定义一个DataGrid类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ExtendedDataGrid : DataGrid, IMultiSelector
{
    // Using a DependencyProperty as the backing store for IsMultiSelectCheckBoxEnabled.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsMultiSelectCheckBoxEnabledProperty =
        DependencyProperty.Register(nameof(IsMultiSelectCheckBoxEnabled), typeof(bool), typeof(ExtendedDataGrid), new PropertyMetadata(true));

    public ExtendedDataGrid()
    {
        DefaultStyleKey = typeof(ExtendedDataGrid);
    }

    public bool IsMultiSelectCheckBoxEnabled
    {
        get { return (bool)GetValue(IsMultiSelectCheckBoxEnabledProperty); }
        set { SetValue(IsMultiSelectCheckBoxEnabledProperty, value); }
    }
}

然后定义一个RowHeaderTemplate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<DataTemplate x:Key="DataGridRowHeaderTemplate">
    <Grid>
        <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}"
                      x:Name="SelectionCheckBox"/>
    </Grid>
</DataTemplate>

在DataGrid的Style上应用这个RowHeaderTemplate。最后再DataGrid的Style的Triggers中添加两个DataTrigger:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<Trigger Property="SelectionMode" Value="Single">
    <Setter Property="HeadersVisibility"  Value="Column" />
</Trigger>
<Trigger Property="IsMultiSelectCheckBoxEnabled" Value="False">
    <Setter Property="HeadersVisibility"  Value="Column"/>
</Trigger>

HeadersVisibility是个DataGridHeadersVisibility的属性,它用于控制DataGrid行和列的Header是否显示,因为我在每一行的开头放了CheckBox(就是使用上面定义的RowHeaderTempalte),所以定一只只显示Column的Header的话相当于隐藏了这个CheckBox,运行效果如下:

5. 结语

ListBox和DataGrid的自定义是个很大的话题,这里只实现最简单的功能,通常会根据业务需求逐渐增加更多需求。如果有更复杂的需求,我建议买商业的控件,毕竟DataGrid的自定义可以很复杂,花时间不如花钱。

6. 参考

How to_ Create ListViewItems with a CheckBox - WPF _ Microsoft Docs

ListBox Class (System.Windows.Controls) _ Microsoft Docs

DataGrid Class (System.Windows.Controls) _ Microsoft Docs

7. 源码

Kino.Toolkit.Wpf_ExtendedListBox.cs at master

Kino.Toolkit.Wpf_ExtendedDataGrid.cs at master

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【图解】物联网设备的N种Wi-Fi配网方式
物联网时代,各种各样的智能物联网设备正走进我们我们的生活,智能门锁、摄像头、猫眼门铃、扫地机、智能音箱、空气净化器、体脂秤等等。
xxpcb
2021/09/29
2.4K0
物联网技术应用机遇与挑战并存,该如何突破困局?
导语 | 随着物联网的蓬勃发展,各个行业都在积极探索“物联网+产业”的应用落地。本文由腾讯云物联网专家工程师陈洁在Techo TVP开发者峰会“「物」所不在,「联」动未来——从万物互联到万物智联”上的演讲 《物联网技术在行业应用中面临的挑战和探索》整理而成。
腾讯云开发者
2021/12/03
8340
物联网技术应用机遇与挑战并存,该如何突破困局?
如何让物联网体验升级
不知不觉,物联网技术的应用,实际上已经有应用于上十年之久,在我们生活的方方面面都发挥着重要作用。
火爆的小茶壶
2022/08/30
1.1K0
如何让物联网体验升级
小程序容器与物联网结合的方案
智能汽车、智能社区、智能穿戴设备等技术的不断成熟及应用,与其紧密结合的物联网(IoT)正日益成为个人和企业工作生活中的重要组成部分,它为企业和个人带来了操作流程的改进和更好的生活体验,再加上人工智能(AI)技术的日趋成熟,IoT 与 AI 的结合愈发紧密,IoT 也被赋予了越来越多的能力和价值。
pak
2022/06/30
6700
物联网发展新动力——小程序
物联网(Internet of Things,缩写:IoT)是基于互联网、传统电信网等信息承载体,让所有能行使独立功能的普通物体实现互联互通的网络。
二山山记
2022/09/02
7060
【LoRaWAN】HT-M00L 单通道网关+节点接入腾讯物联网平台
一、设备与资料 HT-M00L单通道网关,在ESP32 + SX1278 LoRa节点芯片的硬件基础上增加了软件LoRa解调器和软件混频器,让它实现了单通道的LoRaWAN协议通信。主要用于方案验证、通信链路开发、智能家居等场景。 主要特性: 已绑定腾讯云(通过WiFi接入腾讯云物联网开发平台) 可自定义监听频点 软件LoRa解调器和软件混频器 自动自适应扩频因子,支持SF7到SF12 支持 LoRaWAN Class A, Class C 协议 收发状态RGB指示 参考: LoRaWAN 产品简介 腾讯物
丨晋丨
2021/05/14
2.1K0
【LoRaWAN】HT-M00L 单通道网关+节点接入腾讯物联网平台
物联网智能家居战场:共同认可的家庭局域网物联网标准(HAN)
谷歌加入了亚马逊,苹果,Zigbee联盟等公司,以帮助制定智能家居连接标准。我们将详细介绍这将如何改变游戏计划并帮助技术发展,使家庭区域网络更健壮、更安全。
用户4122690
2020/07/12
8550
物联网智能家居战场:共同认可的家庭局域网物联网标准(HAN)
知道WIFI和蓝牙有什么区别吗?5分钟看完后你就知道了
Wi-Fi是一种允许电子设备连接到一个无线局域网(WLAN)的技术,通常使用2.4G UHF或5G SHF ISM 射频频段。连接到无线局域网通常是有密码保护的;但也可是开放的,这样就允许任何在WLAN范围内的设备可以连接上。Wi-Fi是一个无线网络通信技术的品牌,由Wi-Fi联盟所持有。
用户5777378
2019/07/30
14.4K0
Matter协议高速崛起,你真的了解它吗?
说到智能家居,大家应该都不会陌生。早在本世纪初,物联网概念刚刚诞生的时候,最主要的应用领域,就是智能家居。
鲜枣课堂
2023/08/21
4980
Matter协议高速崛起,你真的了解它吗?
ESA2GJK1DH1K微信小程序篇: 微信小程序APUConfig给WI-Fi模块配网并绑定设备,并通过MQTT控制设备(单片机AT指令版 V1.0 使用SSL连接)
前言   有多少人一直在期盼着小程序可以实现SmartConfig或者Airkiss的功能? 来吧!我的这种方式包您满意.  注:APUConfig 是我自己取的名字(哈哈谁让这种方式,我是第一个在微
杨奉武
2020/05/12
6790
物联网开发中常见的几个标准协议
假设你正准备开始一个物联网项目,在开始项目之前你需要做很多选择,有可能你完全不知道从哪开始,这篇文章我们一起来看看如何选择标准的无线通信协议框架。
苏州程序大白
2022/04/14
5720
物联网开发中常见的几个标准协议
物联网探秘:那些来自传感器的数据都是如何上传至云端的?
雷锋网授权转载 作者:Larry Burgess,来自Voler Systems公司的无线技术编辑 编译:老吕IO发布 网站:http://www.leiphone.com/ 微信:leiphone-sz 过去25年来,无线标准发生了翻天覆地的变化,我们传递信息的方式也变的更加多样,传感器,相机和麦克风都可以将大量的数据同步到云端,在需要时又可以随时取用。正因如此,人们才不厌其烦的讨论物联网可能带来的好处。 在物联网时代,困扰应用开发者的一个重要问题就是如何在功率,覆盖范围,传输速率和成本之间找到那个微妙的
大数据文摘
2018/05/21
1.6K0
物联网解决方案,一个基于 Wi-Fi、一个基于 ZigBee,两者的优势和劣势有哪些?
原文链接: https://www.zhihu.com/question/22898725/answer/25503330
用户9840151
2022/06/19
5340
物联网概念以及发展趋势周边知识学习总结
定义: 描述:物联网—The Internet Of Things [万物互联]简称IOT,顾名思义是把所有物品通过网络连接起来,实现任何物体、任何人、任何时间、任何地点(4A)的智能化识别、信息交换与管理。 百度WIKI:物联网是指通过信息传感设备,按约定的协议,将任何物体与网络相连接,物体通过信息传播媒介进行信息交换和通信,以实现智能化识别、定位、跟踪、监管等功能。
全栈工程师修炼指南
2022/09/29
1.2K0
物联网概念以及发展趋势周边知识学习总结
共享单车IOT物联网系统是怎么设计的?
物联网(IOT)的概念很早就有了,只是在共享单车普及之前物联网并不被多数人所熟知,因此听起来很神秘。今天文章的主题就和大家一起聊一聊关于物联网(IOT)相关的话题,并从技术的角度解析下大家每天会使用到的共享单车在系统技术层面的运行原理,希望能够给大家的生活带来点小乐趣。
用户5927304
2019/08/01
3.3K0
共享单车IOT物联网系统是怎么设计的?
ESA2GJK1DH1K微信小程序篇: 测试微信小程序APUConfig给WI-Fi模块配网并绑定设备,并通过MQTT控制设备
前言   1.有多少人一直在期盼着小程序可以实现SmartConfig或者Airkiss的功能? 来吧!我的这种方式包您满意.   注:APUConfig 是我自己取的名字(哈哈谁让这种方式,我是第一
杨奉武
2019/11/05
1.2K0
ESA2GJK1DH1K微信小程序篇: 测试微信小程序APUConfig给WI-Fi模块配网并绑定设备,并通过MQTT控制设备
智能家居通信协议之争,谁会是最终赢家?
清晨和煦的阳光照进屋内,你慢慢苏醒,原来是智能窗帘缓缓为你打开,智能睡眠显示你昨晚翻身起夜等数据在正常范围内,嗯,确实,昨夜你睡得很好,因为智能检测环境系统在实时净化房间里的空气,美好的一天就此开始。
SDNLAB
2020/07/02
2.6K0
智能家居通信协议之争,谁会是最终赢家?
【涂鸦物联网足迹】物联网常见通信协议
一种因其支持 TCP/IP,可以直接接入物联网,如 Wi-Fi、GPRS/3G/4G 等;
涂小航说智能
2021/06/11
3.8K0
Apple主推的智能家居是什么、怎么用?一篇文章带你从零完全入门 HomeKit
如果你对智能家居有所了解,那应该或多或少听人聊起过 HomeKit。由 Apple 开发并主推的的 HomeKit 既因为产品选择少、价格高而难以成为主流,又因其独特的优秀体验和「出身名门」而成为智能家居领域的焦点。HomeKit 究竟是什么?能做什么?怎么做到的?
天天Lotay
2023/03/01
2K0
Apple主推的智能家居是什么、怎么用?一篇文章带你从零完全入门 HomeKit
[物联网] 3.3 "上云"的各种姿势
与全球网络相连接 有两种让设备连接到网络的方式,一种是由设备本身直接连接全球网络,另一种是在本地区域内使用网关来连接全球网络(图 3.21)。近来,“生活记录”型的设备越来越多,其结构更接近前面说的第二种方式,例如通过蓝牙把可穿戴设备和智能手机配对,通过智能手机向服务器发送数据。
科控物联
2022/03/29
1K0
[物联网] 3.3 "上云"的各种姿势
推荐阅读
相关推荐
【图解】物联网设备的N种Wi-Fi配网方式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验