继承 Control 类是 WinForms 自定义控件最直接方式,需禁用默认背景擦除、启用双缓冲与焦点支持,并提供无参构造函数及设计器特性以确保可用性。

继承 Control 类是最直接的方式
自定义控件本质是扩展 System.Windows.Forms.Control(WinForms)或 System.Windows.Controls.Control(WPF),前者更轻量、适合底层绘制与事件控制。不建议从 UserControl 开始——它自带容器逻辑和设计器依赖,反而限制对绘制、消息循环、焦点行为的精细控制。
- 若需完全掌控绘制(比如画波形图、仪表盘),继承
Control并重写OnPaint - 若要支持双缓冲防闪烁,设
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true) - 必须手动调用
base.OnCreateControl()和base.OnHandleCreated(),否则生命周期钩子可能失效 - 别忘了在构造函数里设
ResizeRedraw = true,否则窗口缩放时不会自动重绘
重写 OnPaint 时绕开 GDI+ 默认背景擦除
默认 Control 在每次 OnPaint 前会用 BackColor 填充整个客户区,这会导致自绘内容被覆盖或出现残影。尤其在动画或高频刷新场景下特别明显。
- 在构造函数中关闭默认擦除:
this.SetStyle(ControlStyles.Opaque, true) - 同时禁用背景绘制:
this.SetStyle(ControlStyles.ResizeRedraw, true) - 重写
OnPaintBackground为空实现,避免父类填充背景 -
OnPaint中只用传入的Graphics绘制内容,不要调用e.Graphics.Clear(...)
protected override void OnPaint(PaintEventArgs e) {
// 不调用 base.OnPaint(e)
using (var pen = new Pen(Color.Blue, 2)) {
e.Graphics.DrawLine(pen, 10, 10, 100, 100);
}
}
响应鼠标/键盘事件必须显式启用焦点支持
直接继承 Control 的控件默认不可聚焦,KeyDown、KeyPress 等事件根本不会触发,哪怕你重写了 OnKeyDown。
- 构造函数中调用
this.TabStop = true和this.TabIndex = 0 - 重写
CanSelect属性返回true - 在
OnMouseDown中主动调用this.Focus(),否则点击不会获得输入焦点 - 若需捕获非客户端区域(如边框)的鼠标,需处理
WM_NCHITTEST消息,通过WndProc拦截
设计器支持需要 [ToolboxItem] 和默认构造函数
没有无参构造函数或缺少特性标记,控件拖不到 WinForms 设计器里,且属性面板不显示自定义属性。
- 必须提供 public 无参构造函数:
public MyCustomControl() { InitializeComponent(); } - 添加
[ToolboxItem(true)]特性,否则设计器忽略该类型 - 让属性可序列化:对希望在设计器中编辑的属性加
[Browsable(true)]、[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] - 若属性影响外观(如
LineColor),重写OnPropertyChanged并调用Invalidate()触发重绘
WinForms 自定义控件最难缠的不是绘制,而是消息分发边界——比如鼠标进入区域后移出但未触发 MouseLeave,或键盘焦点在嵌套控件间跳转时丢失。这些往往要靠精确拦截 WndProc 和检查 NativeWindow.AssignHandle 状态来定位。
