Introduction

This recipe provides a simple double buffered window that can be sub-classed to do any customized drawing you might need, without worrying about Paint events, etc.

What Is Double Buffering?

Double buffering is storing the contents of a window in memory (the buffer), so that the screen can be easily refreshed without having to re-draw the whole thing.

Why double Buffer?

Whenever a window displayed on the screen gets damaged, by, for instance, a dialog box being displayed over it, the system asks the program to re-draw it. This is indicated by a Paint event, and is handled in a Paint event handler. Usually the Paint event handler creates a wx.PaintDC, and calls code that re-draws the window. See CustomisedDrawing and WxHowtoDrawing for an example, as well as assorted code in the wxPython demo.

This works fine for simple drawings, but if you have a complex drawing, it can take a while for the program to draw, and the result is that the user has to sit there and wait, while they watch the screen re-draw itself. Personally, I find it really annoying to watch a window slowly redraw itself, just because I moved a dialog box or something.

Double buffering solves this problem, because all a window needs to do to re-draw itself is to copy the buffer to the screen. This is a very fast operation.

What Objects are Involved

This recipe will help you figure out how to use a number of DeviceContexts (DCs), including:

wx.PaintDC -- drawing to the screen, during EVT_PAINT

wx.ClientDC -- drawing to the screen, outside EVT_PAINT

wx.BufferedPaintDC -- drawing to a buffer, then the screen, during EVT_PAINT

wx.BufferedDC -- drawing to a buffer, then the screen, outside EVT_PAINT

Process Overview

The basic process is to create a class derived from wx.Window, that keeps a wx.Bitmap around with a copy of the image on the screen. The OnPaint handler simply copies that bitmap to the screen, and when the image needs to be updated, it is drawn to the bitmap, and the bitmap is re-copied to the screen.

The BufferedWindow class

(warning: drawing not yet updated to the wx namespace)

import wx
import random

Import the usual wx module.

Import the random module, too; We'll need it for random data later in the demo.

#USE_BUFFERED_DC = False
USE_BUFFERED_DC = True

This example can optionally use the wx.BufferedDC.

If USE_BUFFERED_DC is true, it will be used. Otherwise, the program uses the raw wx.MemoryDC, etc.

The wx.BufferedDC is a relatively recent addition that makes it a little easier to double buffer.

Note the style: wx.NO_FULL_REPAINT_ON_RESIZE. This style can reduce flickering when resizing windows on Microsoft Windows. I'm not sure it makes a difference on other platforms (thanks to KevinAltis for that hint).

self.OnSize() is called lastly. It's called last, because OnSize will initialize the buffer, and we want to make sure it's the right size. It may end up getting called twice during initialization on some platforms, but there is little harm done.

It will inspect your system, find out what needs to be drawn, and then draw it.

UpdateDrawing will call Draw. UpdateDrawing will take care of boring details, to make sure that Draw only has to work with a nice drawing context.

This function, Draw, is responsible for all of your drawing code. It is responsible for the scene creation. No other functions will be drawing on the buffer; only this one.

1defOnSize(self,event): 2# The Buffer init is done here, to make sure the buffer is always 3# the same size as the Window 4Size = self.ClientSize 5 6# Make new offscreen bitmap: this bitmap will always have the 7# current drawing in it, so it can be used to save the image to 8# a file, or whatever. 9self._Buffer = wx.EmptyBitmap(*Size) 10self.UpdateDrawing()

The OnSize() Method.

When the window is resized, or on first __init__, this is called.

It's responsible for:

Making a buffer the right size.

Drawing to the buffer.

Copying the buffer to the display.

OnSize will fill the first (making the buffer the right size,) and then delegating the other two to UpdateDrawing.

When OnSize creates the buffer, it's completely blank. UpdateDrawing will fill it (with the help of Draw,) and then blit the buffer to the screen.

UpdateDrawing is a method that we invented, for our convenience; it's not a built-in part of wxPython.

OnSize does not trigger a call to OnPaint(). UpdateDrawing() will make sure that the buffer is drawn to (via Draw()), and UpdateDrawing() will make sure that the buffer is drawn to the screen.

Now the OnPaint() method. It's called whenever ther is a pain event sent by the system: i.e. whenever part of the window gets dirty.

Since the image is stored in the buffer, all we have to do here is copy the buffer to the screen.

Ideally, you'd only copy the damaged region to the screen. But I've found I can't tell the difference, so I don't bother. In fact, on Windows at least, only the damaged region is copied, anyways, with this code: the system has set the update region appropriately.

There are two approaches here, depending on USE_BUFFERED_DC.

USE_BUFFERED_DC is True.

This way is a little easier.

The wx.BufferedPaintDC takes a bitmap as input (self._Buffer,) and creates a DC that you can use to draw to the bitmap. We're not going to draw to the bitmap; we've already drawn to the bitmap. So all we do is create the wx.BufferedPaintDC.

Note that a PaintDC or BufferedPaintDC is unique -- it must be used only inside a paint event, and (on some systems, at least) a PaintDC must be created when there is a paint event.

Now the object is somewhat unique, in that the bitmap is copied to the screen when the object goes out of scope. That is, when the OnPaint method is done, the object is no longer needed, and it is deleted. Just before it's deleted, though, it copies itself to the screen.

USE_BUFFERED_DC is False.

Without the BufferedPaintDC, the process is similar, except that we have to make a call to copy the bitmap to the wx.PaintDC.

Note that we use dc.DrawBitmap(), rather than dc.Blit(). DrawBitmap accomplishes essentially the same thing, but with an easier interface, for our purpose here.

1defUpdateDrawing(self): 2dc = wx.MemoryDC() 3dc.SelectObject(self._Buffer) 4self.Draw(dc) 5deldc# need to get rid of the MemoryDC before Update() is called. 6self.Refresh(eraseBackground=False) 7self.Update()

UpdateDrawing() is a method we've invented here, that you should call whenever the drawing needs to update. The drawing is generated from data found elsewhere in the system. If those data change, the drawing needs to be updated, so be sure to call this function.

This function does all the pre-drawing and post-drawing work. The Draw() function we created earlier, does the actual drawing work. A wx.MemoryDC is created to draw to the bitmap with. You have to remember: a bitmap is not a drawing context! You can't use our neat drawing context functions, when all you hold is a bitmap. You have to actually put the bitmap into the drawing context, and then you can do your neat drawing things on it.

After the drawing is done, the MemoryDC is destroyed, so that the Bitmap is freed to be used elsewhere (in the paint handler). The Refresh(eraseBackground=False)is called to tell the system that the Window needs to be redrawn (eraseBackground=False prevents the system from erasing the background before re-drawing -- which could cause flicker). Update() is called to force a paint event. The actual drawing to the screen happens in the paint event.

1defSaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG): 2## This will save the contents of the buffer 3## to the specified file. See the wx docs for 4## wx.Bitmap::SaveFile for the details 5self._Buffer.SaveFile(FileName, FileType)

The next method saves the contents of the buffer to a file. The buffer always contains the same image as the screen, so this makes it very easy to take a screenshot.

Using the `BufferedWindow` class

In order to use the BufferedWindow class, you must create a subclass, and define a Draw() method appropriate to your application.

The init method is very straight forward. Note that any data needed by your Draw() method must be defined before calling BufferedWindow.__init__(), because the Draw() method is called when it is initialized. In this case, I have created an empty dictionary that will cause the Draw() method to do nothing.

Special Concerns

If your image is big, you may want to have some way to scroll around it. One method is to use a wx.ScrolledWindow, but remember that the buffer has to be updated as you scroll, which can be pretty slow. Another method is to have a really big bitmap, and just blit the appropriate part to the screen as you scroll. This works well, but can only accommodate a moderate sized bitmap. Memory use can get big very fast!