gtkmm:如何在 GtkBuilder XML 文件中使用自定义小部件?

gtkmm: How do I use a custom widget in a GtkBuilder XML file?

我正在编写一个 gtkmm-4 应用程序,并希望将自定义小部件与 GtkBuilder XML 一起使用。如果小部件是从 C++ 构造的,则它可以正常工作。但是,当从 XML.

构建时它不会呈现

注意:这个问题可能被认为是 this one 的重复问题。我不是在询问有关将 Glade 与我的小部件一起使用的问题;我的小部件根本不适用于简单的手写 XML.

很抱歉发布了这么多代码,但我相信这已经是我所能得到的最少的代码了。

自定义小部件代码

#ifndef _TASDI2_JOYSTICK_HPP_
#define _TASDI2_JOYSTICK_HPP_
#include <gtkmm.h>

namespace tasdi2 {
  class Joystick : public Gtk::Widget {
  public:
    Joystick();
    virtual ~Joystick() {}

    virtual Gtk::SizeRequestMode get_request_mode_vfunc() const override {
      return Gtk::Widget::get_request_mode_vfunc();
    }

    virtual void measure_vfunc(
      Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
      int& minimum_baseline, int& natural_baseline) const override;
    
    void on_map() override;
    void on_unmap() override;
    
    void snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot> &snapshot) override;
    
    
    
    Glib::PropertyProxy<int> property_xpos() {
      return prop_xpos.get_proxy();
    }
    Glib::PropertyProxy_ReadOnly<int> property_xpos() const {
      return prop_xpos.get_proxy();
    }
    Glib::PropertyProxy<int> property_ypos() {
      return prop_ypos.get_proxy();
    }
    Glib::PropertyProxy_ReadOnly<int> property_ypos() const {
      return prop_ypos.get_proxy();
    }
  private:
    Glib::Property<int> prop_xpos;
    Glib::Property<int> prop_ypos;
  };
}  // namespace tasdi2
#endif
#include "joystick.hpp"
#include <gdkmm.h>
#include <gtkmm.h>
#include <iostream>
#include <numbers>

namespace {
  inline void circle(
    const Glib::RefPtr<Cairo::Context>& cairo, double cx, double cy, double r) {
    cairo->arc(cx, cy, r, 0, 2 * std::numbers::pi);
  }

  inline void line(
    const Glib::RefPtr<Cairo::Context>& cairo, double x1, double y1, double x2,
    double y2) {
    cairo->move_to(x1, y1);
    cairo->line_to(x2, y2);
  }
}  // namespace

namespace tasdi2 {
  Joystick::Joystick() :
    Glib::ObjectBase("Tasdi2Joystick"),
    Gtk::Widget(),
    prop_xpos(*this, "xpos", 0),
    prop_ypos(*this, "ypos", 0) {
    set_hexpand();
    set_hexpand_set();
    set_vexpand();
    set_vexpand_set();
  }

  void Joystick::measure_vfunc(
    Gtk::Orientation orientation, int for_size, int& minimum, int& natural,
    int& minimum_baseline, int& natural_baseline) const {
    minimum          = 128;
    natural          = 160;
    minimum_baseline = -1;
    natural_baseline = -1;
    return;
  }

  void Joystick::on_map() { Gtk::Widget::on_map(); }
  void Joystick::on_unmap() { Gtk::Widget::on_unmap(); }

  void Joystick::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot) {
    const auto space = get_allocation();
    const Gdk::Rectangle rect(0, 0, space.get_width(), space.get_height());
    std::cout << "Allocation area: " << space.get_x() << ", " << space.get_y() << ", " << space.get_width() << ", " << space.get_height() << "\n";

    auto cairo = snapshot->append_cairo(rect);

    const double w  = space.get_width();
    const double h  = space.get_height();
    const double cx = w / 2;
    const double cy = h / 2;
    // colors
    const Gdk::RGBA color_bg0("#7F7F7F");
    const Gdk::RGBA color_bg1("#FFFFFF");
    const Gdk::RGBA color_oln("#000000");
    const Gdk::RGBA color_cln("#0000FF");
    const Gdk::RGBA color_dot("#FF0000");

    cairo->rectangle(0, 0, w, h);
    Gdk::Cairo::set_source_rgba(cairo, color_bg0);
    cairo->fill();

    circle(cairo, cx, cy, cx);
    Gdk::Cairo::set_source_rgba(cairo, color_bg1);
    cairo->fill_preserve();

    line(cairo, cx, 0, cx, h);
    line(cairo, 0, cy, w, cy);
    Gdk::Cairo::set_source_rgba(cairo, color_oln);
    cairo->stroke();
  }
}  // namespace tasdi2

MVE测试代码

#include <gtkmm.h>
#include "joystick.hpp"

static const std::string ui_data = R"(
<interface>
  <object class="GtkAspectFrame" id="root">
    <property name="margin-start">10</property>
    <property name="margin-end">10</property>
    <property name="margin-top">10</property>
    <property name="margin-bottom">10</property>
    
    <child>
      <object class="gtkmm__CustomObject_Tasdi2Joystick" />
    </child>
  </object>
</interface>
)";

class MainWindow : public Gtk::Window {
public:
  MainWindow() :
    builder(Gtk::Builder::create_from_string(ui_data)) {
    
    set_child(*builder->get_widget<Gtk::AspectFrame>("root"));
    set_size_request(256, 256);
  }
protected:
  Glib::RefPtr<Gtk::Builder> builder;
};

int main(int argc, char* argv[]) {
  auto app = Gtk::Application::create("io.github.jgcodes2020.testapp");
  app->signal_startup().connect([&]() {
    tasdi2::Joystick joystick;
  });
  return app->make_window_and_run<MainWindow>(argc, argv);
}

#include <gtkmm.h>
#include "joystick.hpp"

static const std::string ui_data = R"(
<interface>
  <object class="GtkAspectFrame" id="root">
    <property name="margin-start">10</property>
    <property name="margin-end">10</property>
    <property name="margin-top">10</property>
    <property name="margin-bottom">10</property>
  </object>
</interface>
)";

class MainWindow : public Gtk::Window {
public:
  MainWindow() :
    builder(Gtk::Builder::create_from_string(ui_data)) {
    
    auto& root = *builder->get_widget<Gtk::AspectFrame>("root");
    root.set_child(stick);
    
    set_child(root);
    set_size_request(256, 256);
  }
protected:
  Glib::RefPtr<Gtk::Builder> builder;
  tasdi2::Joystick stick;
};

int main(int argc, char* argv[]) {
  auto app = Gtk::Application::create("io.github.jgcodes2020.testapp");
  return app->make_window_and_run<MainWindow>(argc, argv);
}

使用上面关于包含派生小部件的评论,我查看了 Gnome 开发人员网站上的示例代码,并查看了“操纵杆”小部件的 class 构造函数。我单独留下了您的标准构造函数,因为它对于直接在您的代码中构建的任何小部件都很有用。然后,使用开发者网站上的派生小部件示例,我修改了“joystick.hpp”头文件以包含构建器构造函数原型,如下所示。

Joystick();
Joystick(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade); /* New builder function for a derived widget */
virtual ~Joystick() {}

然后在文件“joystick.cpp”的“tasdi2”命名空间中,我添加了派生的构建器构造函数,基本上是在“操纵杆”class 的标准构造函数中克隆了细节。

Joystick::Joystick(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_Tasdi2Joystick.
  Glib::ObjectBase("Tasdi2Joystick"),
  Gtk::Widget(cobject),
  prop_xpos(*this, "xpos", 0),
  prop_ypos(*this, "ypos", 0)
{
    set_hexpand();
    set_hexpand_set();
    set_vexpand();
    set_vexpand_set();
}

最后,在“main.cpp”的“XML”版本中,我修改了代码以适应派生的小部件。在您的 XML 文本正文中,我添加了一个“ID”属性,以便稍后在程序中调用派生的构建器函数可以挂钩该小部件。

<object class="gtkmm__CustomObject_Tasdi2Joystick" id="stick_range"/>

除了添加“ID”属性外,我还在“MainWindow”构造函数中构建了自定义操纵杆小部件。

  MainWindow() :
    builder(Gtk::Builder::create_from_string(ui_data)) {
    set_child(*builder->get_widget<Gtk::AspectFrame>("root"));
    set_size_request(256, 256);
    tasdi2::Joystick * stick_range = nullptr;
    stick_range = Gtk::Builder::get_widget_derived<tasdi2::Joystick>(builder, "stick_range");
    if (stick_range == nullptr) 
       printf("Joystick widget definition was not found\n");
  }

将您的“XML”版本的“main.cpp”与其他更改相结合的最终结果显示了小部件。

您可能已经从之前的评论中理解了这些东西,但如果没有,这些额外的代码片段可能对您有用。

此致。