【问题标题】:Flickering custom ProgressBar闪烁的自定义进度条
【发布时间】:2021-03-07 19:50:55
【问题描述】:

对于我正在制作的应用,我根据 William Daniel 制作的一个自定义 ProgressBar:How to change the color of progressbar in C# .NET 3.5?

在使用较低百分比值进行一些测试后,我注意到了这种奇怪的闪烁:

我尝试将 DoubleBuffered 设置为 true,但它们仍然出现。

有人知道为什么会出现这些奇怪的线条吗?

【问题讨论】:

    标签: c# winforms graphics progress-bar gdi+


    【解决方案1】:

    那不是闪烁,那是撕裂的一种形式。自定义控件并不总是完全绘制;如果您有一个不允许表单正确重绘自身及其子控件的闭环,则可能会发生这种情况。
    可能,使填充 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);
    }
    

    【讨论】:

    • 起初对此有点麻烦,因为它似乎使事情变得更糟。直到我发现使用this.Refresh() 而不是this.Invalidate() 更好。感谢您的回答!
    • 起初:然后呢? -- 如果您对此实现有一些问题,请发布使用它的代码,以便我查看并建议如何修复它。
    • 它似乎会导致更差的渐变并保留其撕裂,但这可能是因为每次值更改时我都使更新矩形无效。相反,刷新它会更好地去除所有撕裂。
    • 我应该打电话给Invalidate()(异步)。如果您调用Refresh()(同步),则会在执行绘图时阻止当前操作(触发ProgressBar.Value 更改的原因)(不是说它需要很多,但是...)。因此,您可能正在进行同步操作(请注意,标准 ProgressBar 实际上使用 Timer 来不断地重新绘制自身,因此无论如何它是一个改进)。您应该尝试使您的代码异步,一切都会在各个级别上运行得更好。
    猜你喜欢
    • 1970-01-01
    • 2015-07-26
    • 1970-01-01
    • 2014-01-18
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多