How can I get a GTK4 ColumnView to update the entire row when one cell in the row is changed?
The following simple example creates a ColumnView with two columns for a String and the corresponding ASCII code.
Changing the string in a cell will update the model for both String and ASCII, and changing the ASCII updates the model for both as well.
However, the ColumnView display only updates when I use the mouse to leave the row by clicking into another row.
How do I get the display to update without leaving the row?
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gio, GObject
class DataObject(GObject.GObject):
__gtype_name__ = 'DataObject'
text = GObject.Property(type=str, default=None)
number = GObject.Property(type=str, default=None)
def __init__(self, text, number):
super().__init__()
self.text = text
self.number = number
def setup_c1(widget, item):
"""Setup the widget to show in the Gtk.Listview"""
cell = Gtk.EditableLabel()
item.set_child(cell)
cell.connect('changed', on_c1_change, item)
def bind_c1(widget, item):
"""bind data from the store object to the widget"""
#item ListItem object
label = item.get_child() #Gtk.EditableLabel
obj = item.get_item() #DataObject
label.set_text(obj.text)
label.bind_property("text", obj, "text", GObject.BindingFlags.SYNC_CREATE)
def setup_c2(widget, item):
"""Setup the widget to show in the Gtk.Listview"""
cell = Gtk.EditableLabel()
item.set_child(cell)
cell.connect('changed', on_c2_change, item)
def bind_c2(widget, item):
"""bind data from the store object to the widget"""
label = item.get_child() #Gtk.EdiableLabel
obj = item.get_item() #DataObject
label.set_text(obj.number)
label.bind_property("text", obj, "number", GObject.BindingFlags.SYNC_CREATE)
def on_c1_change(self, item):
obj = item.get_item()
obj.number = str(ord(obj.text))
def on_c2_change(self, item):
obj = item.get_item()
if not obj.number == '':
if int(obj.number) >= 65:
obj.text = chr(int(obj.number))
def on_activate(app):
win = Gtk.ApplicationWindow(
application=app,
title="GTK 4 ColumnView is Confusing !!!",
default_height=400,
default_width=400,
)
list_view = Gtk.ColumnView()
factory_c1 = Gtk.SignalListItemFactory()
factory_c1.connect("setup", setup_c1)
factory_c1.connect("bind", bind_c1)
factory_c2 = Gtk.SignalListItemFactory()
factory_c2.connect("setup", setup_c2)
factory_c2.connect("bind", bind_c2)
selection = Gtk.SingleSelection()
store = Gio.ListStore.new(DataObject)
selection.set_model(store)
list_view.set_model(selection)
column1 = Gtk.ColumnViewColumn.new("String", factory_c1)
column2 = Gtk.ColumnViewColumn.new("ASCII Code", factory_c2)
list_view.append_column(column1)
list_view.append_column(column2)
for i in range(65, 91):
store.append(DataObject(chr(i), str(i)))
sw = Gtk.ScrolledWindow()
sw.set_child(list_view)
win.set_child(sw)
win.present()
app = Gtk.Application(application_id="org.gtk.Example")
app.connect("activate", on_activate)
app.run(None)
The problem is that property bindings are, by default, unidirectional (see GObject.Object.bind_property). You set the binding up such that the object will be updated by the label, but not vice versa.
The updating of the label was actually done through binding the item, not through the binding of the properties.
So one solution is to change the binding such that it is bidirectional:
But this seemingly causes endless loops of [Text of label changed] and [Object property changed] calling each other. So I just changed the direction of the binding
and then started to listen to
notify::editableinstead ofchangedfor the labels.Another solution, which I did not try, would be to add two new properties, "set_number" which is then bound like
and "set_text" and the chain of bindings would make updating "set_text" also the label of "text" and then the "text" of the object.
The full updated code: