简体   繁体   English

在Qt中处理“事件”

[英]Dealing with “events” in Qt

I'm trying to understand signals and slots in Qt, and there seems to be a huge lack of signals for most Qt gui objects. 我试图理解Qt中的信号和插槽,并且大多数Qt gui对象似乎都缺少信号。 A lot of objects have "*Event" methods that you can override to deal with certain events, but do I have to make an entire class just to handle these? 许多对象都有“ * Event”方法,您可以重写这些方法来处理某些事件,但是我是否必须制作一个整个类来处理这些事件? Why aren't there more signals so these things can be dealt with in a parent class? 为什么没有更多的信号以便可以在父类中处理这些事情?

Most of the graphical interfaces (Windows, Linux, Mac OS ) use an Event-driven architecture. 大多数图形界面(Windows,Linux,Mac OS)使用事件驱动的体系结构。 It is the historical and standard way to do. 这是历史和标准的方法。 When writing a multiplatform adapter like the Qt Gui module it make sense to use the common denominator of the underlying architectures, which is event-based. 在编写类似Qt Gui模块的多平台适配器时,使用基于事件的基础体系结构的公共标准是有意义的。 And the design event mechanism which consist of propagating events to your "children" objects is very powerful for handling user inputs (keyboard, mouse, etc...). 设计事件机制将事件传播到“孩子”对象中,对于处理用户输入 (键盘,鼠标等)非常强大。

Aside of that, you should probably look at the excellent replies to the question Qt Events and Signal/Slots 除此之外,您可能应该看看对Qt Events和Signal / Slots问题的出色回答。

You can use installEventFilter to avoid overriding the event methods. 您可以使用installEventFilter来避免覆盖事件方法。 As for the differences between events and signals/slots, read this question 至于事件和信号/插槽之间的差异,请阅读此问题

Signals and slots don't really let you easily reimplement an event behavior. 信号和插槽并不能真正让您轻松地重新实现事件行为。 Modification of event behavior is a very common pattern in GUI design. 事件行为的修改是GUI设计中非常常见的模式。 The OOP polymorphism and virtual methods map rather cleanly onto this pattern. OOP多态性和虚拟方法相当清晰地映射到此模式。 Implementing polymorphism using signals and slots is cumbersome, because you have to manually track the previous slot handling given signal, and there are no guarantees that only one slot is connected to a signal. 使用信号和插槽实现多态是很麻烦的,因为您必须手动跟踪给定信号的先前插槽处理,并且不能保证只有一个插槽连接到信号。

In a virtual method, say QWidget::closeEvent , there's always exactly one implementation "connected" to the source of the event (an invocation within QWidget::event ). 在虚拟方法中,说QWidget::closeEvent ,总是有一个完全“连接”到事件源的实现( QWidget::event内的调用)。

So, the way it's currently done is: 因此,当前完成的方式是:

bool QWidget::event(QEvent * ev) {
  switch (ev->type()) {
    ...
  case QEvent::CloseEvent:
    closeEvent(static_cast<QCloseEvent*>(ev));
    return true;
    ...
  }
  return QObject::event(ev);
}

Suppose you were to use a closeEvent signal instead. 假设您要使用closeEvent信号。 The widget would need to connect a slot to its signal, for example: 小部件需要将插槽连接到其信号,例如:

class QWidget {
  Q_OBJECT
public:
  QWidget(..., QWidget * parent = 0) : QObject(parent), ... {
    connect(this, SIGNAL(closeEvent(QCloseEvent*)), SLOT(closeEventSlot(QCloseEvent*));
    ...
  }
protected:
  Q_SIGNAL void closeEvent(QCloseEvent *);
  Q_SLOT void closeEventSlot(QCloseEvent *) { ... }
  bool event(QEvent * ev) {
    switch (ev->type()) {
      ...
    case QEvent::CloseEvent:
      emit closeEvent(static_cast<QCloseEvent*>(ev));
      return true;
      ...
    }
    return QObject::event(ev);
  }
};

The signal and slot need to be both protected, since you're not supposed to use those methods from outside of the widget. 信号和插槽都需要受到保护,因为您不应在小部件外部使用这些方法。 Now a widget reimplementing the closeEvent would have to do the following gymnastics: 现在,重新实现closeEvent的小部件必须执行以下操作:

class MyWidget : public QWidget {
  Q_OBJECT
  Q_SLOT void closeEventSlot(QCloseEvent* ev) {
     ...
     QWidget::closeEventSlot(ev);
  }
public:
  MyWidget(QWidget * parent = 0: QWidget(parent) {
    disconnect(this, SIGNAL(closeEvent(QCloseEvent*)), this, 0);
    connect(this, SIGNAL(closeEvent(QCloseEvent*)), 
      SLOT(closeEventSlot(QCloseEvent*)));
  }
};

You still need to subclass to reimplement the event, but it's now harder, and instead of the small overhead of a virtual method call you now have the overhead of a direct signal slot call. 您仍然需要子类化来重新实现该事件,但是它现在变得更加困难,并且现在不再是虚拟方法调用的小开销,而是直接信号插槽调用的开销。

It'd be a very bad idea to make the event signals and slots public, because it's very easy to break a class's internal state by messing with its event processing. 公开事件信号和插槽是一个非常糟糕的主意 ,因为通过弄乱类的事件处理很容易破坏类的内部状态。 To make you pay attention to this, reimplementing event processing needs subclassing. 为了使您对此有所注意,重新实现事件处理需要子类化。 An intimate bond exists between the reimplementation and the base class that depends on such reimplementation to work correctly. 在重新实现和基类之间存在紧密的联系,该依赖关系取决于这种重新实现才能正常工作。

How would one break the state, you ask? 您会问,如何打破这种状态? By doing exactly what you intended to do, of course. 当然,通过完全按照您的意图去做。 Namely, "handling" the close event outside of the class. 即,在类之外 “处理”关闭事件。

Suppose that the closeEvent signal was public in QWidget . 假设closeEvent信号在QWidget是公共的。 Originally, it's only connected to the handler slot within the class, and derived classes that wish to override it know what signal to disconnect, and what slot to call if the original implementation is to be fallen back on. 最初,它仅连接到该类中的处理程序插槽,并且希望对其进行重写的派生类知道断开连接的信号以及如果要返回原始实现时要调用的插槽。

Now we add an external "handler": 现在,我们添加一个外部“处理程序”:

class CloseHandler : public QObject {
  Q_OBJECT
  Q_SLOT closeEventSlot(QCloseEvent * ev) {
    MyWidget * widget = qobject_cast<MyWidget*>(sender());
    ...
    // We've determined that we want the original handler to handle it.
    // What should we do here? Would the below work?
    widget->closeEventSlot();
  }
public:
  CloseHandler(MyWidget * widget, QObject * parent = 0) : QObject(parent) {
    disconnect(widget, SIGNAL(closeEvent(QCloseEvent*)), widget, 0);
    connect(widget, SIGNAL(closeEvent(QCloseEvent*)), 
      SLOT(closeEventSlot(QCloseEvent*)));
  }
};

Now suppose you have two such handlers. 现在假设您有两个这样的处理程序。 With subclassing, it was easy: the more-derived handler always knew how to get to the inner handler. 使用子类,很容易:派生性更高的处理程序总是知道如何到达内部处理程序。 The handlers, through inheritance, formed a directed acyclic graph (DAG). 处理程序通过继承形成有向无环图(DAG)。 Now, instead, we have the newest handler preempting all the other handlers except for the handler within the class itself. 现在,取而代之的是,我们拥有了最新的处理程序,它抢占了除类本身内的处理程序之外的所有其他处理程序。 If the external handler didn't disconnect the existing handler, it would be a tree, and the internal handler would end up getting called multiple times! 如果外部处理程序没有断开现有处理程序的连接,那将是一棵树,内部处理程序最终将被多次调用!

Recall that there's no way to enumerate the connection list. 回想一下,无法枚举连接列表。 There are good reasons for that - it'd make thread safety of signal-slot connections have much larger overhead (of course not if Jeff Preshing would get his hands dirty on it ;) 这样做有充分的理由-这会使信号插槽连接的线程安全性具有更大的开销(当然,如果杰夫· 普雷辛Jeff Preshing)会弄脏他的话,当然不会;)

The best you could do is to detect if there's a third party handler - even though at that point you can merely abort, as you've broken things. 最好的办法是检测是否有第三方处理程序-即使在那时候您只能中止,因为您已经破坏了事物。

CloseHandler(MyWidget * widget, QObject * parent = 0) : QObject(parent) {
  if (!disconnect(widget, SIGNAL(closeEvent(QCloseEvent*)), widget, 0)) {
    // The widget's own handler is not doing the handling, now what?
    // We don't know who handles the close event. There could be multiple
    // slots connected to it by now, for all we know.
    // :(
    if (disconnect(widget, SIGNAL(closeEvent(QCloseEvent*)), 0, 0)) {
      // oops, there were other listeners :( 
      abort();
    }
  } else {
    // Here we only know that the widget's own handler was listening to
    // the event. But *who else* could have been listening, that we know nothing
    // of?
    if (disconnect(widget, SIGNAL(closeEvent(QCloseEvent*)), 0, 0)) {
      // oops, there were other listeners, they are disconnected now and forever :(
      abort(); // We can only abort at this point.
    }
  }
  connect(widget, SIGNAL(closeEvent(QCloseEvent*)), 
    SLOT(closeEventSlot(QCloseEvent*)));
}

The installable event filter machinery is there to let you intercept events delivered to a given QObject in cases where it makes sense. 可安装的事件过滤器机制可让您在有意义的情况下拦截传递给给定QObject事件。 You're free to use it to do what you wish: 您可以随意使用它来做自己想做的事情:

class My : public QObject {
  QPointer<QWidget> m_widget;
  bool eventFilter(QObject * target, QEvent * event) {
    if (target->isWidgetType() && qobject_cast<QWidget*>(target) == m_widget
        && event->type() == QEvent::Close) {
      ...
      // we've intercepted a close event for the widget
      return true; // the event is stopped from further processing
    }
    return false; // we let the event through
  }
public:
  My(QWidget * target, QObject * parent = 0) : QObject(parent), m_widget(target) {}
};

You do not need to handle events in order to manage scroll area's bars. 您无需处理事件即可管理滚动区域的条形图。 This happens automatically in QScrollArea when you resize the containing widget. 调整包含的小部件的大小时,这会在QScrollArea自动发生。 If widget's size is bigger than the size of scroll area's viewport, scroll bars appear automatically (default behavior). 如果窗口小部件的大小大于滚动区域的视口的大小,则会自动显示滚动条(默认行为)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM