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.
- get_request_mode_vfunc(): (optional) Return what Gtk::SizeRequestMode is preferred by the widget.
- get_preferred_width_vfunc(): Calculate the minimum and natural width of the widget.
- get_preferred_height_vfunc(): Calculate the minimum and natural height of the widget.
- get_preferred_width_for_height_vfunc(): Calculate the minimum and natural width of the widget, if it would be given the specified height.
- get_preferred_height_for_width_vfunc(): Calculate the minimum and natural height of the widget, if it would be given the specified width.
- on_size_allocate(): Position the widget, given the height and width that it has actually been given.
- on_realize(): Associate a Gdk::Window with the widget.
- on_unrealize(): (optional) Break the association with the Gdk::Window.
- on_map(): (optional)
- on_unmap(): (optional)
- on_draw(): Draw on the supplied Cairo::Context.
The first 6 methods in the previous table are also overridden in custom containers. They are briefly described in the Custom Containers section.
Most custom widgets need their own Gdk::Window to draw on. Then you can call Gtk::Widget::set_has_window(true) in your constructor. (This is the default value.) If you do not call set_has_window(false), you must override on_realize() and call Gtk::Widget::set_realized() and Gtk::Widget::set_window() from there.
- 28.2.1. Custom Style Properties
- 28.2.2. 示例
28.2.1. Custom Style Properties
You can add style properties to your widget class, whether it's derived directly from Gtk::Widget or from another widget class. The values of the style properties can be read 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::StyleProperty and Gtk::CssProvider. With Gtk::Widget::get_style_property() you can read the values of both your own style properties and those of your widget's base class. Note that style properties are not wrapped in gtkmm. See GTK+'s documentation for lists of existing style properties. The following example shows a simple use of a custom style property.
28.2.2. 示例
This example implements a widget which draws a Penrose triangle.
File: mywidget.h (For use with gtkmm 3, not gtkmm 2)
#ifndef GTKMM_CUSTOM_WIDGET_MYWIDGET_H #define GTKMM_CUSTOM_WIDGET_MYWIDGET_H #include <gtkmm/widget.h> #include <gtkmm/cssprovider.h> #include <gtkmm/styleproperty.h> class MyWidget : public Gtk::Widget { public: MyWidget(); virtual ~MyWidget(); protected: //Overrides: Gtk::SizeRequestMode get_request_mode_vfunc() const override; void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const override; void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const override; void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override; void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const override; void on_size_allocate(Gtk::Allocation& allocation) override; void on_map() override; void on_unmap() override; void on_realize() override; void on_unrealize() override; bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override; //Signal handler: void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error); Gtk::StyleProperty<int> m_scale_prop; Glib::RefPtr<Gdk::Window> m_refGdkWindow; Glib::RefPtr<Gtk::CssProvider> m_refCssProvider; int m_scale; }; #endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H
File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)
#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::Box m_VBox; MyWidget m_MyWidget; Gtk::ButtonBox m_ButtonBox; Gtk::Button m_Button_Quit; }; #endif //GTKMM_EXAMPLEWINDOW_H
File: examplewindow.cc (For use with gtkmm 3, not gtkmm 2)
#include "examplewindow.h" ExampleWindow::ExampleWindow() : m_VBox(Gtk::ORIENTATION_VERTICAL), m_Button_Quit("Quit") { set_title("Custom Widget example"); set_border_width(6); set_default_size(400, 200); add(m_VBox); m_VBox.pack_start(m_MyWidget, Gtk::PACK_EXPAND_WIDGET); m_MyWidget.show(); m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK); m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK); m_ButtonBox.set_border_width(6); m_ButtonBox.set_layout(Gtk::BUTTONBOX_END); m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit) ); show_all_children(); } ExampleWindow::~ExampleWindow() { } void ExampleWindow::on_button_quit() { hide(); }
File: main.cc (For use with gtkmm 3, not gtkmm 2)
#include "examplewindow.h" #include <gtkmm/application.h> int main(int argc, char *argv[]) { auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example"); ExampleWindow window; //Shows the window and returns when it is closed. return app->run(window); }
File: mywidget.cc (For use with gtkmm 3, not gtkmm 2)
#include "mywidget.h" #include <gdkmm/general.h> // for cairo helper functions #include <iostream> //#include <gtk/gtkwidget.h> //For GTK_IS_WIDGET() #include <cstring> // The MyWidget class uses API which was added in gtkmm 3.15.3 (Gtk::CssProviderError, // Gtk::CssProvider::signal_parsing_error() and Gtk::CssSection) and in gtkmm 3.15.2 // (Gtk::StyleProperty). MyWidget::MyWidget() : //The GType name will actually be gtkmm__CustomObject_mywidget Glib::ObjectBase("mywidget"), Gtk::Widget(), //Install a style property so that an aspect of this widget may be themed //via a CSS style sheet file: m_scale_prop(*this, "example_scale", 500), m_scale(1000) { set_has_window(true); //This shows the GType name, which must be used in the CSS file. 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; 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)); try { m_refCssProvider->load_from_path("custom_gtk.css"); } catch(const Gtk::CssProviderError& ex) { std::cerr << "CssProviderError, Gtk::CssProvider::load_from_path() failed: " << ex.what() << std::endl; } catch(const Glib::Error& ex) { std::cerr << "Error, Gtk::CssProvider::load_from_path() failed: " << ex.what() << std::endl; } } 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::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const { minimum_width = 60; natural_width = 100; } void MyWidget::get_preferred_height_for_width_vfunc(int /* width */, int& minimum_height, int& natural_height) const { minimum_height = 50; natural_height = 70; } void MyWidget::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const { minimum_height = 50; natural_height = 70; } void MyWidget::get_preferred_width_for_height_vfunc(int /* height */, int& minimum_width, int& natural_width) const { minimum_width = 60; natural_width = 100; } void MyWidget::on_size_allocate(Gtk::Allocation& allocation) { //Do something with the space that we have actually been given: //(We will not be given heights or widths less than we have requested, though //we might get more) //Use the offered allocation for this container: set_allocation(allocation); if(m_refGdkWindow) { m_refGdkWindow->move_resize( allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height() ); } } 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() { //Do not call base class Gtk::Widget::on_realize(). //It's intended only for widgets that set_has_window(false). set_realized(); //Get the themed style from the CSS file: m_scale = m_scale_prop.get_value(); std::cout << "m_scale (example_scale from the theme/css-file) is: " << m_scale << std::endl; if(!m_refGdkWindow) { //Create the GdkWindow: GdkWindowAttr attributes; memset(&attributes, 0, sizeof(attributes)); Gtk::Allocation allocation = get_allocation(); //Set initial position and size of the Gdk::Window: attributes.x = allocation.get_x(); attributes.y = allocation.get_y(); attributes.width = allocation.get_width(); attributes.height = allocation.get_height(); attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK; attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; m_refGdkWindow = Gdk::Window::create(get_parent_window(), &attributes, GDK_WA_X | GDK_WA_Y); set_window(m_refGdkWindow); //make the widget receive expose events m_refGdkWindow->set_user_data(gobj()); } } void MyWidget::on_unrealize() { m_refGdkWindow.reset(); //Call base class: Gtk::Widget::on_unrealize(); } bool MyWidget::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) { const Gtk::Allocation allocation = get_allocation(); const double scale_x = (double)allocation.get_width() / m_scale; const double scale_y = (double)allocation.get_height() / m_scale; // paint the background get_style_context()->render_background(cr, allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()); // draw the foreground Gdk::Cairo::set_source_rgba(cr, get_style_context()->get_color()); 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(); return true; } 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) { std::cerr << " URI = " << section->get_file()->get_uri() << std::endl; std::cerr << " start_line = " << section->get_start_line()+1 << ", end_line = " << section->get_end_line()+1 << std::endl; std::cerr << " start_position = " << section->get_start_position() << ", end_position = " << section->get_end_position() << std::endl; } }
File: custom_gtk.css (For use with gtkmm 3, not gtkmm 2)
/* Example of a CSS style sheet with a custom style property. * * The name of the style property must have its canonical form, i.e. characters * other than ASCII letters, digits, and hyphens must be replaced by hyphens. */ * { /* -<widget class name>-<style property canonical name>: <value>; */ -gtkmm__CustomObject_mywidget-example-scale: 920; } gtkmm__CustomObject_mywidget { background-color: rgb(255,0,0); color: rgb(0,0,255); }