Wednesday, September 5, 2012

Dealing with Flickering on Windows

I am often asked if we have solved the flickering problem that is often seen on Windows applications. Unfortunately, this is mostly an issue with Windows itself. Unlike OS X and Linux, Windows does not double-buffer window updates. This is a source for annoying flicker in a wide variety of Windows applications, including those made with Real Studio.

However, there are strategies you can use to minimize flickering in your Windows applications.


Do Not Overlap Controls

The easiest thing you can do to prevent flickering is to not overlap any controls.  Overlapped controls result in more requests to redraw the controls which results in flickering.

Use a Canvas

For best results, display your graphics using the Paint event of a Canvas control.  Stay away from using the Window Paint event, the Canvas.Backdrop property or the ImageWell control.  Although those techniques work fine in certain situations, they often lead to flickering in more complex window layouts.

On the Canvas, the first thing you want to do is enable the DoubleBuffer property and disable the EraseBackground property.  The DoubleBuffer property allows the Canvas to do its updates offscreen to minimize flicker.  The EraseBackground property prevents the Canvas from being erased (and showing as a white rectangle) before it is redrawn, which is a common source of flicker.  Now you can do all your drawing in the Paint event using the supplied graphics object, g.

Note: Do not do any drawing directly to the Canvas.Graphics property. This will likely increase flickering and will definitely slow down graphics updates.

You can have separate methods that update the graphics, but they need to be called from the Paint event with the graphics object supplied to the methods as a parameter.
When you want to update the graphics in the Canvas, you call the Invalidate method:

Canvas1.Invalidate

You can also call the Refresh method:

Canvas1.Refresh

The difference is that Invalidate tells the Canvas to update itself when it gets a redraw request from the operating system.  The Refresh method tells the Canvas to update itself immediately.  Generally, Invalidate is more efficient and should be your first choice.

Both of the above commands do not include the EraseBackground parameter, so it defaults to True. This means that the Canvas is erased before the contents are redrawn, which can result in flicker.  Instead, you should draw the background yourself and disable EraseBackground by passing False instead.

Some Tricks to Make this Easier

In general, you do not want to use EraseBackground at all.  Anywhere that it is specified or used, you should always set it to False (this incudes Canvas, canvas methods and ContainerControl)

One trick you can use to make this easier is to override the Refresh and Invalidate methods to ignore the EraseBackground flag.  For example:

Sub Invalidate(EraseBackground As Boolean = True)
 Super.Invalidate(False)
End Sub

As mentioned above, you also do not want to access Graphics directly.  In order to catch situations where you might do this accidentally, you can add a Graphics method that returns Nil.  This way if you mistakenly call Canvas.Graphics, you will get a NilObjectException:

Function Graphics() As Graphics
 Return Nil
End Function

If you are creating a cross-platform application, Canvas.DoubleBuffer is not needed on OS X or Linux.  For maximum performance, you should disable it on those platforms using conditional compilation:

#If TargetWin32 Then
  Me.DoubleBuffer = True
#Else
  Me.DoubleBuffer = False
#Endif

I hope these tips help you to create great-looking Windows applications in Real Studio.

8 comments:

neonash7777 said...

My problem is that I have a lot of embedded container controls, Container Controls don't have an invalidate function and they always flicker when the user is resizing the window. They also often leave garbage drawing behind :-(

Paul Lefebvre said...

Do you get better results if you set EraseBackground = False on the ContainerControl and any instances of it on a window?

neonash7777 said...

I still get flickering with EraseBackground false. It does paint more fluid (without flickering) if I turn off the HasBackColor but this has to be on so that the containers don't leave garbage-painting behind when resizing.

Paul Lefebvre said...

I haven't seen the "garbage-painting" stuff in a ContainerControl. If you could create a case with a sample project, we can look into it.

anic297 said...

By using only the Graphics parameter of the Paint event, and the Invalidate method to trigger the refresh, I fail to see how it is possible to use a canvas where the user can draw like with a pencil (i.e. the pencil draws from the MouseDrag and the visual drawing should reflect “right now”, not when the system wants to refresh).

Mathias said...

This was really helpfull!

Really weird Windows handles painting this way, the OSX and Linux way are better, I think

anic297 said...

For Mac OS X and Win32, this is historical. Mac OS X started with double buffering (Mac OS 9 didn't, like Win32). So both have kept their standard (for compatibility, I guess). I don't know about Linux.

Paul Lefebvre said...

@anic297: See my companion post "Drawing Objects in a Canvas with the Paint Event" to see how you can move objects with MouseDrag using these techniques.