Search code examples
multithreadingwxpythongdi

how to draw into a PaintDC and MemoryDC in parallel?


I'm looking for a pice of code, how to use a wx.PaintDC() and wx.MemoryDC in parallel. My wxPython is in version 2.8.12 and I do not get it work to draw to a wx.PaintDC() into a wx.Window, while also having a thread running, that draws to a wx.MemoryDC into a bitmap.

Like this:

def onPaint(self, evt):
  self.dc=wx.PaintDC(self)
  imgbuf, (sx, sy), self.refresh_needed=self.osm.getBitmap()
  self.dc.DrawBitmap(imgbuf, sx, sy)

as_thread()
  w, h=self.__getBitmapSize()
  self.bmpbuf=wx.EmptyBitmapRGBA(w, h, 204, 204, 204, 1)
  self.mdc=wx.MemoryDC()
  self.mdc.SelectObject(self.bmpbuf)
  [.....]
  y=0
  for yi in imgs:
    x=0
    for tn, (status, xi) in yi:
      if status!=self.status["GOOD"]:
        xi=wx.EmptyBitmapRGBA(256, 256, red=255, alpha=1)
        if status!=self.status["INVALID"]:
          needs_refresh=True
      self.mdc.DrawBitmap(xi, x, y)
      x+=self.ts
    y+=self.ts

imgbuf and self.bmpbuf are not the same object.

self.bmpbuf is copied with this:

w, h=self.__getBitmapSize()
buf=numpy.empty((w, h, 3), dtype=numpy.uint8)
self.bmpbuf.CopyToBuffer(buf)
self.v[handle].bmpbuf=wx.BitmapFromBuffer(w, h, buf)

But always getting errors like:

[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python: xcb_io.c:165: dequeue_pending_request: Zusicherung »!xcb_xlib_unknown_req_in_deq« nicht erfüllt.

EDIT:
here is a fully working demonstrator-script, that shows the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import wx
import random
import time
import threading

class TestWin(wx.Window):
  def __init__(self, parent, title, size):
    wx.Window.__init__(self, parent)
    self.Bind(wx.EVT_PAINT, self.onPaint)
    self.Bind(wx.EVT_TIMER, self.onTimer)
    t=threading.Thread(target=self.asThread, name="draw")
    t.setDaemon(True)
    t.start()
    self.timer=wx.Timer(self)
    self.timer.Start(100)

  def onPaint(self, evt):
    dc=wx.PaintDC(self)
    dc.SetPen(wx.Pen("BLACK"))
    dc.SetBrush(wx.Brush("BLUE"))
    w, h=self.GetSize()
    dc.DrawCirclePoint((random.randint(0, w), random.randint(0, h)), 5)

  def onTimer(self, evt):
    self.Refresh()

  def asThread(self):
    w, h=self.GetSize()
    bmpbuf=wx.EmptyBitmapRGBA(w, h, 204, 204, 204, 1)
    mdc=wx.MemoryDC()
    mdc.SelectObject(bmpbuf)
    time.sleep(1)
    mdc.SetPen(wx.Pen("BLACK"))
    mdc.SetBrush(wx.Brush("RED"))
    print "now writing to MemoryDC"
    while True:
      #time.sleep(0.0001)
      mdc.DrawCirclePoint((random.randint(0, w), random.randint(0, h)), 5)
      wx.Yield()

class TestFrame(wx.Frame):
  def __init__(self, parent, title, size):
    wx.Frame.__init__(self, None, wx.ID_ANY, title)
    win=TestWin(self, title, size)

if __name__=="__main__":
  app=wx.App(False)
  frame=TestFrame(None, "Test", size=(200, 200))
  frame.Show()
  app.MainLoop()

EDIT2: why I want to build a bitmap in a thread:
I have a class providing a bitmap (showing OpenStreetMap-tiles) for a given window-size, zoom-level and lat/lon-coordinate. The class also draws GPS-tracks and point-lists onto the map/bitmap. Because the dimensions of the bitmap are higher than the window-dimensions, I can move the bitmap under the window without building a new bitmap. To move the bitmap, dc.DrawBitmap(imgbuf, sx, sy) is called with slightly changed values for (sx, sy). This takes 0.1ms per new clipping. Building a new bitmap takes up to 150ms.
When scrolling from one to another position, it scrolls very smooth until a new bitmap is needed.
If it would be possible to prepare the new bitmap, while scrolling over the old bitmap, a continuously smooth scrolling over a long distance should be possible.


Solution

  • You should not (and in most cases, can not) manipulate UI objects from anything other than the main UI thread. That includes DCs and bitmaps. What exactly are you trying to accomplish? There is likely some other way to do it.