我试图理解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
中,当您调整包含小部件的大小时,会自动发生这种情况。如果小部件的大小大于滚动区域的视口大小,则自动显示滚动条(默认行为)。