Usar widgets derivados

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");

Su clase derivada debe tener un constructor que tome un puntero al tipo C subyacente, y a la instancia Gtk::Builder. Todas las clases relevantes de gtkmm crean alias de sus tipos C subyacentes como BaseObjectType (Gtk::Dialog define un alias de BaseObjectType como GtkDialog, por ejemplo).

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

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

Source Code

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: 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: main.cc (For use with gtkmm 4)

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

// Not really used anywhere, but force an instance to be created.
DerivedButton* my_globally_accessible_button = nullptr;

int main (int argc, char **argv)
{
  bool show_icon = false;
  bool is_glad = true;
  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.
    }
  }

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

  // Create a dummy instance before the call to refBuilder->add_from_file().
  // This creation registers DerivedButton's class in the GType system.
  my_globally_accessible_button = new DerivedButton();

  //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;
  }

  //Get the GtkBuilder-instantiated dialog:
  DerivedDialog* pDialog = nullptr;
  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)
  {
    //Start:
    app->run(*pDialog, argc1, argv);
  }

  delete pDialog;

  return 0;
}

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()->add(*pImage);
}

DerivedDialog::~DerivedDialog()
{
}

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