tags:

views:

119

answers:

2

I've got a problem with DCs. I'm trying to make an application that will draw many lines on the screens and needs to update really fast, and since I don't want flickering, I decided to give buffered dcs a shot. But when I run this code, it doesn't draw anything. What am I doing wrong?

import wx

class MainFrame(wx.Frame):
    def __init__(self):
        screensize = wx.GetDisplaySize()
        self.framesize = (screensize[0]/4*3, screensize[1]/4*3)
        wx.Frame.__init__(self, None, -1, "CursorTracker", size=self.framesize,
                          style=wx.SYSTEM_MENU|
                          wx.CAPTION|
                          wx.CLOSE_BOX|
                          wx.MINIMIZE_BOX)
        self.dc = wx.ClientDC(self)
        self.bdc = wx.BufferedDC(self.dc)
        self.SetBackgroundColour(wx.WHITE)
        self.Timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        self.Timer.Start(100)

    def OnTimer(self, event):

        self.bdc.DrawLine(1,1,100,100)


class App(wx.App):
    def OnInit(self):
        frame = MainFrame()
        frame.Show()
        return True

app = App(redirect=False)
app.MainLoop()
+1  A: 

A BufferedDC is copied to the screen only when the object is destroy. Since you never destroy self.bdc, of course its contents will never be copied to the screen. So, you need to arrange to let go of self.bdc (maybe just replace it with a new BufferedDc instance: i.e., redo the assignment self.bdc = wc.BufferedDc(self.dc) at "strategic" moments in time when you do want the drawing to be visualized.

Alex Martelli
A: 

I've used AutoBufferedPaintDC, but I've found doing my own double-buffering with a MemoryDC to be more flexible. Here's a template for you.

import wx

class Frame(wx.Frame):
    def __init__(self):
        super(Frame, self).__init__(None, -1, 'CursorTracker')
        self.mdc = None # memory dc to draw off-screen
        self.Bind(wx.EVT_SIZE, self.on_size)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase)
        self.Bind(wx.EVT_PAINT, self.on_paint)
        w, h = wx.GetDisplaySize()
        w, h = w * 3 / 4, h * 3 / 4
        self.SetSize((w, h))
        self.Center()
        self.on_timer()
    def on_size(self, event):
        # re-create memory dc to fill window
        w, h = self.GetClientSize()
        self.mdc = wx.MemoryDC(wx.EmptyBitmap(w, h))
        self.redraw()
    def on_erase(self, event):
        pass # don't do any erasing to avoid flicker
    def on_paint(self, event):
        # just blit the memory dc
        dc = wx.PaintDC(self)
        if not self.mdc:
            return
        w, h = self.mdc.GetSize()
        dc.Blit(0, 0, w, h, self.mdc, 0, 0)
    def on_timer(self):
        # refresh every N milliseconds
        self.redraw()
        wx.CallLater(100, self.on_timer)
    def redraw(self):
        # do the actual drawing on the memory dc here
        dc = self.mdc
        w, h = dc.GetSize()
        dc.Clear()
        dc.DrawLine(0, 0, w, h)
        self.Refresh()

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = Frame()
    frame.Show()
    app.MainLoop()

The basic approach is:

  • create a memory dc for off-screen drawing
  • if the window is resized, resize the memory dc and redraw
  • when a paint event occurs, just blit the memory dc onto the paint dc
  • do nothing on a erase background event to avoid flicker
  • call redraw when and only when you actually need to change what's on the screen

If you store a reference to that EmptyBitmap that's created in on_size, you can even save the window contents to an image file with wxBitmap.SaveFile()

FogleBird