Using derived widgets
| Chapter 26. Gtk::Builder |

## Using derived widgets

You can use Cambalache and `Gtk::Builder` 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::Window` typedefs `BaseObjectType` as GtkWindow, 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::Window(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::Window(cobject),
      m_builder(builder),
      // Get the GtkBuilder-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::Window(cobject),
      m_builder(builder),
      m_pButton(m_builder->get_widget<Gtk::Button>("quit_button"))
    {
      // ....
    }

### 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)
    {
      // ....
    }

It is possible also to specify properties of derived widgets, declared in C++ using gtkmm, within `.ui` files and load/set these using `Gtk::Builder`. See the documentation of `Gtk::Builder` for more details on how to achieve this. Cambalache won’t recognize such properties as-is, but you should be able to add a few lines of hand-coded XML to the XML code generated by Cambalache.

### Example

This example shows how to load a `.ui` 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>& refBuilder);
      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::Window
    {
    public:
      DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder);
      DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder,
        bool is_glad);
      ~DerivedDialog() override;

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

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

    #endif //GTKMM_EXAMPLE_DERIVED_DIALOG_H

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

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

    // 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>& /* 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_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(
        [](){ std::cout << "- ustring property changed!" << std::endl; }
      );
      property_int().signal_changed().connect(
        [](){ std::cout << "- int property changed!" << std::endl; }
      );
    }

    DerivedButton::~DerivedButton()
    {
    }

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

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

    DerivedDialog::DerivedDialog(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::Window(cobject),
      m_refBuilder(refBuilder),
      m_pButton(nullptr)
    {
      // Get the GtkBuilder-instantiated Button, and connect a signal handler:
      m_pButton = Gtk::Builder::get_widget_derived<DerivedButton>(m_refBuilder, "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>& refBuilder,
      bool is_glad)
    : DerivedDialog(cobject, refBuilder) // Delegate to the other constructor
    {
      // Show an icon.
      auto content_area = refBuilder->get_widget<Gtk::Box>("dialog-content_area");
      if (!content_area)
      {
        std::cerr << "Could not get the content area" << std::endl;
        return;
      }
      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();
      content_area->append(*pImage);
    }

    DerivedDialog::~DerivedDialog()
    {
    }

    void DerivedDialog::on_button_quit()
    {
      // When the application's only window is removed with
      // unset_application(), Gtk::Application::run() will end.
      unset_application();
    }

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.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:
      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.
      app->signal_window_removed().connect([](Gtk::Window* window) { delete window; });

      app->add_window(*pDialog);
      pDialog->set_visible(true);
    }
    } // 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);
    }

Accessing widgets |  | Chapter 27. Internationalization and Localization