ButtonA 纯色自绘按钮控件调用文档
命名空间:CConUI
继承:System.Windows.Forms.Control
可见性:public class ButtonA
默认尺寸:110 × 32
默认事件:Click(继承自 Control;[DefaultEvent("Click")],设计器双击控件即生成该事件处理器)
定位:纯色自绘按钮——支持圆角、边框、三态背景色(常态 / 悬停 / 按下),支持鼠标左键点击、键盘 Space 触发 Click,禁用时自动变暗。
一、作用
一个完全自绘的按钮控件,具备如下特性:
三态背景色:常态
ColorBack/ 悬停ColorEnter/ 按下ColorDown;圆角按钮:
Radius控制四角半径,0 为直角;可自定义边框:
BorderColor/BorderSize,BorderSize=0时无边框;禁用变暗:
Enabled=false时背景色、边框色、文字色都自动变暗(ControlPaint.Dark(..., 0.3f));鼠标点击:左键按下 → 松开(松开时仍在控件内)触发
Click;键盘支持:控件获得焦点后,Space 按下 / 松开触发
Click(无 Enter 支持);手型光标:默认
Cursors.Hand,提示可点击;文字居中:
Text在按钮中央绘制,超长自动省略号。
状态与视觉对照
| 状态 | 背景色 | 边框色 | 文字色 |
|---|---|---|---|
| 常态 | ColorBack | BorderColor | ForeColor |
| 悬停 | ColorEnter | BorderColor | ForeColor |
| 按下(鼠标 / Space) | ColorDown | BorderColor | ForeColor |
| 禁用 | Dark(ColorBack, 0.3) | Dark(BorderColor, 0.3) | Color.Gray |
二、快速上手
using System.Drawing;
using System.Windows.Forms;
using CConUI;
// 1. 创建控件
var btn = new ButtonA();
// 2. 放入容器
btn.Parent = panel1;
btn.Location = new Point(20, 20);
btn.Size = new Size(120, 36);
btn.Text = "确定";
// 3. 订阅点击
btn.Click += (s, e) =>
{
MessageBox.Show("点击了按钮!");
};
// 4. 外观自定义(可选)
btn.ColorBack = Color.FromArgb(90, 100, 115);
btn.ColorEnter = Color.FromArgb(110, 125, 140);
btn.ColorDown = Color.FromArgb(80, 90, 100);
btn.BorderColor = Color.FromArgb(110, 125, 140);
btn.BorderSize = 1;
btn.Radius = 6;
btn.ForeColor = Color.White;
btn.Font = new Font("Microsoft YaHei", 10F);三、构造函数
public ButtonA()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 作用 | 初始化纯色自绘按钮 |
| 实际行为 | ① 启用 UserPaint | AllPaintingInWmPaint | OptimizedDoubleBuffer | ResizeRedraw | Selectable 样式(自绘 + 双缓冲 + 尺寸变化自动重绘 + 可获取焦点);② 设置 ForeColor = Color.FromArgb(250, 250, 250);③ 设置 Size = (110, 32);④ 设置 Cursor = Cursors.Hand |
| 默认尺寸 | 110 × 32 |
| 默认外观 | ColorBack = (90,100,115)、ColorEnter = (110,125,140)、ColorDown = (80,90,100)、BorderColor = (110,125,140)、BorderSize = 0、Radius = 0 |
var btn = new ButtonA
{
Parent = panel1,
Text = "确定"
};四、公开属性
4.1 形状属性
public int Radius
| 项目 | 说明 |
|---|---|
| 类型 | int(像素) |
| 默认 | 0 |
| Category | CCon |
| DefaultValue | 0 |
| 作用 | 按钮四角的圆角半径。0 → 直角矩形;> 0 → 圆角矩形(值越大越圆) |
| 取值范围 | Math.Max(0, value)——不能为负 |
| 绘制保护 | 内部会再限制到 min(Radius, min(width, height) / 2),保证圆角不会超出按钮 |
| 副作用 | 赋值后 Invalidate() 重绘 |
btn.Radius = 0; // 直角 btn.Radius = 6; // 小圆角 btn.Radius = 16; // 大圆角(胶囊形的一半高度左右)
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 = 1; // 1px 边框 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(110, 125, 140) |
| Category | CCon |
| 作用 | 按钮边框的颜色;仅在 BorderSize > 0 时可见;禁用时自动变暗 |
| 副作用 | 赋值后 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() 重绘 |
| 绘制 | 居中(Center / Center),超长自动省略号(EllipsisCharacter) |
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 为当前 ButtonA,e 为 EventArgs.Empty |
| 默认事件 | 是([DefaultEvent("Click")])——设计器双击即生成 |
| 触发时机 | ① 鼠标左键:按下时在控件内 → 松开时仍在控件内(ClientRectangle.Contains(e.Location));② 键盘 Space: OnKeyUp 里按 Space 松开时触发 |
| 不触发情况 | ① 鼠标左键按下后移出控件再松开; ② 右键 / 中键; ③ 控件禁用( Enabled = false)——Control 基类不派发鼠标 / 键盘事件 |
| 备注 | 本类内部通过 base.OnClick(EventArgs.Empty) 触发;如需在 Click 之前拦截,可重写 OnClick(当前未重写,即 Click 直接由基类派发) |
btn.Click += (s, e) =>
{
MessageBox.Show("点击了!");
};六、公开方法
ButtonA没有新增 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) |
GetRoundedRectPath(RectangleF, float) | private | 生成圆角矩形路径的辅助方法 |
七、公开成员速查表
| 成员 | 来源 | 类型 | 说明 |
|---|---|---|---|
ButtonA() | 本类 | 构造函数 | 默认尺寸 110×32,默认无边框、直角 |
Radius | 本类 | 属性(int) | 圆角半径(默认 0,≥0) |
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 |
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 |
禁用优先于按下 / 悬停:即使
Enabled = false时鼠标仍可悬停,也会显示禁用色。
8.2 计算绘制矩形
float w = this.Width; float h = this.Height; float bs = this._borderSize; float halfBorder = bs / 2f; RectangleF rect = new RectangleF(halfBorder, halfBorder, w - bs, h - bs);
| 要点 | 说明 |
|---|---|
rect 内缩 halfBorder | Pen 居中描边时,外边缘会超出路径 BorderSize / 2。若直接 DrawPath 在 (0, 0, W, H) 上,边框一半会画到控件外被裁剪。内缩半个线宽后,边框外边缘正好贴到控件边界 |
bs = 0 时 | halfBorder = 0,rect 就是整个 (0, 0, W, H) 区域,圆角填充铺满整个控件 |
8.3 绘制背景与边框
using (var path = GetRoundedRectPath(rect, this._radius))
{
using (var brush = new SolidBrush(currentBackColor))
g.FillPath(brush, path);
if (bs > 0)
{
Color currentBorderColor = this.Enabled
? this._borderColor
: ControlPaint.Dark(this._borderColor, 0.3f);
using (var pen = new Pen(currentBorderColor, bs))
g.DrawPath(pen, path);
}
}| 要点 | 说明 |
|---|---|
| 先填背景再画边框 | 填充路径与边框路径一致,边框会覆盖在填充区域边缘上 |
| 禁用时边框也变暗 | 用 ControlPaint.Dark(BorderColor, 0.3f) 统一处理 |
| 边框只画一次 | Pen 是变量,bs > 0 才创建;bs = 0 时不画,避免无谓开销 |
8.4 绘制文字
if (!string.IsNullOrEmpty(this.Text))
{
Color textColor = this.Enabled ? this.ForeColor : Color.Gray;
using (var brush = new SolidBrush(textColor))
using (var sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
Trimming = StringTrimming.EllipsisCharacter
})
{
RectangleF textRect = new RectangleF(bs, bs, w - bs * 2f, h - bs * 2f);
g.DrawString(this.Text, this.Font, brush, textRect, sf);
}
}| 要点 | 说明 |
|---|---|
| 禁用时文字变灰 | 用 Color.Gray 替代 ForeColor |
textRect 内缩 bs | 与边框内区域对齐,避免边框过粗时文字被边框遮挡;宽度用 w - bs*2、高度用 h - bs*2,与 rect 的“内缩半线宽”不同——文字需要在边框内侧完整区域 |
Trimming = EllipsisCharacter | 文字超长自动省略号,避免溢出 |
8.5 圆角路径生成
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) | 圆角半径不能超过边长度的一半,否则四角会互相穿透变形 |
| 四段圆弧 | 左上 180→270、右上 270→360、右下 0→90、左下 90→180,组成圆角矩形 |
r ≤ 0 | 直角时直接 AddRectangle |
九、交互一览
| 操作 | 效果 |
|---|---|
| 鼠标进入控件 | _isHover = true,背景变 ColorEnter |
| 鼠标离开控件 | _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 ButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(120, 36),
Text = "确定"
};
btn.Click += (s, e) =>
{
MessageBox.Show("点击了确定!");
};示例 2:圆角 + 边框按钮
var btn = new ButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(140, 40),
Text = "圆角按钮",
Radius = 12,
BorderSize = 1,
ColorBack = Color.FromArgb(60, 120, 200),
ColorEnter = Color.FromArgb(80, 140, 220),
ColorDown = Color.FromArgb(40, 90, 160),
BorderColor = Color.FromArgb(40, 90, 160),
ForeColor = Color.White,
Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold)
};示例 3:胶囊形按钮(圆角 ≈ 高度一半)
var btn = new ButtonA
{
Parent = this,
Location = new Point(20, 20),
Size = new Size(180, 40),
Text = "胶囊按钮",
Radius = 20, // = 高度 / 2
ColorBack = Color.FromArgb(0, 180, 120),
ColorEnter = Color.FromArgb(0, 210, 150),
ColorDown = Color.FromArgb(0, 150, 100),
BorderSize = 0,
ForeColor = Color.White,
Font = new Font("Microsoft YaHei", 10F)
};示例 4:禁用按钮
var btn = new ButtonA
{
Parent = this,
Location = new Point(20, 20),
Text = "禁用按钮",
ColorBack = Color.Orange
};
// 未满足条件时禁用
btn.Enabled = false;
// 条件满足后启用
btn.Enabled = true;示例 5:键盘操作按钮
var btn = new ButtonA
{
Parent = this,
Location = new Point(20, 20),
Text = "Tab 聚焦 + Space 点击",
TabStop = true // 允许 Tab 键聚焦
};
btn.Click += (s, e) => MessageBox.Show("Space 触发");说明:本类已通过
ControlStyles.Selectable允许获取焦点。用户按 Tab 切到按钮后,按 Space 会先显示“按下”视觉,松开 Space 触发Click。
示例 6:动态修改外观(例如“主要 / 次要”切换)
void SetPrimary(ButtonA btn)
{
btn.ColorBack = Color.FromArgb(0, 120, 215);
btn.ColorEnter = Color.FromArgb(0, 150, 255);
btn.ColorDown = Color.FromArgb(0, 90, 180);
btn.ForeColor = Color.White;
}
void SetSecondary(ButtonA btn)
{
btn.ColorBack = Color.FromArgb(230, 230, 230);
btn.ColorEnter = Color.FromArgb(240, 240, 240);
btn.ColorDown = Color.FromArgb(200, 200, 200);
btn.ForeColor = Color.Black;
}
SetPrimary(btnOk);
SetSecondary(btnCancel);示例 7:状态机控制(例如加载中禁用)
async void BtnSubmit_Click(object sender, EventArgs e)
{
btnSubmit.Enabled = false;
btnSubmit.Text = "提交中…";
try
{
await DoSubmitAsync();
btnSubmit.Text = "已提交";
}
finally
{
btnSubmit.Enabled = true;
btnSubmit.Text = "提交";
}
}十一、注意事项
Click需要“按下与松开都在控件内”:用户按下左键后把鼠标拖到控件外再松开不会触发Click。这是本类的设计,避免误点击。右键 / 中键不触发
Click:OnMouseDown / OnMouseUp都只判断MouseButtons.Left。Space 松开时触发,Space 按下时只是视觉反馈:
OnKeyDown只改_isPressed,OnKeyUp才派发Click。不支持 Enter 键触发:本类只处理
Keys.Space。如需 Enter 触发,需要自行扩展OnKeyDown/OnKeyUp。Tab 聚焦需要
TabStop = true:Control的TabStop默认是false,本类未显式设置。要让键盘 Space 在无鼠标点击的情况下生效,请在创建时设置TabStop = true。鼠标按下会自动
Focus():点击按钮后按钮获取焦点,即可继续用 Space 模拟点击。禁用时视觉变暗但不会自动暂停动画:本类无动画,所以无影响。但如果外部有定时器定时改
Text,禁用后文字仍是Color.Gray绘制,不会因禁用而停止更新。ColorBack/ColorEnter/ColorDown/BorderColor都支持 ARGB:可以设置带透明度的颜色;由于本类未开启SupportsTransparentBackColor,背景的实际表现取决于Control默认行为——OnPaint里用SolidBrush填充,透明部分会透出Control默认背景,可能不符合预期,建议保持 A=255。BackColor对按钮无效:已被Browsable(false)隐藏,代码设置也不会改变绘制。要改按钮颜色请用ColorBack。BorderSize增大时,可用文字区域会相应缩小:textRect会内缩BorderSize,所以大边框可能让文字显示区变小,甚至文字被省略号截断。建议BorderSize不要超过控件高度的 1/4。Radius上限会被内部限制:即使设为 100,实际效果也只会是min(100, min(width, height) / 2)——即“胶囊形”是圆角的最大值。控件高度极小时圆角可能绘制异常:
GetRoundedRectPath里对r有 clamp 保护,但高度 < 6 时视觉会非常小,建议Size.Height ≥ 20。Cursor默认为Hand:如果不需要“可点击”提示,可改为Cursors.Default。禁用时Control仍会显示Hand(不会自动切换),如需在禁用时显示默认光标,请自行在EnabledChanged事件中处理。本类未重写
OnClick:Click事件由base.OnClick(EventArgs.Empty)派发。若需要“点击前拦截”,可以继承并重写OnClick,但本类本身不提供该能力。与原生
Button的差异:无
FlatStyle/FlatAppearance/Image/ImageAlign/TextAlign等属性;无
DialogResult属性(如果用于对话框,需要外部处理Click后手动设置DialogResult);无
UseVisualStyleBackColor/AutoSize等;文字始终居中,不支持图片。
DoubleBuffered通过OptimizedDoubleBuffer开启:绘制平滑无闪烁;如果在MouseMove中频繁改状态,也不会造成可见的撕裂。EnabledChanged事件不影响悬停状态:本类在OnPaint里通过!Enabled判断,不依赖事件;从禁用恢复启用时,如果鼠标仍在控件内,会直接显示悬停色(_isHover字段保留),无需额外处理。大小改变立即重绘:
ResizeRedraw样式已开启,无需手动Invalidate。文字使用
ClearTypeGridFit渲染:在透明 / 深色背景下可能有轻微彩色边缘,如需更干净的文字,可改用AntiAliasGridFit(需在源码中修改TextRenderingHint)。不能作为容器:本类是
Control的直接子类,不建议在其上Controls.Add(...)叠加子控件,绘制会被遮挡或布局混乱。
转载请注明出处。