【问题标题】:Dealing with "events" in Qt在 Qt 中处理“事件”
【发布时间】:2014-09-29 03:19:43
【问题描述】:

我正在尝试了解 Qt 中的信号和插槽,但对于大多数 Qt gui 对象来说似乎都严重缺乏信号。很多对象都有“*Event”方法,您可以重写这些方法来处理某些事件,但我是否必须创建一个完整的类来处理这些事件?为什么没有更多的信号以便在父类中处理这些事情?

【问题讨论】:

  • 你到底想做什么?
  • @vahancho 根据内容更改 QScrollArea 条的大小,而不是从它继承。
  • 您想更改 QScrollArea 的默认行为,因此最好在其上安装事件过滤器或从其继承并创建您的自定义类
  • 我不会回避子类化小部件或任何 QObject 来覆盖受保护的方法,如果这样做是有意义的。在这种情况下,据我所知,它有多大的滚动条是小部件自己的事情,没有理由在小部件与其父级之间创建耦合。因此,子类化并将调整大小的滚动条放入小部件似乎是正确的做法。也许未来的某个版本会完全取消滚动条,所以要让父级不知道小部件如何处理滚动。

标签: c++ qt


【解决方案1】:

信号和槽并不能真正让您轻松重新实现事件行为。事件行为的修改是 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 中是公开的。最初,它只连接到类中的处理程序槽,并且希望覆盖它的派生类知道要断开什么信号,以及如果要退回原始实现,则要调用什么槽。

现在我们添加一个外部“处理程序”:

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

【讨论】:

  • +1 以获得很好的答案。但这不会给我们留下一堆只使用过一次的类吗?为什么.NET 不使用这种方法?
  • @Ben .NET 事件处理程序与 Qt 的插槽完全相同,并且几乎受到相同的限制。在 .NET 中,覆盖现有的事件处理程序需要大量额外的工作并且通常不会完成 - 就像基于信号槽的事件处理系统一样。
  • @Ben 仅仅因为某些东西被称为“事件处理程序”并不意味着设计是相同的。在 Qt 中,事件处理程序形成一个 DAG,具有相同的所有优点(和限制)。在 .NET 中,它们只是一个列表。
【解决方案2】:

您无需处理事件即可管理滚动区域的条。当您调整包含小部件的大小时,这会在 QScrollArea 中自动发生。如果小部件的大小大于滚动区域视口的大小,滚动条会自动出现(默认行为)。

【讨论】:

  • 其实我是用OpenGL来绘制QScrollArea里面的一个小部件,所以不会有小部件调整大小,只是移动一个“相机”。
【解决方案3】:

您可以使用installEventFilter 来避免覆盖事件方法。至于事件和信号/槽的区别,看这篇question

【讨论】:

    【解决方案4】:

    大多数图形界面(Windows、Linux、Mac OS)都使用事件驱动架构。这是历史和标准的做法。在编写像 Qt Gui 模块这样的多平台适配器时,使用基于事件的底层架构的公分母是有意义的。包含将事件传播到“子”对象的设计事件机制对于处理user inputs(键盘、鼠标等)非常强大。

    除此之外,您可能应该看看对问题Qt Events and Signal/Slots的出色回复

    【讨论】:

    • 这并不能真正回答问题,因为可以使用任何与观察者模式类似的东西来实现事件驱动架构。信号和槽非常符合要求,除了一些令人讨厌的细节使信号槽模式比 OOP 多态性和虚拟方法更麻烦。我们所追求的只是在可访问前一个处理程序的处理程序的 DAG 层次结构上进行动态调度。如果你真的愿意的话,你可以把它塞进信号和插槽中。但这将毫无意义。事件驱动架构不需要 OOP 多态性!
    • The event-driven architecture does not demand OOP polymorphism。我没有假装有。我解释说缺少信号槽是由于系统被抽象没有使用这种机制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 2017-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多