Éléments graphiques personnalisés

En dérivant directement de la classe Gtk::Widget, vous pouvez faire tous les dessins voulus directement dans votre élément graphique plutôt que seulement placer des éléments graphiques enfants. Par exemple, un objet Gtk::Label écrit directement le texte de l'étiquette sans passer par l'intermédiaire d'autres éléments graphiques.

When deriving from Gtk::Widget, you should override the following virtual methods. The methods marked (optional) need not be overridden in all custom widgets. The base class's methods may be appropriate.

  • get_request_mode_vfunc(): (optional) Return what Gtk::SizeRequestMode is preferred by the widget.
  • measure_vfunc(): Calculate the minimum and natural width or height of the widget.
  • on_size_allocate(): Position the widget, given the height and width that it has actually been given.
  • on_realize(): Associate a Gdk::Surface with the widget.
  • on_unrealize(): (optional) Break the association with the Gdk::Surface.
  • on_map(): (optional)
  • on_unmap(): (optional)
  • snapshot_vfunc(): Create a render node, e.g. a Cairo::Context node, and draw on it.

The first 3 methods in the previous table are also overridden in custom containers. They are briefly described in the Custom Containers section.

XXVI.II.I. Class Init and Instance Init Functions

Some GTK functions, if called at all, must be called from the class init function. Some other GTK functions, if called, must be called from the instance init function. If your custom widget must call any of those functions, you can derive a class from Glib::ExtraClassInit and derive your custom class from that class. The following example shows how that's done.

XXVI.II.II. Custom Style Information

Your widget class, whether it's derived directly from Gtk::Widget or from another widget class, can read some style information from a CSS (Cascading Style Sheets) file. The users of your widget, or the users of an application program with your widget, can then modify the style of your widget without modifying the source code. Useful classes are Gtk::StyleContext and Gtk::CssProvider. With the methods of Gtk::StyleContext you can read the values of your widget's style information. CSS files are described in the documentation of GTK. The following example shows a simple use of Gtk::StyleContext::get_padding().

XXVI.II.III. Exemple

This example implements a widget which draws Penrose triangles.

Figure XXVI.2 Élément graphique personnalisé

Source Code

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