Search code examples
c++gtkgtkmm3

In C++, how to use get_widget_derived() function from gtkmm library


I am learning how to build a C++ application with a Graphical User Interface based on GTK library.

As described in this other question I am trying to use an external XML file and the 'builder'. After some research I believe that the key tool to use is the get_widget_derived() function from gtkmm, but I can't get a working minimal example.

What I have done so far is this:

window.h

#pragma once
#include <gtkmm.h>

class MainWindow : public Gtk::Window
{
public:
  MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
  ~MainWindow() override;

protected:
  //Signal handlers:

  Glib::RefPtr<Gtk::Builder> m_refBuilder;
};

window.cpp

#include "window.h"

MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder)
: Gtk::Window(cobject),  m_refBuilder(refBuilder)
{}

MainWindow::~MainWindow()
{}

main.cpp

#include "window.h"
#include <iostream>
#include <cstring>


namespace
{

MainWindow* pDialog = nullptr;
Glib::RefPtr<Gtk::Application> app;


template <typename T_Widget, typename... Args> inline
void get_widget_derived(const Glib::ustring& name, T_Widget*& widget, Args&&... args)
{
    // Initialize output parameter:
    widget = nullptr;

    // Get the widget from the GtkBuilder file.
    using cwidget_type = typename T_Widget::BaseObjectType;
    auto pCWidget = (cwidget_type*) Gtk::Builder::get_cwidget(name);

    //The error was already reported by get_cwidget().
    if(!pCWidget)
      return;

    //Check whether there is already a C++ wrapper instance associated with this C instance:
    Glib::ObjectBase* pObjectBase = Glib::ObjectBase::_get_current_wrapper((GObject*)pCWidget);

    //If there is already a C++ instance, then return it again:
    if(pObjectBase)
    {
      widget = dynamic_cast<T_Widget*>( Glib::wrap((GtkWidget*)pCWidget) );
      //Newer, more spec-complaint, versions of g++ cannot resolve a specific wrap() function in a template.

      //The dynamic cast checks that it is of the correct type.
      //Somebody might be trying to call get_widget_derived() after already calling get_widget(),
      //or after already calling get_widget_derived() with a different derived C++ type.
      if(!widget)
      g_critical("Gtk::Builder::get_widget_derived(): dynamic_cast<> failed. An existing C++ instance, of a different type, seems to exist.");
    }
    else
    {
      //Create a new C++ instance to wrap the existing C instance:


      Glib::RefPtr<Gtk::Builder> refThis(this);
      refThis->reference(); //take a copy.
      widget = new T_Widget(pCWidget, refThis, std::forward<Args>(args)...);

    }
}



void on_app_activate()
{

  // Load the GtkBuilder file and instantiate its widgets:
  auto refBuilder = Gtk::Builder::create();
  try
  {
    refBuilder->add_from_file("derived.ui");
  }
  catch(const Glib::FileError& ex)
  {
    std::cerr << "FileError: " << ex.what() << std::endl;
    return;
  }
  catch(const Glib::MarkupError& ex)
  {
    std::cerr << "MarkupError: " << ex.what() << std::endl;
    return;
  }
  catch(const Gtk::BuilderError& ex)
  {
    std::cerr << "BuilderError: " << ex.what() << std::endl;
    return;
  }

  // Get the GtkBuilder-instantiated dialog:

  pDialog = get_widget_derived<MainWindow>(refBuilder, "mainWindow");

  if (!pDialog)
  {
    std::cerr << "Could not get the dialog" << std::endl;
    return;
  }

  // It's not possible to delete widgets after app->run() has returned.
  // Delete the dialog with its child widgets before app->run() returns.
  pDialog->signal_hide().connect([] () { delete pDialog; });

  app->add_window(*pDialog);
  pDialog->set_visible(true);
}
} // anonymous namespace

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

  app = Gtk::Application::create("org.gtkmm.example");

  app->signal_activate().connect([] () { on_app_activate(); });

  return app->run(argc, argv);
}

builder.ui

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="3.22"/>
  <object class="GtkWindow" id="mainWindow">
    <property name="default_width">400</property>
    <property name="default_height">300</property>
  </object>
</interface>

If I try to build this code I get the error:

error: cannot call member function ‘GtkWidget* Gtk::Builder::get_cwidget(const Glib::ustring&)’ without object
   23 |     auto pCWidget = (cwidget_type*) Gtk::Builder::get_cwidget(name);

What is the object that should be instantiated, and where?


Solution

  • If you are new to C++, I understand you can be a bit confused by the under documented Gtkmm API. I am using 3.24 as well and here is a working example.

    window.h

    First, as you started doing yourself, you need a MainWindow definition/declaration, somewhere. Here is the declaration:

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

    So I'm inheriting Gtk::Window and in the constructor, I am passing both the C pointer and the builder. This is just the way things have to be done with the builder (this is what is documented online).

    window.cpp Here is the definition:

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

    main.cpp

    Here is the main function. This is where you create the builder instance. Notice that in 3.24, get_widget_derived is not a free function, but a method of the class Gtk::Builder:

    #include <iostream>
    #include <cstring>
    
    #include <gtkmm/application.h>
    
    #include "window.h"
    
    int main(int argc, char** argv)
    {
        // Create an application object. This is mandatory since it initializes
        // the toolkit. If you don't do this, widgets won't show.
        auto app = Gtk::Application::create(argc, argv);
    
        // Since you want to use the builder, you need to instantiate one. You
        // can do this directly using your UI file.
        auto builder = Gtk::Builder::create_from_file("builder.ui");
    
        // At this point, you are ready to create your window. The builder is
        // going to create an instance for you, and you get a handle to this
        // istance to later use.
        // First, you create a MainWindow pointer, which points to nothing. It
        // is going to be used by the builder to get you the handle.
        MainWindow* window = nullptr;
    
        // Then, using you builder instance. You call get_widget_derived and
        // pass the pointer to get the handle.
        builder->get_widget_derived("mainWindow", window);
    
        // You then show the window by using you handle (i.e window).
        return app->run(*window);
    }
    

    If you want to use a newer version of Gtkmm, you will need to build it yourself, using JHBuild. I don't recommend it unless you have a good reason for it. In my experience, this is not so straightforward.