Scroll Jump with Nested Panes (Propagating Size During Dynamic Layout Changes?)

Hi,

I have an annoying problem that is quite hard to reproduce - a scrolled
pane "jumps" to a different scroll position in certain cases.

The code below demonstrates the problem - I am sorry it is not shorter,
but I cannot reproduce what I am seeing in my app with anything simpler.

If you run the code (I am using Python 2.5.2, wx 2.8.7.1 on Linux) you
should see a frame with two columns of unlabelled buttons. The buttons
work something like nested CollapsiblePanes. Unfortunately, however, the
scroll position "jumps" when you click on a button.

You can stop this jumping by expanding the frame vertically (ie make the
main window smaller by using the cursor to drag a corner or edge) so that
more than 10 rows are visible (ie so that an entire "outer" selection can
fit in the screen) - there appears to be no jumping when an entire outer
section is visible.

You may need to play a little to see the problem - it only occurs the
first time a button is used (subsequent clicks on the same button do not
cause the scroll to jump).

Is there a problem with my code? Can anyone explain what is happening?
Do I need to be setting some sizes somewhere?

Thanks in advance for anyone who takes the time to run this,
Andrew

import wx
from wx.lib.buttons import GenToggleButton
from wx.lib.scrolledpanel import ScrolledPanel

class TestApp(wx.App):

    def OnInit(self):
        frame = wx.Frame(None, -1)
        self.scroll = ScrolledPanel(frame)
        scrollsizer = wx.BoxSizer(wx.VERTICAL)
        for i in range(10):
            outer = wx.BoxSizer(wx.HORIZONTAL)
            button = GenToggleButton(self.scroll, size=(10,10))
            outer.Add(button)
            outercontents = wx.BoxSizer(wx.VERTICAL)
            a = wx.StaticText(self.scroll, label='a')
            outercontents.Add(a)
            p = wx.Panel(self.scroll)
            psizer = wx.BoxSizer(wx.VERTICAL)
            for j in range(10):
                inner = wx.BoxSizer(wx.HORIZONTAL)
                button2 = GenToggleButton(p, size=(10,10))
                inner.Add(button2)
                innercontents = wx.BoxSizer(wx.VERTICAL)
                a2 = wx.StaticText(p, label='a2')
                innercontents.Add(a2)
                b = wx.StaticText(p, label='b')
                innercontents.Add(b)
                a2.Show(False)
                p.Bind(wx.EVT_BUTTON,
                       lambda event, x=button2, a=a2, b=b: self.handler(x,
a, b),
                       button2)
                inner.Add(innercontents)
                psizer.Add(inner)
            p.SetSizer(psizer)
            outercontents.Add(p)
            self.scroll.Bind(wx.EVT_BUTTON,
                        lambda event, x=button, a=a, b=p: self.handler(x,
a, b),
                        button)
            a.Show(False)
            outer.Add(outercontents)
            scrollsizer.Add(outer)
        self.scroll.SetSizer(scrollsizer)
        self.scroll.Layout()
        self.scroll.SetupScrolling()
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.scroll, proportion=1, flag=wx.EXPAND|wx.ALL)
        frame.SetSizer(sizer)
        frame.SetSize((100, 100))
        frame.Layout()
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

    def handler(self, button, a, b):
        a.Show(button.GetToggle())
        b.Show(not button.GetToggle())
        self.scroll.Layout()

if __name__ == '__main__':
    app = TestApp(0)
    app.MainLoop()

I forgot to mention, and it is perhaps related, that the scroll area is
not adjusting to the changing size of the contents (so if you shrink
everything you can scroll down to a large blank area). I am unsure how to
fix that too....

Andrew

Hi,

I have an annoying problem that is quite hard to reproduce - a scrolled
pane "jumps" to a different scroll position in certain cases.

[...]

The following now fixes the scroll area size issue (described below), but
still shows the jumping (decsribed in earlier email);

I forgot to mention, and it is perhaps related, that the scroll area is
not adjusting to the changing size of the contents (so if you shrink
everything you can scroll down to a large blank area). I am unsure how to
fix that too....

import wx
from wx.lib.buttons import GenToggleButton
from wx.lib.scrolledpanel import ScrolledPanel

class TestApp(wx.App):

    def OnInit(self):
        frame = wx.Frame(None, -1)
        self.scroll = ScrolledPanel(frame)
        scrollsizer = wx.BoxSizer(wx.VERTICAL)
        for i in range(10):
            outer = wx.BoxSizer(wx.HORIZONTAL)
            button = GenToggleButton(self.scroll, size=(10,10))
            outer.Add(button)
            outercontents = wx.BoxSizer(wx.VERTICAL)
            a = wx.StaticText(self.scroll, label='a')
            outercontents.Add(a)
            p = wx.Panel(self.scroll)
            psizer = wx.BoxSizer(wx.VERTICAL)
            for j in range(10):
                inner = wx.BoxSizer(wx.HORIZONTAL)
                button2 = GenToggleButton(p, size=(10,10))
                inner.Add(button2)
                innercontents = wx.BoxSizer(wx.VERTICAL)
                a2 = wx.StaticText(p, label='a2')
                innercontents.Add(a2)
                b = wx.StaticText(p, label='b')
                innercontents.Add(b)
                a2.Show(False)
                p.Bind(wx.EVT_BUTTON,
                       lambda event, x=button2, a=a2, b=b, p=None: \
                       self.handler(x, a, b, p),
                       button2)
                inner.Add(innercontents)
                psizer.Add(inner)
            p.SetSizer(psizer)
            outercontents.Add(p)
            self.scroll.Bind(wx.EVT_BUTTON,
                             lambda event, x=button, a=a, b=p, p=p: \
                             self.handler(x, a, b, p),
                             button)
            a.Show(False)
            outer.Add(outercontents)
            scrollsizer.Add(outer)
        self.scroll.SetSizer(scrollsizer)
        self.scroll.Layout()
        self.scroll.SetupScrolling()
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.scroll, proportion=1, flag=wx.EXPAND|wx.ALL)
        frame.SetSizer(sizer)
        frame.SetSize((100, 100))
        frame.Layout()
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

    def handler(self, button, a, b, p):
        a.Show(button.GetToggle())
        b.Show(not button.GetToggle())
# if p: p.SetSize(p.GetSizer().GetMinSize())
        self.scroll.SetVirtualSize(self.scroll.GetBestVirtualSize())
        self.scroll.Layout()

if __name__ == '__main__':
    app = TestApp(0)
    app.MainLoop()

andrew cooke wrote:

The following now fixes the scroll area size issue (described below), but
still shows the jumping (decsribed in earlier email);

I forgot to mention, and it is perhaps related, that the scroll area is
not adjusting to the changing size of the contents (so if you shrink
everything you can scroll down to a large blank area). I am unsure how to
fix that too....

IIUC the scrolling is coming from one of the features of ScrolledPanel. When a child window gets the focus then it will try to scroll itself such that the entire child is visible. So as you click on buttons they get the focus, but their parent is the nested panels and that is what the ScrolledPanel will look at. So it is simply scrolling so the whole panel is in view. (Try giving them different background colors and that will make it easier to see what is going on.)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

IIUC the scrolling is coming from one of the features of ScrolledPanel.
  When a child window gets the focus then it will try to scroll itself
such that the entire child is visible. So as you click on buttons they
get the focus, but their parent is the nested panels and that is what
the ScrolledPanel will look at. So it is simply scrolling so the whole
panel is in view. (Try giving them different background colors and that
will make it easier to see what is going on.)

Thanks. I am not sure that was the complete explanation (it doesn't seem
to explain why it works on subsequent clicks, for example) but it was
enough for me to restructure my code to avoid the problem (by changing the
hierarchical structure of the panels into something flatter).

Andrew

···

--
http://www.acooke.org - andrew cooke
Andrew Cooke: C[omp]ute - blog