65.9K
CodeProject 正在变化。 阅读更多。
Home

不要闪烁!使用双缓冲!

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (53投票s)

2006 年 1 月 29 日

CPOL

4分钟阅读

viewsIcon

437884

downloadIcon

13447

演示了几种克服恼人闪烁问题的方法

引言

闪烁是一个常见的问题,每个在 Windows 窗体环境中编程的人都知道。我们都知道,即使 Windows 任务管理器在选择进程列表中的进程时也会闪烁。

如果你曾经研究过这个问题,你可能已经注意到最常见的解决方案是双缓冲。

解释

双缓冲是一种技术,我们将所有的图形需求绘制到存储在内存(缓冲区)中的图像中,并且在我们完成所有的绘图需求之后,我们将完整的图像从内存绘制到屏幕上。 这将屏幕上的绘图(严重影响应用程序性能的操作)集中到单个操作,而不是许多小的操作。

一个容易理解的例子是使用具有多个层的 ProgressBar

  1. 背景层
  2. 边框层
  3. 进度层
  4. 百分比层

对于这些层中的每一层,我们需要调用一些绘图操作,并且在每次绘图操作之后,控件会将其自身重绘到屏幕上。 现在,如果刷新率较低,我们将没有任何问题,但是如果我们加快刷新率,则会发生闪烁(眨眼)。

我们通过将所有层绘制到位于内存中的图像中来解决此问题,并且在将所有层绘制到此图像中之后,我们将图像绘制到屏幕上。 这极大地提高了性能。

技术

注意:以下提到的所有技术都用于本文提供的示例源代码中,除了第一个来自 .NET Framework 1.1 的技术,并且源代码是针对 .NET Framework 2.0。

开始之前您应该知道的事情

  • SetStyle(ControlStyles.AllPaintingInWmPaint, true);

    绘制控件时,会调用两个函数,OnPaintOnPaintBackground。 设置此标志后,它将忽略 OnPaintBackground 函数,并且 OnPaint 函数负责绘制背景和前景。

  • SetStyle(ControlStyles.UserPaint, true);

    当此标志设置为 true 时,控件会自行绘制,而不是由系统操作绘制。

  • 提示(由 Tim McCurdy 提供):SetStyle(ControlStyles.ResizeRedraw, true);

    设置此标志会导致控件在调整大小时重绘自身。

  • ProgressBar 绘图

    对于所有这些示例,我调用一个名为 DrawProgressBar 的函数。 传递给它的参数是一个 Graphics 实例,用于绘图

    private void DrawProgressBar(Graphics ControlGraphics)
    {
        // draw background
        ControlGraphics.FillRectangle(Brushes.Black, ClientRectangle);
        // draw border
        ControlGraphics.DrawRectangle(Pens.White, ClientRectangle);
        // draw progress 
        ControlGraphics.FillRectangle(Brushes.SkyBlue, 0, 0, 
                this.Width * ProgressBarPercentValue, this.Height);
        // draw percent
        ControlGraphics.DrawString(ProgressBarPercentValue.ToString(), 
                           this.Font, Brushes.Red, 
                           new Point(this.Width / 2, this.Height / 2));
    }

开始

  • .NET Framework 1.1 内置双缓冲

    public partial class DoubleBufferedControl : Control
    {
        public DoubleBufferedControl()
        {
            InitializeComponent();
    
            this.SetStyle(
                ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.DoubleBuffer, true);
        }
    
        protected override void OnPaint(PaintEventArgs pe)
        {
            // we draw the progressbar normally 
            // with the flags sets to our settings
            DrawProgressBar(pe.Graphics);
        }
    }

    这项技术来自 .NET Framework 1.1,并提供了一些双缓冲支持。 从我测试的结果来看,这项技术不是很好,我更喜欢对 .NET Framework 1.1 使用手动技术(稍后将介绍)。

  • .NET Framework 2.0 内置双缓冲

    public class DoubleBufferedControl : Control
    {
        public DoubleBufferedControl()
        {
            InitializeComponent();
    
            this.SetStyle(
                ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer, true);
        }
    
        protected override void OnPaint(PaintEventArgs pe)
        {
            // we draw the progressbar normally with 
            // the flags sets to our settings
            DrawProgressBar(pe.Graphics);
        }
    }

    好吧,在 .NET Framework 2.0 中,双缓冲技术的易用性有了很大的改进。 通过使用这项技术获得的性能非常好,我建议所有不想进行太多编码的人使用它。

    我应该提到,当我们设置 Control.DoubleBufferedtrue 时,它会将 ControlStyles.AllPaintingInWmPaintControlStyles.OptimizedDoubleBuffer 设置为 true

  • .NET Framework 1.1 的手动解决方案

    我们在这里所做的是自己创建双缓冲,并通过覆盖控件的 OnPaint 事件或您可能想要在任何其他地方使用的事件来实现它

    public partial class DoubleBufferedControl : Control
    {
        const Bitmap NO_BACK_BUFFER = null;
        const Graphics NO_BUFFER_GRAPHICS = null;
    
        Bitmap BackBuffer;
        Graphics BufferGraphics;
    
        public DoubleBufferedControl()
        {
            InitializeComponent();
            
            Application.ApplicationExit += 
                new EventHandler(MemoryCleanup);
    
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    
            BackBuffer = new Bitmap(this.Width, this.Height);
            BufferGraphics = Graphics.FromImage(BackBuffer);
        }
    
        private void MemoryCleanup(object sender, EventArgs e)
        {
            // clean up the memory
            if (BackBuffer != NO_BACK_BUFFER)
                BackBuffer.Dispose();
    
            if (BufferGraphics != NO_BUFFER_GRAPHICS)
                BufferGraphics.Dispose();
        }
        protected override void OnPaint(PaintEventArgs pe)
        {
            // we draw the progressbar into the image in 
            // the memory
            DrawProgressBar(BufferGraphics);
    
            // now we draw the image into the screen
            pe.Graphics.DrawImageUnscaled(BackBuffer);
        }
    
        private void DoubleBufferedControl_Resize(object sender, 
                                                     EventArgs e)
        {
            if (BackBuffer != NO_BACK_BUFFER)
                BackBuffer.Dispose();
    
            BackBuffer = new Bitmap(this.Width, this.Height);
            BufferGraphics = Graphics.FromImage(BackBuffer);
    
            this.Refresh();
        }
    }
  • .NET Framework 2.0 的手动解决方案

    在 .NET Framework 2.0 中,我们仍然可以使用手动方式。 Microsoft 为我们提供了一些有用的工具,使其更加容易。 新工具是 BufferedGraphicsContextBufferedGraphicsBufferedGraphicsContext 为我们提供了一个替代缓冲区,而不是我们在 .NET Framework 1.1 中使用的 Bitmap,而 BufferedGraphics 处理所有图形操作,例如使用 Render() 函数将缓冲图像绘制到屏幕上,等等。

    public class DoubleBufferedControl : Control
    {
        const BufferedGraphics NO_MANAGED_BACK_BUFFER = null;
    
        BufferedGraphicsContext GraphicManager;
        BufferedGraphics ManagedBackBuffer;
    
        public DoubleBufferedControl()
        {
            InitializeComponent();
            
            Application.ApplicationExit += 
                   new EventHandler(MemoryCleanup);
    
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    
            GraphicManager = BufferedGraphicsManager.Current;
            GraphicManager.MaximumBuffer = 
                   new Size(this.Width + 1, this.Height + 1);
            ManagedBackBuffer = 
                GraphicManager.Allocate(this.CreateGraphics(), 
                                               ClientRectangle);
        }
    
        private void MemoryCleanup(object sender, EventArgs e)
        {
            // clean up the memory
            if (ManagedBackBuffer != NO_MANAGED_BACK_BUFFER)
                ManagedBackBuffer.Dispose();
        }
        protected override void OnPaint(PaintEventArgs pe)
        {
            // we draw the progressbar into the image in the memory
            DrawProgressBar(ManagedBackBuffer.Graphics);
    
            // now we draw the image into the screen
            ManagedBackBuffer.Render(pe.Graphics);
        }
    
        private void DoubleBufferedControl_Resize(object sender, 
                                                      EventArgs e)
        {
            if (ManagedBackBuffer != NO_MANAGED_BACK_BUFFER)
                BackBufferManagedBackBufferDispose();
    
            GraphicManager.MaximumBuffer = 
                  new Size(this.Width + 1, this.Height + 1);
    
            ManagedBackBuffer = 
                GraphicManager.Allocate(this.CreateGraphics(), 
                                                ClientRectangle);
    
            this.Refresh();
        }
    }

结论

双缓冲是一种很好且易于使用的技术,我认为任何曾经处理过一些图形编程的人都应该知道。 我也很高兴看到 Microsoft 花费了大量时间来提高 .NET Framework 的 GUI 性能,并为我们提供了一些更好的工具来处理它们,而不是浪费时间编写一些即兴代码。

历史

  • 2006 年 1 月 29 日:初始版本
© . All rights reserved.