Noticeable Flickering When Bitting From A wxMemoryDC To A wxPaintDC

Greetings;

I've hacked together a simple analog clock using wxPyhton 2.4 an Python 2.2. For the most part, it works. However, every so often there is a visbile flicker across the main frame of the clock face as the time updates.

To perform the updates, I draw the clock face and the hour, minutes and second hands to a wxMemoryDC instance in the main frame's OnPaint event handler, and then blit the contents of the completed wxMemoryDC to the wxPaintDC:

     def OnPaint(self, event):
         clientAreaSize = self.GetClientSizeTuple()
         clientBitmap = wxEmptyBitmap(clientAreaSize[0], clientAreaSize[1])
         memoryDC = wxMemoryDC()
         memoryDC.SelectObject(clientBitmap)
         self.Draw(memoryDC)
         paintDC = wxPaintDC(self)
         paintDC.BeginDrawing()
         # paintDC.Clear()
         paintDC.Blit(0, 0, clientAreaSize[0], clientAreaSize[1], memoryDC,
                      0, 0)
         paintDC.EndDrawing()
         memoryDC.SelectObject(wxNullBitmap)

In short, I thought that by drawing the entire clock face to the memoryDC and then blitting it to the paintDC would be quicker and avoid any flickering problems.

I've tried a number of different approaches, including providing an erase background handler, and setting the style flag on the frame window for wxCLIP_CHILDREN, all to no avail. The flickering still occurs. I'm running the application on a Windows 2000 SP3 based system, with an NVIDIA GeForce2 MX.

I've included the complete listing below, and would greatly appreciate any feedback or suggestions on how I can eliminate the flickering.

Thank you in advance for your time and help.

William Wonneberger
wberger@ccil.org / alundi@acm.org
http://www.ccil.org/~wberger

"The answer is that using a Stradivarius violin to pound nails should not be considered a sound construction technique."
R. Schwartz & T. Phoenix
Learning Perl, 3rd Edition

#!python

···

#
# wxClock - A simple analog clock using the wxPython library.
# $Header: G:\\rcs\\D\\work\\Python\\wxPython\\wxClock.py,v 1.0 2003-02-23 20:35:15-05 wberger Exp wberger $
#
# Revision History:
# $Log: wxClock.py,v $
# Revision 1.0 2003-02-23 20:35:15-05 wberger
# Initial revision
#
import math, sys, string, time
from wxPython.wx import *

class ClockFrame(wxFrame):
     def point(self, tick, range, radius):
         angle = tick * (360.0 / range)
         radiansPerDegree = math.pi / 180
         pointX = int(round(radius * math.sin(angle * radiansPerDegree)))
         pointY = int(round(radius * math.cos(angle * radiansPerDegree)))
         return wxPoint(pointX, pointY)

     def __init__(self):
         # Initialize the timer that drives the update of the clock face...
         self.timer = wxPyTimer(self.Notify)
         self.minuteMarks = 60
         self.hourMarks = 12

         # Initialize the wxFrame...
         wxFrame.__init__(self, None, -1, 'wxClock', wxDefaultPosition,
                          wxSize(325, 375))

         # Initialize the default clock settings...
         backgroundColor = wxColour(red = 225, green = 225, blue = 225)
         self.backgroundColor = [75, 75, 75]
         self.SetBackgroundColour(backgroundColor)
         self.lineColor = [253, 198, 70]
         self.lineCount = 60
         self.lineEnding = 1
         self.lineEndingColor = [251, 196, 68]

         self.timer.Start(950)

         # Event handlers...
         EVT_PAINT(self, self.OnPaint)

     # For handling a paint, create an identical device context in memory
     # and use the Draw() method to update the device context in memory.
     # Once the entire client area has been redrawn in memory, bit blit
     # client area device context in memory out to the actual paint
     # device context. This is essentially performing a buffered update or
     # paint of the entire client area of the main application frame.
     def OnPaint(self, event):
         clientAreaSize = self.GetClientSizeTuple()
         clientBitmap = wxEmptyBitmap(clientAreaSize[0], clientAreaSize[1])
         memoryDC = wxMemoryDC()
         memoryDC.SelectObject(clientBitmap)
         self.Draw(memoryDC)
         paintDC = wxPaintDC(self)
         paintDC.BeginDrawing()
         # paintDC.Clear()
         paintDC.Blit(0, 0, clientAreaSize[0], clientAreaSize[1], memoryDC,
                      0, 0)
         paintDC.EndDrawing()
         memoryDC.SelectObject(wxNullBitmap)

     # Using the current settings, render the points and line endings for the
     # circle inside the specified device context. In this case, the DC is
     # a memory based device context that will be blitted to the actual
     # display DC inside the OnPaint() event handler.
     def Draw(self, drawDC):
         backgroundBrush = wxBrush(wxColour(self.backgroundColor[0],
                                            self.backgroundColor[1],
                                            self.backgroundColor[2]),
                                            wxSOLID)
         drawDC.SetBackground(backgroundBrush)
         drawDC.Clear()
         numberOfLines = self.lineCount

         # Based on the client area frame size, determine the center...
         frameSize = self.GetClientSizeTuple()
         wholeWindowSize = self.GetSizeTuple()
         widthPadding = wholeWindowSize[0] - frameSize[0]
         heightPadding = (wholeWindowSize[1] - frameSize[1]) / 2
         centerX = (frameSize[0] - widthPadding) / 2
         centerY = ((frameSize[1] - heightPadding) / 2)

         # Draw the marks for hours and minutes...
         self.DrawTimeMarks(drawDC, self.minuteMarks, centerX, centerY, 4)
         self.DrawTimeMarks(drawDC, self.hourMarks, centerX, centerY, 9)

         currentTime = time.localtime(time.time())
         hour, minutes, seconds = currentTime[3:6]

         radius = centerX
         x, y = self.point(hour, 12, (radius * .65))
         hourX, hourY = (x + centerX), (centerY - y)
         x, y = self.point(minutes, 60, (radius * .85))
         minutesX, minutesY = (x + centerX), (centerY - y)
         x, y = self.point(seconds, 60, (radius * .85))
         secondsX, secondsY = (x + centerX), (centerY - y)

         # Draw the hour hand...
         lineColor = wxColour(self.lineColor[0], self.lineColor[1],
                              self.lineColor[2])
         linePen = wxPen(lineColor, 3, wxSOLID)
         drawDC.SetPen(linePen)
         drawDC.DrawLine(centerX, centerY, hourX, hourY)

         # Using the same DC pen, draw the minutes hand...
         drawDC.DrawLine(centerX, centerY, minutesX, minutesY)

         # Adjust the thickness of the line pen for seconds and draw the
         # seconds hand...
         linePen = wxPen(lineColor, 1, wxSOLID)
         drawDC.SetPen(linePen)
         drawDC.DrawLine(centerX, centerY, secondsX, secondsY)

     # Draw the specified set of line marks inside the clock face for the
     # hours or minutes...
     def DrawTimeMarks(self, drawDC, markCount, centerX, centerY, markSize):
         for i in range(markCount):
             x, y = self.point(i + 1, markCount, centerX - 16)
             scaledX, scaledY = (x + centerX), (centerY - y)
             lineEndingColor = wxColour(self.lineEndingColor[0],
                                        self.lineEndingColor[1],
                                        self.lineEndingColor[2])
             lineEndingBrush = wxBrush(lineEndingColor, wxSOLID)
             drawDC.SetBrush(lineEndingBrush)
             if self.lineEnding != 0:
                 if self.lineEnding == 2:
                     drawDC.DrawEllipse(scaledX - 2, scaledY, markSize, markSize)
                 else:
                     drawDC.DrawRectangle(scaledX - 3, scaledY, markSize, markSize)

     def OnQuit(self, event):
         self.timer.Stop()
         self.Close(true)

     def OnAdjustSettings(self, event):
         settingsDialog = wxClockSettingsDialog(self)
         settingsDialog.ShowModal()
         returnCode = settingsDialog.GetReturnCode()

         # If the OK button was clicked, get the new settings and repaint
         # the wxClock frame...
         if(returnCode == wxID_OK):
             numberOfLines = settingsDialog.GetLines()
             self.lineEnding = settingsDialog.GetLineEnding()
             self.lineCount = numberOfLines
             self.lineColor = settingsDialog.GetLineColor()
             self.backgroundColor = settingsDialog.GetBackgroundColor()
             backgroundColor = wxColour(self.backgroundColor[0],
                                        self.backgroundColor[1],
                                        self.backgroundColor[2])
             self.SetBackgroundColour(backgroundColor)
             self.lineEndingColor = settingsDialog.GetLineEndingColor()

             # Force an OnPaint event to be handled to update the
             # entire client area of the application frame...
             clientSize = self.GetClientSizeTuple()
             clientRect = (0, 0, clientSize[0], clientSize[1])
             self.Refresh(1, clientRect)

     def Notify(self):
         clientSize = self.GetClientSizeTuple()
         clientRect = (0, 0, clientSize[0], clientSize[1])
         self.Refresh(1, clientRect)

class App(wxApp):
     def OnInit(self):
         frame = ClockFrame()
         frame.Centre(wxBOTH)
         frame.Show(true)
         self.SetTopWindow(frame)
         return true

theApp = App(0)
theApp.MainLoop()

William Wonneberger wrote:

Greetings;

I've hacked together a simple analog clock using wxPyhton 2.4 an Python 2.2. For the most part, it works. However, every so often there is a visbile flicker across the main frame of the clock face as the time updates.

Adding this tkase care of it for me:

         EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
     def OnEraseBackground(self, evt):
         pass

To perform the updates, I draw the clock face and the hour, minutes and second hands to a wxMemoryDC instance in the main frame's OnPaint event handler, and then blit the contents of the completed wxMemoryDC to the wxPaintDC:

Some further optimizations you may want to make is to triple buffer. Draw the background and the tickmarks to a bitmap using a wxMemoryDC at the begining and whenever the size changes. Then in Draw() just draw this btimap to your main buffer, then the hands and then blit to the paint DC. Also, instead of calling Refresh in Notify, use a wxClientDC and draw immediatly.

BTW, this would make a good sample for the wxPython/samples dir if you would like to contribute it.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

If your Draw() method is fast, there may be no gain to doing this buffering. However, I suspect your flickering is a result of using a wxPaintDC inside the OnPaint event, when you may not want that. I just took a look at your code, and see the problem. Your Notify() method calls Refresh() which generates a paint event, which calls Onpaint. The OnPaint handler should only be used when the system tells the app that the screen needs updating. In an OnPaint event, it appears that the entire "damgaed region", in this case the whole window, is cleared and re-painted. That's what's causing your flicker.

What you probably want to do is use a wxClientDC when you want to change the clock.

WARNING: untested code:
def Notify(self):
     dc = wxClientDC(self)
  self.Draw(dc)

def OnPaint:
     dc = wxPaintDC(self)
  self.Draw(dc)

I think that may be it.

Depending on how long it takes to re-draw the clock, you may or may not want to double buffer. Take a look in the Wiki for a demo of a double buffered window:

http://wiki.wxpython.org/index.cgi/DoubleBufferedDrawing

One quick thought. You might want to draw the clock face into a bitmap and keep it around, and just re-draw the hands on top when the time changes. Then your Draw function would just be a dc.DrawBitmap() and the hand drawing code. You would only need to re-draw the clock face when the window was re-sized.

Another note:

You may want to update the clock by sending events from another thread, so as not to tie up your app. see the Throbber.py example in the demo (It's in 2.4, I'm not sure if it is in earlier versions) for how to do that.

I'm not sure what the advantages of to using a thread like Throbber.py does, over using a wxTimer, like your code does. I'd like to know, I need to do something similar. Anyone?

-Chris
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

···

On Saturday, March 1, 2003, at 12:55 PM, William Wonneberger wrote:

Greetings;

I've hacked together a simple analog clock using wxPyhton 2.4 an Python 2.2. For the most part, it works. However, every so often there is a visbile flicker across the main frame of the clock face as the time updates.

To perform the updates, I draw the clock face and the hour, minutes and second hands to a wxMemoryDC instance in the main frame's OnPaint event handler, and then blit the contents of the completed wxMemoryDC to the wxPaintDC:

Chris Barker wrote:

> I've hacked together a simple analog clock using wxPyhton 2.4 an
> Python 2.2. For the most part, it works. However, every so often there
> is a visbile flicker across the main frame of the clock face as the
> time updates.

As per my earlier suggestions (and Robin's) I've made some changes for
you. Now it stores the clock face in a offscreen bitmap, so it only has
to get re-drawn when the window is re-sized. When the wxTimer goes off,
it redraws the hands directly to the screen with a wxClientDC. I also
fixed a bug so that if the window was re-sized to not be square, the
clock will always fit. Another change I made was to include the minutes
when computing where the hour hand should be, so that it moves smoothly
from one hour position to the next. This way, it won't be pointing to
the 3, when it is 3:59, for example, it will be almost pointing to the
4.

If you want to include this with the samples, I suggest making it a
wxWindow, rather than a wxFrame, so it could be used as part of another
frame, panel,etc.

Still looking for info as to why one would want to use a wxTimer rather
than a event from another thread, like Throbber.py....

I've enclosed my edited code.

-Chris

-- --
Christopher Barker, Ph.D.
Oceanographer
                                                
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Clock.py (5.7 KB)

···

On Saturday, March 1, 2003, at 12:55 PM, William Wonneberger wrote: