python-3.xgtk3gtkscrolledwindow

When adding a Gtk.TreeView to a Gtk.ScrolledWindow, the scrollbar cursor is out-of-scale


If I want to make a Gtk.TreeView scrollable, I embed it in a Gtk.ScrolledWindow. Embedding the TreeView produces a cursor which is out of scale (way too large).

enter image description here

There are 7 more rows in the TreeView, below the 3 shown. I would expect the scrollbar cursor to scale to 1/3 allowing me to scroll comfortably.

I can scroll normally with the scroll wheel on the mouse, and scrolling with the arrow keys on the keyboard.

This is the code that creates the TextView:

        self.segment_store = Gtk.ListStore(str, str, str, str, str, str)
        self.segment_view = Gtk.TreeView(model = self.segment_store,
                    hexpand = True)
        
        self.segment_scroller = Gtk.ScrolledWindow()
        self.segment_scroller.add(self.segment_view)

The ScrolledWindow itself is packed into a Gtk.Grid.

I suspected that there was a property in the scrollbar specifying a minimum size, and tried to modify the page size - again no luck.

Here's a complete minimum example. The if True in MainWindow can be changed to False, to enable/disable the use of a ScrollWindow. The three lines mentioning vadj in create_segment_table can be commented out to disable my page size limiting experiment.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  test_embedded_treeview.py
#
#  Copyright 2023 John Coppens <john@jcoppens.com>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#


import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class MainWindow(Gtk.Window):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.connect("destroy", lambda x: Gtk.main_quit())
        self.set_default_size(400, 300)

        self.create_segment_table()
        for i in range(10):
            self.segment_store.append( ('0', )*6 )

        if True:
            grid = Gtk.Grid()
            grid.attach(self.segment_scroller, 1, 1, 1, 1)

            self.add(grid)
        else:
            self.add(self.segment_scroller)

        self.show_all()

    def run(self):
        Gtk.main()


    def create_segment_table(self):
        self.segment_store = Gtk.ListStore(str, str, str, str, str, str)
        self.segment_view = Gtk.TreeView(model = self.segment_store,
                    hexpand = True)
        #   La tabla puede crecer demasiado, asi que agregamos un 'scroller'
        self.segment_scroller = Gtk.ScrolledWindow()
        self.segment_scroller.add(self.segment_view)

        # Trying to change the page size of the scrollbar
        vadj = self.segment_scroller.get_vadjustment()
        vadj.set_page_size(20)
        self.segment_scroller.set_vadjustment(vadj)

        # Creamos las columnas para la tabla
        for colnr, header in enumerate( ('X0', 'Y0', 'Z0', 'X1', 'Y1', 'Z1') ):
            renderer = Gtk.CellRendererText(editable = True)
            # ~ renderer.connect('edited', self.on_cell_edited, colnr)
            col = Gtk.TreeViewColumn(header, renderer, text = colnr)
            col.set_expand(True)
            self.segment_view.append_column(col)


def main(args):
    mainwdw = MainWindow()
    mainwdw.run()

    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))

EDIT: Sylvester's excellent answer below is great solution for the issue. Because this is a correction for a deficiency in the widget, and not a style issue, I decided against using an external .CSS file, which would somewhat complicate maintenance. So I included the CSS in the code itself (note that load_from_data requires a byte string):

        style_provider = Gtk.CssProvider()
        style_provider.load_from_data(b"slider {min-height: 10px; }"
                                      b"trough {min-height: 50px; }")

        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_USER
        )

Solution

  • The problem here of course is that the scrollbar's slider is so big that it can't be moved. In order to fix that, you need to change the slider's minimum height. If the slider can be smaller, it'll have more room to move around.

    Creating the CSS

    You can use CSS to change the slider's minimum height. However, if you change the slider's height, you also need to change the trough's height. If you don't, the ScrolledWindow will just shrink to fit the slider, and you'll be back to where you started.

    So create a CSS file and put this inside:

    slider {
        min-height: 10px;
    }
    trough {
        min-height: 50px;
    }
    

    This will set the slider's minimum height to 10 pixels, and the trough's minimum height to 50 pixels. This makes sure that the slider has enough room to move around in the trough, even at minimum height. You can adjust these values to suit your preference.

    Loading the CSS

    Now you just need to load the CSS. In order to do that, you can use Gtk.CSSProvider and Gtk.StyleContext:

    style_provider = Gtk.CssProvider()
    style_provider.load_from_path("/path/to/theme.css")
    
    Gtk.StyleContext.add_provider_for_screen(
        Gdk.Screen.get_default(),
        style_provider,
        Gtk.STYLE_PROVIDER_PRIORITY_USER
    )
    

    Note that you will have to import Gdk in order for this to work.

    Complete Example

    Here is a complete reproducible example using your code:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    #
    #  test_embedded_treeview.py
    #
    #  Copyright 2023 John Coppens <john@jcoppens.com>
    #
    #  This program is free software; you can redistribute it and/or modify
    #  it under the terms of the GNU General Public License as published by
    #  the Free Software Foundation; either version 2 of the License, or
    #  (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software
    #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    #  MA 02110-1301, USA.
    #
    #
    
    
    import gi
    gi.require_version('Gdk', '3.0')
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gdk # Import Gdk
    from gi.repository import Gtk
    
    class MainWindow(Gtk.Window):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.connect("destroy", lambda x: Gtk.main_quit())
            self.set_default_size(400, 300)
    
            self.create_segment_table()
            for i in range(10):
                self.segment_store.append( ('0', )*6 )
    
            ######################################
            # Load the CSS to change the slider
            style_provider = Gtk.CssProvider()
            style_provider.load_from_path("/path/to/theme.css")
    
            Gtk.StyleContext.add_provider_for_screen(
                Gdk.Screen.get_default(),
                style_provider,
                Gtk.STYLE_PROVIDER_PRIORITY_USER
            )
    
            ######################################
    
            if True:
                grid = Gtk.Grid()
                grid.attach(self.segment_scroller, 1, 1, 1, 1)
    
                self.add(grid)
            else:
                self.add(self.segment_scroller)
    
            self.show_all()
    
        def run(self):
            Gtk.main()
    
    
        def create_segment_table(self):
            self.segment_store = Gtk.ListStore(str, str, str, str, str, str)
            self.segment_view = Gtk.TreeView(model = self.segment_store,
                        hexpand = True)
            #   La tabla puede crecer demasiado, asi que agregamos un 'scroller'
            self.segment_scroller = Gtk.ScrolledWindow()
            self.segment_scroller.add(self.segment_view)
    
            # Creamos las columnas para la tabla
            for colnr, header in enumerate( ('X0', 'Y0', 'Z0', 'X1', 'Y1', 'Z1') ):
                renderer = Gtk.CellRendererText(editable = True)
                # ~ renderer.connect('edited', self.on_cell_edited, colnr)
                col = Gtk.TreeViewColumn(header, renderer, text = colnr)
                col.set_expand(True)
                self.segment_view.append_column(col)
    
    
    def main(args):
        mainwdw = MainWindow()
        mainwdw.run()
    
        return 0
    
    if __name__ == '__main__':
        import sys
        sys.exit(main(sys.argv))
    

    Note that I removed the code that was trying to change the page size, as it is not part of the solution. I hope this helps!