I am learning how to build a C++ application with a Graphical User Interface based on GTK library, in particular using gtkmm version 3.24.
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?
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.