I am working on an embedded Linux project using gtk/gtkmm 3. I use Glade builder files to define the UI and load them during program runtime. That works fine. Now I need a custom circular progress widget, so a derived a class from Gtk::DrawingArea as suggested in the gtkmm documentation.
// MyCircularProgress.hpp
class MyCircularProgress : public Gtk::DrawingArea
{
public:
MyCircularProgress();
MyCircularProgress(GtkDrawingArea* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
virtual ~MyCircularProgress();
protected:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
};
// MyCircularProgress.cpp
#include "CircularProgress.hpp"
MyCircularProgress::MyCircularProgress() :
// To register custom properties, you must register a custom GType. If
// you don't know what that means, don't worry, just remember to add
// this Glib::ObjectBase constructor call to your class' constructor.
// The GType name will be gtkmm__CustomObject_MyCircularProgress.
Glib::ObjectBase("MyCircularProgress")
{
}
MyCircularProgress::MyCircularProgress(GtkDrawingArea* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder) :
// To register custom properties, you must register a custom GType. If
// you don't know what that means, don't worry, just remember to add
// this Glib::ObjectBase constructor call to your class' constructor.
// The GType name will be gtkmm__CustomObject_MyCircularProgress.
Glib::ObjectBase("MyCircularProgress"),
Gtk::DrawingArea(cobject)
{
}
MyCircularProgress::~MyCircularProgress()
{
}
bool MyCircularProgress::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
{
Gtk::Allocation allocation = get_allocation();
const int width = allocation.get_width();
const int height = allocation.get_height();
cr->set_source_rgb(0.8, 0.0, 0.0);
cr->rectangle(0, 0, width, height);
cr->fill();
return true;
}
The corresponding glade file derived.glade:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkApplicationWindow" id="wnd_app">
<property name="name">wnd_app</property>
<property name="width_request">480</property>
<property name="height_request">320</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="baseline_position">top</property>
<child>
<placeholder/>
</child>
<child type="center">
<object class="gtkmm__CustomObject_MyCircularProgress" id="draw_progress">
<property name="width_request">200</property>
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
In the main.cpp I registered the new C++ wrapper before using the builder.
#include <vector>
#include "CircularProgress.hpp"
#include <iostream>
#include <cstring>
#include <type_traits>
#include <typeinfo>
#include <cxxabi.h>
#include <memory>
#include <string>
#include <cstdlib>
template <class T>
std::string
type_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(
#ifndef _MSC_VER
abi::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr),
#else
nullptr,
#endif
std::free
);
std::string r = own != nullptr ? own.get() : typeid(TR).name();
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += "&";
else if (std::is_rvalue_reference<T>::value)
r += "&&";
return r;
}
// Not really used anywhere, but force an instance to be created.
MyCircularProgress* my_global_accessible_progress = nullptr;
int main (int argc, char **argv)
{
auto app = Gtk::Application::create(argc, argv, "my.circularprogress");
// Create a dummy instance before the call to refBuilder->add_from_file().
// This creation registers DerivedButton's class in the GType system.
my_global_accessible_progress = new MyCircularProgress();
//Load the Glade file and instantiate its widgets:
auto refBuilder = Gtk::Builder::create();
try
{
refBuilder->add_from_file("derived.glade");
}
catch(const Glib::FileError& ex)
{
std::cerr << "FileError: " << ex.what() << std::endl;
return 1;
}
catch(const Glib::MarkupError& ex)
{
std::cerr << "MarkupError: " << ex.what() << std::endl;
return 1;
}
catch(const Gtk::BuilderError& ex)
{
std::cerr << "BuilderError: " << ex.what() << std::endl;
return 1;
}
Gtk::ApplicationWindow* pWnd = nullptr;
refBuilder->get_widget("wnd_app", pWnd);
if(pWnd)
{
auto objects = refBuilder->get_objects();
for(auto item : objects)
{
std::cout << typeid(item).name() << std::endl;
std::cout << type_name<decltype(item)>() << std::endl;
std::cout << "----------------------------------------" << std::endl;
}
MyCircularProgress* pProgress;
refBuilder->get_widget_derived("draw_progress", pProgress);
if(pProgress)
{
std::cout << "MyCircularProgress name: " << pProgress->get_name() << std::endl;
std::cout << type_name<decltype(pProgress)>() << std::endl;
}
Gtk::DrawingArea* pDraw;
refBuilder->get_widget("draw_progress", pDraw);
if(pDraw)
{
std::cout << "Gtk::DrawingArea name: " << pDraw->get_name() << std::endl;
std::cout << type_name<decltype(pDraw)>() << std::endl;
}
app->run(*pWnd);
}
delete pWnd;
delete my_global_accessible_progress;
return 0;
}
When I start the program and load the UI using the builder, the derived class MyCircularProgress
is not instantiated. There is no error output to the console, but the expected constructor MyCircularProgress(GtkDrawingArea* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder)
is not called.
When I try to get the MyCircularProgress
instance from the builder using:
MyCircularProgress* pProgress;
refBuilder->get_widget_derived("draw_progress", pProgress);
I get the following error message on the console:
Gtk::Builder::get_widget_derived(): dynamic_cast<> failed. An existing C++ instance, of a different type, seems to exist.
When I try to get the base class instance from the builder using:
Gtk::DrawingArea* pDraw;
refBuilder->get_widget("draw_progress", pDraw);
Then I get a valid pointer.
Has anybody an idea, what's wrong? The Gtk builder seems to create the correct base class Gtk::DrawingArea
of the derived C++ class MyCircularProgress
, but does not assign it correctly.
This line is the problem:
auto objects = refBuilder->get_objects();
I gets all the objects constructed by the builder. In your case, it gets your derived widget as a Gtk::DrawingArea
(this is why the constructor for this type is called). When you try to get the same widget again as another type, it fails.
Note: If you had constructed a minimal example, you would for sure have found this easily.