Tips to initialize GTK4's ColumnView (Python)

So I’ve embarked on the simple task of creating a two column Gtk.ColumnView programmatically. I’ve mostly found people being directed to demos in a different language than the one they are using, or that cover general usage but not the specific question, even on other forums. Hopefully this will save the days it took me to figure this out even with the available reference and tutorials

It’s not complicated, but it does have many steps and it’s certainly confusing. After clearing the confusion it should be simple to use

Also I’m going to commit the same sins and say that I’ll talk about the ColumnView and that I’m using Python, but what I’m explaining is applicable to other contexts

So, to start:

When you create a ColumnView, you need to add the columns you want with append_column() and set a model with set_model()

The model refers to a selection model, that is, if you want to allow selecting a single item, or multiple items, or none, for my case that model is Gtk.SingleSelection

Already we start going into confusing territory: whichever selection model you pick also has it’s own model, and the function is also called set_model(). This model has to be Gio.ListStore or one of the other descendants of Gio.ListModel in Gtk. Since Gio and Gtk are different libraries, you won’t find the descendants in the Gio documentation, but wisely all the descendants in Gtk have ListModel in their name, so you just have to search the Gtk docs for that. If you choose to use Gio.ListStore like me you’ll get the simplest list

With the Gtk views, you don’t create the data containers and add them to the view. Instead, you explain how to create the view. That’s what the ListModel instance is for. In a ListStore, each item corresponds to information for building a row. If you are just putting a single widget, like a label or even a box, in a single column, you can just pass it to ListStore.append(), but otherwise you have to subclass GObject. There is no other way to do it programatically, but it’s not complicated, for example this what I’m using:

class TwoItemRow( GObject.GObject ):
    def __init__(self, start_item, end_item):
        super(TwoItemRow, self).__init__()

        self.start_item = start_item
        self.end_item = end_item

list_store.append( TwoItemRow( Gtk.Label.new( "Hello" ), Gtk.Label.new("world!"  ) )

That class only stores a reference to two widgets, which are passed to the constructor. It doesn’t have to be widgets, but information you need to create for widgets you want to display in the view, per row.

Next, you need the logic to build the columns, and for that you need a Gtk.ColumnViewColumn. I bet you thought the columns or the ListStore were to hold the data to display. To each column you have to set a Gtk.ListViewFactory descendant, so you can have widget creation separated per column. There are two descendants, For each column you set one of two factories, BuilderListItemFactory for declarative language (from a string or .ui file) or SignalListItemFactory

Among the signals from SignalListItemFactory, the first that will get your attention is ‘setup’. The setup signal is only useful if you want the exact same information in every cell of that column at the beginning. The signal you probably want is ‘bind’, which is called when data in the ListModel descendant is bound to a part of the column. You use the signal with good old connect:

factory = Gtk.SignalListItemFactory()
factory.connect( 'bind', bindCallback )

Setting the widget in the ListModel in the cell can be simple:

def bindCallback( factory, item ):
    item.set_child( item.get_item().end_item )

Now marvel at things working :slight_smile:

A few more tips:

  • If you use the setup signal, you will be passed an unset item. This is why it’s useless in most cases: you won’t have the data you put in the ListModel instance, however setup is only run once unlike bind and you can you can set properties and call methods including set_child
  • item is a Gtk.ListItem instance, which among other things let’s you set if a cell is selectable and focusable. get/set_child refer to the widget in the cell, get/set_item refer to the data passed in the ListModel instance
  • ColumnView has the get/set_row_factory methods. A row factory is set exactly like a factory for columns, the difference is that the item you get in the callback is a Gtk.ColumnViewRow that doesn’t have a set_child method (you have use set_child per cell), but you can change properties per row instead of doing it for each cell. You don’t have to set a row factory if you don’t need it
2 Likes

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