I have a custom progress bar–like view that basically consists of two sizers side by side: the one on the left has a red background and the one on the right has a light gray background. By adjusting the relative sizes of the sizers I can indicate different percentages. For whatever reason—maybe because this Window consists only of sizers, with no “substantial” elements—the progress bar is not visible when its containing Frame first appears. When the Frame is resized by any amount in any direction, the progress bar suddenly pops into view, with the correct size and position and everything.
Below is a minimal code sample. It doesn’t quite demonstrate the problem I’m describing: when run as-is, the progress bar will appear correctly.
If I remove the wx.EXPAND flag from either Add() call in TestWindow.init, however, then when the window first appears the progress bar will either be the wrong size or completely invisible. (Resizing the window causes the bar to become visible and/or take the correct size.) When the run_on_main_thread decorator is not applied to update_sizes, though, the bar is always displayed correctly right off the bat, regardless of whether the wx.EXPAND flag was included!
I’m not sure why my efforts toward thread safety have caused this drawing issue. Can anyone suggest a way to make the progress bar display correctly from the beginning, while still ensuring that update_sizes
will only run on the main thread? For example, can I trigger an EVT_SIZE
on the window so that it will refresh itself without any user interaction?
I’m using Python 2.7.5 and wxPython 2.9.5.0 on OS X 10.9.2.
Code
from functools import wraps
from math import floor
import wx
def run_on_main_thread(fn):
"""Decorator. Forces the function to run on the main thread.
Any calls to a function that is wrapped in this decorator will return
immediately; the return value of such a function is not available.
"""
@wraps(fn)
def deferred_caller(*args, **kwargs):
wx.CallAfter(fn, *args, **kwargs)
return deferred_caller
class PercentageBar(wx.Window):
def __init__(self, parent, percentage=0.0):
wx.Window.__init__(self, parent)
border_color = '#000000'
active_color = '#cc0000'
inactive_color = '#d3d7cf'
height = 24
self.percentage = percentage
self.SetBackgroundColour(border_color)
self.active_rectangle = wx.Panel(self, size=(-1, height))
self.active_rectangle.SetBackgroundColour(active_color)
self.inactive_rectangle = wx.Panel(self, size=(-1, height))
self.inactive_rectangle.SetBackgroundColour(inactive_color)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(self.sizer)
self.update_sizes()
@run_on_main_thread
def update_sizes(self):
self.sizer.Clear(False)
if self.percentage != 0.0 and self.percentage != 1.0:
active_flags = wx.EXPAND | (wx.ALL & ~wx.RIGHT)
else:
active_flags = wx.EXPAND | wx.ALL
self.sizer.Add(self.active_rectangle, floor(1000 * self.percentage),
active_flags, 1)
self.sizer.Add(self.inactive_rectangle, floor(1000 * (1.0 - self.percentage)),
wx.EXPAND | wx.ALL, 1)
if self.percentage == 0.0:
self.active_rectangle.Hide()
self.inactive_rectangle.Show()
elif self.percentage == 1.0:
self.active_rectangle.Show()
self.inactive_rectangle.Hide()
else:
self.active_rectangle.Show()
self.inactive_rectangle.Show()
self.sizer.Layout()
self.Refresh()
class TestWindow(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='Test')
vertical_sizer = wx.BoxSizer(wx.VERTICAL)
vertical_sizer.Add((1, 1), 1)
vertical_sizer.Add(PercentageBar(self, percentage=0.33), 2, wx.EXPAND)
vertical_sizer.Add((1, 1), 1)
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
horizontal_sizer.Add((1, 1), 1)
horizontal_sizer.Add(vertical_sizer, 10, wx.EXPAND)
horizontal_sizer.Add((1, 1), 1)
self.SetSizer(horizontal_sizer)
if __name__ == '__main__':
app = wx.App()
window = TestWindow(None)
window.Show()
app.MainLoop()