I have a problem updating a widget immediately with Layout
and Update
. I present a test program that demonstrates the problem.
Overview
The test program lays out a few widgets vertically. First a button that triggers a change in layout, then a text widget surrounded by two panels to more clearly indicate the size of the text widget. It looks like this:
Tests program.
When the button is clicked, the size of the text widget is increased followed by a call to Layout
and Update
on the frame. I would expect the frame to redraw immediately at this point, but it doesn't. At least not correctly.
Test program
The structure of the test program looks like this:
- testlayout.py
import wx import time class TestFrame(wx.Frame): <<TestFrame>> class Text(wx.Panel): <<Text>> if __name__ == "__main__": app = wx.App() frame = TestFrame() frame.Show() app.MainLoop()
If you prefer the read the complete test program, go to Complete test program.
The __init__
method of the test frame lays out the widgets using a vertical sizer and hooks up the click event.
- testlayout.py
- TestFrame
def __init__(self): wx.Frame.__init__(self, None) self.button = wx.Button(self, label="increase text size") self.button.Bind(wx.EVT_BUTTON, self._on_increase_size_click) self.top = wx.Panel(self, size=(-1, 20)) self.top.SetBackgroundColour("yellow") self.text = Text(self) self.bottom = wx.Panel(self, size=(-1, 20)) self.bottom.SetBackgroundColour("pink") self.Sizer = wx.BoxSizer(wx.VERTICAL) self.Sizer.Add( self.button, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.top, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.text, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.bottom, border=5, proportion=0, flag=wx.EXPAND|wx.ALL )
The click event handler increases the size of the text widget and tries to do an immediate update. The sleep is there to see if the update is delayed or not.
- testlayout.py
- TestFrame
def _on_increase_size_click(self, event): print("") print("Click") self.text.increase_size() self.Layout() print("Update") self.Update() time.sleep(2) print("Update done")
The __init__
method of the text widget sets an initial size and hooks up paint and size events.
- testlayout.py
- Text
def __init__(self, parent): wx.Panel.__init__(self, parent) self.min_h = 0 self.increase_size() self.Bind(wx.EVT_PAINT, self._on_paint) self.Bind(wx.EVT_SIZE, self._on_size)
The increase_size
method increases the height and sets a new min size.
- testlayout.py
- Text
def increase_size(self): self.min_h += 10 self.SetMinSize((-1, self.min_h))
The paint event handler draws a bunch of lines of text and also prints the rectangles that were invalidated.
- testlayout.py
- Text
def _on_paint(self, event): print("repaint text") upd = wx.RegionIterator(self.GetUpdateRegion()) while upd.HaveRects(): print(" {}".format(upd.GetRect())) upd.Next() dc = wx.PaintDC(self) y = 0 for x in range(20): line = "line {}".format(x) dc.DrawText(line, 5, y) y += dc.GetTextExtent(line)[1] event.Skip()
The size event just prints the new size.
- testlayout.py
- Text
def _on_size(self, event): print("resize text {}".format(event.GetSize())) event.Skip()
Results
Here is the output of a button click:
Click resize text (396, 20) Update repaint text (0, 0, 396, 10) Update done repaint text (0, 0, 396, 20)
- First, the click event hander is entered.
- The text widget properly gets a resize event. (My guess is as a result of the
Layout
call.) - Then
Update
is called. - A repaint of the text widget is then done, but the invalidated rectangle is only 10px high. The new height is 20.
- After the
sleep
call, a repaint of the text widget is done again, now with the correct rectangle.
So in practice, nothing is shown on the screen until after the sleep call.
It seems to me that the Layout
call depends on something happening by following events. Adding a Refresh
call immediately after Layout
does not seem to have any effect.
Why does the immediate repaint don't get the correct size?
Complete test program
import wx import time class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None) self.button = wx.Button(self, label="increase text size") self.button.Bind(wx.EVT_BUTTON, self._on_increase_size_click) self.top = wx.Panel(self, size=(-1, 20)) self.top.SetBackgroundColour("yellow") self.text = Text(self) self.bottom = wx.Panel(self, size=(-1, 20)) self.bottom.SetBackgroundColour("pink") self.Sizer = wx.BoxSizer(wx.VERTICAL) self.Sizer.Add( self.button, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.top, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.text, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) self.Sizer.Add( self.bottom, border=5, proportion=0, flag=wx.EXPAND|wx.ALL ) def _on_increase_size_click(self, event): print("") print("Click") self.text.increase_size() self.Layout() print("Update") self.Update() time.sleep(2) print("Update done") class Text(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.min_h = 0 self.increase_size() self.Bind(wx.EVT_PAINT, self._on_paint) self.Bind(wx.EVT_SIZE, self._on_size) def increase_size(self): self.min_h += 10 self.SetMinSize((-1, self.min_h)) def _on_paint(self, event): print("repaint text") upd = wx.RegionIterator(self.GetUpdateRegion()) while upd.HaveRects(): print(" {}".format(upd.GetRect())) upd.Next() dc = wx.PaintDC(self) y = 0 for x in range(20): line = "line {}".format(x) dc.DrawText(line, 5, y) y += dc.GetTextExtent(line)[1] event.Skip() def _on_size(self, event): print("resize text {}".format(event.GetSize())) event.Skip() if __name__ == "__main__": app = wx.App() frame = TestFrame() frame.Show() app.MainLoop()