1

I have: A Panel with an image buffer where the forms are drawn on using Cairo. The buffer is realized just like the example here: http://wiki.wxpython.org/BufferedCanvas

I want: A thread which does all the drawing when the buffer is updated (on creating/resizing/zooming)

The question: What is a nice way to solve this?

I have tried doing the drawing itself in the thread and got an assertation error because the dc is not finished. Do I have to create the dc in the thread or where? Is there some tutorial about threads and DCs?

EDIT:

I tried combining the example with the BufferedCanvas and this: LongRunningTasks

see here: http://pastebin.com/X9kqSMKT

sometimes it gives X Window System Errors, sometimes it works poorly

Thanks for any help

Community
  • 1
  • 1
any1
  • 292
  • 3
  • 9

1 Answers1

5

Here is an example with 3 panels. First is drawn using Paint and Resize events. Second uses double-buffering, so it does not flicker. Third uses another thread to draw the bitmap and in Paint event it just shows the bitmap.

import wx
from wx.lib.delayedresult import startWorker
import array

#=============================================================================
class DrawPanel(wx.Panel):
    """
    Basic panel with graphics drawn in Pain Event
    """
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

    #-------------------------------------------------------------------------
    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        dc.SetBackground(wx.Brush(wx.BLACK))
        dc.Clear()
        w, h = self.GetClientSizeTuple()
        dc.DrawCirclePoint((w / 2, h / 2), 50)

    #-------------------------------------------------------------------------
    def OnSize(self, event):
        self.Refresh()
        self.Update()

    #-------------------------------------------------------------------------
    def OnEraseBackground(self, event):
        pass # Or None

#============================================================================= 
class DrawPanelDB(wx.Panel):
    """
    Basic panel with graphics drawn in Pain Event using Double Buffering
    """
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

    #-------------------------------------------------------------------------
    def OnPaint(self, event):
        # Switches the buffers when 'dc' goes out of scope
        dc = wx.BufferedPaintDC(self)
        dc.SetBackground(wx.Brush(wx.BLACK))
        dc.Clear()
        w, h = self.GetClientSizeTuple()
        dc.DrawCirclePoint((w / 2, h / 2), 50)

    #-------------------------------------------------------------------------
    def OnSize(self, event):
        self.Refresh()
        self.Update()

    #-------------------------------------------------------------------------
    def OnEraseBackground(self, event):
        pass # Or None

#=============================================================================
class DrawPanelDBT(wx.Panel):
    """
    Complex panel with its content drawn in another thread
    """
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.t = None
        self.w, self.h = self.GetClientSizeTuple()
        self.buffer = wx.EmptyBitmap(self.w, self.h)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)

        self.SizeUpdate()

    #-------------------------------------------------------------------------
    def OnPaint(self, event):
        # Just draw prepared bitmap
        wx.BufferedPaintDC(self, self.buffer)

    #-------------------------------------------------------------------------
    def OnSize(self, event):
        self.w, self.h = self.GetClientSizeTuple()
        self.buffer = wx.EmptyBitmap(self.w, self.h)
        self.Refresh()
        self.Update()
        # After drawing empty bitmap start update
        self.SizeUpdate()

    #-------------------------------------------------------------------------
    def OnEraseBackground(self, event):
        pass # Or None

    #-------------------------------------------------------------------------
    def OnTimer(self, event):
        # Start another thread which will update the bitmap
        # But only if another is not still running!
        if self.t is None:
            self.timer.Stop()
            self.t = startWorker(self.ComputationDone, self.Compute)

    #-------------------------------------------------------------------------
    def SizeUpdate(self):
        # The timer is used to wait for last thread to finish
        self.timer.Stop()
        self.timer.Start(100)

    #-------------------------------------------------------------------------
    def Compute(self):
        # Compute Fractal
        MI = 20

        def mapme(x, minimal, maximal, newmin, newmax):
            return(((float(x) - minimal) / (maximal - minimal)) 
                   * (newmax - newmin) + newmin)

        def compute(x, y):
            z = complex(0, 0)
            c = complex(x, y)
            for i in range(MI):
                z = z**2 + c
                if abs(z) > 2:
                    return i+1
            return 0

        def color(i):
            a = int(mapme(i, 1, MI, 0, 255))
            return(a, a, a)

        def compute_buff(x1, x2, y1, y2, w, h):
            buffer = array.array('B') 
            for y in range(h):
                for x in range(w):
                    i = compute(mapme(x, 0, w, x1, x2),
                                mapme(y, 0, h, y2, y1))
                    if i == 0:
                        buffer.extend((255, 255, 255))
                    else:
                        buffer.extend(color(i))
            return buffer

        width, height = self.w, self.h
        x = -0.5
        y =  0.0
        w =  2.4
        h = w * height / width
        data = compute_buff(x - w/2, x + w/2, y - h/2, y + h/2, width, height)
        temp_buffer = wx.BitmapFromBuffer(width, height, data)
        return temp_buffer

    #-------------------------------------------------------------------------
    def ComputationDone(self, r):
        # When done, take bitmap and place it to the drawing buffer
        # Invalidate panel, so it is redrawn
        # But not if the later thread is waiting!
        temp = r.get()
        if not self.timer.IsRunning():
            self.buffer = temp
            self.Refresh()
            self.Update()
        self.t = None

#=============================================================================
class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = wx.Panel(self)
        self.drawing = DrawPanel(self.panel, size=(300, 300))
        self.drawingDB = DrawPanelDB(self.panel, size=(300, 300))
        self.drawingDBT = DrawPanelDBT(self.panel, size=(300, 300))
        self.sizerPanel = wx.BoxSizer()
        self.sizerPanel.Add(self.panel, proportion=1, flag=wx.EXPAND)
        self.sizerMain = wx.BoxSizer()
        self.sizerMain.Add(self.drawing, 1, wx.ALL | wx.EXPAND, 5)
        self.sizerMain.Add(self.drawingDB, 1, wx.ALL | wx.EXPAND, 5)
        self.sizerMain.Add(self.drawingDBT, 1, wx.ALL | wx.EXPAND, 5)
        self.panel.SetSizerAndFit(self.sizerMain)
        self.SetSizerAndFit(self.sizerPanel)      
        self.Show()

app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
Fenikso
  • 9,251
  • 5
  • 44
  • 72
  • Also notice that `wx` has convenience module for threading: `wx.lib.delayedresult`. Wiki page is in my opinion obsolete. Also for creating new events, there is `wx.lib.newevent` module. – Fenikso Apr 22 '11 at 09:00
  • Thanks for the reply. With this example, I get this: python2.7: xcb_io.c:140: dequeue_pending_request: Assertation »req == dpy->xcb->pending_requests« failed. Now I simply started a thread with this: `thread.start_new(wx.CallAfter(update, panel))` and it works.. – any1 Apr 25 '11 at 10:12
  • Hmmm, it works fine on my system. What is your OS, Python and wxPython version? – Fenikso Apr 25 '11 at 11:43
  • I checked on Windows, works perfectly there. `python2 2.7.1-9` `wxpython 2.8.12.0-1` OS is Arch Linux with `xorg-server 1.10.1-1` I will check if it works with a different version of xserver – any1 Apr 25 '11 at 16:58
  • Hi @Fenikso, I'm trying your examples, but find that the third one does not work properly under Mac OS 10.12.1 (the third panel is black). But it works fine under Win10. Any ideas? (on Mac: Python 2.7.10, wx-3.0.2.0 osx-cocoa (classic)); on win10: Python-2.7.12, wx-3.0.2.0 msw (classic)) – ying17zi Dec 15 '16 at 08:13
  • @ying17zi sorry, no ideas. I do not use Mac and I also do not use wx for a few years. – Fenikso Dec 16 '16 at 09:36