Using derived widgets

You can use Gtk::Builder and Glade to layout your own custom widgets derived from gtkmm widget classes. This keeps your code organized and encapsulated, separating declarative presentation from business logic, avoiding having most of your source just be setting properties and packing in containers.

Use Gtk::Builder::get_widget_derived() like so:

auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived");

Your derived class must have a constructor that takes a pointer to the underlying C type, and the Gtk::Builder instance. All relevant classes of gtkmm typedef their underlying C type as BaseObjectType (Gtk::Dialog typedefs BaseObjectType as GtkDialog, for instance).

You must call the base class's constructor in the initialization list, providing the C pointer. For instance,

DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Gtk::Dialog(cobject)
{
}

You could then encapsulate the manipulation of the child widgets in the constructor of the derived class, maybe using get_widget() or get_widget_derived() again. For instance,

DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Gtk::Dialog(cobject),
  m_builder(builder),
  //Get the Glade-instantiated Button, and connect a signal handler:
  m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
{
  if(m_pButton)
  {
    m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
  }
}

It's possible to pass additional arguments from get_widget_derived() to the constructor of the derived widget. For instance, this call to get_widget_derived()

auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived", true);
can invoke this constructor
DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder, bool warning)
: Gtk::Dialog(cobject),
  m_builder(builder),
  m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
{
  // ....
}

24.3.1. Gtk::Builder and Glib::Property

If your derived widget uses Glib::Property, it becomes slightly more complicated. A derived widget that contains Glib::Property members must be registered with its own name in the GType system. It must be registered before any of the create_from_*() or add_from_*() methods are called, meaning that you may have to create an instance of your derived widget just to have its class registered. Your derived widget must have a constructor that has the parameters required by get_widget_derived() and calls the Glib::ObjectBase constructor to register the GType.

DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
: Glib::ObjectBase("MyButton"), // The GType name will be gtkmm__CustomObject_MyButton.
  Gtk::Button(cobject),
  prop_ustring(*this, "button-ustring"),
  prop_int(*this, "button-int", 10)
{
  // ....
}

When using gtkmm with a version of glibmm from 2.62 onwards, it is possible also to specify properties of derived widgets, declared in C++ using gtkmm, within .glade files and load/set these using Gtk::Builder. See the documentation of Gtk::Builder for more details on how to achieve this. Glade won’t recognise such properties as-is, but it should be able to through use of property class definitions and a catalog declaring those new properties.

24.3.2. Example

This example shows how to load a Glade file at runtime and access the widgets via derived classes.

Source Code

File: derivedbutton.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_DERIVED_BUTTON_H
#define GTKMM_EXAMPLE_DERIVED_BUTTON_H

#include <gtkmm.h>

class DerivedButton : public Gtk::Button
{
public:
  DerivedButton();
  DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
  virtual ~DerivedButton();

  // Provide proxies for the properties. The proxy allows you to connect to
  // the 'changed' signal, etc.
  Glib::PropertyProxy<Glib::ustring> property_ustring() { return prop_ustring.get_proxy(); }
  Glib::PropertyProxy<int> property_int() { return prop_int.get_proxy(); }

private:
  Glib::Property<Glib::ustring> prop_ustring;
  Glib::Property<int> prop_int;
};

#endif //GTKMM_EXAMPLE_DERIVED_BUTTON_H

File: deriveddialog.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_DERIVED_DIALOG_H
#define GTKMM_EXAMPLE_DERIVED_DIALOG_H

#include <gtkmm.h>
#include "derivedbutton.h"

class DerivedDialog : public Gtk::Dialog
{
public:
  DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
  DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade,
    bool is_glad);
  virtual ~DerivedDialog();

protected:
  //Signal handlers:
  void on_button_quit();

  Glib::RefPtr<Gtk::Builder> m_refGlade;
  DerivedButton* m_pButton;
};

#endif //GTKMM_EXAMPLE_DERIVED_DIALOG_H

File: main.cc (For use with gtkmm 4)

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

namespace
{
bool show_icon = false;
bool is_glad = true;

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

void on_app_activate()
{
  // Create a dummy instance before the call to refBuilder->add_from_file().
  // This creation registers DerivedButton's class in the GType system.
  // This is necessary because DerivedButton contains user-defined properties
  // (Glib::Property) and is created by Gtk::Builder.
  static_cast<void>(DerivedButton());

  // Load the GtkBuilder 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;
  }
  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:
  if (show_icon)
    pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived", is_glad);
  else
    pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(refBuilder, "DialogDerived");

  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->show();
}
} // anonymous namespace

int main(int argc, char** argv)
{
  int argc1 = argc;
  if (argc > 1)
  {
    if (std::strcmp(argv[1], "--glad") == 0)
    {
      show_icon = true;
      is_glad = true;
      argc1 = 1; // Don't give the command line arguments to Gtk::Application.
    }
    else if (std::strcmp(argv[1], "--sad") == 0)
    {
      show_icon = true;
      is_glad = false;
      argc1 = 1; // Don't give the command line arguments to Gtk::Application.
    }
  }

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

  // Instantiate a dialog when the application has been activated.
  // This can only be done after the application has been registered.
  // It's possible to call app->register_application() explicitly, but
  // usually it's easier to let app->run() do it for you.
  app->signal_activate().connect([] () { on_app_activate(); });

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

File: derivedbutton.cc (For use with gtkmm 4)

#include "derivedbutton.h"
#include <iostream>

namespace
{
void on_ustring_changed()
{
  std::cout << "- ustring property changed!" << std::endl;
}

void on_int_changed()
{
  std::cout << "- int property changed!" << std::endl;
}
} // anonymous namespace

// For creating a dummy object in main.cc.
DerivedButton::DerivedButton()
: Glib::ObjectBase("MyButton"),
  prop_ustring(*this, "button-ustring"),
  prop_int(*this, "button-int", 10)
{
}

DerivedButton::DerivedButton(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* refGlade */)
: // 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_MyButton.
  Glib::ObjectBase("MyButton"),
  Gtk::Button(cobject),
  // register the properties with the object and give them names
  prop_ustring(*this, "button-ustring"),
  // this one has a default value
  prop_int(*this, "button-int", 10)
{
  // Register some handlers that will be called when the values of the
  // specified parameters are changed.
  property_ustring().signal_changed().connect(sigc::ptr_fun(&on_ustring_changed));
  property_int().signal_changed().connect(sigc::ptr_fun(&on_int_changed));
}

DerivedButton::~DerivedButton()
{
}

File: deriveddialog.cc (For use with gtkmm 4)

#include "deriveddialog.h"
#include <iostream>

DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade)
: Gtk::Dialog(cobject),
  m_refGlade(refGlade),
  m_pButton(nullptr)
{
  // Get the Glade-instantiated Button, and connect a signal handler:
  m_pButton = Gtk::Builder::get_widget_derived<DerivedButton>(m_refGlade, "quit_button");
  if (m_pButton)
  {
    m_pButton->signal_clicked().connect( sigc::mem_fun(*this, &DerivedDialog::on_button_quit) );
    std::cout << "ustring, int: " << m_pButton->property_ustring()
              << ", " << m_pButton->property_int() << std::endl;
    m_pButton->property_int() = 99;
    std::cout << "ustring, int: " << m_pButton->property_ustring()
              << ", " << m_pButton->property_int() << std::endl;
  }
}

// The first two parameters are mandatory in a constructor that will be called
// from Gtk::Builder::get_widget_derived().
// Additional parameters, if any, correspond to additional arguments in the call
// to Gtk::Builder::get_widget_derived().
DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade,
  bool is_glad)
: DerivedDialog(cobject, refGlade) // Delegate to the other constructor
{
  // Show an icon.
  auto pImage = Gtk::make_managed<Gtk::Image>();
  pImage->set_from_icon_name(is_glad ? "face-smile" : "face-sad");
  pImage->set_icon_size(Gtk::IconSize::LARGE);
  pImage->set_expand();
  get_content_area()->append(*pImage);
}

DerivedDialog::~DerivedDialog()
{
}

void DerivedDialog::on_button_quit()
{
  hide(); //hide() will cause Gtk::Application::run() to end.
}