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方法中?避免继承控件时重复重写的代码? 待后续研究实现。

相关文章
|
算法 API C#
Winform控件优化之圆角按钮【各种实现中的推荐做法】(下)
最终优化实现ButtonPro按钮(继承自Button),既提供Button原生功能,又提供扩展功能,除了圆角以外,还实现了圆形、圆角矩形的脚尖效果、边框大小和颜色、背景渐变颜色...
2086 0
Winform控件优化之圆角按钮【各种实现中的推荐做法】(下)
|
C# 图形学
Winform控件优化之Paint事件实现圆角组件(提取绘制圆角的扩展方法)
Paint事件方法中实现圆角控件不要通过事件参数`e.ClipRectangle`获取控件区域范围,原因见最后介绍;注意设置控件背景透明(参见[Winform控件优化之背景透明那些事2...
850 0
Winform控件优化之Paint事件实现圆角组件(提取绘制圆角的扩展方法)
|
编解码 C# 图形学
winform-SunnyUI控件解决大小位置变化
winform-SunnyUI控件解决大小位置变化
513 0
|
C# 图形学 Windows
Winform控件优化之圆角按钮【各种实现中的推荐做法】(上)
Windows 11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化...尝试介绍很常见的圆角效果,通过重写控件的OnPaint方法实现绘制,并在后面进一步探索对应的优化和可能的问题
1469 0
Winform控件优化之圆角按钮【各种实现中的推荐做法】(上)
Winform控件优化之圆角Panel【绘制时需要注意的几点和扩展】
圆角的实现(原理和绘制方法)之前基本都已经介绍,本篇主要是实现圆角Panel时介绍几点注意点和一些扩展。一是BackColor应始终为Transparent;二是Draw完全显示绘制出的线条...
1662 0
Winform控件优化之圆角Panel【绘制时需要注意的几点和扩展】
|
人工智能
VB编程:自定义过程改变窗体颜色-53
VB编程:自定义过程改变窗体颜色-53
188 0
|
人工智能
VB编程:自定义过程改变窗体颜色
VB编程:自定义过程改变窗体颜色
328 0
VB编程:自定义过程改变窗体颜色
|
前端开发 C#
使用MVVM DataTriggers在WPF XAML视图之间切换/Window窗口自适应内容大小并居中
原文 使用MVVM DataTriggers在WPF XAML视图之间切换 相关文章: http://www.technical-recipes.com/2016/switching-between-wpf-xaml-views-using-mvvm-datatemplate/ 这篇文章解决了能够根据ViewModel类的属性在不同视图之间切换的问题。
1879 0
|
C#
WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)
原文:WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆) 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.csdn.net/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
1266 0
|
C# 数据安全/隐私保护
WPF 使用附加属性增加控件属性
原文:WPF 使用附加属性增加控件属性 使用附加属性增加控件属性,使得这个附加属性在使用的时候没有局限性,可以在任何的控件中使用它来增加所需要的属性,使得控件的属性使用起来非常灵活   一、自定义附加属性 1 2 3 4 5 6 7 8 9 10 11 ...
896 0

相关实验场景

更多