c++xmluser-interfacegtkmm

How to add widgets (e.g. a button) to a window in a C++ application using gtkmm and builder XML file


I am working on a simple example of a GUI application written in C++ using gtkmm (version 3.24) and an external XML file.

Thanks to the answer to this other question I managed to create an empty window following the approach that I want (a header myWindow.h file for a 'mainWindow' class declaration, another myWindow.cpp file for the class definition, a main.cpp file, and a builder.ui file with the widgets).

My goal is to populate the window with widgets, maybe starting with a simple button. But due to my poor knowledge of C++ I can't.

After some research, I tried to incorporate code from similar examples, in particular from here, and this is what I have so far:

myWindow.h

#pragma once

#include <gtkmm/window.h>
#include <gtkmm/builder.h>
#include <gtkmm/box.h>
#include <gtkmm/button.h>

class MainWindow : public Gtk::Window
{
public:

  MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder);

protected:

  Glib::RefPtr<Gtk::Builder> m_builder;

};

myWindow.cpp

#include "myWindow.h"

MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder)
: Gtk::Window(cobject)
{
    this->m_builder = p_builder;

    auto pButton = p_builder->get_widget<Gtk::Button>("button1");
}

main.cpp

#include <iostream>
#include <cstring>

#include <gtkmm/application.h>
#include <gtkmm/builder.h>

#include "myWindow.h"

int main(int argc, char** argv)
{

    auto app = Gtk::Application::create(argc, argv);

    auto builder = Gtk::Builder::create_from_file("builder.ui");

    MainWindow* window = nullptr;

    builder->get_widget_derived("mainWindow", window);

   try
{
        // You then show the window by using you handle (i.e window).
    app->run(*window);
}

catch(const Gtk::BuilderError& bdre)
{
    // specific handling for BuilderError
    std::cout << "Builder error: " << bdre.what() << std::endl;
}

catch(const std::runtime_error& re)
{
    // specific handling for runtime_error
    std::cout << "Other runtime error: " << re.what() << std::endl;
}
catch(const std::exception& ex)
{
    // specific handling for all exceptions extending std::exception, except
    // std::runtime_error which is handled explicitly
    std::cout << "Error occurred: " << ex.what() << std::endl;
}
catch(...)
{
    // catch any other errors (that we have no information about)
    std::cout << "Unknown failure occurred. Possible memory corruption" << std::endl;
}

return 0;
}

builder.ui

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.22"/>
  <object class="GtkWindow" id="mainWindow">
    <property name="title">Grid</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object id="button1" class="GtkButton">
        <property name="label">Button</property>
        <layout>
          <property name="column">1</property>
          <property name="row">1</property>
        </layout>
      </object>
    </child>
  </object>
</interface>

When I build this code I get the following error:

myWindow.cpp:14:54: error: no matching function for call to ‘Gtk::Builder::get_widget<Gtk::Button>(const char [8])’
   14 |     auto pButton = p_builder->get_widget<Gtk::Button>("button1");

If I add

Gtk::Button  *addButton;

as protected pointer in the header file myWindow.h, and I use the get_widget() function in the following way

p_builder->get_widget("button1", addButton);

in the constructor definition in file myWindow.cpp, then I am able to build the executable, but then I get the following runtime error:

terminate called after throwing an instance of 'Gtk::BuilderError'
Aborted (core dumped)

with no further information (i.e. the 'try / catch' can not catch the error details).

The syslog reports the following:

systemd-coredump[826232]: Process 826227 (base) of user 1000 dumped core.#012#012Module libzstd.so.1 from deb libzstd-1.5.5+dfsg2-2build1.1.amd64#012Module libsystemd.so.0 from deb systemd-255.4-1ubuntu8.4.amd64#012Stack trace of thread 826227:#012#0  0x000073d024a9eb1c __pthread_kill_implementation (libc.so.6 + 0x9eb1c)#012#1  0x000073d024a4526e __GI_raise (libc.so.6 + 0x4526e)#012#2  0x000073d024a288ff __GI_abort (libc.so.6 + 0x288ff)#012#3  0x000073d024ea5ffe n/a (libstdc++.so.6 + 0xa5ffe)#012#4  0x000073d024ebae9c n/a (libstdc++.so.6 + 0xbae9c)#012#5  0x000073d024ea5a49 _ZSt9terminatev (libstdc++.so.6 + 0xa5a49)#012#6  0x000073d024ebb128 __cxa_throw (libstdc++.so.6 + 0xbb128)#012#7  0x000073d025614130 _ZN3Gtk12BuilderError10throw_funcEP7_GError (libgtkmm-3.0.so.1 + 0x214130)#012#8  0x000073d0253dc34c _ZN4Glib5Error15throw_exceptionEP7_GError (libglibmm-2.4.so.1 + 0x6834c)#012#9  0x000073d02564819e _ZN3Gtk7Builder13add_from_fileERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE (libgtkmm-3.0.so.1 + 0x24819e)#012#10 0x000073d0256481ee _ZN3Gtk7Builder16create_from_fileERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE (libgtkmm-3.0.so.1 + 0x2481ee)#012#11 0x000055e7fb40e6c8 n/a (/home/fabio/Documents/programming/C++/Esperimenti_GTK/GTK4/simple_button/build/base + 0x76c8)#012#12 0x000073d024a2a1ca __libc_start_call_main (libc.so.6 + 0x2a1ca)#012#13 0x000073d024a2a28b __libc_start_main_impl (libc.so.6 + 0x2a28b)#012#14 0x000055e7fb40e565 n/a (/home/fabio/Documents/programming/C++/Esperimenti_GTK/GTK4/simple_button/build/base + 0x7565)#012ELF object binary architecture: AMD x86-64

What am I doing wrong? Where should I 'call' the widgets defined in the XML file, and how?


Solution

  • The problem in the presented code comes from a poor understanding of how Gtk::Builder works.

    Indeed, once the builder is instantiated from the XML file, all the widgets present in the XML file will be created and shown in the window, with no need to 'call' or instantiate them in the code.

    A minimal working example, with few widgets (buttons) in the window, is the following:

    myWindow.h

    #pragma once
    
    #include <gtkmm/window.h>
    #include <gtkmm/builder.h>
    
    class MainWindow : public Gtk::Window
    {
    
    public:
    
      MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder);
    
    protected:
    
      Glib::RefPtr<Gtk::Builder> m_builder;
    
    };
    

    myWindow.cpp

    #include "myWindow.h"
    
    #include <gtkmm/builder.h>
    
    MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& p_builder)
    : Gtk::Window(cobject), m_builder{p_builder}
    {
    }
    

    main.cpp

    #include <iostream>
    #include <cstring>
    
    #include <gtkmm/application.h>
    #include <gtkmm/builder.h>
    
    #include "myWindow.h"
    
    int main(int argc, char** argv)
    {
        auto app = Gtk::Application::create(argc, argv);
    
        auto builder = Gtk::Builder::create_from_file("builder.ui");
    
        MainWindow* window = nullptr;
    
        builder->get_widget_derived("mainWindow", window);
    
        app->run(*window);
    
       return 0;
    }
    

    builder.ui

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <object id="mainWindow" class="GtkWindow">
        <property name="title">Pippo</property>
        <property name="default_width">400</property>
        <property name="default_height">300</property>
    
        <child>
         <object class="GtkGrid" id="grid">
            <property name="visible">True</property>
            <child>
              <object id="button1" class="GtkButton">
                <property name="label">Button 1</property>
                <property name="visible">True</property>
              </object>
    
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">0</property>
              </packing>
    
            </child>
    
            <child>
              <object id="button2" class="GtkButton">
                <property name="label">Button 2</property>
                <property name="visible">True</property>
              </object>
              <packing>
                <property name="left-attach">1</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
    
            <child>
              <object id="button3" class="GtkButton">
                <property name="label">Button 3</property>
                <property name="visible">True</property>
              </object>
              <packing>
                <property name="left-attach">2</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
    
            <child>
              <object id="button4" class="GtkButton">
                <property name="label">Button 4</property>
                <property name="visible">True</property>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">1</property>
              </packing>
            </child>
    
         </object>
        </child>
    
      </object>
    </interface>