自定义控件
当内置控件无法满足需求时,我们就需要开发自定义控件。Winform中自定义控件主要有两种路线:组合控件(UserControl,把现有控件组合成一个可复用单元)和自绘控件(继承Control,自己用GDI+绘制全部外观)。这篇笔记我们分别介绍这两种方式。
UserControl 组合控件
UserControl是最常用的自定义控件方式,它本质上是一个可以复用的小窗体,我们把一组控件摆上去、写好交互逻辑,之后就可以像普通控件一样反复将其拖放到各个窗体上使用。
创建组合控件
在工程上点击右键,选择添加->用户控件,即可创建一个UserControl。例如我们做一个带清除按钮的搜索框控件,把一个TextBox和一个Button拖上去。

暴露属性
控件内部控件的属性不应该让使用者直接访问,标准做法是在UserControl上定义公开属性做转发。
namespace Gacfox.Demo;
public partial class SearchBox : UserControl
{
public SearchBox()
{
InitializeComponent();
}
public string SearchText
{
get => textBox.Text;
set => textBox.Text = value;
}
private void clearButton_Click(object sender, EventArgs e)
{
textBox.Clear();
}
}
暴露的属性如果想在设计器的属性面板中出现并支持序列化,还可以加上特性标注。
[Category("自定义")]
[Description("搜索框中的文本")]
[DefaultValue("")]
public string SearchText
{
get => textBox1.Text;
set => textBox1.Text = value;
}
Category控制属性在属性面板中的分组,Description是属性面板底部的说明文字,DefaultValue告诉设计器默认值是什么。
暴露事件
同理,内部控件的事件也需要包装后再暴露给使用者。
[Category("自定义")]
[Description("搜索文本发生变化时触发")]
public event EventHandler SearchTextChanged;
private void textBox1_TextChanged(object sender, EventArgs e)
{
SearchTextChanged?.Invoke(this, EventArgs.Empty);
}
编译工程后,自定义的UserControl会自动出现在工具箱顶部的工程分组中,它可以拖放到任何窗体上,其公开属性和事件在设计器中都能正常使用。

自绘控件
如果控件的外观完全无法用现有控件拼出来,例如仪表盘、进度环、游戏地图等,此时就需要继承Control自己绘制。
using System.ComponentModel;
using System.Drawing.Drawing2D;
namespace Gacfox.Demo;
public partial class CircleProgress : UserControl
{
private int _value;
[Category("自定义")]
[Description("当前进度(0-100)")]
[DefaultValue(0)]
public int Value
{
get => _value;
set
{
_value = Math.Max(0, Math.Min(100, value));
// 属性变化后请求重绘
Invalidate();
}
}
public CircleProgress()
{
// 开启双缓冲三件套,防止闪烁
SetStyle(ControlStyles.OptimizedDoubleBuffer
| ControlStyles.AllPaintingInWmPaint
| ControlStyles.UserPaint
| ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics graphics = e.Graphics;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = new Rectangle(10, 10, Width - 20, Height - 20);
using (Pen penBack = new Pen(Color.LightGray, 8))
using (Pen penValue = new Pen(Color.DodgerBlue, 8))
{
graphics.DrawEllipse(penBack, rect);
graphics.DrawArc(penValue, rect, -90, _value * 3.6f);
}
}
}
编译工程后,自定义的自绘控件也会出现在工具箱中,且支持预览。
自绘控件的要点:
- 绘制逻辑统一写在
OnPaint()重写中,使用e.Graphics绘制,自定义属性变化时调用Invalidate()请求重绘,这和GDI+章节的最佳实践完全一致。 - 构造函数中应使用
SetStyle()开启双缓冲和自绘样式。 - 需要响应交互时应重写
OnMouseDown()、OnMouseMove()、OnKeyDown()等方法,而非订阅自己的事件。
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。