WPF CheckBox 控件详解与使用
---WPF 中的控件(6)--复选框控件(CheckBox)
文章目录
- [WPF CheckBox 控件详解与使用](#WPF CheckBox 控件详解与使用)
-
- 一、控件基础概述
-
- [1. 命名空间与继承链](#1. 命名空间与继承链)
- 二、核心属性详解
- 三、核心事件详解
- 四、基础使用示例(带详细注释)
-
-
- [UI 部分](#UI 部分)
- [后台 C# 代码](# 代码)
-
- 五、三态模式使用(IsThreeState)
- [六、MVVM 数据绑定示例](#六、MVVM 数据绑定示例)
-
- [1.ViewModelBase 代码](#1.ViewModelBase 代码)
- [2. ViewModel 代码(实现属性通知)](#2. ViewModel 代码(实现属性通知))
- [3. XAML 绑定](#3. XAML 绑定)
- 七、自定义样式与外观
- 八、常见场景与注意事项
- 九、源码
- 十、显示效果
官方文档 CheckBox 类
CheckBox 是 WPF 中最常用的 选择类控件 ,继承自 ToggleButton,核心特性是支持 多选 、可选的 三态模式(勾选/未勾选/不确定),常用于协议同意、功能开关、批量选项筛选等场景。
一、控件基础概述
1. 命名空间与继承链
- 命名空间:
System.Windows.Controls - 继承链:
CheckBox → ToggleButton → ButtonBase → ContentControl → Control → ... - 核心特性:
- 默认支持 两种状态 :勾选(
IsChecked=true)、未勾选(IsChecked=false) - 开启
IsThreeState后支持 三种状态 :新增「不确定」(IsChecked=null) - 属于
ContentControl,内容可承载任意 WPF 元素(文字、图片、布局面板等)
- 默认支持 两种状态 :勾选(
二、核心属性详解
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
IsChecked |
bool?(可空布尔) |
false |
控件的勾选状态:true = 勾选,false = 未勾选,null = 不确定 |
IsThreeState |
bool |
false |
是否启用三态模式;启用后用户点击可循环切换三种状态 |
Content |
object |
空 | 显示的内容,支持文本、图片、布局等任意元素 |
IsEnabled |
bool |
true |
控件是否可用,禁用后呈灰色且无法交互 |
Command |
ICommand |
空 | 点击时执行的命令,MVVM 场景常用 |
CommandParameter |
object |
空 | 传递给 Command 的参数 |
Foreground/Background |
Brush |
系统默认 | 文字颜色/背景色 |
FontSize/FontFamily |
数值/字体 | 系统默认 | 文字大小与字体 |
三、核心事件详解
| 事件名 | 触发时机 |
|---|---|
Checked |
当 IsChecked 变为 true(勾选)时触发 |
Unchecked |
当 IsChecked 变为 false(未勾选)时触发 |
Indeterminate |
当 IsChecked 变为 null(不确定)时触发(仅三态模式下用户可触发) |
Click |
控件被点击时触发(无论状态是否变化) |
四、基础使用示例(带详细注释)
UI 部分
xaml
<!--CheckBox 基础示例-->
<StackPanel Margin="20" >
<!-- 1. 最基础的CheckBox:绑定勾选/取消勾选事件 -->
<CheckBox x:Name="chkAgreement"
Content="我已阅读并同意用户服务协议"
FontSize="14"
Checked="ChkAgreement_Checked"
Unchecked="ChkAgreement_Unchecked"/>
<!-- 2. 初始状态为勾选的CheckBox -->
<CheckBox x:Name="chkAutoLogin"
Content="自动登录账号"
IsChecked="True"
FontSize="14"/>
<!-- 3. 禁用状态的CheckBox -->
<CheckBox x:Name="chkDisabled"
Content="该选项暂不可用"
IsEnabled="False"
FontSize="14"/>
<!-- 4. 图文混排CheckBox(Content承载复杂内容) -->
<CheckBox x:Name="chkImage"
FontSize="14">
<!-- Content可以是任意布局,实现图文组合 -->
<StackPanel Orientation="Horizontal" >
<Image Source="/Tank.png" Width="18" Height="18"/>
<TextBlock Text="开启消息提醒" Margin="8,2"/>
</StackPanel>
</CheckBox>
<!-- 按钮:获取所有CheckBox状态 -->
<Button Content="获取选中状态"
Width="120" Height="30"
Click="BtnGetState_Click"
Margin="0 10 0 0" HorizontalAlignment="Left"/>
</StackPanel>
后台 C# 代码
#region CheckBox 基础示例
/// <summary>
/// CheckBox勾选时触发
/// </summary>
private void ChkAgreement_Checked(object sender, RoutedEventArgs e)
{
// sender 是触发事件的控件对象,强转为CheckBox即可访问属性
if (sender is CheckBox checkBox)
{
MessageBox.Show($"已勾选:{checkBox.Content}");
}
}
/// <summary>
/// CheckBox取消勾选时触发
/// </summary>
private void ChkAgreement_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"已取消勾选:{checkBox.Content}");
}
}
/// <summary>
/// 按钮点击:获取CheckBox状态
/// </summary>
private void BtnGetState_Click(object sender, RoutedEventArgs e)
{
// 注意:IsChecked是bool?可空类型,必须用 == true 判断
string result = $"协议勾选:{chkAgreement.IsChecked == true}\n" +
$"自动登录:{chkAutoLogin.IsChecked == true}\n" +
$"消息提醒:{chkImage.IsChecked == true}";
MessageBox.Show(result, "当前状态");
}
#endregion CheckBox 基础示例
⚠️ 注意:
IsChecked是bool?(可空布尔),不能直接写if (chk.IsChecked),必须显式判断== true,否则null和false都会被判定为 false。
五、三态模式使用(IsThreeState)
当需要表示「部分选中」「不确定」状态时(例如树形结构的父节点、批量选择的全选框),启用三态模式。
示例代码
xaml
<!-- 全选框:启用三态模式 -->
<CheckBox x:Name="chkAll" Margin="10 10 0 0"
Content="全选框:启用三态模式"
FontSize="14"
IsThreeState="True"
Checked="ChkAll_Checked"
Unchecked="ChkAll_Unchecked"
Indeterminate="ChkAll_Indeterminate"/>
c#
#region 全选框:启用三态模式
/// <summary>
/// 全选框:启用三态模式-已选中(已选中)
/// </summary>
private void ChkAll_Checked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-已选中(已选中)");
}
}
/// <summary>
/// 全选框:启用三态模式-未选中(已清除)
/// </summary>
private void ChkAll_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-未选中(已清除)");
}
}
/// <summary>
/// 全选框:启用三态模式-不确定
/// </summary>
private void ChkAll_Indeterminate(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-不确定");
}
}
#endregion 全选框:启用三态模式
提示:即使
IsThreeState=false,代码也可以手动给IsChecked赋值null,只是用户无法通过点击切换到该状态。
六、MVVM 数据绑定示例
WPF 推荐使用 MVVM 模式,通过绑定实现 UI 与逻辑解耦。
1.ViewModelBase 代码
c#
using System.ComponentModel;
using System.Runtime.CompilerServices;
/// <summary>
/// ViewModel 基类,实现 INotifyPropertyChanged 接口
/// 负责属性变更通知,当属性值改变时通知 UI 刷新
/// </summary>
public class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// 属性变更事件,WPF 绑定系统会自动监听此事件
/// 当属性值变化时触发该事件,UI 随之更新
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 触发属性变更通知
/// </summary>
/// <param name="propertyName"> 变更的属性名,使用 CallerMemberName 自动获取 </param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
// 如果有订阅者,则触发事件
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 通用属性设置方法,自动判断值是否变化并触发通知
/// 简化属性定义,避免在每个属性中重复编写判断逻辑
/// </summary>
/// <typeparam name="T"> 属性类型 </typeparam>
/// <param name="field"> backing field 引用 </param>
/// <param name="newValue"> 新值 </param>
/// <param name="propertyName"> 属性名(自动获取)</param>
/// <returns> 值是否发生了变化 </returns>
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{
// 等值预判断:使用默认相等比较器比较当前字段值与新值
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
// 值未变化,直接返回 false,不触发任何事件
return false;
}
//触发「属性即将变更」事件(INotifyPropertyChanging)件发生在赋值之前,订阅者可以在此获取旧值
//OnPropertyChanging(propertyName);
//实际赋值
field = newValue;
//触发「属性已变更」事件(INotifyPropertyChanged)。事件发生在赋值之后,通知 UI 绑定重新读取属性值
OnPropertyChanged(propertyName);
// 返回 true 表示属性值已成功变更并发出通知
return true;
}
/// <summary>
/// 通过回调委托为外部模型对象设置属性值,并在值变更时触发变更通知事件
/// 用于为不实现变更通知的外部模型类(如 DTO、数据库实体、第三方模型)包装可观察的代理属性。
/// </summary>
/// <typeparam name="TModel"> 目标模型的类型,必须为引用类型 </typeparam>
/// <typeparam name="T"> 属性值的类型 </typeparam>
/// <param name="oldValue"> 模型属性的当前值(无法直接读取模型字段,需调用方手动传入)</param>
/// <param name="newValue"> 属性的新值 </param>
/// <param name="model"> 目标模型对象实例 </param>
/// <param name="callback"> 赋值回调:接收模型实例和新值,执行具体的属性赋值操作 </param>
/// <param name="propertyName"> 属性名称,默认由编译器自动填充 </param>
/// <returns> 若值发生了实际变更则返回 true;若新旧值相等则返回 false </returns>
protected bool SetProperty<TModel, T>(T oldValue,T newValue,TModel model,Action<TModel, T> callback, [CallerMemberName] string? propertyName = null) where TModel : class
{
// 参数非空校验
ArgumentNullException.ThrowIfNull(model);
ArgumentNullException.ThrowIfNull(callback);
// 等值判断
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
// 值未变化,直接返回
return false;
}
// 触发「属性即将变更」事件
//OnPropertyChanging(propertyName);
// 执行赋值回调:将新值写入模型对象的对应属性
// 具体赋值逻辑由调用方通过 callback 委托传入,本方法不关心模型的内部结构
callback(model, newValue);
// 触发「属性已变更」事件,通知 UI 更新
OnPropertyChanged(propertyName);
//返回 true 表示属性值已成功变更
return true;
}
}
2. ViewModel 代码(实现属性通知)
c#
using MVVM_manual.Core;
public class SettingViewModel : ViewModelBase
{
// backing field:后台存储字段
private bool? _isAutoUpdate=false;
private bool _isOpenNotification;
private string chkResult = "--";
public bool? IsAutoUpdate
{
get => _isAutoUpdate;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty <T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isAutoUpdate, value))
{
if (_isAutoUpdate==null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
public string ChkResult
{
get => chkResult;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty <T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
SetProperty(ref chkResult, value);
}
}
public bool IsOpenNotification
{
get => _isOpenNotification;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty <T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isOpenNotification, value))
{
if (_isAutoUpdate == null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
}
3. XAML 绑定
xaml
<!-- 设置DataContext,关联ViewModel -->
<Window.DataContext>
<local:SettingViewModel/>
</Window.DataContext>
<TextBlock Text="MVVM 数据绑定示例 " />
<StackPanel Margin="20" >
<!-- 双向绑定IsChecked,Mode=TwoWay可省略(CheckBox默认双向) -->
<CheckBox Content="自动更新软件(三态)"
IsThreeState="True"
IsChecked="{Binding IsAutoUpdate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="14"/>
<CheckBox Content="开启桌面通知(二态)"
IsChecked="{Binding IsOpenNotification, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="14"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="两个CheckBox的选择结果:" Margin="10,2" />
<TextBox Text="{Binding ChkResult, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,2"/>
</StackPanel>
</StackPanel>
七、自定义样式与外观
WPF 原生 CheckBox 的勾选框由系统渲染,若需自定义颜色、形状,需重写 ControlTemplate。
简易自定义样式示例
xaml
<Window.Resources>
<!-- 自定义CheckBox样式,修改勾选框颜色 -->
<Style x:Key="CustomCheckBoxStyle" TargetType="CheckBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0 5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<!-- 外层布局:勾选框 + 内容 -->
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<!-- 自定义勾选框边框 -->
<Border x:Name="Border"
Width="16" Height="16"
BorderBrush="#333"
BorderThickness="1"
Background="White"
CornerRadius="2">
<!-- 勾选标记:默认隐藏 -->
<Path x:Name="CheckMark"
Width="10" Height="10"
Data="M 2 5 L 5 8 L 9 2"
Stroke="#2196F3"
StrokeThickness="2"
Visibility="Collapsed"/>
</Border>
</BulletDecorator.Bullet>
<!-- 内容显示区 -->
<ContentPresenter Margin="8 0 0 0"
VerticalAlignment="Center"/>
</BulletDecorator>
<!-- 触发器:根据IsChecked状态切换显示 -->
<ControlTemplate.Triggers>
<!-- 勾选状态:显示对勾 -->
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
</Trigger>
<!-- 不确定状态:显示方块 -->
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
<Setter TargetName="CheckMark" Property="Data" Value="M 3 3 L 13 3 L 13 13 L 3 13 Z"/>
<Setter TargetName="CheckMark" Property="Fill" Value="#2196F3"/>
</Trigger>
<!-- 禁用状态 -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="#EEE"/>
<Setter Property="Foreground" Value="#999"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<!-- 使用自定义样式 -->
<CheckBox Content="自定义样式勾选框" Style="{StaticResource CustomCheckBoxStyle}"/>
八、常见场景与注意事项
- 批量获取选中项 多个 CheckBox 放在容器中时,可通过遍历容器子元素、或绑定集合的方式获取选中状态;推荐使用数据绑定(
ObservableCollection+ 项模板)实现批量管理。 - 与 RadioButton 的区别
CheckBox:支持多选,彼此无互斥关系RadioButton:同组内单选,必须通过GroupName分组实现互斥
- 性能优化 大量 CheckBox 列表场景,使用
VirtualizingStackPanel开启虚拟化,避免一次性渲染所有控件。 - 三态使用限制
Indeterminate状态仅用于表示「部分选中」的中间态,不建议作为独立的业务值存储,最终业务数据应映射为明确的 true/false。
九、源码
1.UI
xaml
<Window x:Class="CheckBoxCtrl.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:CheckBoxCtrl"
mc:Ignorable="d"
Title="CheckBox 示例" Height="600" Width="800">
<!-- 设置DataContext,关联ViewModel -->
<Window.DataContext>
<local:SettingViewModel/>
</Window.DataContext>
<Window.Resources>
<!-- 自定义CheckBox样式,修改勾选框颜色 -->
<Style x:Key="CustomCheckBoxStyle" TargetType="CheckBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0 5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<!-- 外层布局:勾选框 + 内容 -->
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<!-- 自定义勾选框边框 -->
<Border x:Name="Border"
Width="16" Height="16"
BorderBrush="#333"
BorderThickness="1"
Background="White"
CornerRadius="2">
<!-- 勾选标记:默认隐藏 -->
<Path x:Name="CheckMark"
Width="10" Height="10"
Data="M 2 5 L 5 8 L 9 2"
Stroke="#2196F3"
StrokeThickness="2"
Visibility="Collapsed"/>
</Border>
</BulletDecorator.Bullet>
<!-- 内容显示区 -->
<ContentPresenter Margin="8 0 0 0"
VerticalAlignment="Center"/>
</BulletDecorator>
<!-- 触发器:根据IsChecked状态切换显示 -->
<ControlTemplate.Triggers>
<!-- 勾选状态:显示对勾 -->
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
</Trigger>
<!-- 不确定状态:显示方块 -->
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
<Setter TargetName="CheckMark" Property="Data" Value="M 3 3 L 13 3 L 13 13 L 3 13 Z"/>
<Setter TargetName="CheckMark" Property="Fill" Value="#2196F3"/>
</Trigger>
<!-- 禁用状态 -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="#EEE"/>
<Setter Property="Foreground" Value="#999"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock Text="CheckBox 基础示例 " />
<StackPanel Margin="20" >
<!-- 1. 最基础的CheckBox:绑定勾选/取消勾选事件 -->
<CheckBox x:Name="chkAgreement"
Content="我已阅读并同意用户服务协议"
FontSize="14"
Checked="ChkAgreement_Checked"
Unchecked="ChkAgreement_Unchecked"/>
<!-- 2. 初始状态为勾选的CheckBox -->
<CheckBox x:Name="chkAutoLogin"
Content="自动登录账号"
IsChecked="True"
FontSize="14"/>
<!-- 3. 禁用状态的CheckBox -->
<CheckBox x:Name="chkDisabled"
Content="该选项暂不可用"
IsEnabled="False"
FontSize="14"/>
<!-- 4. 图文混排CheckBox(Content承载复杂内容) -->
<CheckBox x:Name="chkImage"
FontSize="14">
<!-- Content可以是任意布局,实现图文组合 -->
<StackPanel Orientation="Horizontal" >
<Image Source="/Tank.png" Width="18" Height="18"/>
<TextBlock Text="开启消息提醒" Margin="8,2"/>
</StackPanel>
</CheckBox>
<!-- 按钮:获取所有CheckBox状态 -->
<Button Content="获取选中状态"
Width="120" Height="30"
Click="BtnGetState_Click"
Margin="0 10 0 0" HorizontalAlignment="Left"/>
</StackPanel>
<Separator Background="Red" Height="10" Margin="20,2" />
<TextBlock Text="全选框:启用三态模式 " />
<CheckBox x:Name="chkAll" Margin="10 10 0 0"
Content="全选框:启用三态模式"
FontSize="14"
IsThreeState="True"
Checked="ChkAll_Checked"
Unchecked="ChkAll_Unchecked"
Indeterminate="ChkAll_Indeterminate"/>
<Separator Background="Red" Height="10" Margin="20,2" />
<TextBlock Text="MVVM 数据绑定示例 " />
<StackPanel Margin="20" >
<!-- 双向绑定IsChecked,Mode=TwoWay可省略(CheckBox默认双向) -->
<CheckBox Content="自动更新软件(三态)"
IsThreeState="True"
IsChecked="{Binding IsAutoUpdate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="14"/>
<CheckBox Content="开启桌面通知(二态)"
IsChecked="{Binding IsOpenNotification, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="14"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="两个CheckBox的选择结果:" Margin="10,2" />
<TextBox Text="{Binding ChkResult, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,2"/>
</StackPanel>
</StackPanel>
<Separator Background="Red" Height="10" Margin="20,2" />
<TextBlock Text="使用自定义样式" />
<CheckBox Content="自定义样式勾选框" Style="{StaticResource CustomCheckBoxStyle}" Margin="10,2" />
</StackPanel>
</Window>
2.代码
c#
//1
using System.Windows;
using System.Windows.Controls;
namespace CheckBoxCtrl
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
#region CheckBox 基础示例
/// <summary>
/// CheckBox勾选时触发
/// </summary>
private void ChkAgreement_Checked(object sender, RoutedEventArgs e)
{
// sender 是触发事件的控件对象,强转为CheckBox即可访问属性
if (sender is CheckBox checkBox)
{
MessageBox.Show($"已勾选:{checkBox.Content}");
}
}
/// <summary>
/// CheckBox取消勾选时触发
/// </summary>
private void ChkAgreement_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"已取消勾选:{checkBox.Content}");
}
}
/// <summary>
/// 按钮点击:获取CheckBox状态
/// </summary>
private void BtnGetState_Click(object sender, RoutedEventArgs e)
{
// 注意:IsChecked是bool?可空类型,必须用 == true 判断
string result = $"协议勾选:{chkAgreement.IsChecked == true}\n" +
$"自动登录:{chkAutoLogin.IsChecked == true}\n" +
$"消息提醒:{chkImage.IsChecked == true}";
MessageBox.Show(result, "当前状态");
}
#endregion CheckBox 基础示例
#region 全选框:启用三态模式
/// <summary>
/// 全选框:启用三态模式-已选中(已选中)
/// </summary>
private void ChkAll_Checked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-已选中(已选中)");
}
}
/// <summary>
/// 全选框:启用三态模式-未选中(已清除)
/// </summary>
private void ChkAll_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-未选中(已清除)");
}
}
/// <summary>
/// 全选框:启用三态模式-不确定
/// </summary>
private void ChkAll_Indeterminate(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox)
{
MessageBox.Show($"启用三态模式-不确定");
}
}
#endregion 全选框:启用三态模式
}
}
//2
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MVVM_manual.Core
{
/// <summary>
/// ViewModel 基类,实现 INotifyPropertyChanged 接口
/// 负责属性变更通知,当属性值改变时通知 UI 刷新
/// </summary>
public class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// 属性变更事件,WPF 绑定系统会自动监听此事件
/// 当属性值变化时触发该事件,UI 随之更新
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 触发属性变更通知
/// </summary>
/// <param name="propertyName">变更的属性名,使用 CallerMemberName 自动获取</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
// 如果有订阅者,则触发事件
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 通用属性设置方法,自动判断值是否变化并触发通知
/// 简化属性定义,避免在每个属性中重复编写判断逻辑
/// </summary>
/// <typeparam name="T">属性类型</typeparam>
/// <param name="field"> backing field 引用</param>
/// <param name="newValue">新值</param>
/// <param name="propertyName">属性名(自动获取)</param>
/// <returns>值是否发生了变化</returns>
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{
// 等值预判断:使用默认相等比较器比较当前字段值与新值
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
// 值未变化,直接返回 false,不触发任何事件
return false;
}
//触发「属性即将变更」事件(INotifyPropertyChanging)件发生在赋值之前,订阅者可以在此获取旧值
//OnPropertyChanging(propertyName);
//实际赋值
field = newValue;
//触发「属性已变更」事件(INotifyPropertyChanged)。事件发生在赋值之后,通知 UI 绑定重新读取属性值
OnPropertyChanged(propertyName);
// 返回 true 表示属性值已成功变更并发出通知
return true;
}
//protected bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
//{
// // 等值预判断:使用默认相等比较器比较当前字段值与新值
// if (EqualityComparer<T>.Default.Equals(field, newValue))
// {
// // 值未变化,直接返回 false,不触发任何事件
// return false;
// }
// //触发「属性即将变更」事件(INotifyPropertyChanging)件发生在赋值之前,订阅者可以在此获取旧值
// //OnPropertyChanging(propertyName);
// //实际赋值
// field = newValue;
// //触发「属性已变更」事件(INotifyPropertyChanged)。事件发生在赋值之后,通知 UI 绑定重新读取属性值
// OnPropertyChanged(propertyName);
// // 返回 true 表示属性值已成功变更并发出通知
// return true;
//}
/// <summary>
/// 通过回调委托为外部模型对象设置属性值,并在值变更时触发变更通知事件
/// 用于为不实现变更通知的外部模型类(如 DTO、数据库实体、第三方模型)包装可观察的代理属性。
/// </summary>
/// <typeparam name="TModel">目标模型的类型,必须为引用类型</typeparam>
/// <typeparam name="T">属性值的类型</typeparam>
/// <param name="oldValue">模型属性的当前值(无法直接读取模型字段,需调用方手动传入)</param>
/// <param name="newValue">属性的新值</param>
/// <param name="model">目标模型对象实例</param>
/// <param name="callback">赋值回调:接收模型实例和新值,执行具体的属性赋值操作</param>
/// <param name="propertyName">属性名称,默认由编译器自动填充</param>
/// <returns>若值发生了实际变更则返回 true;若新旧值相等则返回 false</returns>
protected bool SetProperty<TModel, T>(T oldValue,T newValue,TModel model,Action<TModel, T> callback,[CallerMemberName] string? propertyName = null) where TModel : class
{
// 参数非空校验
ArgumentNullException.ThrowIfNull(model);
ArgumentNullException.ThrowIfNull(callback);
// 等值判断
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
// 值未变化,直接返回
return false;
}
// 触发「属性即将变更」事件
//OnPropertyChanging(propertyName);
// 执行赋值回调:将新值写入模型对象的对应属性
// 具体赋值逻辑由调用方通过 callback 委托传入,本方法不关心模型的内部结构
callback(model, newValue);
// 触发「属性已变更」事件,通知 UI 更新
OnPropertyChanged(propertyName);
//返回 true 表示属性值已成功变更
return true;
}
}
}
//3
using MVVM_manual.Core;
namespace CheckBoxCtrl
{
public class SettingViewModel : ViewModelBase
{
// backing field:后台存储字段
private bool? _isAutoUpdate=false;
private bool _isOpenNotification;
private string chkResult = "--";
public bool? IsAutoUpdate
{
get => _isAutoUpdate;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isAutoUpdate, value))
{
if (_isAutoUpdate==null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
public string ChkResult
{
get => chkResult;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
SetProperty(ref chkResult, value);
}
}
public bool IsOpenNotification
{
get => _isOpenNotification;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isOpenNotification, value))
{
if (_isAutoUpdate == null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
}
}
using MVVM_manual.Core;
namespace CheckBoxCtrl
{
public class SettingViewModel : ViewModelBase
{
// backing field:后台存储字段
private bool? _isAutoUpdate=false;
private bool _isOpenNotification;
private string chkResult = "--";
public bool? IsAutoUpdate
{
get => _isAutoUpdate;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isAutoUpdate, value))
{
if (_isAutoUpdate==null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
public string ChkResult
{
get => chkResult;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
SetProperty(ref chkResult, value);
}
}
public bool IsOpenNotification
{
get => _isOpenNotification;
set
{
// 使用基类 SetProperty 自动判断并触发通知
//bool SetProperty<T>(ref T field, T newValue,[CallerMemberName] string? propertyName = null)
if (SetProperty(ref _isOpenNotification, value))
{
if (_isAutoUpdate == null) ChkResult = $"IsAutoUpdate=null,IsOpenNotification={_isOpenNotification.ToString()}";
else ChkResult = $"IsAutoUpdate={_isAutoUpdate.ToString()},IsOpenNotification={_isOpenNotification.ToString()}";
}
}
}
}
}
十、显示效果
