3.14. Improve Redraw Speeds for GDI+
Every control and form in .NET inherits from the base Control class. In .NET 2.0, the Control class sports a new property named DoubleBuffered. If you set this property to TRue, the form or control will automatically use double-buffering, which dramatically reduces flicker when you add custom drawing code.
Note: Need to turbocharge your GDI+ animations? In . NET 2.0, the Form class can do the double-buffering for you.
3.14.1. How do I do that?
In some applications you need to repaint a window or control frequently. For example, you might refresh a window every 10 milliseconds to create the illusion of a continuous animation. Every time the window is refreshed, you need to erase the current contents and draw the new frame from scratch.
In a simple application, your drawing logic might draw a single shape. In a more complex animation, you could easily end up rendering dozens of different graphical elements at a time. Rendering these elements takes a small but significant amount of time. The problem is that if you paint each graphical element directly on the form, the animation will flicker as the image is repeatedly erased and reconstructed. To avoid this annoying problem, developers commonly use a technique known as double-buffering. With double-buffering, each new frame is fully assembled in memory, and only painted on the form when it's complete.
.NET 2.0 completely saves you the hassle of double-buffering. All you need to do is set the DoubleBuffered property of the form or control to TRue. For example, imagine you create a form and handle the Paint event to supply your own custom painting logic. If the form is set to use double-buffering, it won't be refreshed until the Paint event handler has finished, at which point it will copy the completed image directly onto the form. If DoubleBuffered is set to False, every time you draw an individual element onto the form in the Paint event handler, the form will be refreshed. As a result, the form will be refreshed dozens of times for anything but the simplest operations.
Example 3-5 features a form that makes use of custom drawing logic. When the user clicks the cmdStart button, a timer is switched on. This timer fires every few milliseconds and invalidates the form by calling its Invalidate( ) method. In response, Windows asks the application to repaint the window, triggering the OnPaint( ) method with the custom drawing code.
Example 3-5. An animated form
Public Class AnimationForm ' Indicates whether the animation is currently being shown. Private IsAnimating As Boolean = False ' Track how long the animation has been going on. Private StartTime As DateTime Private Sub Form_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint ' Check if the animation is in progress. If IsAnimating Then ' Get reading to draw the current frame. Dim g As Graphics = e.Graphics g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality ' Paint the background. Dim BackBrush As New LinearGradientBrush( _ New Point(0, 0), New Point(100, 100), _ Color.Blue, Color.LightBlue) g.FillRectangle(BackBrush, New Rectangle(New Point(0, 0), _ Me.ClientSize)) g.FillRectangle(Brushes.LightPink, New Rectangle(New Point(10, 10), _ New Point(Me.Width - 30, Me.Height - 50))) ' Calculate elapsed time. Dim Elapsed As Double = DateTime.Now.Subtract(StartTime).TotalSeconds Dim Pos As Double = (-100 + 24 * Elapsed ^ 2) / 10 ' Draw some moving objects. Dim Pen As New Pen(Color.Blue, 10) Dim Brush As Brush = Brushes.Chartreuse g.DrawEllipse(Pen, CInt(Elapsed * 100), CInt(Pos), 10, 10) g.FillEllipse(Brush, CInt(Elapsed * 100), CInt(Pos), 10, 10) g.DrawEllipse(Pen, CInt(Elapsed * 50), CInt(Pos), 10, 10) g.FillEllipse(Brush, CInt(Elapsed * 50), CInt(Pos), 10, 10) g.DrawEllipse(Pen, CInt(Elapsed * 76), CInt(Pos) * 2, 10, 10) g.FillEllipse(Brush, CInt(Elapsed * 55), CInt(Pos) * 3, 10, 10) g.DrawEllipse(Pen, CInt(Elapsed * 66), CInt(Pos) * 4, 10, 10) g.FillEllipse(Brush, CInt(Elapsed * 72), CInt(Pos) * 3, 10, 10) If Elapsed > 10 Then ' Stop the animation. tmrInvalidate.Stop( ) IsAnimating = False End If Else ' There is no animation underway. Paint the background. MyBase.OnPaintBackground(e) End If End Sub Private Sub tmrInvalidate_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles tmrInvalidate.Tick ' Invalidate the form, which will trigger a refresh. Me.Invalidate( ) End Sub Private Sub cmdStart_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdStart.Click ' Start the timer, which will trigger the repainting process ' at regular intervals. Me.DoubleBuffered = True IsAnimating = True StartTime = DateTime.Now tmrInvalidate.Start( ) End Sub ' Ensure that the form background is not repainted automatically ' when the form is invalidated. This isn't necessary, because the ' Paint event will handle the painting for the form. ' If you don't override this method, every time the form is painted ' the window will be cleared and the background color will be painted ' on the surface, which causes extra flicker. Protected Overrides Sub OnPaintBackground( _ ByVal pevent As System.Windows.Forms.PaintEventArgs) ' Do nothing. End Sub End Class
Try running this example with and without double-buffering. You'll see a dramatic difference in the amount of flicker.
3.14.2. What about...
...owner-drawn controls? Double-buffering works exactly the same way with owner-drawn controls as with forms, because both the Form and Control classes provide the DoubleBuffered property and the Paint event. Of course, there's no point in double-buffering both a form and its controls, since that will only cause your application to consume unnecessary extra memory.
3.14.3. Where can I learn more?
Overall, the GDI+ drawing functions remain essentially the same in .NET 2.0. To learn more about drawing with GDI+, look up "GDI+ Examples" in the index of the MSDN help library. You may also be interested in the "GDI+ Images" and "GDI+ Text" entries.