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.
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.
size_allocate_vfunc()
: (optional) Position the widget, given the height and width that it has actually been given.
on_realize()
: (optional)
on_unrealize()
: (optional)
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.
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.
This example implements a widget which draws Penrose triangles.
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(); }