Skip to content

Build1 publisher2 min readPublished

A Field Guide to GTK Widgets, Part 3: How Hand-Patched Lists Get the Row Index Wrong, and Where GListModel Fits In

The third post in a GTK widget field guide replaces a loop over ListBox rows with a GListStore the view watches, and it pays for the convenience with borrow() checks at runtime in place of property access at compile time.

The Engineer · Build desk

Illustration accompanying A Field Guide to GTK Widgets, Part 3: How Hand-Patched Lists Get the Row Index Wrong, and Where GListModel Fits In

What happened

  • The third entry in a dev.to GTK widget field guide jumps to lists ahead of layout and navigation, on the grounds that lists are the material the reference documents worst.
  • Its starting example creates a gtk::ListBox, loops over the tasks, builds one left-aligned gtk::Label per task, and appends each label to the box.
  • The rewritten version is split into task.rs for the data, task_row.rs for turning one task into one row, and window.rs for the wiring, mirroring the companion repo.
  • Older GTK solved the same problem with GtkTreeView, GtkListStore and cell renderers, an apparatus the post says is largely deprecated now.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost Boxing plain data moves item access from compile-time property checks to borrow() and borrow_mut() at runtime, so the failure surfaces in a build that compiled clean.
  • constraint Any feature that has to watch a property change is shut out of the shortcut, so the subclassing work the post defers to another series is the real price for a long-lived app.
  • decision The item class is baked into the store at construction, so the subclass-or-box choice has to be settled before the view code exists.
  • exposure Deprecated TreeView code still compiles and runs, so an older answer that works can put a new app on an unsupported path without anything failing.

Under loop-and-append, a task marked done somewhere else in the app becomes a widget-tree edit: find the row, remove it, rebuild it, reinsert it at the right index [5]. Get the index wrong and the app silently updates a different task [6].

The rewrite does not delete the loop. window.rs still iterates starting_tasks() and calls append() on each item; the receiver changed from the ListBox to a gio::ListStore [13][20]. The difference is who owns order and change notification. GListModel is not a widget, and the post describes it as an interface any GObject can implement to say "I'm an ordered, indexable collection, and I'll tell you when I change" [9]. GListStore is the concrete type you reach for: plain, mutable, in-memory [10]. In the split version, task.rs never imports gtk [18].

The entry fee is the type system. GListStore holds GObjects, so a plain Rust Task cannot go in directly [14]. One route is a GObject subclass with real properties, which the author calls the right call for anything long-lived and has covered in a separate GNOME/Rust series [15]. The other is glib::BoxedAnyObject, which wraps any Rust value as a GObject with no subclassing and charges borrow() and borrow_mut() runtime checks in place of compile-time property access [16]. The compiler stops helping at that line. The stated rule is BoxedAnyObject when you just need data in a list model, and a real subclass when something needs to observe a property changing [17].

Whether the model layer pays depends on how much the list moves. starting_tasks() returns three tasks, one of them already done [12], so the completed-to-the-bottom sort that motivates the redesign moves one row on the initial data [21]. At that size the author agrees the loop is "completely fine" [4]. The bookkeeping he describes only turns expensive once rows arrive, leave and change position after first render [5].

The older apparatus still runs, which is how the mistake gets made. The series intro recounts finding a six-year-old Stack Overflow answer built on the deprecated TreeView machinery, getting it working, and only later discovering the toolkit had moved on [8]. The published excerpt breaks off shortly after the file-split section, so the sort and filter models that would do the re-sorting appear as the motivating pain and not as working code [22]. The complete runnable version is in the companion repo [19].

What to watch

  • A later entry that actually wires a sort model and a filter model would show whether the index bookkeeping disappears or just moves.
  • If the subclassing entry lands, its property boilerplate can be weighed directly against BoxedAnyObject's borrow() checks.
  • Removal rather than deprecation of GtkTreeView in a GTK release would turn copied older answers from working code into build failures.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories