ColumnView/GridView with Python

Warning: a scrollable Grid is never going to scale over more than 100 widgets. If you’re going to have an arbitrarily large number of items in the grid, you’re going to have to use a GridView (or a ColumnView).

PyGObject is maintained by volunteers: documentation happens because people contribute to it.

Good to know…

I did read that somewhere that the Grid had a limit of some sort. Just so I know 100, widgets… that’s a definite limit or is that a suggestion to avoid sluggishness in an application ?

Also is there a complete working example of a Gridview / Columnview via Python available so I can see how things are put together ?

Once I get a bit more acquainted with PyGObject I’ll volunteer some time and to document the use of the widgets with Python.

It’s not a hard limit, but it’s a side effect of CSS invalidations and layout.

GridView and ColumnView are functionally identical to ListView:

  • populate a ListModel implementation (like Gio.ListStore) with objects representing the data you want to display
  • create a factory and provide setup/teardown/bind/unbind implementations (a SignalListItemFactory is likely the easiest way to do this)
  • inside the ListItemFactory::setup you create the row/item widget that represents the model data
  • inside the ListItemFactory::bind you bind properties of the model item to the widget
  • ListItemFactory::unbind and ListItemFactory::teardown are the duals of bind and setup, and are used when the view widget recycles the items

I wrote a simple ColumnView example a while ago; GitLab is down, at the moment, but here it is:

import gi

gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")

from gi.repository import Adw, Gio, GObject, Gtk  # noqa


class Country(GObject.Object):
    __gtype_name__ = "Country"

    def __init__(self, country_id, country_name, pm):
        super().__init__()

        self._country_id = country_id
        self._country_name = country_name
        self._country_pm = pm

    @GObject.Property(type=str)
    def country_id(self):
        return self._country_id

    @GObject.Property(type=str)
    def country_name(self):
        return self._country_name

    @GObject.Property(type=str)
    def country_pm(self):
        return self._country_pm

    def __repr__(self):
        return f"Country(country_id={self.country_id}, country_name={self.country_name})"  # noqa

class ExampleWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app, title="Column View", default_width=300)

        nodes = {
            "au": ("Austria", "Van der Bellen"),
            "uk": ("United Kingdom", "Charles III"),
            "us": ("United States", "Biden"),
        }

        self.model = Gio.ListStore(item_type=Country)
        for n in nodes.keys():
            self.model.append(Country(country_id=n, country_name=nodes[n][0], pm=nodes[n][1]))

        factory = Gtk.SignalListItemFactory()
        factory.connect("setup", self._on_factory_setup)
        factory.connect("bind", self._on_factory_bind, "country_name")
        factory.connect("unbind", self._on_factory_unbind, "country_name")
        factory.connect("teardown", self._on_factory_teardown)

        factory2 = Gtk.SignalListItemFactory()
        factory2.connect("setup", self._on_factory_setup)
        factory2.connect("bind", self._on_factory_bind, "country_pm")
        factory2.connect("unbind", self._on_factory_unbind, "country_pm")
        factory2.connect("teardown", self._on_factory_teardown)

        self.cv = Gtk.ColumnView(model=Gtk.NoSelection(model=self.model))
        col1 = Gtk.ColumnViewColumn(title="Country", factory=factory)
        col1.props.expand = True
        self.cv.append_column(col1)
        col2 = Gtk.ColumnViewColumn(title="Head of State", factory=factory2)
        col2.props.expand = True
        self.cv.append_column(col2)
        self.cv.props.hexpand = True
        self.cv.props.vexpand = True

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
                      spacing=12,
                      valign=Gtk.Align.CENTER)
        box.props.margin_start = 12
        box.props.margin_end = 12
        box.props.margin_top = 6
        box.props.margin_bottom = 6
        box.append(Gtk.Label(label="Some Table:"))
        box.append(self.cv)

        self.set_child(box)
    def _on_factory_setup(self, factory, list_item):
        cell = Gtk.Inscription()
        cell._binding = None
        list_item.set_child(cell)

    def _on_factory_bind(self, factory, list_item, what):
        cell = list_item.get_child()
        country = list_item.get_item()
        cell._binding = country.bind_property(what, cell, "text", GObject.BindingFlags.SYNC_CREATE)

    def _on_factory_unbind(self, factory, list_item, what):
        cell = list_item.get_child()
        if cell._binding:
            cell._binding.unbind()
            cell._binding = None

    def _on_factory_teardown(self, factory, list_item):
        cell = list_item.get_child()
        cell._binding = None

    def _on_selected_item_notify(self, dropdown, _):
        country = dropdown.get_selected_item()
        print(f"Selected item: {country}")


class ExampleApp(Adw.Application):
    def __init__(self):
        super().__init__()
        self.window = None

    def do_activate(self):
        if self.window is None:
            self.window = ExampleWindow(self)
        self.window.present()


app = ExampleApp()
app.run([])

These days you could also use Expressions, but that’s just an extra.

1 Like

Thanks Emmanuele

I’ll follow that code through with my app.

Your last statement to use expressions …

What did you mean ?

Don

I mean using Gtk.Expression instead of simple property bindings.

Thanks Emmanuele

Instead of bothering with Grid I’ll run with your suggestion to avoid any potential problems with CSS / Layout.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.