I've got a task that I want to show progress with over a few custom progress
bars, what I've noticed is that the actual drawing, or maybe the events that get
sent is actually slowing the main task down. (I've included some demo code of a
bunch of my custom bars and some native ones to compare the difference). So I
believe something isn't threading quite right somewhere, maybe in something
underlying. This is much worse when I run on mac than on windows, but from the
demo code you can see that the native bars allow the threaded worker task to
complete faster. How can I stop gui updates slowing down the worker threads?
I thought maybe its because the bars are getting asked to draw too many times
but I think I'm right in saying the refresh I call in setValue just invalidates
the bar and it will only redraw when it is able to.
Things get really slow if you start off the native ones then starts the custom
bars, surely as the work is threaded doing this shouldn't affect the speed of
the individual tasks. Also sometimes running the custom bars locks up the GUI as
well.
I looked into wx.UpdateUIEvent.SetUpdateInterval to see if I could reduce the
number of times it was drawing but I couldn't get it to work properly.
Any ideas what I'm doing wrong / what is wrong / of any fixes?
I guess I just need a little more understanding about what's actually happening
to be able to understand how to solve this.
import wx,time,threading
from wx.lib import newevent
from wx.lib.embeddedimage import PyEmbeddedImage
diagLinesPattern = PyEmbeddedImage(
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAGXRFWHRTb2Z0d2FyZQBBZG9i"
"ZSBJbWFnZVJlYWR5ccllPAAAACRJREFUeNpi/P//PwMMMDIyKjKABKCCimAamQNTDeeAMECA"
"AQCApx5rSauzmwAAAABJRU5ErkJggg==")
Gauge1Event, EVT_GAUGE_1= newevent.NewEvent()
Gauge2Event, EVT_GAUGE_2= newevent.NewEvent()
class CustomGauge(wx.Panel):
themes= {"blue": {"mainbar":wx.Colour(149,207,251),
"pattern":wx.Colour(110,190,250),
"border" :wx.Colour(92,173,234),
"gradient":wx.Colour(92,173,234,60),
"height":16},
"green":{"mainbar":wx.Colour(177,208,90),
"pattern":wx.Colour(150,191,60),
"border" :wx.Colour(112,171,0),
"gradient":wx.Colour(112,171,0,60),
"height":22}}
def
__init__(self,parent,bgColour,id=-1,theme="blue",pos=wx.DefaultPosition,size=wx.
DefaultSize,style=wx.TRANSPARENT_WINDOW|wx.NO_BORDER,name=wx.ControlNameStr):
self.parent= parent
idd = wx.NewId()
if theme not in self.themes.keys():
theme="blue"
self.theme= self.themes.get(theme)
size= wx.Size(-1,self.theme.get("height"))
wx.Panel.__init__(self,parent,id,pos,size,style,name)
self.bgColour= bgColour
self.Bind(wx.EVT_PAINT, self.OnPaint, id=id)
#self.Bind(wx.EVT_UPDATE_UI, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.patternBmp= diagLinesPattern.GetBitmap()
#self.patternBmp.SetHeight(4)
#self.patternBmp.SetWidth(4)
self._value=0.
self._range=1.
def setValue(self,value):
if value<0:
value=0
elif value>100:
value=100
self._value= value
self.Refresh()
#self.Update()
def setTheme(self,theme):
if theme not in self.themes.keys():
theme="blue"
self.theme= self.themes.get(theme)
self.Refresh()
def OnPaint(self,event):
dc= wx.BufferedPaintDC(self)
self.Draw(dc)
event.Skip()
def Draw(self,dc):
dc= wx.GCDC(dc)
width,height= self.GetClientSize()
if not width or not height:
return
dc.SetBackground(wx.Brush(self.bgColour))
dc.Clear()
#Rounded rect
dc.SetPen(wx.Pen(wx.Colour(219,219,219),1))
dc.SetBrush(wx.Brush(wx.Colour(230,230,230)))
rect= wx.Rect(1,0,width-1,height-2)
dc.DrawRoundedRectangleRect(rect,1)
#Top line
dc.SetPen(wx.Pen(wx.Colour(199,199,199),1))
dc.DrawLine(2,0,width-2,0)
#top shadow
dc.SetPen(wx.Pen(wx.Colour(221,221,221),1))
dc.DrawLine(2,1,width-2,1)
#bottom highlight
dc.SetPen(wx.Pen(wx.Colour(255,255,255,128),1))
dc.DrawLine(1,height-2,width-1,height-2)
if self._value > 0:
#mainbar
dc.SetPen(wx.Pen(self.theme.get("border"),1))
dc.SetBrush(wx.Brush(self.theme.get("mainbar")))
barwidth= (width-1)*(self._value/self._range)
rect= wx.Rect(1,0,barwidth,height-2)
dc.DrawRoundedRectangleRect(rect,1)
#pattern
brush= wx.Brush(self.theme.get("mainbar"),wx.STIPPLE)
brush.SetStipple(self.patternBmp)
dc.SetBrush(brush)
dc.DrawRoundedRectangleRect(rect,1)
#gradient
rect= wx.Rect(2,1,barwidth-2,height-4)
dc.GradientFillLinear(rect,self.theme.get("gradient"),wx.Colour(255,255,255,60),
wx.NORTH)
dc.DestroyClippingRegion()
#highlight
dc.SetPen(wx.Pen(wx.Colour(255,255,255,84),1))
dc.DrawLine(2,1,barwidth-1,1)
#shadow
dc.SetPen(wx.Pen(wx.Colour(0,0,0,15),1))
dc.DrawLine(0,1,0,height-2)
dc.DrawLine(barwidth+1,1,barwidth+1,height-2)
dc.DrawLine(1,height-1,barwidth,height-1)
dc.SetPen(wx.Pen(wx.Colour(0,0,0,40),1))
dc.DrawLine(1,height-2,barwidth,height-2)
def OnEraseBackground(self,event):
pass
class TestFrame(wx.Frame):
def __init__(self,parent,id,title):
wx.Frame.__init__(self,parent,id,title,size=(800,500))
panel= wx.Panel(self,-1)
bgColour= wx.Colour(230,230,230)
panel.SetBackgroundColour(bgColour)
self.steps=10000000
#wx.UpdateUIEvent.SetMode(wx.UPDATE_UI_PROCESS_SPECIFIED)
wx.UpdateUIEvent.SetUpdateInterval(500)
sizer= wx.BoxSizer(wx.VERTICAL)
custom_gauge_sizer= wx.BoxSizer(wx.HORIZONTAL)
gauges_sizer= wx.BoxSizer(wx.VERTICAL)
self.gauges=[]
for x in xrange(10):
g= CustomGauge(panel,bgColour,-1,"blue")
self.gauges.append(g)
gauges_sizer.Add(g,0,wx.EXPAND|wx.ALL^wx.BOTTOM,3)
custom_gauge_sizer.Add(gauges_sizer,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
self.go_btn_2= wx.Button(panel,-1,"Go")
custom_gauge_sizer.Add(self.go_btn_2,0,wx.LEFT,5)
sizer.Add(custom_gauge_sizer,0,wx.EXPAND|wx.ALL^wx.BOTTOM,5)
normal_gauge_sizer= wx.BoxSizer(wx.HORIZONTAL)
gauges_sizer= wx.BoxSizer(wx.VERTICAL)
self.ngauges=[]
for x in xrange(10):
g= wx.Gauge(panel,-1,self.steps)
#g= PG.PyGauge(panel,-1)
#g.SetRange(self.steps)
self.ngauges.append(g)
gauges_sizer.Add(g,0,wx.EXPAND|wx.ALL^wx.BOTTOM,3)
normal_gauge_sizer.Add(gauges_sizer,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
self.go_btn_1= wx.Button(panel,-1,"Go")
normal_gauge_sizer.Add(self.go_btn_1,0,wx.LEFT,5)
sizer.Add(normal_gauge_sizer,0,wx.EXPAND|wx.ALL^wx.BOTTOM,5)
self.go_btn_1.Bind(wx.EVT_BUTTON,self.OnGo1)
self.go_btn_2.Bind(wx.EVT_BUTTON,self.OnGo2)
self.Bind(EVT_GAUGE_1,self.Update1)
self.Bind(EVT_GAUGE_2,self.Update2)
panel.SetSizer(sizer)
self.Show()
self.UpdateWindowUI()
def Update1(self,event):
v= event.value
for g in self.ngauges:
g.SetValue(v)
self.UpdateWindowUI()
def Update2(self,event):
v= event.value
for g in self.gauges:
g.setValue(v)
self.UpdateWindowUI()
def OnGo1(self,event):
worker= Worker(self.steps,self,Gauge1Event,True)
worker.start()
def OnGo2(self,event):
worker= Worker(self.steps,self,Gauge2Event,False)
worker.start()
class Worker(threading.Thread):
def __init__(self,step_num,target,event,send_ints=False):
threading.Thread.__init__(self)
self.step_num= step_num
self.event_target= target
self.event= event
self.send_ints= send_ints
def run(self):
start=time.time()
sent_last=0
for x in xrange(self.step_num):
if time.time()-sent_last>0.04:
perc= float(x)/self.step_num
evt= self.event(value=(x if self.send_ints else perc))
wx.CallAfter(wx.PostEvent,self.event_target,evt)
sent_last=time.time()
perc= float(x)/self.step_num
evt= self.event(value=(x if self.send_ints else perc))
wx.CallAfter(wx.PostEvent,self.event_target,evt)
print time.time()-start
if __name__ == "__main__":
app= wx.App(False)
frame= TestFrame(None,-1,"Test Gauges")
app.MainLoop()