Introduction
Owner-drawn UserControls with dynamic user interfaces usually have several distinct visual states. As you begin to write one of these controls, you will soon notice that controls which require a lot of painting to change states tend to flicker and, depending on the complexity of the painting operation, the user may experience a lag while the control is repainted. While the flicker problem can be dealt with by setting the ControlStyles.DoubleBuffer
style on the control, this doesn't address processor lag problem. The class described in this article addresses both of these problems by providing an easy way to cache the different visual states a control may have. The demo application illustrates the use of this class by creating a simple button control with three states, a normal state, a mouse-over state, and a mouse-down state.
Background
The topics discussed in this article are very simple and should be easy to understand for most people. However, it is assumed that you have some experience with the creation of UserControls and GDI+.
The source is divided into two projects, GraphicsBuffer and GraphicsBufferText. The GraphicsBuffer project contains the source for the GraphicsBuffer
class and the GraphicsBufferTest project contains the code for the demo control that uses the GraphicsBuffer
class. The projects were both created in Visual Studio .NET 2003 but none of the code uses constructs that were not available in the earlier Visual Studio .NET release.
Using the GraphicsBuffer Object
Using the GraphicsBuffer
object is a three-step process:
- Create the buffer.
- Write to the buffer with standard GDI+ method calls.
- Pull the cached image from the object and draw it on the target control.
1. Creating the buffer
To create a buffer for your image, the GraphicsBuffer
object requires you to specify a key and image size for the buffer. The key is used to access the buffer later. The size of the image buffer cannot be changed once the buffer is created, but it can be discarded and a new one created. If the buffer is used to cache the visual state of a control, the size of the buffer should be the same size as the control.
GraphicsBuffer _graphicsBuffer = new GraphicsBuffer();
_graphicsBuffer.Create("Normal",100,50);
_graphicsBuffer.Create("MouseDown",100,50);
_graphicsBuffer.Create("MouseOver",100,50);
2. Writing to the buffer
The buffer exposes a Graphics
object through its indexer. The key for the indexer is the name specified when the buffer was created. Because the Graphics
object is exposed, all standard drawing functionality is available. The code below draws a gradated background on the buffer, creates a solid border, and draws some text in the center of it.
Graphics g; Rectangle rect;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
rect = new Rectangle(0,0,100,50);
g = _graphicsBuffer["Normal"];
Brush b = new LinearGradientBrush (rect,
Color.FromArgb(125,125,150),
Color.FromArgb(150,150,200),45F,true);
g.FillRectangle(b,rect);
g.DrawRectangle(new Pen(Color.FromArgb(125,125,175),1), rect);
g.DrawString("Hello World",new Font("Arial",10),
new SolidBrush(Color.White),rect,sf);
As a practical suggestion, I would recommend wrapping the code which draws to the buffer in a function block so that it can be called within your application at any time. This is because, there are several situations where you may want to scrap the image in the buffer and rebuild it. For example, if your image depends on the size of the control it's displayed on, then it will need to get recreated when the control is resized. Also, there could be several properties of your control which may affect the visual appearance of your control. When any one of those properties is changed, the buffer will need to be rebuilt. To discard the contents of the buffer, you could use the Clear()
or Remove()
methods.
_graphicsBuffer.Clear();
_graphicsBuffer.Remove("Normal");
3. Display Cached Image on Control
To retrieve a cached image from the control, use the GetImage()
method. In more cases than not, this would be used in the Paint event of your control.
private void BufferedButton_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawImage(_graphicsBuffer.GetImage("Normal"),
this.ClientRectangle);
}
The example above always draws the "Normal
" state for the control. In your application, you would probably have a switch
statement to select between the different possible states to be displayed. The code in the demo project illustrates this concept.
Conclusion
By itself, the code in the GraphicsBuffer
class is nothing revolutionary. The extreme coolness comes from the crispness it adds to your controls when you write code structured to use it. When the class is used properly, your control only repaints itself exactly as many times as it needs to. Too many controls repeat all of their drawing procedures every time the Paint event is fired. Using this simple class will create a measurable improvement in the professionalism in your controls and applications.
History
- August 3, 2004 - Initial creation.