When I setup a status bar I can only see part of the last widget when I scroll to the bottom

My app needs a status bar at the bottom of the Frame, however, on panels that need to be scrollable the last widget is only partially seen. This is a large app so creating a test app is not really possible.

If I comment out the code that creates the status bar everything scrolls perfectly, so how do I get the virtual window big enough to accommodate the status bar when I scroll to the bottom, assuming that’s the issue?

As I said this is a large app and many panels are hidden and then shown. This problem exists on all panels.

Hi and welcome to Discuss wxPython,

Without any details of the layout of your app and how you are hiding/showing the panels, it’s difficult to know what is happening.

I’ve just created the simple test app below, which has a Frame containing a button and 2 panels. One panel contains a multiline TextCtrl, the other contains a ListBox. Both controls have sufficient entries that required them to need scroll bars. The Frame also has a StatusBar.

At start-up the TextCtrl is shown and the ListBox is hidden. When the ‘Swap’ button is pressed, the visibility of the controls is swapped.

I originally called self.Layout() at the end of the swapPanels() method, but the result was that the ListBox didn’t occupy the full space available. When I changed it to call self.main_sizer.Layout() the ListBox was displayed correctly.

I have only tested this using Python 3.10.12 + wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1 on Linux Mint 21.2.

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("Test Show/Hide Panels + StatusBar")
        self.frame_statusbar = self.CreateStatusBar(1)
        self.frame_statusbar.SetStatusWidths([-1])
        self.main_panel = wx.Panel(self, wx.ID_ANY)
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.swap_button = wx.Button(self.main_panel, wx.ID_ANY, "Swap")
        self.main_sizer.Add(self.swap_button, 0, 0, 0)
        self.top_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.main_sizer.Add(self.top_panel, 1, wx.EXPAND, 0)
        top_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.text_ctrl = wx.TextCtrl(self.top_panel, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        top_sizer.Add(self.text_ctrl, 1, wx.EXPAND, 0)
        self.bottom_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.main_sizer.Add(self.bottom_panel, 1, wx.EXPAND, 0)
        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.list_box = wx.ListBox(self.bottom_panel, wx.ID_ANY, choices=[])
        bottom_sizer.Add(self.list_box, 1, wx.EXPAND, 0)
        self.bottom_panel.SetSizer(bottom_sizer)
        self.top_panel.SetSizer(top_sizer)
        self.main_panel.SetSizer(self.main_sizer)
        self.Layout()

        self.Bind(wx.EVT_BUTTON, self.OnSwap, self.swap_button)

        for i in range(20):
            self.text_ctrl.AppendText("TextCtrl line %d\n" % i)
            self.list_box.Append("Listbox item %d" % i)

        self.active_panel = 2
        self.swapPanels()

    def swapPanels(self):
        if self.active_panel == 1:
            self.main_sizer.Hide(1)
            self.main_sizer.Show(2)
            self.active_panel = 2
        else:
            self.main_sizer.Hide(2)
            self.main_sizer.Show(1)
            self.active_panel = 1
        self.main_sizer.Layout()


    def OnSwap(self, _event):
        self.swapPanels()


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()
1 Like

I think you got him in a twist :joy:

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("Test Show/Hide Panels + StatusBar")
        self.frame_statusbar = self.CreateStatusBar(1)
        self.frame_statusbar.SetStatusWidths([-1])
        self.main_panel = wx.Panel(self, wx.ID_ANY)
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.swap_button = wx.Button(self.main_panel, wx.ID_ANY, "Swap")
        self.main_sizer.Add(self.swap_button, 0, 0, 0)
        self.top_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.main_sizer.Add(self.top_panel, 1, wx.EXPAND, 0)
        top_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.text_ctrl = wx.TextCtrl(self.top_panel, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        top_sizer.Add(self.text_ctrl, 1, wx.EXPAND, 0)
        self.bottom_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.main_sizer.Add(self.bottom_panel, 1, wx.EXPAND, 0)
        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.list_box = wx.ListBox(self.bottom_panel, wx.ID_ANY, choices=[])
        bottom_sizer.Add(self.list_box, 1, wx.EXPAND, 0)
        self.bottom_panel.SetSizer(bottom_sizer)
        self.top_panel.SetSizer(top_sizer)
        self.main_panel.SetSizer(self.main_sizer)

        self.Bind(wx.EVT_BUTTON, self.OnSwap, self.swap_button)

        for i in range(20):
            self.text_ctrl.AppendText("TextCtrl line %d\n" % i)
            self.list_box.Append("Listbox item %d" % i)

        self.OnSwap(None)

    def OnSwap(self, _):
        if self.main_sizer.IsShown(2):
            self.main_sizer.Hide(2)
            self.main_sizer.Show(1)
        else:
            self.main_sizer.Hide(1)
            self.main_sizer.Show(2)
        self.main_sizer.Layout()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

I’m using the ScrolledPanel class on any panel that needs to be scrolled.

I have it somewhat fixed by adding the height of the status bar to the sizer. Then when SetupScrolling is called it works fine. It works because SetupScrolling calls SetVirtualSize. This problem still exists when I resize the parent frame, however. This is the next thing I’m working on.

My bad. I assumed you were using controls that had their own scrollbars and didn’t think you meant putting controls in ScrolledPanels. I hope you are able to fix the problem.

isn’t it better to replace ScrolledPanel by

        pnl = wx.Panel(self)
        scrwin = wx.ScrolledWindow(pnl)
        ...
        pnl.SetSizer(sizer)

If you look at the source code for ScrolledPanel you’ll see that it is essentially ScrolledWindow.

class ScrolledPanel(wx.ScrolledWindow):
    """
    :class:`ScrolledPanel` fills a "hole" in the implementation of
    :class:`ScrolledWindow`, providing automatic scrollbar and scrolling
    behavior and the tab traversal management that :class:`ScrolledWindow` lacks.
    """

In other words ScrolledPanel does a better job than ScrolledWindow does and from my experience it does.

well, I’m not doing wxPython long enough, but the ScrolledWindow referred to in the ScrolledPanel (as you hinted) can not be the wx.ScrolledWindow derived from wx.Scrolled because

  • wx.ScrolledWindow, aka ScrolledPanel, is equivalent to wx.ScrolledWindow from earlier versions. Derived from wx.Panel, it shares wx.Panel’s behaviour with regard to TAB traversal and focus handling. Use this if the scrolled window will have child controls.

my interest would be an example from your experience were the panel does a better job than the window (many thanks in advance) :innocent:

I’m also new at wxPython development, however, I’ve been writing code for over 45 years and Python for about 23 years. I’m writing a bookkeeping system. And it has many panels that switch in and out of the main Frame. Most of these panels need scrolling some panels are broken up into two or more sub panels where some may need scrolling.

As I mentioned above this app is very complex there is a lot of subclassing, multiple inheritance, and other crazy things going on, so it’s just about impossible to make a test script that shows the problems I run into. I’ve been thinking of creating some sort of block diagram that shows how everything is related. MOF, I remember using a Python thingy that did that for me. I’ll have to look at what I used.

I’ve experimented with a scrollable quite a bit (especially to get the header properly in place) but I never had a problem with the StatusBar: that thing is fixed to the top frame and doesn’t move at all !

the change (over the years) seems to be towards using sizers

here is something to play with to keep you going :sweat_smile:

ChuckM_scrwin_blth_refactored.py (1.6 KB) scrwin_pnl_win_fgs.py (12.4 KB)

I have also been writing Python for 23 years and wxPython for nearly 20 years. In that time I don’t remember ever using a ScrolledPanel. So I have taken the example from the wxPython Demo, added a StatusBar to the Frame and made it a stand-alone app. When I run it on linux I don’t see any cases of parts of the controls not being visible.

However, it’s a very simple example. The main panel is derived from ScrolledPanel and it contains 3 child ScrolledPanels. It doesn’t do anything complex like hiding/showing of child panels. This suggests to me there is not a basic problem with using ScrolledPanels with a StatusBar (on linux at least).

Perhaps, instead of commenting out parts out of your existing app, you could try adding some of the more complex features to this simple example and see what it is that makes it start showing the problem?

import wx
import wx.lib.scrolledpanel as scrolled

#----------------------------------------------------------------------

text = "one two buckle my shoe three four shut the door five six pick up sticks seven eight lay them straight nine ten big fat hen"


class TestPanel(scrolled.ScrolledPanel):
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent, -1)

        vbox = wx.BoxSizer(wx.VERTICAL)
        desc = wx.StaticText(self, -1,
                            "ScrolledPanel extends wx.ScrolledWindow, adding all "
                            "the necessary bits to set up scroll handling for you.\n\n"
                            "Here are three fixed size examples of its use. The "
                            "demo panel for this sample is also using it -- the \nwxStaticLine "
                            "below is intentionally made too long so a scrollbar will be "
                            "activated."
                            )
        desc.SetForegroundColour("Blue")
        vbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALL, 5)
        vbox.Add(wx.StaticLine(self, -1, size=(1024,-1)), 0, wx.ALL, 5)
        vbox.Add((20,20))

        words = text.split()

        panel1 = scrolled.ScrolledPanel(self, -1, size=(140, 300),
                                 style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="panel1" )
        fgs1 = wx.FlexGridSizer(cols=2, vgap=4, hgap=4)

        for word in words:
            label = wx.StaticText(panel1, -1, word+":")

            # A test for scrolling with a too big control
            #if word == "three":
            #    tc = wx.TextCtrl(panel1, -1, word, size=(150,-1))
            #else:
            #    tc = wx.TextCtrl(panel1, -1, word, size=(50,-1))

            tc = wx.TextCtrl(panel1, -1, word, size=(50,-1))

            fgs1.Add(label, flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=10)
            fgs1.Add(tc, flag=wx.RIGHT, border=10)

        panel1.SetSizer( fgs1 )
        panel1.SetAutoLayout(1)
        panel1.SetupScrolling()

        panel2 = scrolled.ScrolledPanel(self, -1, size=(350, 50),
                                 style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="panel2")
        panel3 = scrolled.ScrolledPanel(self, -1, size=(200,100),
                                 style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="panel3")

        fgs2 = wx.FlexGridSizer(cols=25, vgap=4, hgap=4)
        fgs3 = wx.FlexGridSizer(cols=5, vgap=4, hgap=4)

        for i in range(len(words)):
            word = words[i]
            if i % 5 != 4:
                label2 = wx.StaticText(panel2, -1, word)
                fgs2.Add(label2, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
                label3 = wx.StaticText(panel3, -1, word)
                fgs3.Add(label3, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
            else:
                tc2 = wx.TextCtrl(panel2, -1, word, size=(50,-1))
                fgs2.Add(tc2, flag=wx.LEFT, border=5)
                tc3 = wx.TextCtrl(panel3, -1, word )
                fgs3.Add(tc3, flag=wx.LEFT, border=5)

        panel2.SetSizer( fgs2 )
        panel2.SetAutoLayout(1)
        panel2.SetupScrolling(scroll_y = False)

        panel3.SetSizer( fgs3 )
        panel3.SetAutoLayout(1)
        panel3.SetupScrolling()

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add((20,20))
        hbox.Add(panel1, 0, wx.FIXED_MINSIZE)
        hbox.Add((40, 10))

        vbox2 = wx.BoxSizer(wx.VERTICAL)
        vbox2.Add(panel2, 0, wx.FIXED_MINSIZE)
        vbox2.Add((20, 50))

        vbox2.Add(panel3, 0, wx.FIXED_MINSIZE)
        vbox2.Add((20, 10))
        hbox.Add(vbox2)

        vbox.Add(hbox, 0)
        self.SetSizer(vbox)
        self.SetAutoLayout(1)
        self.SetupScrolling()


class TestFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.SetSize((800, 600))
        self.SetTitle("Test ScrolledPanel")

        # Add a status bar
        self.statusbar = self.CreateStatusBar(1, wx.STB_DEFAULT_STYLE)
        self.statusbar.SetStatusWidths([-1])

        self.panel = TestPanel(self)
        self.Layout()


app = wx.App()
frame = TestFrame(None)
frame.Show()
app.MainLoop()

I can see you’ve been working with Python for a long time, you don’t use self on all your variables like most wxPython developers do. It’s bad enough that wxPython itself breaks just about all of the style rules in PEP-8. LOL

Well, It seems like I’ve fixed all my issues now. I needed to insert resize events in the main Frame and in one panel with two panels in it (where one is a ScrolledPanel). I actually needed to set the size on the panels compensating for the status bar height. I also need to set the height of the bottom ScrolledPanel which always seemed to have its height set to 2 instead of updating correctly when resized. I did all this stuff in an EVT_IDLE after the EVT_SIZE stopped.

Thanks for your script above, though I didn’t need it.

well, self is only required if the name is used as an attribute of the object (i.e. used across methods etc) and, as you may well know, locals are kept more efficient

I suppose this inflating use of self is misunderstood gc (there is still wx.App) :rofl:

I think with sizers one never ever sets a size :cowboy_hat_face:

In most cases I’m using the GridBagSizer in a BoxSizer and yes I never set the size of a sizer, just the panel that is its parent.

Yeah, people want to write GUI code but don’t really understand the language, or maybe better they don’t understand Object Oriented code regardless of language. In the early 2000s one of the developers that was in my team wrote all the code in one huge method, if I remember, it was about 800 lines. He also made it static because he didn’t understand how instantiation worked. This was in Java, not Python, but it’s the same idea.

If anyone wants to look at my code it at Bookkeeper. It’s nowhere near done yet. Any comments are welcome.

I meant more: even a panel sits in a frame and only the frame may be min/maxed or pulled by the user (were the pulling is most interesting and idle events are a dead end)

I’m not sure what your leaning is but having everything in self is as useless as just having one method for a bid of speed: the structure of the object should be intuitively sound (plenty of getters/setters granted, or are you for attributes?)
but take sizers f.i.: they are mostly only a helper needed for local use to build up a control
on top of that wx has many getters/setters to work on sub controls and if that is only occasionally self will look cleaner if only the control is kept

but to structure the 800 line method one can still use defs within that method and use nonlocal for self (just to tease, some like functionally objects, not to mention lambdas) :sunglasses: