Hiding scroll bars causes rendering to change (Possible bug?)

Hi there, :raised_hand_with_fingers_splayed:

(I’ll start off by saying I’m using the GTK version on Ubuntu)

I’m trying to create an area in my app that has custom scroll behavior. Basically a panel of objects that scroll horizontally. I do not want to the scroll bars to be visible and so I am calling Scroll(x,y) on mouse wheel events, so that you can scroll over any part of the panel.

Everything works fine until I add the following line to hide the bars:

self.scrollable.ShowScrollbars(horz=wx.SHOW_SB_NEVER, vert=wx.SHOW_SB_NEVER)

(Which is the only way that I’m aware of to hide the scroll bar. Is there another?).

The bar does disappear, but the panel itself changes it’s rendering behavior. Children don’t render as if they are part of a scrolling window, they get squished. And if you do call Scroll(…) at that point, the children that should be off screen to the right are not rendered at all (or don’t exist).

Bellow, I have included a short program to show what I am talking about. Here are two screenshots:

Without hiding the bars (top photo): (Buttons continue rendering off screen, and can be scrolled to. The scroll bar is not visible but will show when you hover over it)
Screenshot from 2021-07-20 15-58-28

With hidden bars (bottom photo): (There should be several more buttons to the right)

Thank you.
To me this behavior seems like it’s probably unintended. It’s the same story with both ScrolledWindow and ScrolledPanel.
Has anyone experienced this before/know a work around?

Sample:
Here is the sample program. Comment/Uncomment line 33 to observe the changes.
(Also recommend setting the frame width to something that cuts off one of the buttons so that you can observe that change)

import wx

# Run this and click the scroll button
# Then comment out the noted line (33) and re run. Notice how it changes.

class AppWindow(wx.Frame):
    def __init__(self, parent, title):
        self.dirname=''
        # Pick a window width that causes one of the buttons to be compressed
        wx.Frame.__init__(self, parent, title=title, size=(350,200))
        frame_sizer = wx.BoxSizer(wx.VERTICAL)

        # Create a scrollable panel
        self.scrollable = wx.ScrolledWindow(self)
        scrollable_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # Add Buttons
        for i in range(1, 8):
            scrollable_sizer.Add(wx.Button(self.scrollable, label=f"Button {i}"), 1, wx.ALL, 5)

        # Add a scrollbar
        self.scrollable.SetScrollbars(10,10,10,10)

        self.scrollable.SetSizer(scrollable_sizer)
        frame_sizer.Add(self.scrollable, 1, wx.ALL | wx.EXPAND)

        scroll_right = wx.Button(self, label="Click to Scroll Right")
        scroll_right.Bind(wx.EVT_BUTTON, self.Scroll)
        frame_sizer.Add(scroll_right, 0, wx.ALL, 5)

        # COMMENT OUT THIS LINE #
        # This is the line in question. Comment it out and see the difference.
        self.scrollable.ShowScrollbars(horz=wx.SHOW_SB_NEVER, vert=wx.SHOW_SB_NEVER)
        # ^^^^^^ THIS ONE ^^^^^^^ #
        # All it should do is hide the scroll bars but it also changes how the content is rendered

        self.SetSizer(frame_sizer)
        self.Show()

    # Scrolls to right
    def Scroll(self, event):
        print("Scroll")
        start, _ = self.scrollable.GetViewStart()
        self.scrollable.Scroll(3+start,0)

app = wx.App(False)
frame = AppWindow(None, "Test")
app.MainLoop()

Hi,

From the inspection, it is found that the sizer doesn’t know the client was scrolled when it was scrolled.
Below, the red line indicates the scrollable_sizer area.
scrlbar_#2021_0722-0

First, add the sizing event in the Scroll function.

    def Scroll(self, event):
        ...
        self.scrollable.SendSizeEvent()

Then, the sizer recognizes the correct position.
scrlbar_#2021_0722-1

However, it doesn’t know the correct size yet. The sizer decided its size to fit the client because the scrolling interface is missing. So, we have to specify the correct or sufficient size to display the client.
For example,

    def Scroll(self, event):
        ...
        self.scrollable.SendSizeEvent()
        sizer = self.scrollable.Sizer
        sizer.SetDimension(sizer.Position, sizer.MinSize)

scrlbar_#2021_0722-2

You can check the layout information shown above using InspectionTool. Sizer may behave differently due to the version and OS. I tested with wxpython 4.1.1 on Windows 64.

2 Likes

You’re a legend! Thank you so much! :heart:
I still don’t understand why disabling the scrollbar makes this required but I’m very pleased to have a solution. I was on the cusp of moving to QT (Is that a dirty word around here?)
:shushing_face:

for the line minimalists try inserting

scrollable_sizer.SetDimension(scrollable_sizer.Position, scrollable_sizer.MinSize)

after self.Show()

less lines & better performance :crazy_face:

1 Like

Hi,

:memo: Note: For example, consider Control-base object, e.g., ListCtrl. The scrollable control doesn’t show the scrollbar normally. But, when the control comes to be bigger than the parent window, it appears. While it is shown it can not be hidden.
ScrolledWindow-based objects, on the other hand, can hide scrollbars. However, as we’ve experienced, calling the Scroll method when the scrollbar isn’t visible seems to cause problems such as a broken layout.

:shushing_face: Qt/PySide is also good Tool Kit, especially pyqtgraph is special. I think one of the weaknesses of wxWidgets is the speed of image rendering.