Introduction

This page collects helpful hints for writing your own GTK+ widgets. Not all of these hints have to be followed in all situations. E.g. if your application is not translated, then internationalization will not be a concern for you. By following all of the recommendations on this page, you should end up with a widget that is as good as the widgets that are included in GTK+.

The examples below are written for a hypothetical MyButton widget that is derived from GtkButton.

What to derive from?

One of the first questions to answer when you find that you need a custom widget is: what existing GTK+ widget should I use as a base class?

There is no hard and fast rule for this, but in general:

  • GtkWidget should be used if you don't want to add widgets

  • GtkBin is a good choice if you want to use templates (see below)

Templates

If your widget is composed of multiple child widgets, you should consider using a GtkBuilder template.

In this case, you want to likely subclass from GtkBin as it contains implementations for everything that a single child needs. If you want to write a widget that mimics a certain existing GTK+ widget - say a custom GtkBox - it is usually more convenient to subclass from GtkBin and set up the GtkBox as the first child in the template.

The basic rules for templates are:

  • use <template> instead of <object> for the toplevel object in your ui file

  • Call gtk_widget_init_template in your _init function
  • Call gtk_widget_set_template or gtk_widget_set_template_from_resource in your class_init function
  • Call gtk_widget_class_bind_template_child (or one of its variants) as needed for all members that you need to access

Template setup is described in detail in http://blogs.gnome.org/tvb/2013/04/09/announcing-composite-widget-templates/.

What virtual functions to implement?

Virtual functions in GObject are overridden by assigning your own implementation in your class_init function.

Your implementation should have the proper signature, which you can find in the class definition (in this example below, GtkContainerClass in gtkcontainer.h). The function will only be accessed through this function pointer, and should be declared static.

static void
my_button_add (GtkContainer *button, GtkWidget *child)
{
  ...
}

static void
my_button_class_init (MyButtonClass *class)
{
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);

  ...

  container_class->add = my_button_add;

  ...
}

For many vfuncs, it is mandatory to chain up, which typically looks as follows:

static void
my_button_finalize (GObject *object)
{
  ...

  G_OBJECT_CLASS (my_button_parent_class)->finalize (object);
}

Note that my_button_parent_class is automatically declared and assigned by the G_DEFINE_TYPE() macros.

GObject basics

It is highly recommended that you use the G_DEFINE_TYPE() family of macros to define your object:

G_DEFINE_TYPE (MyButton, my_button, GTK_TYPE_BUTTON)

This takes care of most of the GObject boilerplate such as defining the my_button_get_type() function, declaring the my_button_parent_class pointer, and providing prototypes for my_button_init() and my_button_class_init().

init
Every class must have an instance init function.
In it, you should set the priv pointer of your instance, and initialize members to their proper initial values. Calling arbitrary functions from init can be tricky and should be avoided (in particular functions that may call back into GType). Functions that are appropriate to call from init include gtk_widget_set_can_focus() and gtk_widget_set_has_window().
Unlike most of the other vfuncs, you don't have to assign the init function pointer yourself, G_DEFINE_TYPE() takes care of it.
class_init
Every class must have a class_init function.
Here, you set the vfuncs that you are overriding, declare properties and signals, and set up your private struct.
Unlike most of the other vfuncs, you don't have to assign the init function pointer yourself, G_DEFINE_TYPE() takes care of it.
object_class->finalize
Most classes have a finalize function.
Here, you free memory that is associated with your instance. References to other objects should be freed in dispose instead. finalize functions must chain up at the end.
object_class->dispose
Many classes have a dispose function.
Here you drop references to other objects. This is necessary to deal with reference cycles. For widgets, the destroy vfunc is equivalent and normally used instead. dispose functions must chain up at the end.
object_class->get_property
object_class->set_property
You need to implement get/set_property if your object has any readable/writeable properties.

Widget resource handling

widget_class->destroy
Destroy is the preferred variant of dispose for widgets. It should break references to other objects. Chain up at the end.
widget_class->realize
You need to override realize if your widget has any GDK windows (regardless of whether they are input-output or input-only) - realize is where you create them. Typically, you will call gtk_widget_get_allocation() to know the size for your windows. You need to call gtk_widget_register_window (widget, window) to inform GTK+ that this window belongs to your widget. In realize implementations, it is common to chain up early.
widget_class->unrealize
Unrealize must match realize - its job is to destroy the resources you created in realize.
widget_class->map
Map should match realize - its job is to call gdk_window_show() on your widget's GDK windows. In map implementations, it is common to chain up early.
widget_class->unmap
Unmap should match unmap - its job is to call gdk_window_hide() on your widget's GDK windows.
widget_class->screen_changed
In screen_changed, you should recreate all screen-dependent resources that your widget is using, such as Pango layouts, or GTK+ settings. This does not include windows, which are taken care of by re-realizing, which is done automatically by GTK+ when the screen changes.

Widget state handling

widget_class->state_flags_changed
In state_flags_changed, you should update anything in your widget that depends on the widget's state, e.g. to draw differently based on whether the widget is hovered. Chain up.
widget_class->direction_changed
If your widget has content that depends on the text direction, update it here. Chain up.

Size allocation

For in-depth information about geometry management in GTK+, see the GtkWidget and GtkContainer documentation.

widget_class->size_allocate
If your widget has children, size_allocate is where you distribute the available space to them. If your widget has windows, you update their size and position here when the size of your widget changes. You generally want to chain up here. If you don't, you can't reuse the base class draw implementation.
widget_class->get_request_mode
Override this if you want to implement height-for-width or width-for-height geometry management. The default implementation returns GTK_SIZE_REQUEST_CONSTANT_SIZE which disables these modes.
widget_class->get_preferred_width
widget_class->get_preferred_height
Override these and return your widgets minimum and natural size. The idea of minimum size is to cater to space-constrained situations by e.g. ellipsizing text. The natural size is size that your widget would ideally need to display its content.
widget_class->get_preferred_width_for_height
widget_class->get_preferred_height_for_width
You only need to override these functions if you implement height-for-width or width-for-height geometry management. Note that they need to both be implemented. For details, see the documentation cited above.

Custom drawing

widget_class->draw
Draw gets passed a cairo context to draw to, which is transformed so that you can always draw to the rectangle with corners (0,0) and (width, height) - the allocated size of your widget. A typical draw function looks like this:

static gboolean
my_button_draw (GtkWidget *widget, cairo_t *cr)
{
  MyButton *button = MY_BUTTON (widget);
  GtkStyleContext *context = gtk_widget_get_style_context (widget);
  int width = gtk_widget_get_allocated_width (widget);
  int height = gtk_widget_get_allocated_height (widget);

  /* Draw the basic background and border as per the theme CSS */
  gtk_render_background (context, cr, 0, 0, width, height);
  gtk_render_frame (context, cr, 0, 0, width, height);

  /* ... custom drawing goes here ... */

  return FALSE;
}

Container functionality

container_class->add
container_class->remove

If you inherit from GtkBin or another container implementation, chances are that you may not need to reimplement add/remove. Otherwise, you need to call gtk_widget_set_parent(child, widget) on the new child in add, and gtk_widget_unparent(child) in remove.

container_class->forall

If you can't inherit this, your implementation should call the callback for each child. Note that internal children are widgets that are not added by the user of your widget, but as part of the implementation of your widget, like the combobox arrow button.

container_class->get_path_for_child

You only need to override get_path_for_child if you want to enable special theming for the children of your container, such as :nth-child(first) or :nth-child(even). In this case, you need to construct the path from the widget path of your container with gtk_widget_path_append_with_siblings().

Event handling

For mouse and touch events, it is possible to do most things by setting up gestures. That is the preferred way to handle these events nowadays, wherever possible.

For other or more low-level purposes, various virtual functions can be implemented to handle events:

widget_class->button_press_event
widget_class->button_release_event
If your widget needs to react to button press or release events, implement these. Note that you need to set a suitable event mask for your window to receive these events. See advice below for the best way to check for primary or secondary button presses. Your handlers should return TRUE if the event has been handled, and FALSE otherwise to propagate the event further.
widget_class->motion_notify_event
If your widget needs to react to pointer motion, implement this function. Note that you need to set a suitable event mask for your window to receive these events. A typical case for implementing this is to initiate a drag operation by calling gdk_drag_begin() if the drag threshold is hit.
widget_class->enter_notify_event
widget_class->leave_notify_event
If you need to track whether the pointer is over your widgets window, implement enter/leave_notify_event. Note that you need to add GDK_ENTER/LEAVE_NOTIFY_MASK to the event mask of your window to receive these events. The handlers should generally return FALSE.
widget_class->key_press_event
widget_class->key_release_event

You may implement these to handle key press or release events on your widget. The handlers should return TRUE if they handled the event, and they should chain up - this is the way GtkBindingSet gets a shot at handling the event.

widget_class->focus_in_event
widget_class->focus_out_event

It is unlikely that you need to implement focus_in/out_event for your widget, the default implementation in GtkWidget should be good enough for anything other than actual text-input controls like GtkEntry or GtkTextView, which need to propagate focus-in and -out to their input method context.

Keyboard navigation

widget_class->focus

You normally don't have to implement this function; the generic GtkWidget and GtkContainer implementations work for almost all situations. The only case in which you may need to override this if your widget has multiple focus locations which are not child widgets, such as a label with multiple embedded links.

widget_class->popup_menu
If your widget has a context menu, you should implement popup_menu and open your context menu from here. This ensures that the your widget reacts as expected to the Menu key.

Drag-and-drop

widget_class->drag_begin
widget_class->drag_end
widget_class->drag_drop
widget_class->drag_motion
widget_class->drag_leave
widget_class->drag_data_received
widget_class->drag_data_get
widget_class->drag_data_delete

Use a window or not?

No-window is generally the right answer. If you need to catch input events, you can use an input-only window.

Proper GObject API

  • Create objects
  • Expose properties as such
  • Avoid construct-only properties, if you can. These often cause problems for language bindings
  • Avoid custom memory management for structs
  • Register structs as boxed types
  • Use the G_DEFINE_TYPE()

Internationalization

  • Include gi18n.h
  • Use _() for strings
  • Use message context for strings that might occur in different contexts, see C_()
  • Add translator comments
  • Don't break strings into pieces. Translators need to see a full sentence with % placeholders.
  • Avoid markup inside translated strings
  • RTL flipping works mostly automatic if you use existing containers for horizontal layout. You may have to adjust custom rendering

Keyboard navigation

  • Make sure that you can reach all locations with Tab / Shift-Tab
  • Use GtkBindingSet and key-binding signals where appropriate

  • Propagate key events that you don't handle
  • Set default widgets in dialogs
  • Make search work by just typing - implementing this often requires handling key events on the toplevel window

Accessibility

  • Proper keyboard navigation is 90%
  • Set a proper accessible name, role and description. If you just need to set the role, use gtk_widget_class_set_accessible_role() in class_init
  • You may need to implement a custom accessible to implement a11y interfaces like AtkValue - in this case, include gtk-a11y.h and derive from the accessible implementation of your parent widget. A good way to associate the accessible implementation with your widget is to use gtk_widget_class_set_accessible_type() in class_init

GtkBuilder / glade support

  • Proper GObject API is 90%
  • Implement GtkBuildable

Theming

  • Use standard CSS drawing and the available gtk_render_*() functions
  • Avoid widget style properties; theming should be done using standard CSS properties
  • Don't hard-code colors, fonts or sizes in the widget; let the CSS drawing get those from the theme
  • Where appropriate, use themed icons, preferably with standard icon names
  • Where appropriate, try to reuse standard style classes to give themes a chance to affect your widget in suitable ways
  • Test your widget with the themes that are included in GTK+: Adwaita, Adwaita:dark, HighContrast, HighContrastInverse

Cross-platform

  • Use GdkModifierIntent instead of hard-coding mouse buttons. E.g. when checking for a button press that activates your widget, use event->button == GDK_BUTTON_PRIMARY instead of event->button == 1. And when checking for a right click, use gdk_event_triggers_context_menu (event).

Introspection / language binding support

  • Proper GObject API is 90%
  • g_object_new (YOUR_TYPE, NULL) should work, i.e. produce a valid object with all-default property values
  • Use varargs only as C convenience
  • Follow established conventions about boolean returns with GError
  • Add introspection annotations to your doc comments

Established patterns and interfaces

  • When it makes sense, implement GtkOrientable

  • Respect expand, align and margin properties
  • Avoid pointer and keyboard grabs as far as possible. Generally, you should only grab a device while an override-redirect (popup) window requires exclusive control (like a menu or combobox popup). When working with pointer or keyboard grabs, handle the grab-broken events. This is also true for implicit pointer grabs established by button presses: if you need to do any cleanup in your button-released handler, do the same cleanups in the grab_broken handler.
  • When displaying an animation, or doing a button-repeat timeout (like e.g. GtkSpinButton), make sure you stop it when necessary: when getting grab-shadowed, when becoming insensitive, when unmapped.

Extensibility

  • If your widget is a container, allow arbitrary children (don't enforce a particular type of child)

Documentation

  • Add gtk-doc comments for public API, even if you don't generate API docs

  • Also document signals and properties. Note that gtk-doc uses Class::name for signals, and Class:name for properties

HowDoI/CustomWidgets (last edited 2018-04-24 21:36:35 by DanielBoles)