那不是闪烁,那是撕裂的一种形式。自定义控件并不总是完全绘制;如果您有一个不允许表单正确重绘自身及其子控件的闭环,则可能会发生这种情况。
可能,使填充 ProgressBar 的过程异步。
一些建议的修改使其更流畅:
-
使用浮点值进行计算(不要转换为 int),并使用 RectagleF 定义绘图的边界。
-
移除位图并设置ControlStyles.OptimizedDoubleBuffer:
如果为 true,则控件首先被绘制到缓冲区而不是直接绘制到屏幕上,这样可以减少闪烁。如果你设置这个
属性为 true,您还应该将 AllPaintingInWmPaint 设置为 true。
-
那么当然设置ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint:
AllPaintingInWmPaint:如果为真,则控件忽略窗口消息WM_ERASEBKGND 以减少闪烁。
仅当 UserPaint 位设置为 true 时才应应用此样式。
UserPaint:如果为 true,则控件绘制自身而不是
操作系统这样做。如果为 false,则不会引发 Paint 事件。
此样式仅适用于从 Control 派生的类。
-
ControlStyles.Opaque 移除背景并让ProgressBarRenderer 完成其工作,填充 ProgressBar 的基本图形。
根据当前Value集合计算ProgressBar的彩色部分的当前宽度:
float width = (this.Width - 3) * ((float)this.Value / this.Maximum)
如果 width > 0,则使用 Forecolor 和 BackColor 属性绘制 ProgressBar 颜色。当然,您可以使用一组不同的属性来定义这些颜色。
为绘图添加一些抗锯齿,设置Graphics.SmoothingMode,使LinearGradientBrush生成的颜色过渡更平滑。这在设置LinearGradientMode.Horizontal 时更有用。
导致:
public class ProgressBarCustom : ProgressBar
{
public ProgressBarCustom()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.Opaque, true);
}
protected override void OnPaint(PaintEventArgs e)
{
ProgressBarRenderer.DrawHorizontalBar(e.Graphics, this.ClientRectangle);
float width = (this.Width - 3) * ((float)this.Value / this.Maximum);
if (width > 0) {
var rect = new RectangleF(1, 1, width, this.Height - 3);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
using (var brush = new LinearGradientBrush(rect, this.BackColor, this.ForeColor, LinearGradientMode.Horizontal)){
e.Graphics.FillRectangle(brush, rect);
}
}
}
}
作为一个小改进,您可以在 ProgressBar 的顶部绘制一条高度为 9 像素、颜色为半透明的线,以模拟原始的反射。
改代码添加Graphics.DrawLine():
// [...]
using (var brush = new LinearGradientBrush(rect, this.BackColor, this.ForeColor, LinearGradientMode.Horizontal))
using (var pen = new Pen(Color.FromArgb(40, 240,240,240), 9)) {
e.Graphics.FillRectangle(brush, rect);
e.Graphics.DrawLine(pen, 1.0f, 1.0f, width, 1.0f);
}