As described in the appendix event signals are propagated in 3 phases:
A keyboard event is first sent to the toplevel window
(Gtk::Window
), where it will be checked
for any keyboard shortcuts that may be set (accelerator keys and mnemonics,
used for selecting menu items from the keyboard). After this (and assuming
the event wasn't handled), it is propagated down until it reaches the widget
which has keyboard focus. The event will then propagate up until it reaches
the top-level widget, or until you stop the propagation by returning
true
from an event handler.
Notice, that after canceling an event, no other function will be called (even if it is from the same widget).
In this example there are 9 EventControllerKey
s,
3 in each of a Gtk::Window
, a Gtk::Box
and a Gtk::Label
. In each of the widgets there is
one event controller for each propagation phase.
The purpose of this example is to show the steps the event takes when it is emitted.
When you write in the label, a key press event will be emitted,
which will go first, in the capture phase, to the toplevel window
(Gtk::Window
), then down to its child, the
box, then to the box's child, the label with the keyboard focus.
In the target phase the event goes only to the widget with the keyboard
focus (the label). In the bubble phase the event goes first to the widget
with the keyboard focus (the label), then to its parent (the box), then
to the box's parent (the window). If the event propagates all the way
down to the label and then up to the window without being stopped,
the text you're writing will appear in the Label
above the Label
you're writing in.
File: examplewindow.h
(For use with gtkmm 4)
#ifndef GTKMM_EVENT_PROPAGATION_H
#define GTKMM_EVENT_PROPAGATION_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
private:
// Signal handlers:
bool label2_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool box_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool window_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
bool m_first = true;
Gtk::Box m_container;
Gtk::Frame m_frame;
Gtk::Label m_label1 {"A label"};
Gtk::Label m_label2 {"Write here"};
Gtk::CheckButton m_checkbutton_can_propagate_down {"Can propagate down in the capture phase"};
Gtk::CheckButton m_checkbutton_can_propagate_up {"Can propagate up in the bubble phase"};
};
#endif //GTKMM_EVENT_PROPAGATION_H
File: examplewindow.cc
(For use with gtkmm 4)
#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
{
set_title("Event Propagation");
m_container.set_margin(10);
set_child(m_container);
m_frame.set_child(m_label2);
m_label2.set_selectable();
m_checkbutton_can_propagate_down.set_active();
m_checkbutton_can_propagate_up.set_active();
// Main container
m_container.set_orientation(Gtk::Orientation::VERTICAL);
m_container.append(m_label1);
m_container.append(m_frame);
m_container.append(m_checkbutton_can_propagate_down);
m_container.append(m_checkbutton_can_propagate_up);
// Event controllers
const bool after = false; // Run before or after the default signal handlers.
// Called in the capture phase of the event handling.
auto controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "capture"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "capture"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "capture"), after);
add_controller(controller);
// Called in the target phase of the event handling.
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "target"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "target"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::TARGET);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "target"), after);
add_controller(controller);
// Called in the bubble phase of the event handling.
// This is the default, if set_propagation_phase() is not called.
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::label2_key_pressed), "bubble"), after);
m_label2.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::box_key_pressed), "bubble"), after);
m_container.add_controller(controller);
controller = Gtk::EventControllerKey::create();
controller->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
controller->signal_key_pressed().connect(
sigc::bind(sigc::mem_fun(*this, &ExampleWindow::window_key_pressed), "bubble"), after);
add_controller(controller);
}
// By changing the return value we allow, or don't allow, the event to propagate to other elements.
bool ExampleWindow::label2_key_pressed(guint keyval, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
std::cout << "Label, " << phase << " phase" << std::endl;
if (phase == "bubble")
{
const gunichar unichar = gdk_keyval_to_unicode(keyval);
if (unichar != 0)
{
if (m_first)
{
m_label2.set_label("");
m_first = false;
}
if (unichar == '\b')
m_label2.set_label("");
else
{
const Glib::ustring newchar(1, unichar);
m_label2.set_label(m_label2.get_label() + newchar);
}
}
if (!m_checkbutton_can_propagate_up.get_active())
return true; // Don't propagate
}
return false;
}
bool ExampleWindow::box_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
std::cout << "Box, " << phase << " phase" << std::endl;
// Let it propagate
return false;
}
// This will set the second label's text in the first label every time a key is pressed.
bool ExampleWindow::window_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
if (phase == "capture")
std::cout << std::endl;
std::cout << "Window, " << phase << " phase";
// Checking if the second label is on focus, otherwise the label would get
// changed by pressing keys on the window (when the label is not on focus),
// even if m_checkbutton_can_propagate_up wasn't active.
if (phase == "bubble" && m_label2.has_focus())
{
m_label1.set_label(m_label2.get_label());
std::cout << ", " << m_label2.get_label();
}
std::cout << std::endl;
if (phase == "capture" && !m_checkbutton_can_propagate_down.get_active())
return true; // Don't propagate
return false;
}
ExampleWindow::~ExampleWindow()
{
}
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);
}