Custom Widgets

By deriving directly from Gtk::Widget you can do all the drawing for your widget directly, instead of just arranging child widgets. For instance, a Gtk::Label draws the text of the label, but does not do this by using other widgets.

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.

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

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 custom CSS name example shows how that's done.

Example

This example implements a widget which draws Penrose triangles.

Figure 28.2. Custom Widget

Custom Widget

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/border.h>
#include <gdkmm/rgba.h>

class MyWidget : public Gtk::Widget
{
public:
  MyWidget();
  ~MyWidget() override;

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;

  Gtk::Border m_padding;
  Gdk::RGBA m_foreground{0.0, 0.0, 1.0};
  Gdk::RGBA m_background{1.0, 0.0, 0.0};
};

#endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H

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()
{
  set_visible(false);
}

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

#include "mywidget.h"
#include <gdkmm/general.h>  // for Gdk::Cairo:: functions
#include <gtkmm/snapshot.h>

MyWidget::MyWidget()
{
  // Expand, if there is extra space.
  set_expand(true);

  m_padding.set_left(5);
  m_padding.set_right(15);
  m_padding.set_top(10);
  m_padding.set_bottom(20);
}

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()
{
  //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());

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

  // paint the background
  Gdk::Cairo::set_source_rgba(cr, m_background);
  Gdk::Cairo::add_rectangle_to_path(cr, rect);
  cr->fill();

  // 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, m_foreground);
  cr->translate(m_padding.get_left(), m_padding.get_top());
  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();
}