wxNoob: sizer & window redraw behaviour

Greetings!

  I am a total beginner when it comes to wxPython, and am looking forward to getting savvy with it, so, first of all, hello to all my wxComrades!

  I have a real project to try to produce in wxPython. I have Noel & Robin's fine book and am trying to work through various parts of it. I have read chapters 1 & 2 and part of 3 (and will probably be reviewing 3 again) and am now trying to jump around to the parts I think I need, which is sizers at the moment.

  What I want to know is why the BlockWindow's of the Basic Grid Sizer example do not repaint correctly after squishing the window very small and then dragging it back out (i.e., there are all sorts of artifacts left in the individual BlockWindow frames. For example if you squish the window narrow enough so that the 'n' in 'nine' is drawn within the boundaires of the 'eight' box, when you stretch it back out an 'n' is left showing in the 'eight' block.)

   Note that if you cover the whole window with some other window, and then expose it, it all re-renders cleanly. The code for listing 11.1 and 11.2 is below (slightly modified) so that you can try it, but what I'm really looking for is an explanation of exactly why this behaves the way it does and what code changes could be made so that these artifacts don't occur in the first place (or to re-paint the individual BlockWindow's after each resize if that's what it takes to correct it).

Thanks for taking the time to look at my problem. Looking forward to learning to be productive with wxPython! :wink:

-ej

Here's the code:

#!/usr/bin/env python
'BlockWindow.py'

import wx

···

#=======================================================================
class BlockWindow(wx.Panel):

    def __init__(self, parent, id=-1, label="",
                 pos=wx.DefaultPosition, size=(100, 25)):
        wx.Panel.__init__(self, parent, id, pos, size,
                          wx.RAISED_BORDER, label)
        self.label = label
        self.SetBackgroundColour('white')
        self.SetMinSize(size)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, evt):
        sz = self.GetClientSize()
        dc = wx.PaintDC(self)
        (w, h) = dc.GetTextExtent(self.label)
        dc.SetFont(self.GetFont())
        dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)
#=======================================================================

#!/usr/bin/env python

import wx
from BlockWindow import BlockWindow

#=======================================================================
class GridSizerFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
        sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)

        labels = "one two three four five six seven eight nine".split()
        for l in labels:
            bw = BlockWindow(self, label=l)
            sizer.Add(bw, 0, 0)
        self.SetSizer(sizer)
        self.Fit()
#=======================================================================

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = GridSizerFrame()
    frame.Show()
    app.MainLoop()

ejohnso9@earthlink.net wrote:

   Note that if you cover the whole window with some other window,
and then expose it, it all re-renders cleanly. The code for listing
11.1 and 11.2 is below (slightly modified) so that you can try it, but
what I'm really looking for is an explanation of exactly why this
behaves the way it does and what code changes could be made so that
these artifacts don't occur in the first place (or to re-paint the
individual BlockWindow's after each resize if that's what it takes to
correct it).

I do not know why resizing causes such artifacts, but they can be
repaired with relative ease through the use of a timer. See my changes
to the GridSizerFrame. You can tune the first argument to self.t.Start
up or down to reduce processor load or increase responsiveness,
respectively.

- Josiah

···

#=======================================================================
class GridSizerFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
        sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)

        labels = "one two three four five six seven eight nine".split()
        for l in labels:
            bw = BlockWindow(self, label=l)
            sizer.Add(bw, 0, 0)
        self.SetSizer(sizer)
        self.Fit()
        
        self.Bind(wx.EVT_SIZE, self._OnSize)
        self.t = wx.Timer(self, wx.NewId())
        self.Bind(wx.EVT_TIMER, self._TimerExpire, self.t)
    
    def _OnSize(self, evt):
        self.t.Start(100, wx.TIMER_ONE_SHOT)
        evt.Skip()
    
    def _TimerExpire(self, evt):
        self.Refresh()
        self.Update()
#=======================================================================

Hi ej,

    sometimes dealing with owner-drawn controls (such as BlockWindow,
which has a custom OnPaint handler) can be a pain. In this case, a way
to overcome this problem and avoid too much flicker while resizing, is
as follow:

- in the wx.Frame __init__ method, use this construction:

wx.Frame.__init__(self, None, -1, "Basic Grid Sizer",
                            style=wx.DEFAULT_FRAME_STYLE|
                            wx.FULL_REPAINT_ON_RESIZE)

The wx.FULL_REPAINT_ON_RESIZE tells the frame to repaint itself (and
its children) at every resize. However, using only this constructor
will lead to a horrible flicker when resizing. You can use as a
workaround, this modified OnPaint method:

    def OnPaint(self, evt):
        sz = self.GetClientSize()
        dc = wx.BufferedPaintDC(self)
        dc.Clear()
        (w, h) = dc.GetTextExtent(self.label)
        dc.SetFont(self.GetFont())
        dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)

At last, when you use BufferedPaintDC instead of wx.PantDC, it is
useful to bind to the frame also the wx.EVT_ERASE_BACKGROUND, leaving
it empty:

    self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)

    def OnErase(self, evt):
        pass

In this case, having an empty handler, the frame will not erase its
background at every resize, and the flicker is almost completely gone.
I attach the complete code to the e-mail.
This is tested on Windows XP, Python 2.5, wxPython 2.8.0.1

Hope this helps.

Andrea.

GridSizerFrame.py (1.62 KB)

···

On 1/18/07, ejohnso9@earthlink.net <ejohnso9@earthlink.net> wrote:

Greetings!

I am a total beginner when it comes to wxPython, and am looking forward to getting savvy with it, so, first of all, hello to all my wxComrades!

I have a real project to try to produce in wxPython. I have Noel & Robin's fine book and am trying to work through various parts of it. I have read chapters 1 & 2 and part of 3 (and will probably be reviewing 3 again) and am now trying to jump around to the parts I think I need, which is sizers at the moment.

What I want to know is why the BlockWindow's of the Basic Grid Sizer example do not repaint correctly after squishing the window very small and then dragging it back out (i.e., there are all sorts of artifacts left in the individual BlockWindow frames. For example if you squish the window narrow enough so that the 'n' in 'nine' is drawn within the boundaires of the 'eight' box, when you stretch it back out an 'n' is left showing in the 'eight' block.)

  Note that if you cover the whole window with some other window, and then expose it, it all re-renders cleanly. The code for listing 11.1 and 11.2 is below (slightly modified) so that you can try it, but what I'm really looking for is an explanation of exactly why this behaves the way it does and what code changes could be made so that these artifacts don't occur in the first place (or to re-paint the individual BlockWindow's after each resize if that's what it takes to correct it).

Thanks for taking the time to look at my problem. Looking forward to learning to be productive with wxPython! :wink:

-ej

Here's the code:

#!/usr/bin/env python
'BlockWindow.py'

import wx

#=======================================================================
class BlockWindow(wx.Panel):

   def __init__(self, parent, id=-1, label="",
                pos=wx.DefaultPosition, size=(100, 25)):
       wx.Panel.__init__(self, parent, id, pos, size,
                         wx.RAISED_BORDER, label)
       self.label = label
       self.SetBackgroundColour('white')
       self.SetMinSize(size)
       self.Bind(wx.EVT_PAINT, self.OnPaint)

   def OnPaint(self, evt):
       sz = self.GetClientSize()
       dc = wx.PaintDC(self)
       (w, h) = dc.GetTextExtent(self.label)
       dc.SetFont(self.GetFont())
       dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)
#=======================================================================

#!/usr/bin/env python

import wx
from BlockWindow import BlockWindow

#=======================================================================
class GridSizerFrame(wx.Frame):

   def __init__(self):
       wx.Frame.__init__(self, None, -1, "Basic Grid Sizer")
       sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5)

       labels = "one two three four five six seven eight nine".split()
       for l in labels:
           bw = BlockWindow(self, label=l)
           sizer.Add(bw, 0, 0)
       self.SetSizer(sizer)
       self.Fit()
#=======================================================================

if __name__ == '__main__':
   app = wx.PySimpleApp()
   frame = GridSizerFrame()
   frame.Show()
   app.MainLoop()

---------------------------------------------------------------------
To unsubscribe, e-mail: wxPython-users-unsubscribe@lists.wxwidgets.org
For additional commands, e-mail: wxPython-users-help@lists.wxwidgets.org

--
Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.virgilio.it/infinity77/