GTK4 - ColumnView Sorting

How sorting can be used for ColumnView columns?

Using the following code for columns does not work when column headers are clicked:

sorter = Gtk.CustomSorter.new(self.sort_func)
col1.set_sorter(sorter)

def sort_func(self, object_a, object_b, _data):
    print("test")
    ....

Example application code (source):

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([])

Reading ColumnViewColumn.set_sorter docs it says:

See gtk_column_view_get_sorter() for the necessary steps for setting up customizable sorting for GtkColumnView.

In ColumnView.get_sorter docs says:

To allow users to customizable sorting by clicking on column headers, this sorter needs to be set on the sort model underneath the model that is displayed by the view.

And gives a C example:

gtk_column_view_column_set_sorter (column, sorter);
gtk_column_view_append_column (view, column);
sorter = g_object_ref (gtk_column_view_get_sorter (view)));
model = gtk_sort_list_model_new (store, sorter);
selection = gtk_no_selection_new (model);
gtk_column_view_set_model (view, selection);

To the python example this translates to:

self.cv = Gtk.ColumnView()
sorter_model = Gtk.SortListModel(model=self.model, sorter=self.cv.get_sorter())
self.cv.set_model(Gtk.NoSelection(model=sorter_model))

So this is an extra step needed to enable sorting.

1 Like

It worked. I updated the code with TreeListModel and TreeListRowSorter.
The code worked after TreeListModel is added. But sorting order is not correct after TreeListRowSorter is added. I have read the example in the GTK tutorial.
I do not know C. There may be mistakes.

Example in the tutorial:

column_sorter = gtk_column_view_get_sorter (view);
sorter = gtk_tree_list_row_sorter_new (g_object_ref (column_sorter));
sort_model = gtk_sort_list_model_new (tree_model, sorter);
selection = gtk_single_selection_new (sort_model);
gtk_column_view_set_model (view, G_LIST_MODEL (selection));

What is the problem here?

self.model = Gio.ListStore()
tree_list_model = Gtk.TreeListModel.new(self.model, True, True, self.model_func)
column_view_sorter = self.cv.get_sorter()
tree_list_row_sorter = Gtk.TreeListRowSorter.new(column_view_sorter)
sorter_model = Gtk.SortListModel(model=tree_list_model, sorter=tree_list_row_sorter)
selection = Gtk.SingleSelection.new(model=sorter_model)
self.cv.set_model(selection)

Note: model_func is empty (pass) and rows do not have child rows.

Looks like the key is in the TreeListRowSorter.new() docs:

Note that this sorter relies on GtkTreeListModel:passthrough being FALSE as it can only sort GtkTreeListRows.

This modification of the original example worked for me:

self.cv = Gtk.ColumnView()
tree_model = Gtk.TreeListModel.new(self.model, False, True, self.model_func)
tree_sorter = Gtk.TreeListRowSorter.new(self.cv.get_sorter())
sorter_model = Gtk.SortListModel(model=tree_model, sorter=tree_sorter)
selection = Gtk.SingleSelection.new(model=sorter_model)
self.cv.set_model(selection)

Now you will also have to modify your factory bind function. When using Gtk.TreeListModel, Gtk.ListItem now won’t contain the Country object directly but a Gtk.TreeListRow.

So you have to call get_item() two times, one for Gtk.ListItem and another for Gtk.TreeListRow:

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

one for Gtk.ListItem and another for Gtk.TreeListRow:

The updated code worked after changing True value to False and adding one more .get_item().

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