在Qt中处理"events"

  • 本文关键字:events 处理 Qt c++ qt
  • 更新时间 :
  • 英文 :


我试图理解Qt中的信号和插槽,并且大多数Qt gui对象似乎都缺乏信号。许多对象都有"*Event"方法,您可以覆盖以处理某些事件,但我是否必须制作整个类来处理这些?为什么没有更多的信号,这样这些事情就可以在父类中处理?

大多数图形界面(Windows, Linux, Mac OS)使用Event-driven架构。这是历史和标准的做法。当编写像Qt Gui模块这样的多平台适配器时,使用底层架构的公分母是有意义的,即基于事件的。而将事件传播给"子"对象的设计事件机制对于处理用户输入(键盘、鼠标等)非常强大。

除此之外,你可能应该看看对Qt事件和信号/插槽问题的出色回答

您可以使用installEventFilter来避免重写事件方法。至于事件和信号/槽的区别,请阅读这个问题

信号和插槽并不能让您轻松地重新实现事件行为。修改事件行为是GUI设计中非常常见的模式。OOP多态性和虚拟方法相当清晰地映射到这个模式上。使用信号和插槽实现多态性是很麻烦的,因为您必须手动跟踪先前的插槽处理给定的信号,并且不能保证只有一个插槽连接到一个信号。

在一个虚拟方法中,比如QWidget::closeEvent,总是只有一个实现"连接"到事件的源(QWidget::event中的调用)。

所以,目前的做法是:

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

假设你使用closeEvent信号代替。小部件需要将一个槽连接到它的信号,例如:

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);
  }
};

信号和插槽都需要被保护,因为您不应该从小部件外部使用这些方法。现在,重新实现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*)));
  }
};

您仍然需要子类来重新实现该事件,但现在更难了,并且您现在有了直接调用信号槽的开销,而不是虚拟方法调用的小开销。

让事件信号和插槽公开是一个非常糟糕的主意,因为通过干扰事件处理很容易破坏类的内部状态。为了让您注意到这一点,重新实现事件处理需要子类化。在重新实现和基类之间存在着紧密的联系,基类依赖于这样的重新实现才能正常工作。

你会问,如何打破状态?当然,要做你想做的事。也就是说,在类的之外"处理"关闭事件

假设closeEvent信号在QWidget中是公开的。最初,它只连接到类中的处理程序槽,并且希望覆盖它的派生类知道要断开哪个信号,以及如果要返回到原始实现,要调用哪个槽。

现在我们添加一个外部"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*)));
  }
};
现在假设您有两个这样的处理程序。有了子类,这很容易:派生更多的处理程序总是知道如何到达内部处理程序。处理程序通过继承形成了一个有向无环图(DAG)。现在,我们有了最新的处理程序,除了类本身中的处理程序之外,抢占了所有其他处理程序。如果外部处理程序没有断开现有处理程序,它将是一个树,并且内部处理程序最终将被多次调用!

回想一下,没有办法枚举连接列表。这样做有很好的理由——它会使信号槽连接的线程安全性有更大的开销(当然,如果Jeff Preshing会弄脏它的话就不会了;)

你能做的最好的事情是检测是否存在第三方处理程序——即使在这种情况下你只能中止,因为你已经破坏了一些东西。

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*)));
}

可安装的事件过滤器机制允许您在有意义的情况下拦截传递到给定QObject的事件。你可以自由使用它做你想做的事:

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

您不需要处理事件来管理滚动区域的条。在QScrollArea中,当您调整包含小部件的大小时,会自动发生这种情况。如果小部件的大小大于滚动区域的视口大小,则自动显示滚动条(默认行为)。

最新更新