命名空间:CConUI
继承:System.Windows.Forms.Control
可见性:public class CheckBox
默认尺寸:130 × 20
默认事件:CheckedChanged(设计器双击控件即生成该事件处理器)
定位:自绘多选框——左侧圆角方框 + 勾号/复选方块 + 右侧文字;支持深色模式配色、三态复选(未选 / 选中 / 复选),自动响应鼠标悬停与按下状态。
一、作用
完全自绘的多选框控件,具备以下特性:
二态模式(默认):点击在 [未选] ↔ [选中] 之间切换;
三态模式:ThreeState = true 时按 [未选] → [选中] → [复选] → [未选] 循环;
自绘外观:圆角方框 + 白色勾号 / 内部实心方块 + 右侧文字;
状态配色:边框有 常态 / 悬停 / 按下 三态颜色;选中 / 复选时边框与填充自动使用 CheckedColor;
尺寸自适应:方框边长 = Height - BoxPadding × 2,高度决定方框大小;
透明背景:启用 SupportsTransparentBackColor,背景由父容器透出;
文字:方框右侧偏移 6px 绘制 Text,ForeColor / Font 可控,超长自动省略号。
三种状态视觉对照
| 状态 | 外框 | 内部 | 边框色 |
|---|
| 未选 | 圆角方框,镂空 | 无 | 按悬停 / 按下 / 常态取色 |
选中(Checked=true) | 圆角方框,实心填充 CheckedColor | 白色勾号 ✓ | CheckedColor(忽略悬停/按下) |
复选(Indeterminate=true,需 ThreeState=true) | 圆角方框,镂空 | 内部实心小方块(CheckedColor) | 按悬停 / 按下 / 常态取色 |
二、快速上手
using System.Drawing;
using System.Windows.Forms;
using CConUI;
// 1. 创建控件
var cb = new CheckBox();
// 2. 放入容器
cb.Parent = panel1;
cb.Location = new Point(20, 20);
cb.Text = "同意用户协议";
// 3. 订阅状态变化
cb.CheckedChanged += (s, e) =>
{
var c = (CheckBox)s;
Console.WriteLine(c.Text + " → Checked=" + c.Checked + ",Indeterminate=" + c.Indeterminate);
};
// 4. 外观自定义(可选)
cb.CheckedColor = Color.FromArgb(88, 166, 255);
cb.BorderColor = Color.FromArgb(139, 148, 158);
cb.HoverColor = Color.FromArgb(173, 186, 199);
cb.PressedColor = Color.FromArgb(88, 166, 255);
cb.CornerRadius = 3f;
cb.BorderThickness = 2f;
cb.BoxPadding = 0;
cb.ForeColor = Color.White;
cb.Font = new Font("Microsoft YaHei", 9F);
// 5. 开启三态(可选)
cb.ThreeState = true;
三、构造函数
public CheckBox()
| 项目 | 说明 |
|---|
| 参数 | 无 |
| 作用 | 初始化自绘多选框 |
| 实际行为 | ① 启用 UserPaint | AllPaintingInWmPaint | OptimizedDoubleBuffer | ResizeRedraw | SupportsTransparentBackColor 样式; ② 设置 BackColor = Color.Transparent; ③ 设置 Size = (130, 20); ④ 设置 ForeColor = Color.FromArgb(238, 238, 238); ⑤ 设置 Cursor = Cursors.Hand |
| 默认尺寸 | 130 × 20 |
| 默认状态 | Checked = false、Indeterminate = false、ThreeState = false |
| 默认外观 | CornerRadius = 3f、BorderThickness = 2f、BoxPadding = 0、InnerBoxPadding = 3f |
var cb = new CheckBox
{
Parent = panel1,
Text = "选项 A"
};
四、公开属性
4.1 状态属性
public bool Checked
| 项目 | 说明 |
|---|
| 类型 | bool |
| 默认 | false |
| Category | CCon |
| DefaultValue | false |
| 作用 | 控件的选中状态 |
| 副作用(值有变化时) | ① 新值为 true 时自动把 Indeterminate 置为 false(互斥); ② 触发 CheckedChanging(Checked 仍为旧值); ③ 写入新值; ④ 触发 CheckedChanged; ⑤ Invalidate() 重绘 |
| 幂等 | 赋相同值直接返回,不触发事件、不重绘 |
cb.Checked = true; // 触发 CheckedChanging + CheckedChanged
cb.Checked = true; // 相同值,不触发
cb.Checked = false; // 触发事件
public bool Indeterminate
| 项目 | 说明 |
|---|
| 类型 | bool |
| 默认 | false |
| Category | CCon |
| DefaultValue | false |
| 作用 | 复选(半选)状态,视觉上表现为外框镂空 + 内部实心小方块 |
| 约束 | 只在 ThreeState = true 时有效: 若 ThreeState = false 且试图设为 true,静默忽略(保持原值,不抛异常,不触发事件) |
| 副作用(值有变化时) | ① 新值为 true 时自动把 Checked 置为 false(互斥); ② 触发 CheckedChanging; ③ 写入新值; ④ 触发 CheckedChanged; ⑤ Invalidate() 重绘 |
| 幂等 | 赋相同值直接返回 |
cb.ThreeState = true;
cb.Indeterminate = true; // OK
cb.ThreeState = false;
cb.Indeterminate = true; // 无效(静默忽略)
public bool ThreeState
| 项目 | 说明 |
|---|
| 类型 | bool |
| 默认 | false |
| Category | CCon |
| DefaultValue | false |
| 作用 | 是否开启三态复选模式:
false → 二态:未选 ↔ 选中;
true → 三态循环:未选 → 选中 → 复选 → 未选 |
| 副作用 | ① 赋值后 Invalidate(); ② 由 true 改 false 且当前 Indeterminate = true 时,会主动调用 Indeterminate = false(触发相关事件) |
| 幂等 | 赋相同值直接返回 |
cb.ThreeState = true; // 开启三态
cb.ThreeState = false; // 关闭三态,若当前是复选则自动回到未选
4.2 尺寸与形状属性
public int BoxPadding
| 项目 | 说明 |
|---|
| 类型 | int(像素) |
| 默认 | 0 |
| Category | CCon |
| DefaultValue | 0 |
| 作用 | 方框与控件上下边缘之间的内边距:
0 → 方框边长 = 控件高度;
> 0 → 方框边长 = Height - BoxPadding × 2 |
| 取值范围 | Math.Max(0, value)——不能为负 |
| 副作用 | 赋值后 Invalidate() 重绘 |
cb.BoxPadding = 0; // 方框紧贴上下边缘(默认)
cb.BoxPadding = 2; // 方框边长 = 高度 - 4
public float CornerRadius
| 项目 | 说明 |
|---|
| 类型 | float(像素) |
| 默认 | 3f |
| Category | CCon |
| DefaultValue | 3f |
| 作用 | 方框的圆角半径。0 为直角,值越大越圆 |
| 取值范围 | Math.Max(0f, value)——不能为负 |
| 绘制保护 | 内部限制为 min(radius, min(width, height) / 2),保证圆角不超出方框 |
cb.CornerRadius = 0f; // 直角
cb.CornerRadius = 3f; // 默认
cb.CornerRadius = 8f; // 大圆角
public float BorderThickness
| 项目 | 说明 |
|---|
| 类型 | float(像素) |
| 默认 | 2f |
| Category | CCon |
| DefaultValue | 2f |
| 作用 | 方框边框的线宽 |
| 取值范围 | Math.Max(0.5f, value)——最小 0.5f |
| 绘制处理 | 绘制前 boxRect 向内缩 BorderThickness / 2,避免画笔居中描边时超出外接矩形 |
cb.BorderThickness = 2f;
cb.BorderThickness = 1f; // 更细
cb.BorderThickness = 4f; // 更粗
public float InnerBoxPadding
| 项目 | 说明 |
|---|
| 类型 | float(像素) |
| 默认 | 3f(字段初值;DefaultValue 标注为 2f,见注意事项) |
| Category | CCon |
| DefaultValue | 2f(属性特性标注) |
| 作用 | 复选状态下内部小实心方块距离外框边缘的内边距。数值越小内部方块越大 |
| 取值范围 | Math.Max(0f, value)——不能为负 |
| 绘制保护 | 内部方块边长 = boxRect.Width - InnerBoxPadding × 2;≤0 时不绘制内部方块 |
cb.ThreeState = true;
cb.Indeterminate = true;
cb.InnerBoxPadding = 3f; // 默认
cb.InnerBoxPadding = 1f; // 内部方块更大
cb.InnerBoxPadding = 6f; // 内部方块更小
4.3 颜色属性
public Color BorderColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(139, 148, 158)(中灰) |
| Category | CCon |
| 作用 | 未选 / 复选状态下、常态(未悬停、未按下)的边框颜色 |
public Color CheckedColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(88, 166, 255)(亮蓝) |
| Category | CCon |
| 作用 | 选中时的外框填充色和边框色,以及复选时内部小方块的颜色 |
public Color HoverColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(173, 186, 199)(浅灰) |
| Category | CCon |
| 作用 | 未选 / 复选状态下、鼠标悬停时的边框颜色 |
public Color PressedColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(88, 166, 255)(亮蓝) |
| Category | CCon |
| 作用 | 未选 / 复选状态下、鼠标按下时的边框颜色 |
cb.BorderColor = Color.FromArgb(139, 148, 158);
cb.CheckedColor = Color.FromArgb(88, 166, 255);
cb.HoverColor = Color.FromArgb(173, 186, 199);
cb.PressedColor = Color.FromArgb(88, 166, 255);
4.4 重写的外观属性
public override string Text
| 项目 | 说明 |
|---|
| 类型 | string |
| Category | CCon |
| 作用 | 方框右侧显示的文字;空串时不绘制 |
| 副作用 | 赋值后 Invalidate() |
| 绘制 | 起始 X = 方框右边缘(含 padding)+ 6px;LineAlignment = Center 垂直居中;超长自动省略号 |
public override Color ForeColor
| 项目 | 说明 |
|---|
| 类型 | Color |
| 默认 | Color.FromArgb(238, 238, 238)(浅白) |
| Category | CCon |
| 作用 | 文字颜色 |
public override Font Font
| 项目 | 说明 |
|---|
| 类型 | Font |
| Category | CCon |
| 作用 | 文字字体 |
五、公开事件
public event EventHandler CheckedChanged
| 项目 | 说明 |
|---|
| 参数 | EventHandler:sender 为当前 CheckBox,e 为 EventArgs.Empty |
| Category | CCon |
| 默认事件 | 是([DefaultEvent("CheckedChanged")])——设计器双击即生成 |
| 触发时机 | Checked 或 Indeterminate 状态改变后触发 |
| 不触发情况 | ① 赋相同值;② Indeterminate 在 ThreeState = false 下被设置(静默忽略) |
| 触发顺序 | CheckedChanging → 更新状态 → CheckedChanged |
public event EventHandler CheckedChanging
| 项目 | 说明 |
|---|
| 参数 | EventHandler |
| Category | CCon |
| 触发时机 | Checked 或 Indeterminate 状态即将改变前——此时两个属性仍是旧值 |
| 不触发情况 | 与 CheckedChanged 相同 |
| 用途 | 记录切换前的状态;EventArgs 为空,不支持取消 |
bool _oldChecked = false;
bool _oldIndeterminate = false;
cb.CheckedChanging += (s, e) =>
{
var c = (CheckBox)s;
_oldChecked = c.Checked;
_oldIndeterminate = c.Indeterminate;
Console.WriteLine("即将改变:旧状态 Checked=" + _oldChecked + ",Indeterminate=" + _oldIndeterminate);
};
cb.CheckedChanged += (s, e) =>
{
var c = (CheckBox)s;
Console.WriteLine("已改变:新状态 Checked=" + c.Checked + ",Indeterminate=" + c.Indeterminate);
};
六、公开方法
CheckBox 没有新增 public 方法。所有公开方法继承自 Control(如 Invalidate()、Focus()、BringToFront() 等)。
内部重写方法(protected override,外部不可直接调用)
| 方法 | 可见性 | 作用 |
|---|
OnPaint(PaintEventArgs e) | protected override | 绘制:外框 → 勾号 / 内部方块 → 文字 |
OnMouseEnter(EventArgs e) | protected override | 设置 _isHover = true 并重绘 |
OnMouseLeave(EventArgs e) | protected override | 清除悬停与按下状态并重绘 |
OnMouseDown(MouseEventArgs e) | protected override | 左键按下时设置 _isPressed = true 并重绘 |
OnMouseUp(MouseEventArgs e) | protected override | 左键抬起时根据 ThreeState 循环切换状态 |
GetRoundedRectPath(RectangleF, float) | private | 生成圆角矩形路径的辅助方法 |
七、公开成员速查表
| 成员 | 来源 | 类型 | 说明 |
|---|
CheckBox() | 本类 | 构造函数 | 默认尺寸 130×20,透明背景,手型光标 |
Checked | 本类 | 属性(bool) | 选中状态;设为 true 时自动清空 Indeterminate |
Indeterminate | 本类 | 属性(bool) | 复选状态(仅 ThreeState=true 时有效);设为 true 时自动清空 Checked |
ThreeState | 本类 | 属性(bool) | 是否三态;关闭时若处于复选会重置为未选 |
BoxPadding | 本类 | 属性(int) | 方框距控件上下边缘的内边距(默认 0) |
CornerRadius | 本类 | 属性(float) | 方框圆角半径(默认 3f) |
BorderThickness | 本类 | 属性(float) | 方框边框粗细(默认 2f,≥0.5f) |
InnerBoxPadding | 本类 | 属性(float) | 复选内部小方块的内边距(默认 3f,≥0) |
BorderColor | 本类 | 属性(Color) | 常态边框颜色 |
CheckedColor | 本类 | 属性(Color) | 选中填充 / 边框 / 复选方块颜色 |
HoverColor | 本类 | 属性(Color) | 悬停边框颜色 |
PressedColor | 本类 | 属性(Color) | 按下边框颜色 |
Text(重写) | Control | 属性(string) | 方框右侧文字 |
ForeColor(重写) | Control | 属性(Color) | 文字颜色 |
Font(重写) | Control | 属性(Font) | 文字字体 |
BackColor | Control | 属性(Color) | 默认 Transparent;本类绘制不使用它,背景由父容器透出 |
Size / ClientSize | Control | 属性(Size) | 控件尺寸;高度决定方框大小 |
Cursor | Control | 属性 | 默认 Hand(手型) |
CheckedChanged | 本类 | 事件(默认事件) | 状态改变后触发 |
CheckedChanging | 本类 | 事件 | 状态改变前触发(属性仍是旧值) |
Invalidate() | Control | 方法 | 触发重绘 |
OnPaint(e) | 本类(override) | 方法(protected) | 绘制逻辑,外部不可直接调用 |
OnMouseEnter / OnMouseLeave / OnMouseDown / OnMouseUp | 本类(override) | 方法(protected) | 鼠标交互,外部不可直接调用 |
八、绘制原理(深入理解)
8.1 计算方框尺寸
float boxSize = this.Height - (this._boxPadding * 2f);
if (boxSize <= 4f) boxSize = 4f;
float boxX = this._boxPadding;
float boxY = (this.Height - boxSize) / 2f;
float halfThickness = this._borderThickness / 2f;
RectangleF boxRect = new RectangleF(
boxX + halfThickness, boxY + halfThickness,
boxSize - this._borderThickness, boxSize - this._borderThickness);
| 要点 | 说明 |
|---|
boxSize = Height - BoxPadding × 2 | 上下各留 BoxPadding,所以减去 × 2。
BoxPadding=0 时方框边长 = 控件高度,紧贴上下边缘 |
≤ 4f 兜底 | 控件过小或 padding 过大时强制 4,避免 0 或负值 |
boxY = (Height - boxSize) / 2 | 垂直居中 |
boxRect 内缩 halfThickness | Pen 居中描边时外边缘会超出路径 BorderThickness / 2。内缩半个线宽后,方框外边缘正好贴到 boxSize 区域,不会被裁剪 |
8.2 决定边框颜色(优先级)
Color currentBorder;
if (this._isPressed)
currentBorder = this._pressedColor;
else if (this._isHover)
currentBorder = this._hoverColor;
else
currentBorder = this._borderColor;
优先级:按下 > 悬停 > 常态。
8.3 绘制外框
using (var path = GetRoundedRectPath(boxRect, this._cornerRadius))
{
if (this._checked)
{
using (var brush = new SolidBrush(this._checkedColor))
g.FillPath(brush, path); // 选中时填充
}
Color drawBorderColor = this._checked ? this._checkedColor : currentBorder;
using (var pen = new Pen(drawBorderColor, this._borderThickness))
g.DrawPath(pen, path);
}| 状态 | 填充 | 边框色 |
|---|
Checked = true | CheckedColor 填充 | CheckedColor(忽略悬停 / 按下) |
Indeterminate = true | 不填充(镂空) | 按悬停 / 按下 / 常态 |
| 未选 | 不填充(镂空) | 按悬停 / 按下 / 常态 |
8.4 绘制勾号(选中时)
float cx1 = boxRect.X + boxRect.Width * 0.25f;
float cy1 = boxRect.Y + boxRect.Height * 0.50f;
float cx2 = boxRect.X + boxRect.Width * 0.45f;
float cy2 = boxRect.Y + boxRect.Height * 0.70f;
float cx3 = boxRect.X + boxRect.Width * 0.75f;
float cy3 = boxRect.Y + boxRect.Height * 0.30f;
float tickThickness = Math.Max(1.5f, boxSize * 0.12f);
using (var pen = new Pen(Color.White, tickThickness))
{
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
pen.LineJoin = LineJoin.Round;
g.DrawLines(pen, new PointF[] {
new PointF(cx1, cy1),
new PointF(cx2, cy2),
new PointF(cx3, cy3)
});
}| 要点 | 说明 |
|---|
| 三个点比例坐标 | (25%, 50%) → (45%, 70%) → (75%, 30%),形成标准 ✓ 形 |
tickThickness | 至少 1.5px,随方框大小按 12% 缩放,小方框也能看清 |
| 端点 / 连接样式 | Round:勾号两端和拐角圆润,视觉更顺滑 |
| 颜色 | 固定白色;如需自定义可修改源码 |
8.5 绘制复选内部方块(Indeterminate=true 时)
float innerSize = boxRect.Width - (this._innerBoxPadding * 2f);
if (innerSize > 0)
{
float innerX = boxRect.X + this._innerBoxPadding;
float innerY = boxRect.Y + this._innerBoxPadding;
RectangleF innerRect = new RectangleF(innerX, innerY, innerSize, innerSize);
using (var brush = new SolidBrush(this._checkedColor))
g.FillRectangle(brush, innerRect);
}| 要点 | 说明 |
|---|
innerSize = boxRect.Width - InnerBoxPadding × 2 | 左右各留 InnerBoxPadding,所以减去 × 2 |
innerSize ≤ 0 保护 | 内边距过大时不绘制内部方块(只剩外框,视觉上像未选) |
| 颜色 | 固定 CheckedColor |
| 形状 | 实心矩形(直角),与外框圆角形成视觉对比 |
8.6 绘制文字
float textX = boxX + boxSize + 6f;
RectangleF textRect = new RectangleF(textX, 0, this.Width - textX, this.Height);
using (var brush = new SolidBrush(this.ForeColor))
using (var sf = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center,
Trimming = StringTrimming.EllipsisCharacter
})
{
g.DrawString(this.Text, this.Font, brush, textRect, sf);
}| 要点 | 说明 |
|---|
textX = boxX + boxSize + 6 | 方框右边缘 + 6px 间距,避免文字贴边 |
LineAlignment = Center | 文字垂直居中于控件高度 |
Trimming = EllipsisCharacter | 文字过长时自动省略号 |
| 宽度计算 | Width - textX,即方框右侧到控件右边缘的剩余宽度 |
8.7 圆角路径生成
private GraphicsPath GetRoundedRectPath(RectangleF rect, float radius)
{
var path = new GraphicsPath();
float r = Math.Min(radius, Math.Min(rect.Width, rect.Height) / 2f);
float d = r * 2f;
if (r <= 0)
{
path.AddRectangle(rect);
return path;
}
path.AddArc(rect.X, rect.Y, d, d, 180, 90); // 左上角
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90); // 右上角
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90); // 右下角
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90); // 左下角
path.CloseFigure();
return path;
}| 要点 | 说明 |
|---|
r = min(radius, min(width, height) / 2) | 圆角半径不能超过边长度的一半,否则 AddArc 会异常 |
| 四段圆弧 | 左上 180→270、右上 270→360、右下 0→90、左下 90→180,组成圆角矩形 |
r ≤ 0 | 直角时直接 AddRectangle |
九、交互一览
| 操作 | 效果 |
|---|
| 鼠标进入控件 | _isHover = true,边框变 HoverColor(选中时仍用 CheckedColor) |
| 鼠标离开控件 | _isHover = false、_isPressed = false,边框恢复 |
| 左键按下 | _isPressed = true,边框变 PressedColor(选中时仍用 CheckedColor) |
| 左键抬起(在控件内) | 按 ThreeState 循环切换状态 |
| 左键抬起(移出控件外) | 不切换,仅清除 _isPressed |
| 右键 / 中键 | 不响应 |
| 键盘 | 未重写键盘处理:Space / Enter 不切换;如需键盘支持请自行扩展 |
状态循环(ThreeState = true 时)
| 当前状态 | 点击后 |
|---|
未选(Checked=false, Indeterminate=false) | 选中(Checked=true) |
选中(Checked=true) | 复选(Indeterminate=true) |
复选(Indeterminate=true) | 未选(两属性均为 false) |
十、完整使用示例
示例 1:基础二态多选框
using System.Drawing;
using System.Windows.Forms;
using CConUI;
var cb = new CheckBox
{
Parent = panel1,
Location = new Point(20, 20),
Text = "同意用户协议",
Checked = true
};
cb.CheckedChanged += (s, e) =>
{
var c = (CheckBox)s;
btnSubmit.Enabled = c.Checked;
};示例 2:三态多选框(表格全选场景)
var cbAll = new CheckBox
{
Parent = this,
Location = new Point(20, 20),
Text = "全选",
ThreeState = true
};
// 表格行选中数变化时,更新 cbAll 的三态
void UpdateAllCheckBox(int checkedCount, int total)
{
if (checkedCount == 0)
{
cbAll.Checked = false;
cbAll.Indeterminate = false; // 未选
}
else if (checkedCount == total)
{
cbAll.Checked = true; // 选中(会自动清 Indeterminate)
}
else
{
cbAll.Indeterminate = true; // 复选(会自动清 Checked)
}
}示例 3:深色主题外观
var cb = new CheckBox
{
Parent = this,
Location = new Point(20, 20),
Text = "深色主题选项",
ForeColor = Color.White,
BorderColor = Color.FromArgb(90, 90, 95),
HoverColor = Color.FromArgb(140, 140, 150),
PressedColor = Color.FromArgb(0, 200, 255),
CheckedColor = Color.FromArgb(0, 200, 255),
BorderThickness = 2f,
CornerRadius = 4f,
BoxPadding = 2
};示例 4:大号复选框
var cb = new CheckBox
{
Parent = this,
Size = new Size(200, 28),
Text = "大号复选框",
BorderThickness = 2.5f,
CornerRadius = 5f,
BoxPadding = 2
// 方框边长 = 28 - 2×2 = 24
};示例 5:直角极简风格
var cb = new CheckBox
{
Parent = this,
Text = "极简风格",
CornerRadius = 0f,
BorderThickness = 1.5f,
BoxPadding = 2,
CheckedColor = Color.Black,
BorderColor = Color.Gray,
ForeColor = Color.Black
};示例 6:用 CheckedChanging 记录状态,CheckedChanged 做业务
bool _wasChecked = false;
cb.CheckedChanging += (s, e) =>
{
var c = (CheckBox)s;
_wasChecked = c.Checked;
};
cb.CheckedChanged += (s, e) =>
{
var c = (CheckBox)s;
if (c.Checked && !_wasChecked)
{
Console.WriteLine("用户主动勾选");
}
else if (!c.Checked && _wasChecked)
{
Console.WriteLine("用户取消勾选");
}
};示例 7:三态循环手动测试
var cb = new CheckBox
{
Parent = this,
Location = new Point(20, 20),
Text = "点击我循环 3 态",
ThreeState = true
};
cb.CheckedChanged += (s, e) =>
{
var c = (CheckBox)s;
string state;
if (c.Checked) state = "选中";
else if (c.Indeterminate) state = "复选";
else state = "未选";
Console.WriteLine("当前状态:" + state);
};