'How do you add Gtk::Notebook tabs at runtime from Glade definitions?

I am creating a new GTK app (using gtkmm) and the main interface is tabbed, a bit like a web browser.

I want to create a new tab for each document the user opens, with the content of each tab being defined by Glade. I'm new to gtkmm, so I am not sure how to do this.

If I design my tab in Glade as a self-contained window, when I call Gtk::Builder::get_widget() it returns a pointer to the Gtk::Window, but I can't pass this to Gtk::Notebook::append_page() because that function requires a reference and I only have a pointer. I don't want to dereference the pointer, because I believe each call to get_widget() returns the same pointer - so if I want to add five pages the same, I need to somehow clone the UI structure before adding it to the Gtk::Notebook - otherwise changing a UI element on one tab will cause it to change on the other four tabs too as they are all pointing to the same UI widgets.

What is the best way to take a window designed in Glade and dynamically add it multiple times to a Gtk::Notebook?


EDIT:

Putting each tab's definitions into a separate *.glade file does allow me to load multiple instances so that part is all good.

However I still can't add the loaded structure to the Gtk::Notebook:

  • If I tell Gtk::Builder::get_widget() to retrieve the root Gtk::Window from the .glade file and add that as the new tab, it won't compile because I have to pass a Gtk::Widget to append_page() and it won't accept a Gtk::Window parameter instead.
  • If I tell it to pick the top-most widget instead, then I get a bunch of runtime errors:

    Can't set a parent on widget which has a parent
    gtkcontainer.c:858: container class `gtkmm__GtkWindow' has no child property named `tab-expand'
    gtkcontainer.c:858: container class `gtkmm__GtkWindow' has no child property named `tab-fill'
    gtkcontainer.c:858: container class `gtkmm__GtkWindow' has no child property named `tab-label'
    gtkcontainer.c:858: container class `gtkmm__GtkWindow' has no child property named `menu-label'
    gtkcontainer.c:858: container class `gtkmm__GtkWindow' has no child property named `position'
    Gtk:ERROR:gtkcontainer.c:3541:gtk_container_propagate_draw: assertion failed: (gtk_widget_get_parent (child) == GTK_WIDGET (container))
    Aborted (core dumped)
    
  • If I take the top-most widget and instead call reparent() and pass the Gtk::Notebook as the new parent then the new tab does show up with all the widgets correctly arranged, but then I can't control the tab's title.

There must be a 'correct' way to do this, I just can't figure out what it is!



Solution 1:[1]

Thanks to help from the gtkmm mailing list, I have the solution.

In Glade, I had created a Gtk::Window with a single Gtk::Box in it, and put all my controls in the Box. This caused problems when trying to add the Box as a new tab, because the Box already had a parent (the Gtk::Window) so it was troublesome trying to make the Gtk::Notebook the new parent.

Instead, I found you can right-click on the button for the Gtk::Box in Glade, and choose "Add widget as toplevel". There is then no need for a Gtk::Window at all, and as the Box has no parent, calling Gtk::Notebook::append_page() and passing the Gtk::Box works perfectly. I can then use the variant of append_page() that lets me set the tab's title at the same time.

It is still a requirement however to create a new Gtk::Builder instance to load the controls for each tab, because each tab needs a new instance of the controls. If you just keep calling Gtk::Builder::get_widget() on the same object, it returns the same instance of the controls.

Solution 2:[2]

Generally, you should first make the layout of your GUI in Glade, complete with Window, Notebook and/or other nested widgets. If you want different tabs then you also can make them in Glade, but as seperate objects that aren't nested in your toplevel Window. Save the .xml file and load it into your program.

Declare your widgets as pointers, initialized with the nullptr and use something like this to get access to the widget you made in Glade:

 m_refBuilder->get_widget("my_tabbox00", m_pTab00);

Now you are able to use (the Glade id) my_tabbox00 as (the C++) pointer m_pTab00. Just make sure my_tabbox00 has no parent in Glade, it can contain childs. You also can use get_widget() for the Notebook and the Viewports (even if the widget has a parent). To append a page you can use:

m_pNotebook->append_page(*m_pViewport00, *m_pTab00);

Beware that this solution restricts you a little bit because for every tab you have to make a new tab and viewport in Glade. Maybe a better way would be to try to make a class for the tabs and using multiple objects for this.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Malvineous
Solution 2 useruser