c++gtkgtkmmdrawingarea

Derived Gtk::DrawingArea widget is not loaded from Glade builder file correctly


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.


Solution

  • 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.