信号和槽并不能真正让您轻松重新实现事件行为。事件行为的修改是 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) {}
};