继承 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状态来定位。
