CircularButtonA 纯色圆形按钮控件调用文档
命名空间:CConUI
继承:System.Windows.Forms.Control
可见性:public class CircularButtonA
默认尺寸:60 × 60(正方形)
默认事件:Click(继承自 Control;[DefaultEvent("Click")],设计器双击控件即生成该事件处理器)
定位:纯色自绘圆形按钮——自适应绘制正圆(即使控件是矩形,也按短边尺寸画正圆),支持三态背景色(常态 / 悬停 / 按下)、可自定义边框、禁用时自动变暗,支持鼠标左键点击与键盘 Space 触发 Click。
一、作用
完全自绘的圆形按钮,特点:
自适应正圆:以
min(宽, 高)作为直径,居中绘制正圆,控件非正方形时圆仍不变形;三态背景色:常态
ColorBack/ 悬停ColorEnter/ 按下ColorDown;边框智能高亮:悬停或按下时边框自动变亮 20%(
ControlPaint.Light),常态为BorderColor;禁用变暗:
Enabled = false时背景、边框都按ControlPaint.Dark(..., 0.3f)变暗 30%,文字变Color.Gray;鼠标点击:左键按下 → 松开(松开时仍在控件内)触发
Click;键盘支持:控件获得焦点后,Space 按下 / 松开触发
Click;手型光标:默认
Cursors.Hand;文字居中:
Text在正圆区域内水平 + 垂直居中绘制,超长自动省略号。
状态与视觉对照
| 状态 | 背景色 | 边框色 | 文字色 |
|---|---|---|---|
| 常态 | ColorBack | BorderColor | ForeColor |
| 悬停 | ColorEnter | Light(BorderColor, 0.2) | ForeColor |
| 按下(鼠标 / Space) | ColorDown | Light(BorderColor, 0.2) | ForeColor |
| 禁用 | Dark(ColorBack, 0.3) | Dark(BorderColor, 0.3) | Color.Gray |
二、快速上手
using System.Drawing;
using System.Windows.Forms;
using CConUI;
// 1. 创建控件
var btn = new CircularButtonA();
// 2. 放入容器
btn.Parent = panel1;
btn.Location = new Point(20, 20);
// 尺寸默认 60×60,可放大;建议保持正方形
btn.Size = new Size(72, 72);
btn.Text = "GO";
// 3. 订阅点击
btn.Click += (s, e) =>
{
MessageBox.Show("点击了圆形按钮!");
};
// 4. 外观自定义(可选)
btn.ColorBack = Color.FromArgb(0, 120, 215);
btn.ColorEnter = Color.FromArgb(0, 150, 255);
btn.ColorDown = Color.FromArgb(0, 90, 180);
btn.BorderColor = Color.FromArgb(0, 80, 150);
btn.BorderSize = 2;
btn.ForeColor = Color.White;
btn.Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold);三、构造函数
public CircularButtonA()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 作用 | 初始化纯色圆形按钮 |
| 实际行为 | ① 启用 UserPaint | AllPaintingInWmPaint | OptimizedDoubleBuffer | ResizeRedraw | Selectable 样式(自绘 + 双缓冲 + 尺寸变化自动重绘 + 可获取焦点);② 设置 ForeColor = Color.FromArgb(250, 250, 250);③ 设置 Size = (60, 60)(正方形,保证默认就是正圆);④ 设置 Cursor = Cursors.Hand |
| 默认尺寸 | 60 × 60(正方形) |
| 默认外观 | ColorBack = (90,100,115)、ColorEnter = (110,125,140)、ColorDown = (80,90,100)、BorderColor = (139,148,158)、BorderSize = 0 |
var btn = new CircularButtonA
{
Parent = panel1,
Text = "OK"
};四、公开属性
4.1 形状与边框
public int BorderSize
| 项目 | 说明 |
|---|---|
| 类型 | int(像素) |
| 默认 | 0 |
| Category | CCon |
| DefaultValue | 0 |
| 作用 | 边框粗细。0 → 不绘制边框;> 0 → 用 BorderColor 绘制相应粗细的圆形边框 |
| 取值范围 | Math.Max(0, value)——不能为负 |
| 绘制处理 | Pen 居中描边,因此圆形路径内缩 BorderSize / 2,让边框外边缘贴到圆的外接正方形边界 |
| 副作用 | 赋值后 Invalidate() 重绘 |
btn.BorderSize = 0; // 无边框(默认) btn.BorderSize = 2; // 2px 边框
4.2 颜色属性
public Color ColorBack
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| 默认 | Color.FromArgb(90, 100, 115)(暗蓝灰) |
| Category | CCon |
| 作用 | 按钮常态(未悬停、未按下、未禁用)下的圆形背景色 |
| 副作用 | 赋值后 Invalidate() 重绘 |
public Color ColorEnter
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| 默认 | Color.FromArgb(110, 125, 140)(中亮蓝灰) |
| Category | CCon |
| 作用 | 鼠标悬停时的背景色,通常比 ColorBack 亮作为高亮反馈 |
| 副作用 | 赋值后 Invalidate() 重绘 |
public Color ColorDown
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| 默认 | Color.FromArgb(80, 90, 100)(较暗蓝灰) |
| Category | CCon |
| 作用 | 按钮按下(鼠标左键按下 / Space 按下)时的背景色,形成“下沉”视觉效果 |
| 副作用 | 赋值后 Invalidate() 重绘 |
public Color BorderColor
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| 默认 | Color.FromArgb(139, 148, 158)(浅灰) |
| Category | CCon |
| 作用 | 按钮边框颜色;仅在 BorderSize > 0 时可见;悬停 / 按下时会自动变亮 20%,禁用时自动变暗 30% |
| 副作用 | 赋值后 Invalidate() 重绘 |
btn.ColorBack = Color.FromArgb(0, 120, 215); // 常态:蓝 btn.ColorEnter = Color.FromArgb(0, 150, 255); // 悬停:亮蓝 btn.ColorDown = Color.FromArgb(0, 90, 180); // 按下:暗蓝 btn.BorderColor = Color.FromArgb(0, 80, 150); // 边框:深蓝
4.3 重写 / 隐藏的属性
public override string Text
| 项目 | 说明 |
|---|---|
| 类型 | string |
| Category | CCon |
| 作用 | 圆中央显示的文字;空串时不绘制 |
| 副作用 | 赋值后 Invalidate() 重绘 |
| 绘制 | 使用 TextRenderer.DrawText 在圆的外接正方形内 水平 + 垂直居中;去掉 NoPadding 以修正 GDI+ 文字偏上问题;超长自动省略号(EndEllipsis) |
public override Color ForeColor
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| 默认 | Color.FromArgb(250, 250, 250)(构造时设置) |
| Category | CCon |
| 作用 | 文字颜色;禁用时自动变为 Color.Gray |
public override Font Font
| 项目 | 说明 |
|---|---|
| 类型 | Font |
| Category | CCon |
| 作用 | 文字字体 |
public override Color BackColor(隐藏)
| 项目 | 说明 |
|---|---|
| 类型 | Color |
| Browsable | false(属性面板中隐藏) |
| 作用 | 对按钮外观无影响——圆形背景由 ColorBack 控制。此属性仅为兼容 Control 基类而保留,读写不会改变绘制效果 |
五、公开事件
public event EventHandler Click(继承自 Control)
| 项目 | 说明 |
|---|---|
| 参数 | EventHandler:sender 为当前 CircularButtonA,e 为 EventArgs.Empty |
| 默认事件 | 是([DefaultEvent("Click")])——设计器双击即生成 |
| 触发时机 | ① 鼠标左键:按下时在控件内 → 松开时仍在控件内(ClientRectangle.Contains(e.Location));② 键盘 Space: OnKeyUp 里按 Space 松开时触发 |
| 不触发情况 | ① 鼠标左键按下后移出控件再松开; ② 右键 / 中键; ③ 控件禁用( Enabled = false)——Control 基类不派发鼠标 / 键盘事件 |
| 备注 | 内部通过 base.OnClick(EventArgs.Empty) 触发;本类未重写 OnClick,Click 由基类直接派发 |
btn.Click += (s, e) =>
{
MessageBox.Show("点击了!");
};六、公开方法
CircularButtonA没有新增 public 方法。所有公开方法继承自Control(如Invalidate()、Focus()、BringToFront()等)。
内部重写方法(protected override,外部不可直接调用)
| 方法 | 可见性 | 作用 |
|---|---|---|
OnPaint(PaintEventArgs e) | protected override | 整体绘制:正圆背景 → 边框 → 居中文字;禁用时自动变暗 |
OnMouseEnter(EventArgs e) | protected override | 设置 _isHover = true 并重绘 |
OnMouseLeave(EventArgs e) | protected override | 清除 _isHover 与 _isPressed 并重绘(防止按住拖出后卡在“按下色”) |
OnMouseDown(MouseEventArgs e) | protected override | 左键按下时 _isPressed = true,并调用 Focus() 获取焦点 |
OnMouseUp(MouseEventArgs e) | protected override | 左键松开时清 _isPressed;若松开点在客户区内则调用 OnClick(EventArgs.Empty) |
OnKeyDown(KeyEventArgs e) | protected override | Space 按下 → _isPressed = true(视觉上按下去) |
OnKeyUp(KeyEventArgs e) | protected override | Space 松开 → _isPressed = false + 触发 OnClick(EventArgs.Empty) |
七、公开成员速查表
| 成员 | 来源 | 类型 | 说明 |
|---|---|---|---|
CircularButtonA() | 本类 | 构造函数 | 默认尺寸 60×60,默认无边框 |
BorderSize | 本类 | 属性(int) | 边框粗细(默认 0,≥0) |
ColorBack | 本类 | 属性(Color) | 常态背景色 |
ColorEnter | 本类 | 属性(Color) | 悬停背景色 |
ColorDown | 本类 | 属性(Color) | 按下背景色 |
BorderColor | 本类 | 属性(Color) | 边框颜色(BorderSize > 0 时可见);悬停自动变亮、禁用自动变暗 |
Text(重写) | Control | 属性(string) | 圆中央文字 |
ForeColor(重写) | Control | 属性(Color) | 文字颜色(禁用时变 Gray) |
Font(重写) | Control | 属性(Font) | 文字字体 |
BackColor(隐藏) | Control | 属性(Color) | Browsable(false);对按钮外观无影响 |
Enabled | Control | 属性(bool) | 是否启用;禁用时背景 / 边框 / 文字自动变暗,鼠标键盘事件不派发 |
Cursor | Control | 属性 | 默认 Hand;可自行改为 Default |
TabStop | Control | 属性(bool) | 默认 false;要支持 Tab 聚焦 + Space 触发需设为 true |
Size / ClientSize | Control | 属性(Size) | 控件尺寸;建议保持正方形,非正方形时圆取短边尺寸 |
Click | Control | 事件(默认事件) | 鼠标左键或 Space 松开时触发 |
Invalidate() | Control | 方法 | 触发重绘 |
Focus() | Control | 方法 | 让按钮获取焦点(鼠标按下时已自动调用) |
OnPaint(e) | 本类(override) | 方法(protected) | 绘制逻辑,外部不可直接调用 |
OnMouseEnter / OnMouseLeave / OnMouseDown / OnMouseUp | 本类(override) | 方法(protected) | 鼠标交互,外部不可直接调用 |
OnKeyDown / OnKeyUp | 本类(override) | 方法(protected) | 键盘交互,外部不可直接调用 |
八、绘制原理(深入理解)
8.1 确定背景色(优先级)
Color currentBackColor; if (!this.Enabled) currentBackColor = ControlPaint.Dark(this._colorBack, 0.3f); else if (this._isPressed) currentBackColor = this._colorDown; else if (this._isHover) currentBackColor = this._colorEnter; else currentBackColor = this._colorBack;
| 优先级 | 状态 | 背景色 |
|---|---|---|
| 1(最高) | 禁用 | ControlPaint.Dark(ColorBack, 0.3f)——变暗 30% |
| 2 | 按下 | ColorDown |
| 3 | 悬停 | ColorEnter |
| 4 | 常态 | ColorBack |
8.2 计算自适应正圆
float side = Math.Min(ClientSize.Width, ClientSize.Height); if (side <= 4f) return; float ox = (ClientSize.Width - side) / 2f; float oy = (ClientSize.Height - side) / 2f; RectangleF outerRect = new RectangleF(ox, oy, side, side); float bs = this._borderSize; float halfBorder = bs / 2f; RectangleF innerRect = RectangleF.Inflate(outerRect, -halfBorder, -halfBorder);
| 要点 | 说明 |
|---|---|
side = min(宽, 高) | 取短边作为圆的直径——无论控件是什么形状都画正圆,多余空间留白 |
ox / oy | 水平 / 垂直居中偏移:(Client - side) / 2 是多出的半宽 / 半高 |
side ≤ 4 | 控件过小时直接不绘制,避免异常半径 |
innerRect 内缩 halfBorder | Pen 居中描边,外边缘会超出路径 BorderSize / 2。内缩半个线宽后,边框外边缘正好贴到 outerRect |
8.3 绘制背景与边框
using (var brush = new SolidBrush(currentBackColor))
g.FillEllipse(brush, innerRect);
if (bs > 0)
{
Color currentBorderColor;
if (!this.Enabled)
currentBorderColor = ControlPaint.Dark(this._borderColor, 0.3f);
else if (this._isHover || this._isPressed)
currentBorderColor = ControlPaint.Light(this._borderColor, 0.2f);
else
currentBorderColor = this._borderColor;
using (var pen = new Pen(currentBorderColor, bs))
g.DrawEllipse(pen, innerRect);
}| 要点 | 说明 |
|---|---|
背景与边框都使用 innerRect | 两者共享同一几何路径,视觉对齐 |
| 禁用时边框变暗 | Dark(BorderColor, 0.3f) 与背景变暗的力度一致 |
| 悬停 / 按下时边框变亮 | Light(BorderColor, 0.2f),让交互反馈更明显 |
bs = 0 时 | 跳过边框绘制;背景仍填充在 innerRect 上(因 halfBorder = 0,即 outerRect) |
8.4 绘制文字(用 TextRenderer 修正居中问题)
if (!string.IsNullOrEmpty(this.Text))
{
Color textColor = this.Enabled ? this.ForeColor : Color.Gray;
TextRenderer.DrawText(
g,
this.Text,
this.Font,
Rectangle.Round(outerRect),
textColor,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.EndEllipsis |
TextFormatFlags.NoPadding
);
}| 要点 | 说明 |
|---|---|
TextRenderer 而非 Graphics.DrawString | 基于 GDI 绘制,字体渲染更清晰; 关键修复: DrawString 在圆中垂直居中时常常偏上(因 GDI+ 字体度量方式),改用 TextRenderer + NoPadding 可完美居中 |
NoPadding | 去掉文本周围内边距,保证视觉绝对居中 |
EndEllipsis | 文字超长自动省略号 |
绘制区域为 outerRect | 即正圆的外接正方形;文字在正方形内居中 → 视觉上在圆中央 |
| 禁用时文字变灰 | 用 Color.Gray 替代 ForeColor |
8.5 圆角在非正方形控件下的表现
| 控件尺寸 | 绘制效果 |
|---|---|
| 正方形(宽=高) | 圆铺满整个客户区(扣除边框) |
| 宽 > 高 | 圆直径 = 高,水平居中,左右留白 |
| 宽 < 高 | 圆直径 = 宽,垂直居中,上下留白 |
建议:为获得最佳视觉,尽量把
Size设为正方形;否则周围留白看起来可能不美观。
九、交互一览
| 操作 | 效果 |
|---|---|
| 鼠标进入控件 | _isHover = true,背景变 ColorEnter,边框变亮 20% |
| 鼠标离开控件 | _isHover = false、_isPressed = false,背景 / 边框恢复常态 |
| 左键按下 | _isPressed = true,Focus() 获取焦点,背景变 ColorDown |
| 左键松开(在控件内) | _isPressed = false,触发 Click,背景回到悬停或常态 |
| 左键松开(移出控件) | _isPressed = false,不触发 Click |
| Space 按下(有焦点) | _isPressed = true,背景变 ColorDown |
| Space 松开(有焦点) | _isPressed = false,触发 Click,背景恢复 |
| Enter / 其它键 | 不响应(本类只处理 Space) |
| 右键 / 中键 | 不响应,不触发 Click,也不改变视觉状态 |
禁用(Enabled=false) | 背景 / 边框 / 文字自动变暗;鼠标键盘事件不派发;不响应悬停 / 按下 |
十、完整使用示例
示例 1:基础圆形按钮
using System.Drawing;
using System.Windows.Forms;
using CConUI;
var btn = new CircularButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(60, 60),
Text = "GO"
};
btn.Click += (s, e) =>
{
MessageBox.Show("点击了圆形按钮!");
};示例 2:大号 + 边框圆形按钮
var btn = new CircularButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(90, 90),
Text = "开始",
BorderSize = 3,
ColorBack = Color.FromArgb(0, 120, 215),
ColorEnter = Color.FromArgb(0, 150, 255),
ColorDown = Color.FromArgb(0, 90, 180),
BorderColor = Color.FromArgb(0, 80, 150),
ForeColor = Color.White,
Font = new Font("Microsoft YaHei", 12F, FontStyle.Bold)
};示例 3:圆形图标按钮(无边框)
var btn = new CircularButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(48, 48),
Text = "+",
BorderSize = 0,
ColorBack = Color.FromArgb(0, 180, 120),
ColorEnter = Color.FromArgb(0, 210, 150),
ColorDown = Color.FromArgb(0, 150, 100),
ForeColor = Color.White,
Font = new Font("Segoe UI", 18F, FontStyle.Bold)
};示例 4:禁用状态
var btn = new CircularButtonA
{
Parent = this,
Text = "禁用",
ColorBack = Color.Orange,
BorderSize = 2,
Enabled = false // 禁用:背景/边框/文字自动变暗
};
// 条件满足时启用
btn.Enabled = true;示例 5:键盘操作(Tab 聚焦 + Space 点击)
var btn = new CircularButtonA
{
Parent = this,
Size = new Size(64, 64),
Text = "OK",
TabStop = true // 允许 Tab 键聚焦
};
btn.Click += (s, e) => MessageBox.Show("Space 触发");说明:本类已通过
ControlStyles.Selectable允许获取焦点。用户按 Tab 切到按钮后,按 Space 会先显示“按下”视觉,松开 Space 触发Click。
示例 6:主题切换(圆形按钮组)
void ApplyPrimaryTheme(CircularButtonA btn)
{
btn.ColorBack = Color.FromArgb(0, 120, 215);
btn.ColorEnter = Color.FromArgb(0, 150, 255);
btn.ColorDown = Color.FromArgb(0, 90, 180);
btn.BorderColor = Color.FromArgb(0, 80, 150);
btn.ForeColor = Color.White;
}
void ApplyDangerTheme(CircularButtonA btn)
{
btn.ColorBack = Color.FromArgb(200, 50, 50);
btn.ColorEnter = Color.FromArgb(230, 70, 70);
btn.ColorDown = Color.FromArgb(160, 30, 30);
btn.BorderColor = Color.FromArgb(140, 20, 20);
btn.ForeColor = Color.White;
}
ApplyPrimaryTheme(btnPlay);
ApplyDangerTheme(btnStop);示例 7:非正方形控件(自动画正圆)
// 控件是长方形,但依然画正圆,周围留白
var btn = new CircularButtonA
{
Parent = this,
Size = new Size(120, 60), // 实际圆直径 = min(120, 60) = 60
Text = "OK",
ColorBack = Color.DodgerBlue,
BorderSize = 2
};转载请注明出处。