自定义部件

通过从Gtk::Widget派生部件你可以为部件绘制所有的图形,而不仅仅是排列子部件。例如:Gtk::Label不需要使用其他部件即可绘制标签文本。

当你从Gtk::Widget派生部件时,你应该覆写以下虚函数。其中标记为可选的的虚函数不必在所有自定义部件中都进行覆写。它们的基类方法通常情况下已经很合适。

  • get_request_mode_vfunc():覆写此虚函数是可选的,返回此部件的Gtk::SizeRequestMode偏好。
  • measure_vfunc():计算此部件的最小自然高度或宽度。
  • on_size_allocate():提供将实际给予部件的宽度和高度以定位部件。
  • on_realize():将一个Gdk::Surface与部件相关联。
  • on_unrealize():覆写此虚函数是可选的,断开Gdk::Surface与部件的关联。
  • on_map():覆写此虚函数是可选的。
  • on_unmap():覆写此虚函数是可选的。
  • snapshot_vfunc():创建一个渲染节点(例如:一个Cairo::Context节点),并在其上进行绘制。

上述前三个函数在自定义容器中也被覆写。它们在自定义容器小节做了简要说明。

26.2.1. 类初始化和实例初始化函数

一些GTK函数必须在类的init函数中才能调用。另一些GTK函数必须在实例的init函数中才能调用。如果你的自定义部件必须调用这些函数中的任意一个,那么你可以从Glib::ExtraClassInit派生一个类,并从该类派生你的自定义类。以下示例演示了如何完成这些操作。

26.2.2. 自定义样式信息

无论你的部件类是从Gtk::Widget还是从其他部件类派生的,它都可以从CSS文件中读取样式信息。然后对于使用你的部件的用户或是使用你的部件的应用程序的用户而言,他们可以不需要修改源代码就能修改部件的样式。用于实现此功能的类是Gtk::StyleContextGtk::CssProvider。你可以使用Gtk::StyleContext类的方法读取部件样式信息。GTK文档描述了其对于CSS文件的支持情况。以下是示例演示了Gtk::StyleContext::get_padding()的简单用法。

26.2.3. 示例

本示例实现了绘制彭罗斯三角形的部件

Figure 26-2自定义部件

源代码

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

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

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

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  virtual ~ExampleWindow();

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

  //Child widgets:
  Gtk::Grid m_Grid;
  MyWidget m_MyWidgetS1;
  MyWidget m_MyWidgetS2;
  Gtk::Box m_ButtonBox;
  Gtk::Button m_Button_Quit;
};

#endif //GTKMM_EXAMPLEWINDOW_H

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

#ifndef GTKMM_CUSTOM_WIDGET_MYWIDGET_H
#define GTKMM_CUSTOM_WIDGET_MYWIDGET_H

#include <gtkmm/widget.h>
#include <gtkmm/cssprovider.h>
#include "myextrainit.h"

class MyWidget
:
public MyExtraInit,
public Gtk::Widget
{
public:
  MyWidget();
  virtual ~MyWidget();

protected:

  //Overrides:
  Gtk::SizeRequestMode get_request_mode_vfunc() const override;
  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 on_realize() override;
  void on_unrealize() override;
  void snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot) override;

  //Signal handler:
  void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error);

  Gtk::Border m_padding;
  Glib::RefPtr<Gtk::CssProvider> m_refCssProvider;
};

#endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H

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

#ifndef GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H
#define GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H

#include <glibmm/extraclassinit.h>
#include <glibmm/ustring.h>

// Calls gtk_widget_class_set_css_name() in the class init function
// and gtk_set_has_window() in the instance init function.
class MyExtraInit : public Glib::ExtraClassInit
{
public:
  MyExtraInit(const Glib::ustring& css_name);

private:
  Glib::ustring m_css_name;
};

#endif //GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H

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

#include "examplewindow.h"
#include <gtkmm/application.h>

int main(int argc, char *argv[])
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  //Shows the window and returns when it is closed.
  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

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

#include "examplewindow.h"

ExampleWindow::ExampleWindow()
: m_Button_Quit("Quit")
{
  set_title("Custom Widget example");
  set_default_size(600, 400);

  m_Grid.set_margin(6);
  m_Grid.set_row_spacing(10);
  m_Grid.set_column_spacing(10);

  set_child(m_Grid);

  m_Grid.attach(m_MyWidgetS1, 0, 0);
  m_Grid.attach(m_MyWidgetS2, 1, 1);

  m_Grid.attach(m_ButtonBox, 0, 2, 2, 1);

  m_ButtonBox.append(m_Button_Quit);
  m_ButtonBox.set_margin(6);
  m_Button_Quit.set_hexpand(true);
  m_Button_Quit.set_halign(Gtk::Align::END);
  m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit) );
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit()
{
  hide();
}

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

#include "myextrainit.h"
#include <gtkmm/widget.h>
#include <gtk/gtk.h>

namespace
{
using BaseObjectType = GtkWidget;
using BaseClassType = GtkWidgetClass;

// Extra class init function.
void class_init_function(void* g_class, void* class_data)
{
  g_return_if_fail(GTK_IS_WIDGET_CLASS(g_class));

  const auto klass = static_cast<BaseClassType*>(g_class);
  const auto css_name = static_cast<Glib::ustring*>(class_data);

  gtk_widget_class_set_css_name(klass, css_name->c_str());
}

// Extra instance init function.
void instance_init_function(GTypeInstance* instance, void* /* g_class */)
{
  g_return_if_fail(GTK_IS_WIDGET(instance));

  // Nothing to do here.
  // This extra instance init function just shows how such a function can
  // be added to a custom widget, if necessary.
}

} // anonymous namespace

MyExtraInit::MyExtraInit(const Glib::ustring& css_name)
:
Glib::ExtraClassInit(class_init_function, &m_css_name, instance_init_function),
m_css_name(css_name)
{
}

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

#include "mywidget.h"
#include <gdkmm/general.h>  // for cairo helper functions
#include <gtkmm/snapshot.h>
#include <iostream>
//#include <gtk/gtkwidget.h> //For GTK_IS_WIDGET()
#include <cstring>


MyWidget::MyWidget() :
  //The GType name will actually be gtkmm__CustomObject_MyWidget
  Glib::ObjectBase("MyWidget"),
  MyExtraInit("my-widget"), // CSS node name, which must be used in the CSS file.
  Gtk::Widget(),
  m_padding()
{
  // Expand, if there is extra space.
  set_expand(true);

  //This shows the GType name.
  std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;

  //This shows that the GType still derives from GtkWidget:
  //std::cout << "Gtype is a GtkWidget?:" << GTK_IS_WIDGET(gobj()) << std::endl;

  // The CSS name can be set either
  // - for a GType (in this case for your custom class) with gtk_widget_class_set_css_name(), or
  // - for a widget instance with gtk_widget_set_name() (Gtk::Widget::set_name()).
  //
  // gtk_widget_class_set_css_name(), if used, must be called in the class init function.
  // It has not been wrapped in a C++ function.
  // Gtk::Widget::set_name() can be called in a C++ constructor.
  //
  // Another alternative: The custom widget inherits the CSS name "widget" from
  // GtkWidget. That name can be used in the CSS file. This is not a very good
  // alternative. GtkWidget's CSS name is not documented. It can probably be
  // changed or removed in the future.

  m_refCssProvider = Gtk::CssProvider::create();
  auto refStyleContext = get_style_context();
  refStyleContext->add_provider(m_refCssProvider,
    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  m_refCssProvider->signal_parsing_error().connect(
    sigc::mem_fun(*this, &MyWidget::on_parsing_error));

  m_refCssProvider->load_from_path("custom_gtk.css");
}

MyWidget::~MyWidget()
{
}

Gtk::SizeRequestMode MyWidget::get_request_mode_vfunc() const
{
  //Accept the default value supplied by the base class.
  return Gtk::Widget::get_request_mode_vfunc();
}

//Discover the total amount of minimum space and natural space needed by
//this widget.
//Let's make this simple example widget always need minimum 60 by 50 and
//natural 100 by 70.
void MyWidget::measure_vfunc(Gtk::Orientation orientation, int /* for_size */,
  int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const
{
  if (orientation == Gtk::Orientation::HORIZONTAL)
  {
    minimum = 60;
    natural = 100;
  }
  else
  {
    minimum = 50;
    natural = 70;
  }

  // Don't use baseline alignment.
  minimum_baseline = -1;
  natural_baseline = -1;
}

void MyWidget::on_map()
{
  //Call base class:
  Gtk::Widget::on_map();
}

void MyWidget::on_unmap()
{
  //Call base class:
  Gtk::Widget::on_unmap();
}

void MyWidget::on_realize()
{
  //Get the themed padding from the CSS file:
  m_padding = get_style_context()->get_padding();
  std::cout << "m_padding from the theme/css-file is"
    << ": top=" << m_padding.get_top()
    << ", right=" << m_padding.get_right()
    << ", bottom=" << m_padding.get_bottom()
    << ", left=" << m_padding.get_left() << std::endl;

  //Call base class:
  Gtk::Widget::on_realize();
}

void MyWidget::on_unrealize()
{
  //Call base class:
  Gtk::Widget::on_unrealize();
}

void MyWidget::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot>& snapshot)
{
  const auto allocation = get_allocation();
  const Gdk::Rectangle rect(0, 0, allocation.get_width(), allocation.get_height());
  auto refStyleContext = get_style_context();

  // Create a cairo context to draw on.
  auto cr = snapshot->append_cairo(rect);

  // paint the background
  refStyleContext->render_background(cr,
    -m_padding.get_left(), -m_padding.get_top(), allocation.get_width(), allocation.get_height());

  // draw the foreground
  const double scale_x = 0.001 * (allocation.get_width() - m_padding.get_left() - m_padding.get_right());
  const double scale_y = 0.001 * (allocation.get_height() - m_padding.get_top() - m_padding.get_bottom());
  Gdk::Cairo::set_source_rgba(cr, refStyleContext->get_color());
  cr->rectangle(0.0, 0.0, 1000.0*scale_x, 1000.0*scale_y);
  cr->move_to(155.*scale_x, 165.*scale_y);
  cr->line_to(155.*scale_x, 838.*scale_y);
  cr->line_to(265.*scale_x, 900.*scale_y);
  cr->line_to(849.*scale_x, 564.*scale_y);
  cr->line_to(849.*scale_x, 438.*scale_y);
  cr->line_to(265.*scale_x, 100.*scale_y);
  cr->line_to(155.*scale_x, 165.*scale_y);
  cr->move_to(265.*scale_x, 100.*scale_y);
  cr->line_to(265.*scale_x, 652.*scale_y);
  cr->line_to(526.*scale_x, 502.*scale_y);
  cr->move_to(369.*scale_x, 411.*scale_y);
  cr->line_to(633.*scale_x, 564.*scale_y);
  cr->move_to(369.*scale_x, 286.*scale_y);
  cr->line_to(369.*scale_x, 592.*scale_y);
  cr->move_to(369.*scale_x, 286.*scale_y);
  cr->line_to(849.*scale_x, 564.*scale_y);
  cr->move_to(633.*scale_x, 564.*scale_y);
  cr->line_to(155.*scale_x, 838.*scale_y);
  cr->stroke();
}

void MyWidget::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error)
{
  std::cerr << "on_parsing_error(): " << error.what() << std::endl;
  if (section)
  {
    const auto file = section->get_file();
    if (file)
    {
      std::cerr << "  URI = " << file->get_uri() << std::endl;
    }

    auto start_location = section->get_start_location();
    auto end_location = section->get_end_location();
    std::cerr << "  start_line = " << start_location.get_lines()+1
              << ", end_line = " << end_location.get_lines()+1 << std::endl;
    std::cerr << "  start_position = " << start_location.get_line_chars()
              << ", end_position = " << end_location.get_line_chars() << std::endl;
  }
}

File: custom_gtk.css (For use with gtkmm 4)

/* Example of a CSS style sheet. */

my-widget {
  background-color: rgb(255,0,0);
  color:            rgb(0,0,255);
  padding:          10px 15px 20px 5px; /* top right bottom left */
}