How to force a "refresh" of widget values in a ColumnView?

Hi everyone. I have precisely the same issue as:
ColumnView, ListStore item changes

When a user edits a value in a ColumnView, I update values in the same row, in other columns. Changes in the “model” aren’t automatically sync’d to existing widgets. However if I scroll WAY past the row being edited ( and the widgets get recycled ), I can then scroll back and see the changes. But there must be some kind of signal I can emit to tell the widgets in a given row to “rebind”? Is there some way to trigger a bind() call on a given row? Or is there no such concept, and I have to manually get hold of the factory all other required args, and call the bind() method myself?

Are you actually binding the models properties to the widgets In the bind() call? If not maybe the gobject binding docs might be of help. Otherwise the widgets won’t update alongside their respective models.

No, I’m not binding any properties. I’ve never seen an example of this - eg in the docs: Gtk.ListView - in bind_listitem_cb there’s no actual binding of properties. Can you point me to an example that does this? I looked at the gobject docs you mentioned, but I’m still not really seeing how this all fits together :confused:

The issue, here, is one of design.

The way you’re supposed to use the list and column views in GTK4 is via the model-view-controller pattern:

  • you have an object that contains the whole state you want to represent for each row
  • you have a list object, containing all the rows
  • you have a row widget that represents a row object in the list
  • you have a view widget that represents the (visible) rows

Each row widget has to be “bound” to a row object; whenever the row object changes state, the row widget gets updated. The way this happens is through GObject properties, which have an automatic notification system on change.

That’s what the “bind” function is supposed to do: bind one or more properties on the list view item to one or more properties to the list view widget.

By way of an example, let’s suppose you have a “Person” object that contains an identifier and a name:

class Person(GObject.Object):
    __gtype_name__ = 'Person'

    def __init__(self, person_id, name):
        super().__init__()

        self._id = person_id
        self._name = name

    @GObject.Property(type=str, flags=GObject.ParamFlags.READABLE)
    def person_id(self):
        return self._id

    @GObject.Property(type=str)
    def person_name(self):
        return self._name

    @person_name.setter
    def set_person_name(self, name):
        if self._name != name:
            self._name = name
            self.notify("person-name")

Now we populate a Gio.ListStore with a bunch of people:

        nodes = {
            "aaa": "Clark Kent",
            "aab": "Bruce Wayne",
            "aac": "Barry Allen",
            # ...
        }

        self.model = Gio.ListStore(item_type=Person)
        for n in nodes.keys():
            self.model.append(Person(person_id=n, person_name=nodes[n]))

And we set up a factory to be used by any list or column view, so we can bind rows in the views to rows in the model:

        factory = Gtk.SignalListItemFactory()

        def _on_factory_setup(factory, list_item):
            label = Gtk.Label()
            list_item.set_child(label)
        factory.connect("setup", _on_factory_setup)

        def _on_factory_bind(factory, list_item):
            label = list_item.get_child()
            person = list_item.get_item()
            person.bind_property("person_name",
                                label, "label",
                                GObject.BindingFlags.SYNC_CREATE)
        factory.connect("bind", _on_factory_bind)

Now, every time a Person object in the model changes the value of the “person-name” property, the label widget that represents that Person in the view will update the “label” property.

If you have more complex rows, you will need to create your own composite widget and add properties to them that you can bind to objects in your model. In C, some of the complexity between nesting widgets and properties can be avoided by using GtkExpression instead of GObject.bind_property, but that’s not available to the Python bindings.

1 Like

Thank you Emmanuele, that’s very helpful :slight_smile:

However I am faced with one final complexity - I have no control over the data being displayed / edited, as the end goal here is as a class to display records from a SQL query that a user can provide at run-time ( amongst other things - I actually make heavy use of lists based on SQL queries in a number of applications ). Having to define a class with each column as a GObject Property doesn’t seem like a feasible approach, though I guess I could generate such a class at run-time and dynamically load it - if this is even possible in Python ( I’m a Perl guy, slowly moving to the dark side ).

A possible solution would be to stringify each record, and then have a single property for the entire record. This would obscure the actual column that’s been changed, but it seems like a reasonable tradeoff - unless someone has a better idea …

@sameeralwosaby - you might be interested in this?

At this point, I have no idea what you’re trying to achieve, really.

If you’re querying a SQL database you’ll have an object that contains the result of each row in the table; you’ll also need a widget that presents those results. If the result object is a string, and the view widget is a label, you need a string property that can be bound to a GtkLabel’s label property.

If you are doing something more complex with the presentation layer you will need a result object that can be bound to other presentation widgets.

Hi @ebassi,

I have tried your suggestion but this leads to another issue.
Here is my code extract where I apply some formatting on the object properties:

    def _factory_bind(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem, property_name: str) -> None:
        label = list_item.get_child()
        data = list_item.get_item()
        data.bind_property(property_name, label, "label", GObject.BindingFlags.SYNC_CREATE)

        if property_name == "path":
            value = data.get_property(property_name).replace(self._window.folder_path, "")
        else:
            value = str(round(data.get_property(property_name)))
        label.set_text(value)

If I update object properties then the value are directly refreshed but the formatting is not applied:

If I select another row, then the formatting is applied:

So, your suggestion does not really solve my issue.

Does this means that I should create some dedicated properties on the main object which return the formatted data directly ?

Regards,
@vcottineau

You should use bind_property_full if you need to apply formatting (or other transformations) to a property binding.

Hi @jfrancis,

Thanks for you answer.
Can you provide a code sample on how to use a GObject.Closure structure ?

Regards,
@vcottineau

Sadly, bind_property_full() isn’t available in Python because nobody wrote a wrapper.

You can connect to the notify signal directly and do the mapping in your signal handler—after all, that’s what bind_property() does.

Hi @ebassi,

I have tried your suggestion and it’s working fine. Thanks !
However, I am still wondering why I have to do this as the Gio.ListStore object is already bound to the GtkColumnView.

Regards,
@vcottineau

Your _factory_bind function is only called when binding a new row to an object, or binding a new object to a row. If you change the properties on the model object, you still need to propagate those changes to the widget. This is why you need to use bind_property or the notify signal.

One way to remove the need for this is to use immutable objects; each time an object changes, instead of updating it, you can remove the old one from the ListStore and replace it with a new object. But that may be less efficient than simply updating the object.

1 Like

The ListModel implementation only tracks whenever the list of objects changes; internal changes in the objects are not tracked by ListModel implementations: it’s up to objects to do that.

1 Like

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