事件传播

事件传播意味着,当事件在特定的部件中发出时,它可以被传递到该部件的父部件(这个部件又可以将信号传递给它的父部件,以此类推),如果父部件有对该事件的事件处理程序,该处理程序将被调用。

与其他事件相反,键盘事件首先被发送到顶级窗口(Gtk::Window),在此检查任何可能被设置的键盘快捷键(用于从键盘选择菜单项的快捷键、助记符)。然后(假设事件没有被处理)将其发送到焦点部件,然后从此部件开始传播。

事件将一直传播直到顶级窗口为止或直到你通过事件处理程序返回true为止。

请注意,取消事件之后,不再会调用其他函数(哪怕它来自同一个部件)。

21.2.1. 示例

在此示例中,Gtk::Window在默认事件处理程序之后调用了三个事件处理程序,一个在Gtk::Entry中、一个在Gtk::Grid中、一个在Gtk::Window中。

Gtk::Window中我们还覆盖了默认处理程序(on_key_release_event()),并在调用默认处理程序之前调用了另一个处理程序(windowKeyReleaseBefore())。

本示例的目的是显示事件发出时进行的步骤。

当你在条目中写入的时候,将发出一个按键放开事件,该事件将先进入顶层窗口(Gtk::Window),因为我们之前设置了一个事件处理程序,所以windowKeyReleaseBefore()将先被调用。然后调用已被覆盖的默认信号处理程序,然后将事件传播到具有焦点的部件,也就是示例中的Entry,根据事件处理程序的返回值,该事件可以被传播到GridWindow。如果该事件被传播,你写入的文本将出现在Entry上方的Label中。

Figure 21-2键盘事件 - 事件传播

源代码

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()
{
}