GTKMM crash on TreeExpander set_child()

hi,
I’m getting seg fault in GTKMM when I try to Gtk::TreeExpander.set_child()inside the bind signal of a tree ListView factory.
It crashes in widget.h in the function GtkWidget* gobj() { return reinterpret_cast<GtkWidget*>(gobject_); }.
It tried to call gobj() in the TreeExpander class in the function set_child() , the exact line is gtk_tree_expander_set_child(gobj(), (widget).gobj()); (since thats the only thing set_child does).
Here’s the call stack

libgtkmm-4.0.so.0!Gtk::Widget::gobj(Gtk::Widget * const this) (/home/user/projects/gtkmm/src/build/gtk/gtkmm/widget.h:162)
libgtkmm-4.0.so.0!Gtk::TreeExpander::set_child(Gtk::TreeExpander * const this, Gtk::Widget & widget) (/home/user/projects/gtkmm/src/build/gtk/gtkmm/treeexpander.cc:162)
ProjectTree::on_bind_row(ProjectTree * const this, const Glib::RefPtr & list_item) (/home/user/projects/source/gui/graphics/workspace/projecttree/ProjectTree.cpp:331)

What I’m trying to do is a ListView that each row is a Gtk::Box and since I want a tree they need to be inside a TreeExpander.
I used has base the following example gtkmm/example_listview_treelist.cc at master · GNOME/gtkmm · GitHub
There’s no example for dynamic tree loading so I tried to implement mine.

It will be difficult to give advice with only the information you’ve shared. You are likely mismanaging object lifetime. Also the on_bind() methods in the example don’t call set_child(), so I don’t know what you’re doing. Can you make a simple test case?

Give some time to isolate the code and I will post a small project to test

Here’s a small project to test the features I’m trying to implement.
The point of this project is to create a ListView that is a tree.
It uses a custom model ProjectModel that stores a Gtk::Box to be used inside the tree.
When the user selects a row it can create a child row under it.

Link to repo: GitHub - Samega7Cattac/Gtkmm_ListView_ProjectTree

How to compile:

  1. mkdir build
  2. cd build
  3. cmake ..
  4. make
  5. Run with ./test

Steps to reproduce the bug:

  1. Add a new row to root by clicking in the ... button on the bottom and click on Add Row
  2. Click on the new row Row 0
  3. ***poof***

The first thing I noticed was that you were putting make_managed widgets into a Glib::RefPtr. Generally everything that’s a widget is never put into a refptr (widgets will end up in a container, so there shouldn’t be a need to track references until it ends up in the container). That fixed the crashes on add row.

Adding to a child row and removing rows was broken still; thats because in on_bind() you are inserting TreeListRows, but from get_item() you tried to cast them to ProjectModel.

I opened a PR with these fixes. However, adding a child row is still broken. Your “ProjectModel” is analogous to the gtkmm examples “ModelColumns”, but you are putting ListStores in this object which the example doesn’t do. Its a little confusing for me to untangle this so I’ll leave that as an exercise for you.

1 Like

Thx for the PR.
I will think a bit about my model.
I had no issues adding child rows, but it crashes if I expand a row with childs, collapse and expand again, it crashes inside GTKMM it seems, the issue still is related to the set child.

If u want to try it urself:

  1. Add a new row
  2. Select the new row “Row 0”
  3. Expand “Row 0” (“Row 1” should show has child)
  4. Collapse “Row 0”
  5. Expand “Row 0” again
  6. Watch GTKMM crash calling gtk_tree_expander_set_child

From what I understood from the docs I need to return a store in create_model() so I can add childs later on, so I create a new store like they do in the example but I save it in my model so I can get it later and add new rows under it.

Also just commited and simplified the code and the model.
Removed a lot of redundant and unnecessary code.

I wanted to start with something I was more confident in, so I added two buttons to the gtkmm example that adds children, you can find my changes here.

In the end I did have to add a handle to a liststore in the ColumnModel. I think it does what you want.

I checked that but I don’t see why the data should be duplicated between the Model and the Cell and when adding it needs to be added to both Model childs and cell childs from what I understood

Perhaps there’s some dissonance because this example program is a little contrived.

In a real program, your data would be in e.g. a database. In the example program, the database is represented by the original root vector of cells. Gtk wants a gobject to propagate widget changes onto, so the ModelColumn represents a realization of the database data into a gobject.

So to say it another way, ModelColumn::create(cell) realizes your backend cell data as a gobject. For your program that is only manipulating widgets and never goes anywhere, yes you could make a ModelColumn::create() that makes a gobject from nothing.

In a real program it is depressingly common to transport data between databases/network objects/xml files and gobjects, so thats why i kept to the create cell → create model from cell pattern.

The most important part is that keeping to the gtkmm example structure I was able to properly manage the object lifetime issue. Looking back at your code, your AddRow() action is creating treeview widgets. That’s something that should be being done in on_setup(). gtk wants to manage the treeview widgets, it might be reusing existing widgets or hold off on creating more until the user scrolls the rows into view.

1 Like

(Some of this comment duplicates Andrew Potter’s latest comment.)

You call TreeExpander::set_child() in on_bind_row(). And you set a child that
you get from GetRowBox(). That’s the problem. on_bind_row() can be called
multiple times for the same row in the list. You set the same Box multiple
times, possibly in different ListItems. There is not necessarily one ListItem
per row in the model. A ListItem can later be reused for a different row.
When a Box becomes the child of a TreeExpander, it’s owned by the TreeExpander,
which can delete it when it’s no longer needed by that TreeExpander.

To be on the safe side, you should create a row’s widgets in on_setup_row().
Calls to TreeExpander::set_child(), Box::append() and similar functions should
also go there. ProjectModel should contain the row’s data (strings, images, etc.),
not the row’s widgets.

If you plan to have a list where not all rows contain the same widgets,
it becomes more complicated. If the differences are moderate, you can perhaps
populate all rows with all necessary widgets, and turn them on or off
with set_visible(true) and set_visible(false) in on_bind_row().

Well yes, that’s the thing, I have no control on what widgets are inside the box.
And on_setup I have no access to the model tho

The following trick worked when I tested it:

In on_bind_row(), replace row_expander->set_child(*new_box); by

    if (row_expander->get_child() != new_box)
    {
      new_box->reference();
      row_expander->set_child(*new_box);
    }

I don’t guarantee that it will always work. It depends on what GtkTreeExpander
in GTK does when it gets rid of its child. Right now it calls gtk_widget_unparent()
which calls g_object_unref(). g_object_unref() can be neutralized by the call to
new_box->reference().

It’s not nice. Applications are not supposed to call Glib::ObjectBase::reference() directly.

1 Like

Ya, its not nice but It work for now has a workaround.
I will update the code in the repo so others don’t have to pass by the same thing and has a reference.
Maybe later someone finds a way.
Thx guys

Ah, the solution is so simple! Why didn’t I think of it yesterday!

The Gtk::Box which is created in AddRow shall not be managed. That is, its future parent
(a TreeExpander) shall not delete it when the parent is done with it.

Forget about my previous comment. row_expander->set_child(*new_box); can be
called in on_bind_row() like you did from the beginning, without calling new_box->reference().
Change the creation of the Box in AddRow to

    Gtk::Box* new_box = new Gtk::Box(Gtk::Orientation::HORIZONTAL);

Now you will be responsible for deleting the box when it’s not needed.
For instance, store the pointer in a std::unique_ptr in ProjectModel.

1 Like

Funny, that’s the solution I arrived yesterday while passing the code in the example repo to my project, but I didn’t test if I would delete in time.
I will update the repo when I can.

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