1. 基础实现:从CheckBox到自定义控件
1.1 继承CheckBox并添加核心属性
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace CustomControls
{
public class AnimatedToggle : CheckBox
{
// === 核心属性 ===
private Color _onColor = Color.MediumSlateBlue; // 开状态背景色
private Color _offColor = Color.Gray; // 关状态背景色
private Color _toggleColor = Color.WhiteSmoke; // 圆点颜色
private bool _isOn = false; // 当前开关状态
private Timer _animationTimer = new Timer(); // 动画计时器
private float _togglePosition = 0f; // 圆点当前位置(0-1)
// === 构造函数初始化 ===
public AnimatedToggle()
{
// 设置最小尺寸和双缓冲
this.MinimumSize = new Size(45, 22);
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); // 防止闪烁
// 配置动画计时器
_animationTimer.Interval = 10; // 每10ms更新一次
_animationTimer.Tick += AnimationTimer_Tick;
// 隐藏原生CheckBox的勾选框
this.FlatAppearance.BorderSize = 0;
this.FlatStyle = FlatStyle.Flat;
this.Cursor = Cursors.Hand; // 鼠标悬停显示手型
}
// === 自定义属性 ===
public Color OnColor
{
get => _onColor;
set
{
_onColor = value;
this.Invalidate(); // 触发重绘
}
}
public Color OffColor
{
get => _offColor;
set
{
_offColor = value;
this.Invalidate();
}
}
public Color ToggleColor
{
get => _toggleColor;
set
{
_toggleColor = value;
this.Invalidate();
}
}
}
}
注释:
ControlStyles.DoubleBuffer
:启用双缓冲,消除绘制闪烁;Timer
:用于逐帧动画,每10ms更新一次;SetStyle()
:确保控件支持平滑渲染和自定义绘制。
1.2 绘制核心逻辑:OnPaint事件
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.Antialiased; // 抗锯齿
// === 绘制背景 ===
using (Brush bgBrush = new SolidBrush(_isOn ? _onColor : _offColor))
{
g.FillRoundedRectangle(bgBrush, new Rectangle(0, 0, this.Width, this.Height), 12); // 圆角矩形
}
// === 绘制圆点 ===
int toggleRadius = this.Height / 2 - 2;
float toggleX = _togglePosition * (this.Width - 2 * toggleRadius); // 动态计算圆心X坐标
using (Brush toggleBrush = new SolidBrush(_toggleColor))
{
g.FillEllipse(toggleBrush,
new Rectangle(
(int)(toggleX + 1),
1,
toggleRadius * 2,
toggleRadius * 2));
}
}
注释:
FillRoundedRectangle
:使用圆角矩形模拟开关背景;SmoothingMode.Antialiased
:确保边缘平滑无锯齿;_togglePosition
:通过动画平滑变化实现圆点移动。
2. 动画实现:平滑切换与缓动效果
2.1 计时器驱动的逐帧动画
private void AnimationTimer_Tick(object sender, EventArgs e)
{
// === 计算动画进度 ===
float step = _isOn ? 0.05f : -0.05f; // 开/关方向
_togglePosition += step;
// === 边界检查 ===
if (_togglePosition >= 1.0f) _togglePosition = 1.0f;
if (_togglePosition <= 0.0f) _togglePosition = 0.0f;
// === 触发重绘 ===
this.Invalidate();
// === 动画完成判断 ===
if ((_isOn && _togglePosition >= 0.95f) || (!_isOn && _togglePosition <= 0.05f))
{
_animationTimer.Stop();
OnValueChanged(); // 触发状态改变事件
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
_isOn = !_isOn; // 翻转状态
_animationTimer.Start(); // 开始动画
}
注释:
step
:每帧移动的距离(0.05为5%的宽度);OnValueChanged()
:在动画结束后触发,通知状态改变;Invalidate()
:强制控件重绘。
2.2 高级缓动效果(参考WPF缓动公式)
// === 缓动公式实现(如回弹效果) ===
private float BackEaseOut(float t)
{
float s = 1.70158f; // 回弹系数
return (float)(t = t - 1) * t * ((s + 1) * t + s) + 1;
}
// === 修改AnimationTimer_Tick实现缓动 ===
private float _animationProgress = 0f; // 动画进度(0-1)
private void AnimationTimer_Tick(object sender, EventArgs e)
{
_animationProgress += 0.05f; // 每帧进度增量
if (_animationProgress > 1f) _animationProgress = 1f;
// 使用缓动公式计算当前位置
float easedProgress = BackEaseOut(_animationProgress);
_togglePosition = _isOn ? easedProgress : 1f - easedProgress;
// ... 其余逻辑同上 ...
}
注释:
BackEaseOut
:实现类似弹簧回弹的动画效果;- 缓动公式可替换为其他类型(如指数、弹性),实现不同效果。
3. 高级定制:主题切换与无障碍设计
3.1 动态主题适配
// === 主题切换接口 ===
public void ApplyTheme(Theme theme)
{
switch (theme)
{
case Theme.Light:
OnColor = Color.LightSkyBlue;
OffColor = Color.LightGray;
ToggleColor = Color.White;
break;
case Theme.Dark:
OnColor = Color.FromArgb(255, 50, 150, 255);
OffColor = Color.FromArgb(255, 40, 40, 40);
ToggleColor = Color.FromArgb(255, 200, 200, 200);
break;
}
this.Invalidate(); // 应用新主题
}
// === 枚举定义 ===
public enum Theme
{
Light,
Dark
}
注释:
- 通过
ApplyTheme()
方法实现主题切换;- 可扩展为从配置文件或系统主题获取颜色。
3.2 无障碍支持:键盘与屏幕阅读器
// === 支持键盘操作 ===
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Space)
{
_isOn = !_isOn;
_animationTimer.Start();
e.Handled = true;
}
base.OnKeyDown(e);
}
// === 提供ARIA标签(适用于屏幕阅读器) ===
protected override void OnAccessibleValueChange(AccessibleValueChangeEventArgs e)
{
base.OnAccessibleValueChange(e);
this.AccessibleName = _isOn ? "Switch enabled" : "Switch disabled";
}
注释:
OnKeyDown()
:支持空格键切换;AccessibleName
:为屏幕阅读器提供状态描述。
4. 实战案例:权限管理界面的开关应用
4.1 在WinForms中使用自定义控件
// === 窗体代码 ===
public partial class SettingsForm : Form
{
public SettingsForm()
{
InitializeComponent();
// 初始化开关按钮
AnimatedToggle toggle = new AnimatedToggle
{
OnColor = Color.ForestGreen,
OffColor = Color.IndianRed,
ToggleColor = Color.White,
Location = new Point(20, 20),
Size = new Size(80, 30)
};
// 绑定事件
toggle.CheckedChanged += (s, e) =>
{
MessageBox.Show($"Switch is now {(toggle._isOn ? "ON" : "OFF")}");
};
this.Controls.Add(toggle);
}
}
注释:
- 通过
CheckedChanged
事件响应状态变化;- 可扩展为控制后端服务开关。
5. 进阶技巧:动态响应与组合控件
5.1 响应式布局适配
// === 自动调整尺寸 ===
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Width = Math.Max(45, this.Width); // 最小宽度限制
this.Height = Math.Max(22, this.Height); // 最小高度限制
}
注释:
- 确保控件在父容器缩放时保持最小尺寸。
6. 性能优化:减少重绘开销
// === 优化OnPaint性能 ===
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// 禁用背景重绘(由OnPaint统一处理)
return;
}
// === 使用双缓冲 ===
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED,启用双缓冲
return cp;
}
}
注释:
WS_EX_COMPOSITED
:启用双缓冲减少闪烁;OnPaintBackground()
:避免重复绘制背景。
通过以上6大核心技术,你已经掌握了C#开关按钮的“核武器库”:
- 平滑动画:用计时器+缓动公式实现丝滑切换;
- 主题适配:一键切换暗/亮模式;
- 无障碍支持:键盘操作与屏幕阅读器兼容;
- 性能优化:双缓冲与响应式布局保障流畅体验;
- 实战应用:直接集成到权限管理、设置面板等场景。