使用派生部件

你可以使用Gtk::BuilderGlade布局你从gtkmm部件类派生的自定义部件。这使得你的代码保持有序和封装性,将声明式表述与业务逻辑分离开来,避免你的大多数源代码都是在设置属性和装填容器。

像这样用Gtk::Builder::get_widget_derived()

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

你的派生类必须有一个将指向底层C类型的指针和Gtk::Builder实例作为参数的构造函数。所有与gtkmm相关的类的都将其的底层C类型别名(typedef)为BaseObjectType(例如:Gtk::DialogGtkDialog别名为BaseObjectType)。

你必须在初始化列表中调用基类的构造函数,并提供C指针。例如:

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

然后你可以将对子部件的操作封装于派生类的构造函数中,可以在构造函数中再次使用get_widget()get_widget_derived()。例如:

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

可以传递额外的参数给get_widget_derived(),他们将被转发给派生部件的构造函数。例如这样调用get_widget_derived()

auto pDialog = Gtk::Builder::get_widget_derived<DerivedDialog>(builder, "DialogDerived", true);
可以调用此构造函数:
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和Glib::Property

如果你派生的部件使用了Glib::Property,它将变得更复杂了一些。包涵Glib::Property成员的派生部件必须在GType系统中用自己的名称进行注册。必须在调用任何create_from_*()add_from_*()方法之前进行注册,这意味着你可能需要完成对该类的注册才能创建派生部件的实例。你派生的部件必须有一个具有get_widget_derived()所需的所有参数以及调用Glib::ObjectBase构造函数以注册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)
{
  // ....
}

如果将gtkmmglibmm2.62以上的版本一起使用,则还可以在.glade文件中指定使用gtkmm在C++代码中声明的派生部件的属性,并使用Gtk::Builder加载/设置这些属性。更多实现此功能的详细信息请参阅Gtk::Builder的文档。Glade不会按原样识别这些属性,但这应该能通过使用属性类定义和声明一个新属性目录来实现。

24.3.2. 示例

本示例展示了如何在运行时加载Glade文件以及如何通过派生类访问部件。

源代码

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