In this article, you will see how double buffering is a good and simple to use technique that helps overcome the flickering problem.
Introduction
Flickering is a common problem known to everyone who has programmed in the Windows Forms environment. We all know that even the Windows Task Manager flickers when we select a process from the process list.
If you have ever looked around the subject, you might have probably noticed that the most common solution to this is Double Buffering.
Explanation
Double Buffer is a technique where we draw all our graphic needs to an image stored in the memory (buffer) and after we are done with all our drawing needs, we draw a complete image from the memory onto the screen. This concentrates the drawing to the screen (an operation that badly effects the performance of the application) to a single operation rather than many small ones.
An easy example to understand this would be to use a ProgressBar
that has several layers:
- background layer
- border layer
- progress layer
- percent layer
For each of these layers, we need to call some drawing operation, and after each drawing operation, the control redraws itself to the screen. Now, if the refresh rate is low, we won't have any problem but if we speed up the refresh rate, flickering (blinking) occurs.
We solve this by drawing all the layers to an image that is located in the memory and after drawing all the layers into this image we draw the image onto the screen. This improves the performance dramatically.
Techniques
Note: All of the techniques that are mentioned below are used in the example source code provided with this article except the first one which is from .NET Framework 1.1, and the source code is for .NET Framework 2.0.
Things You Should Know Before We Start
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
When a control is painted, there are two functions that are called, the OnPaint
and the OnPaintBackground
. When this flag set, it ignores the OnPaintBackground
function and the OnPaint
function takes care of drawing both the background and the foreground.
SetStyle(ControlStyles.UserPaint, true);
When this flag is set to true
, the control paints itself and is not painted by the system operation.
- Tip (by Tim McCurdy):
SetStyle(ControlStyles.ResizeRedraw, true);
Setting this flag causes the control to repaint itself when resized.
ProgressBar
drawing
For all these examples, I call a function called DrawProgressBar
. The parameter passed to it is a Graphics
instance that is used for drawing:
private void DrawProgressBar(Graphics ControlGraphics)
{
ControlGraphics.FillRectangle(Brushes.Black, ClientRectangle);
ControlGraphics.DrawRectangle(Pens.White, ClientRectangle);
ControlGraphics.FillRectangle(Brushes.SkyBlue, 0, 0,
this.Width * ProgressBarPercentValue, this.Height);
ControlGraphics.DrawString(ProgressBarPercentValue.ToString(),
this.Font, Brushes.Red,
new Point(this.Width / 2, this.Height / 2));
}
Starting Off
-
.NET Framework 1.1 built-in double buffer
public partial class DoubleBufferedControl : Control
{
public DoubleBufferedControl()
{
InitializeComponent();
this.SetStyle(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs pe)
{
DrawProgressBar(pe.Graphics);
}
}
This technique comes with .NET Framework 1.1 and provides some double buffer support. From what I have tested, this technique is not very good and I prefer using the manual technique for .NET Framework 1.1 (which will be shown later).
-
.NET Framework 2.0 built-in double buffer
public class DoubleBufferedControl : Control
{
public DoubleBufferedControl()
{
InitializeComponent();
this.SetStyle(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs pe)
{
DrawProgressBar(pe.Graphics);
}
}
Well, in .NET Framework 2.0, there is a big improvement in the ease and use of double buffering technique. The performance that we get by using this technique is very good and I recommend this for everyone who doesn't want to get into too much of coding.
I should mention that when we set Control.DoubleBuffered
to true
, it will set the ControlStyles.AllPaintingInWmPaint
and ControlStyles.OptimizedDoubleBuffer
to true
.
-
The manual solution for .NET Framework 1.1
What we do here is create the double buffer ourselves and implement it by overriding the OnPaint
event of a control or anything else that you might want to use it on:
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)
{
if (BackBuffer != NO_BACK_BUFFER)
BackBuffer.Dispose();
if (BufferGraphics != NO_BUFFER_GRAPHICS)
BufferGraphics.Dispose();
}
protected override void OnPaint(PaintEventArgs pe)
{
DrawProgressBar(BufferGraphics);
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();
}
}
-
The manual solution for .NET Framework 2.0
In .NET Framework 2.0, we can still use the manual way. Microsoft has provided us with some useful tools to make it even easier. The new tools are BufferedGraphicsContext
and BufferedGraphics
. BufferedGraphicsContext
provides us an alternative buffer instead of the Bitmap
that we used in .NET Framework 1.1 and BufferedGraphics
handles all the graphics operations like drawing the buffered image to the screen using the Render()
function, etc.:
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)
{
if (ManagedBackBuffer != NO_MANAGED_BACK_BUFFER)
ManagedBackBuffer.Dispose();
}
protected override void OnPaint(PaintEventArgs pe)
{
DrawProgressBar(ManagedBackBuffer.Graphics);
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();
}
}
Conclusion
Double buffering is a good and simple to use technique that I think anyone who has ever dealt with some graphics programming should know. I am also glad to see that Microsoft has put up lot of time to improve the GUI performance of the .NET Framework and provided us with some better tools to deal with them instead of wasting our time on writing some improvised code.
History
- 29th January, 2006: Initial version