Painting techniques using Windows Forms for the Microsoft .NET Framework

Get the samples for this article. Unzip the folder and see the readme.txt file for instructions on running the samples.

This article begins by introducing the basic architecture of painting within the .Net Framework. After a brief introduction and sample, a few painting techniques are discussed along with their advantages, disadvantages, and other considerations.

Introduction

Fortunately, for the typical client application written using Windows Forms, painting/rendering/drawing is not a foremost consideration. And why should it be? By using the .Net Framework, a developer can drag a series of controls onto a Form, write some code 'behind the scenes' to hook up a few events, and hit F5. There you have it, a full blown application ready for deployment! All the controls will draw themselves, resize nicely, and scale appropriately. Every so often there will be an application which demands a little more attention to rendering. Games, a custom chart control, or even a screensaver are examples of applications that will require the programmer to develop code responding to the paint events.

This article will target those Windows Forms developers that will benefit from employing simple drawing techniques in their applications. To start, the basic concept of painting will be discussed. Who's responsible for painting what? How does a Windows Form program know when to paint? And where should all this fancy painting code exist? After this, the basic technique of double buffering will be introduced. You will take a look at how it works and what the trade offs are for using such a method. Finally, there will be a discussion about "intelligent invalidation". In other words, redrawing or erasing only the parts of your application that are absolutely necessary. Hopefully, these ideas and techniques will lead the reader of this article down a path towards better (faster & more attractive) Windows Forms applications.

It should be noted that Windows Forms uses the GDI+ graphics engine. All of the painting code in this article references the managed wrappers defined by the .Net Framework to manipulate the GDI+ graphics engine.

Although this article does start out with a basic introduction of how painting a Form works it does accelerate quickly towards more performance-oriented techniques and ideas. Because of this, it is recommended that the reader have a basic knowledge of the .Net Framework including event handling in Windows Forms, and simple GDI+ objects such as lines, pens, and brushes. A solid understanding of the Visual Basic and/or the C# programming language is also necessary.

The Concept Of Painting

Every Windows application is responsible for painting itself. An application will receive painting information when it is "dirtied". In other words, when the window is resized, partially covered by other applications, or even restored from a minimized state. Windows has named this transition to a dirty state "invalidated". When a Windows Form application is invalidated, it will receive painting information from Windows packaged into messages passed into the application. From here, this information is parsed and passed along to the Form's PaintBackground and Paint events. These events are the proper locations for adding your own specialized painting code.

A simple painting exercise:

Imports System
Imports System.Drawing
Imports System.Windows.Forms

Public Class BasicX
   Inherits Form
   
   
   Public Sub New()
      InitializeComponent()
   End Sub 'New
   
   
   Private Sub BasicX_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.BasicX_Paint
      Dim g As Graphics = e.Graphics
      Dim p As New Pen(Color.Red)
      Dim width As Integer = ClientRectangle.Width
      Dim height As Integer = ClientRectangle.Height
      g.DrawLine(p, 0, 0, width, height)
      g.DrawLine(p, 0, height, width, 0)
      p.Dispose()
   End Sub 'BasicX_Paint
   
   
   Private Sub InitializeComponent()
      Me.SetStyle(ControlStyles.ResizeRedraw, True)
      Me.ClientSize = New System.Drawing.Size(300, 300)
      Me.Text = "BasicX"
   End Sub 'InitializeComponent
   
   
   Public Shared  _
   Sub Main()
      Application.Run(New BasicX())
   End Sub 'Main
End Class 'BasicX

The above code can be divided into two basic steps for creating this sample application. First, within the InitializeComponent method, a few properties are set and an event handler is added to respond to the Form's Paint event. Note that a control style is being set in this method as well. Control styles are a way of customizing some behavior of a Windows Forms control. For example, the "ResizeRedraw" control style in this form dictates that every time the Form is resized it is completely invalidated - meaning that the information passed into the paint method will always claim that the entire client area of the Form needs to be redrawn. The "client area" is defined as the body of the form everything excluding the title bar and borders of the window. As an interesting exercise, remove this style set in the sample code and run the application. It should be apparent as to why this style is often convenient to set - only the invalidated areas of the form are redrawn.

Now, pay attention to the BasicX_Paint method. As stated earlier, the Paint event will fire whenever the application is invalidated. Since the sample Form listens to this Paint event, it can respond appropriately to the message. Note that the BasicX_Paint method is invoked with an object (the sender) and a PaintEventArgs class (defined as 'e' in this case). The PaintEventArgs class contains two important pieces of information. The first being the Graphics object of the Form. This Graphics object, obtained from a property on the PaintEventArgs class, represents the Form's rendering surface or "canvas" for drawing lines, text, images, etc The second piece of information surfaced from a property in the PaintEventArgs class is the ClipRectangle. This is a Rectangle object representing the invalidated area of the Form - or the area of the Form that needs to be repainted. Keep in mind, that the "ResizeRedraw" control style will set this Rectangle to always be the same size as the Form's client area when resized, or the obscured area if covered by another application. The use of a clipping rectangle will be discussed in further detail in the intelligent invalidation section further along in this article.


The BasicX sample application up and running

Double Buffering, Made Nice & Easy

Double buffering is a technique used to make drawing intensive applications faster and appear smoother by reducing flicker. The basic idea is to take the drawing operations used to paint your application and apply them to an off-screen canvas. Once all of the drawing operations have finished, this off-screen canvas is then 'pushed forward' to the surface - or drawn as a single image onto the control. This gives the user the appearance of a much faster application.

The samples included in this section illustrate the concept and implementation of double buffering. However, in real world use, this functionality is integrated into Windows Forms. This behavior can be leveraged by setting a control style, which will be mentioned at the end of this section.

To see the benefits of this simple double buffering technique, run the SpiderWeb sample. Once the program is up and running, resize the Form. Grab the lower right corner of the Form and quickly move the mouse around the screen. You can see the algorithm used to draw the lines is not very efficient resulting in a great deal of flicker.


The SpiderWeb sample application, with no double buffering

Upon inspection of the SpiderWeb source code, you can see that drawing these lines is done through a method called LineDrawRoutine, which is invoked from the application's paint method. The LineDrawRoutine method takes two arguments: 1) the Graphics object on which to draw the lines, and 2) the Pen object (the rendering tool) to draw the lines with. The code is fairly straightforward: looping through several iterations, defined by the LINEFREQ constant, it draws a line from the left side of the Form's surface to the top. Note that this sample uses the float data type to calculate the drawing positions onto the Form, so to provide better accuracy when resizing.

    	Private Sub LineDrawRoutine(g As Graphics, p As Pen)
	   Dim width As Single = ClientRectangle.Width
	   Dim height As Single = ClientRectangle.Height
   
	   Dim xDelta As Single = width / LINEFREQ
	   Dim yDelta As Single = height / LINEFREQ
   
	   Dim i As Integer
	   For i = 0 To LINEFREQ - 1
	      g.DrawLine(p, 0, height - yDelta * i, xDelta * i, 0)
	   Next i
	End Sub 'LineDrawRoutine 

The sample code written to respond to the Paint event, SpiderWeb_Paint, is even simpler. As mentioned in the previous section, The Concept Of Painting, the Graphics object representing the Form's surface can be extracted from the PaintEventArgs object. This Graphics object along with a newly created Pen object are passed along to the LineDrawRoutine method to render the spider web looking lines. After this, the Graphics and Pen objects are disposed of, and the painting work is complete.

	Private Sub SpiderWeb_Paint(sender As Object, e As PaintEventArgs)
	   Dim g As Graphics = e.Graphics
	   Dim redPen As New Pen(Color.Red)
   
	   'call our isolated drawing routing
	   LineDrawRoutine(g, redPen)
   
	   redPen.Dispose()
	   g.Dispose()
	End Sub 'SpiderWeb_Paint

So, what needs to be changed to the above SpiderWeb application to implement a simple double buffering technique? Instead of the drawing operations being rendered to the surface of the application, they will be redirected to an off-screen bitmap. The LineDrawRoutine can be supplied with the hidden bitmap's Graphics object to do all the rendering behind the scenes. After this is finished, the off-screen image can be 'pushed forward' to the surface of the Form by using the Graphics.DrawImage method. Finally, a few other modifications can be done to the application which specifically targets the Form's painting performance.

Compare the code below, defining a double buffered paint event, to the simple SpiderWeb_Paint event mentioned above:

	Private Sub SpiderWeb_DblBuff_Paint(sender As Object, e As PaintEventArgs)
	   Dim g As Graphics = e.Graphics
	   Dim bluePen As New Pen(Color.Blue)
   
	   'create our offscreen bitmap
	   Dim localBitmap As New Bitmap(ClientRectangle.Width, ClientRectangle.Height)
	   Dim bitmapGraphics As Graphics = Graphics.FromImage(localBitmap)
   
	   'call our isolated drawing routing
	   LineDrawRoutine(bitmapGraphics, bluePen)
   
	   'push our bitmap forward to the screen
	   g.DrawImage(localBitmap, 0, 0)
   
	   bitmapGraphics.Dispose()
	   bluePen.Dispose()
	   localBitmap.Dispose()
	   g.Dispose()
	End Sub

The above sample code creates a local bitmap the size of our Form's client area (the Form's drawing surface). By making a call to Graphics.FromImage, the Graphics object referencing the local bitmap is returned. This object now represents the off-screen canvas and will be used as the drawing surface for the double buffering technique. Remember that one of the arguments to the LineDrawRoutine method is a Graphics object. The double buffering technique can nearly be completed by passing in the localBitmap Graphics object to the drawing routine method. Now that the spider web looking lines have been rendered to the localBitmap, the next step is to push this bitmap forward to the surface of the Form with a standard DrawImage call. This results in the spider web image being placed instantly onto the Form eliminating the flicker of the lines being drawn individually.

These series of steps are not quite enough to efficiently complete the technique. As mentioned before, control styles are a way of defining generic behaviors of a Windows Forms application. To better implement double buffering, the Opaque control style can be set. This style dictates that the Form will not be responsible for drawing it's background. In other words, if this style bit is set, the painting code must be written with clearing/redrawing the background as an additional consideration. The double buffered version of the Spider Web application will work well with this behavior since on every paint event, the entire surface of the Form is redrawn. To do this properly, the surface is cleared with the back color of the From.

	Public Sub New()
	   SetStyle(ControlStyles.ResizeRedraw Or ControlStyles.Opaque, True)
	End Sub 'New 

	Private Sub SpiderWeb_DblBuff_Paint(sender As Object, e As PaintEventArgs)
   
	   'create our offscreen bitmap
	   Dim localBitmap As New Bitmap(ClientRectangle.Width, ClientRectangle.Height)
	   Dim bitmapGraphics As Graphics = Graphics.FromImage(localBitmap)
	   bitmapGraphics.Clear(BackColor)
   
	   'call our isolated drawing routing
	   LineDrawRoutine(bitmapGraphics, bluePen)
	End Sub 

The result? A much smoother rendering application. Since the spider web lines are completely drawn off screen then pushed forward, there is essentially no flicker. The delay that can now be perceived is the time taken to push the image onto the Form's surface. As a finishing touch, the user can add one additional piece of code to help bring out the smoothness of the lines.

    bitmapGraphics.SmoothingMode = SmoothingMode.AntiAlias

By placing this line of code immediately after extracting the bitmapGraphics object from the localBitmap, the user is essentially stating that everything drawn to this canvas should be rendered using AntiAliasing, to smooth the jagged lines.


The finished result: SpiderWeb_DblBuff sample - A double buffered app. using AntiAliasing

After completing the simple double buffering section of this article there are a couple of issues for the reader to recognize. Certain controls within the .Net Framework (Button, PictureBox, Label, even the PropertyGrid) already take advantage of this technique! These controls are automatically double buffered by default. This is enabled by another control style "DoubleBuffer". Any user can create a doubled buffered control by simply setting this control style. So a user could effectively develop a Windows Form which hosts a PictureBox that draws the spider web looking lines. Since the PictureBox control is double buffered, the result would be very similar to the SpiderWeb_DblBuff application described above.

A final consideration about the double buffering idea described here is that this is neither fully optimized or without its disadvantages. While double buffering is a great way to reduce the flickering of your Windows Forms painting, it is memory intensive. Essentially, using twice the effective memory: the application's screen image as well as the off screen image. Also, dynamically creating a Bitmap object for every paint event is extremely expensive as is using the Graphics.DrawImage to push the off-screen image to the surface of the Form. The DoubleBuffer control style performs all of these optimizations as is generally the most efficient way to do double buffering.

The most efficient implementation of an offscreen buffer using GDI+ is implemented using a DIB Section bitmap. The implementation of the DoubleBuffer control style takes advantage of this. DIB Sections are a low level Win32 native bitmap that can very efficiently be rendered to the screen.

Also, it is interesting to note that GDI+, in it's first version, is only hardware accelerated for calls that can be directly implemented using GDI primitives and functions. Due to this limitation features like antialiased or translucent painting can be slow when rendered directly to the screen. In general, although double buffering consumes some memory, you can increase performance of operations like these by always painting on a double buffered control.

Intelligent Invalidation, i.e. Think Before You Paint

The term "intelligent invalidation" implies that you, as a developer, should recognize and only redraw the areas of your application that have become invalidated. In turn, drawing only a fraction of your Form's surface will yield better painting performance. The idea of painting smaller more efficient sections of a Form can be expanded to the use of Regions. By implementing Regions you can exclude, paint, or contort certain areas of a Form to gain painting performance.

To start, inspect the BasicClip sample application. This application makes use of the ClipRectangle object stored in the PaintEventArgs object. As mentioned earlier, whenever the application is resized, its Paint event will fire. The BasicClip sample fills the clipping rectangle with alternating colors (red and blue). After resizing the application several times with varying speeds the reader will note that the painted rectangles represent the invalidated (the new or dirtied) areas of the Form.

The code within the sample's Paint event, looks something like this:

	Private Sub BasicClip_Paint(sender As Object, e As PaintEventArgs)
	   Dim g As Graphics = e.Graphics
   
	   'swap colors
	   If currentBrush.Color = Color.Red Then
	      currentBrush.Color = Color.Blue
	   Else
	      currentBrush.Color = Color.Red
	   End If 
	   g.FillRectangle(currentBrush, e.ClipRectangle)
   
	   g.Dispose()
	End Sub 

The only purpose this application serves is to demonstrate how efficient targeting the clipping rectangles can be.


The BasicClip sample, the colored rectangles represent the invalidated client area as the Form is resized downward towards the right.

To move forward with the idea of intelligent invalidation you should explore the use of Regions as a way to specify the smallest possible areas in which to paint. A Region is an object that can be used to define sections of a Windows Form or control. When it comes time to paint, the application can paint only the specific region, and painting a small region is obviously faster than painting a large one.

To better demonstrate the use of Regions, turn your attention to the TextClipping sample. To begin with, this sample overrides both the OnPaintBackground and OnPaint methods. Instead of listening for events, directly overriding these methods will guarantee that the code will be called before other painting calls and is a more efficient way to paint on your own control. Also, for clarity, this sample will have a Setup method which is used to define the graphics objects used throughout the application.

	Private Sub Setup()

	   Dim textPath As New GraphicsPath()
	   textPath.AddString(displayString, FontFamily.GenericSerif, 0, 75, New Point(10, 50), New StringFormat())
	   textRegion = New [Region](textPath)
   
	   backgroundBrush = New TextureBrush(New Bitmap("CoffeeBeanSmall.jpg"), WrapMode.Tile)
	   foregroundBrush = New SolidBrush(Color.Red)
	End Sub 

The Setup method above first defines an empty GraphicsPath. From here, the boundaries of a string ("Windows Forms") is added to the GraphicsPath. A Region is then created around this GraphicsPath. This results in an efficient area (Region) which defines the location of where the string will be drawn onto the surface of the Form. Finally, the Setup method will define a textured background brush and a solid foreground brush that will be used to paint the Form.

	Protected Overrides Sub OnPaintBackground(e As PaintEventArgs)
	   MyBase.OnPaintBackground(e)
   
	   Dim bgGraphics As Graphics = e.Graphics
   
	   bgGraphics.SetClip(textRegion, CombineMode.Exclude)
	   bgGraphics.FillRectangle(backgroundBrush, e.ClipRectangle)
   
	   bgGraphics.Dispose()
	End Sub 

The OnPaintBackground method defined above begins by immediately invoking it's base method - this should be done to ensure all lower level painting code is processed. Next, the Graphics object is obtained. From the reference to this Graphics object, the clipping area can be defined to be the textRegion object. By specifying the CombineMode.Exclude value, this is essentially saying that no matter where or how you paint to this Graphics object, do not render within this textRegion.

	Protected Overrides Sub OnPaint(e As PaintEventArgs)
	   MyBase.OnPaint(e)
   
	   Dim fgGraphics As Graphics = e.Graphics
   
	   fgGraphics.FillRegion(foregroundBrush, textRegion)
   
	   fgGraphics.Dispose()
	End Sub 

Finally, the OnPaint event needs to actually draw the text. This can be easily done by using the FillRegion method supplied by the Graphics object. By specifying the foregroundBrush and the textRegion, only this area is painted. The result? A Windows Form application that thinks - before it paints.


The TextClipping sample, the "Windows Forms" text is defined by a Region. This enables the application to isolate the area for painting purposes.

With the proper use of regions and intelligent invalidation you can produce fast painting code that doesn't flicker and consumes less memory than an equivalent double buffered implementation.

Conclusion

If your Windows Forms application is drawing intensive, there are several techniques to employ that will enhance painting performance. Making use of the proper control style and handling the Paint event properly is a great start to a solid application. Properly using a double buffering technique can yield much better "eye candy" results - as long as the tradeoffs are understood. Finally, thinking before painting can be extremely beneficial if client rectangles and Regions are used.

Hopefully the reader will take away from this article a deeper understanding of the .Net Framework with regards to painting and how to better applications.

Top 5 Things To Remember

  • Leverage the power of the .Net Framework. Some controls, such as the PictureBox are double buffered automatically. Also, pay attention to how the ResizeRedraw, Opaque, DoubleBuffer, and other control styles can benefit you.
  • Consolidate painting code. The basic logic used to render your Windows Forms application should exist in the OnPaint and OnPaintBackground methods. Avoid creating drawing code in methods responding to Resize, Load, or Show events.
  • Think before you paint. Obviously, draw as little as possible for the best performance. Make use of Regions and clipping rectangles.
  • Employ a double buffering technique if feasible.
  • Know your tradeoffs. For example: keeping global Brush objects may benefit the speed of your application, however the memory footprint will be larger. Also, even though the floating point data type is larger than an integer, the use of floats will provide more accurate scaling.
   

© Copyright 2003 Microsoft Corporation. All Rights Reserved.
VB.NET Conversion by Nobuyuki 2007
Terms of Use | Privacy Statement | Code of Conduct