Propagación de eventos

Propagación de eventos significa que, cuando se emite un evento en un widget particular, puede pasarse a su widget padre (y ese widget puede pasárselo a su padre, y así sucesivamente) y, si el padre tiene un gestor de eventos, se llamará.

Al contrario que otros eventos, los eventos del teclado primero se envían a la ventana de nivel superior (Gtk::Window), donde se verifican los atajos del teclado establecidos (teclas aceleradoras y combinaciones, utilizadas para seleccionar elementos del menú desde el teclado). Después de esto (y asumuiendo que no se manejó el evento), se envía al widget que tiene el foco, y la propagación comienza desde allí.

El evento se propagará hasta que alcance el widget de mayor nivel, o hasta que detenga la propagación devolviendo true desde un gestor de eventos.

Tenga en cuenta que, después de haber cancelado un evento, no se llamará a ninguna otra función (incluso si es del mismo widget).

21.2.1. Ejemplo

En este ejemplo hay tres gestores de eventos que se llaman después del gestor de eventos predeterminado de Gtk::Window, uno en la Gtk::Entry, uno en el Gtk::Grid y uno en la Gtk::Window.

En la Gtk::Window, también está la sobrecarga del gestor predeterminado (on_key_release_event()), y otro gestor que se llama antes del predeterminado (windowKeyReleaseBefore()).

El propósito de este ejemplo es mostrar los pasos que el evento sigue cuando se emite.

Cuando escriba en el «entry», se emitirá un evento de liberación de tecla, que primero irá a la ventana superior (Gtk::Window), dado que hay un gestor de eventos establecido para que se llame antes, y ese es el que se llama primero (windowKeyReleaseBefore()). Después se llama al gestor de eventos predeterminado (al que se ha sobrecargado), y luego se envía el evento al widget que tiene el foco, el Entry en el ejemplo y, dependiendo de si lo dejamos propagar o no, puede alcanzar los gestores de eventos del Grid y la Window. Si se propaga, el texto que esté escribiendo aparecerá en el Label arriba del Entry.

Figura 21-2Eventos de teclado: propagación de eventos

Código fuente

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();
  virtual ~ExampleWindow();

private:
  // Signal handlers:
  bool label2_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state, const Glib::ustring& phase);
  bool grid_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;
  Gtk::Label m_label2;
  Gtk::CheckButton m_checkbutton_can_propagate_down;
  Gtk::CheckButton m_checkbutton_can_propagate_up;
};

#endif //GTKMM_EVENT_PROPAGATION_H

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

#include "examplewindow.h"
#include <iostream>

ExampleWindow::ExampleWindow()
:
m_label1("A label"),
m_label2("Write here"),
m_checkbutton_can_propagate_down("Can propagate down"),
m_checkbutton_can_propagate_up("Can propagate up")
{
  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);

  // Events
  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::grid_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::grid_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::grid_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::grid_key_pressed(guint, guint, Gdk::ModifierType, const Glib::ustring& phase)
{
  std::cout << "Grid,   " << 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_text(m_label2.get_text());
    std::cout << ", " << m_label2.get_text();
  }
  std::cout << std::endl;

  if (phase == "capture" && !m_checkbutton_can_propagate_down.get_active())
    return true; // Don't propagate
  return false;
}

ExampleWindow::~ExampleWindow()
{
}