Winform控件优化之自定义控件的本质【从圆角控件看自定义的本质,Region区域无法反锯齿的问题】

简介: 自定义控件的本质只有两点:重绘控件Region区域(圆角、多边形、图片等),这是整个控件的真实范围;重绘图形,在原有Region范围内,重绘不同的图形(圆角、多边形、图片等)作为背景......

自定义控件的本质

自定义控件的本质只有两点:

  • 重绘控件Region区域(圆角、多边形、图片等),这是整个控件的真实范围。缺点是Region无法抗锯齿,自定义的Region范围是有锯齿的,无法消除;此外新的Region还会和绘制的背景产生1像素的白边(在圆角或图形拐角部分),且几乎无法有效的消除。【后续会介绍FillRegion填充区域,而不是绘制图形与Region产生'白边'】
  • 重绘图形,在原有Region【矩形区域】范围内,重绘不同的图形(圆角、多边形、图片等)作为背景,因为绘制图形时可以实现抗锯齿,因此看起来像无锯齿控件一样。本质上是在固定Region内绘制的尽可能大的无锯齿图形,因此需要注意设置透明背景(透明自定义图形外的Region部分)

如果再加一条的话,自定义控件还有一点,就是:

  • 在原有控件上通过设置图片/图标、背景颜色、布局大小、多个不同控件组合等,同时结合重绘图形(或重绘Region区域),实现一定的自定义。

在其实现方法上,都是使用GDI/GDI+进行绘制,并添加一些默认的功能、事件或属性;组合控件也可能不需要使用GDI+绘制;还有一些基于基础控件的扩展控件,在原控件的基础上进行一些扩展,也用不到绘制方法。

赋值新Region和直接重绘图形的区别

无论是在控件的Paint事件方法中,还是通过继承控件在OnPaint方法中,重绘控件的两种方式:赋值新Region和直接重绘图形,在进行绘制时都有两个相同的区别。

  • 仅仅赋值新Region原则上不需要再重新绘制背景颜色、文本等;
  • 只要发生了绘制(无论直接在原Region上绘制图形、还是赋值了新Region后绘制图形),都需要再重新绘制文本。即绘制图形会覆盖原来的内容,需要背景、文本都进行绘制

不要使用e.ClipRectangle、不要直接修改Region

无论是在继承控件的OnPaint方法中,还是在控件的Paint事件方法中,都不要使用参数e.ClipRectangle作为控件重绘的区域(绘制图形或重新创建Region),原因之前文章已经介绍。

另外,对于赋值新Region,不要直接在代码中实现,原因和上面一样,在发生控件大小、位置、拖动等变化时,会发生显示错乱或不完全的部分(直接赋值Region则固定了Region区域),正确的做法还是要在Paint事件或OnPaint方法中。

Region区域无法反锯齿

没有最佳圆角最佳自定义控件的实现,除非可以创建无锯齿Region。

无法对Region反锯齿

【目前来说,自定义控件时新建Region没有最优雅或最优实现】,任何创建新Region的方法都是有锯齿的【个人所知🥲】。

在原有Region上绘制没有锯齿的原因,是因为绘制的圆角图形没锯齿,而默认的Region是个大于圆角矩形的直角矩形(直角没锯齿),但Region的直角相对于圆角图形多出来的部分是透明的"看不到",只看到了绘制出来的无锯齿的圆角矩形,形成了"无锯齿的"控件效果(实际是无锯齿的图形)。

以圆角为例,通过GraphicsPath创建的圆角路径而创建的Region区域,在仅赋值新Region的情况下,可以免去绘制Graphics的圆角图形和文字的麻烦,但是会有锯齿问题。

比如,之前介绍的创建新Region:

Rectangle controlRect = new Rectangle(0, 0, this.Width, this.Height);

var controlPath = controlRect.GetRoundedRectPath(roundRadius);

// 要在绘制之前指定Region,否则无效
this.Region = new Region(controlPath);

GDI+的APICreateRoundRectRgn

GDI+ API 的 CreateRoundRectRgn 方法是另外一种创建Region的方式(Region.FromHrgn()方法),但是其仍然无法做到抗锯齿。

[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn
(
    int nLeftRect,     // x-coordinate of upper-left corner
    int nTopRect,      // y-coordinate of upper-left corner
    int nRightRect,    // x-coordinate of lower-right corner
    int nBottomRect,   // y-coordinate of lower-right corner
    int nWidthEllipse, // height of ellipse
    int nHeightEllipse // width of ellipse
);

// ...
// 创建Region
Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, this.Width, this.Height, roundRadius, roundRadius)); //同样无法抗锯齿

Paint事件中使用CreateRoundRectRgn API的示例

// button1 需处理为无边框
button1.FlatStyle = FlatStyle.Flat;
button1.FlatAppearance.BorderSize = 0;
button1.FlatAppearance.CheckedBackColor = Color.Transparent;

button1.Paint += Button_Paint;
panel1.Paint += Panel_Paint; ;

label1.Paint += Label1_Paint;

// ........  

private void Label1_Paint(object sender, PaintEventArgs e)
{
    var pnl = (Label)sender;
    pnl.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, pnl.Width, pnl.Height, 30, 30));
}

private void Panel_Paint(object sender, PaintEventArgs e)
{
    var pnl = (Panel)sender;
    pnl.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, pnl.Width, pnl.Height, 30, 30));
}

private void Button_Paint(object sender, PaintEventArgs e)
{
    var btn = (Button)sender;
    btn.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, btn.Width, btn.Height, 30, 30));

}

效果如下,可以看到有着响应的锯齿。和直接使用托管代码new Region(roundedPath)创建圆角Region是一样的。

Graphics.FillRegion 填充Region同样有锯齿

后面看到Graphics.FillRegion方法,填充Region也是一样,依旧会有锯齿和圆角边缘的白边问题。

g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.SmoothingMode = SmoothingMode.AntiAlias; // SmoothingMode.HighQuality 
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBilinear;

var brush = new SolidBrush(bgcolor);
g.FillRegion(brush,region);

关于自定义控件的代码注入或代码共同【如何实现?】

可以发现,对于圆角控件(或自定义)部分的代码,几乎对所有控件处理都是一致。则这样的情况下,针对每个控件都处理添加相同的代码,会显得非常冗余,且如果需要修改,就修改每一个相同的内容。

因此,考虑是否有办法直接将自定义控件的代码通过某种方法注入到控件中,而外部只维护一套自定义的代码【写好测试,任何修改都要考虑是否影响到所有控件】。

如何实现直接注入圆角实现的代码到OnPaint方法中?避免继承控件时重复重写的代码? 待后续研究实现。

相关文章
|
算法 Windows
Winform控件优化之实现无锯齿的圆角窗体(或任意图形的无锯齿丝滑的窗体或控件)【借助LayeredWindow】
在一般能搜到的所有实现圆角窗体的示例中,都有着惨不忍睹的锯齿...而借助于Layered Windows,是可以实现丝滑无锯齿效果的Form窗体的,其具体原理就是分层窗体....
2253 0
Winform控件优化之实现无锯齿的圆角窗体(或任意图形的无锯齿丝滑的窗体或控件)【借助LayeredWindow】
|
小程序 C#
C#WinForm实现Loading等待界面
上篇博客中解决了程序加载时屏幕闪烁的问题。 但是,加载的过程变得很缓慢。 这个给用户的体验也不是很好,我这里想加一个Loading的进度条。 项目启动的时候,加载进度条,界面UI加载完毕,进度条消失。
750 0
|
C# 索引 Windows
Winform控件优化之TabControl控件的使用和常用功能
TabControl是一个分页切换(tab)控件,不同的页框内可以呈现不同的内容,将主要介绍调整tab的左右侧显示、设置多行tab、禁用或删除tabpage、隐藏TabControl头部的选项卡等
7759 0
Winform控件优化之TabControl控件的使用和常用功能
|
前端开发 API C#
C#使用外部字体、嵌入字体到程序资源中(Winform)及字体的版权问题
应用程序能够使用一个好的字体,是用户界面很重要的一部分,但是很多字体如果系统没有安装,则需要额外引入,这就涉及到极其重要的字体版权问题,及额外字体的使用和安装。最好的方式应该是将字体嵌入到程序中...
5650 1
C#使用外部字体、嵌入字体到程序资源中(Winform)及字体的版权问题
|
API C# Windows
Winform控件优化之无边框窗体及其拖动、调整大小和实现最大最小化关闭功能的自定义标题栏效果
Winform中实现无边框窗体只需要设置FormBorderStyle = FormBorderStyle.None,但是无边框下我们需要保留移动窗体、拖拽调整大小、自定义美观好看的标题栏等...
5243 0
Winform控件优化之无边框窗体及其拖动、调整大小和实现最大最小化关闭功能的自定义标题栏效果
|
C# 容器
Winform控件优化之TabControl控件的美化和功能扩展
在基本的TabControl控件使用和功能之上,可以尝试对其进行美化和功能扩展,比如动态删除或添加tab、绘制图标按钮及鼠标hover时的背景变化、Tab从右向左布局的优化处理等。最重要...
3769 0
Winform控件优化之TabControl控件的美化和功能扩展
|
Windows
Winform控件优化之背景透明那些事1:Button控件等背景透明
WinForm不支持真正的透明,其控件透明的实现都是背景颜色设置和对应位置的父控件背景相同。 Winform中控件的背景透明只有三种:Button控件的透明、其他控件的透明...
3702 0
Winform控件优化之背景透明那些事1:Button控件等背景透明
|
C# 图形学 Windows
Winform控件优化之背景透明那些事2:窗体背景透明、镂空穿透、SetStyle、GDI透明效果等
两行代码就能实现Form窗体的(背景)透明效果,它不是Opacity属性的整个窗体透明,`TransparencyKey`实现窗体的透明、窗体中间部分镂空效果...
4415 0
Winform控件优化之背景透明那些事2:窗体背景透明、镂空穿透、SetStyle、GDI透明效果等
|
C# Windows 容器
C#或Winform中的消息通知之系统托盘的气泡提示窗口(系统toast通知)、ToolTip控件和ToolTipText属性
NotifyIcon控件表示系统右下角任务栏上的托盘图标,其ShowBalloonTip方法用于显示气球状提示框(Win10只有为本地Toast通知),ToolTip\oolTipText可以...
3290 0
C#或Winform中的消息通知之系统托盘的气泡提示窗口(系统toast通知)、ToolTip控件和ToolTipText属性
C#或Winform中的消息通知之自定义优雅漂亮的通知效果
Custom Notification自定义通知提示,一款非常优雅漂亮的自定义通知效果,主要介绍其实现思路、调整和优化...
1711 0
C#或Winform中的消息通知之自定义优雅漂亮的通知效果