【发布时间】:2015-07-23 16:23:04
【问题描述】:
有没有办法在连接到同一信号的多个插槽之一中消耗未来的插槽(并阻止它们执行)?
我的目标是向许多 QObject 发出带有消息的信号,并在消息所属的 QObject 找到它时使用(停止对未来槽的迭代)。
据我了解Qt Documentation:
如果多个插槽连接到一个信号,插槽将 一个接一个地执行,按照它们连接的顺序, 当信号发出时。
我希望能够在一个槽内停止这个过程。
建议?
【问题讨论】:
有没有办法在连接到同一信号的多个插槽之一中消耗未来的插槽(并阻止它们执行)?
我的目标是向许多 QObject 发出带有消息的信号,并在消息所属的 QObject 找到它时使用(停止对未来槽的迭代)。
据我了解Qt Documentation:
如果多个插槽连接到一个信号,插槽将 一个接一个地执行,按照它们连接的顺序, 当信号发出时。
我希望能够在一个槽内停止这个过程。
建议?
【问题讨论】:
不,没有办法,你不应该这样想。无论有多少插槽连接到信号,发送方都应该执行相同的操作。这就是信号槽机制的基本约定:发送者与接收者完全解耦,并且不知道接收者。
您要做的是合格的调度:有多个接收者,每个接收者可以处理一种或多种消息类型。一种实现方式如下:
发出(信号)QEvent。这使您可以保持发射器和接收器之间的信号槽解耦。
然后可以由自定义事件调度程序使用该事件,该事件调度程序知道哪些对象处理给定类型的事件。
对象以通常的方式发送事件,并在其event() 方法中接收。
下面的实现允许接收器对象存在于其他线程中。这就是它需要能够克隆事件的原因。
class <QCoreApplication>
class <QEvent>
class ClonableEvent : public QEvent {
Q_DISABLE_COPY(ClonableEvent)
public:
ClonableEvent(int type) : QEvent(static_cast<QEvent::Type>(type)) {}
virtual ClonableEvent * clone() const { return new ClonableEvent(type()); }
}
Q_REGISTER_METATYPE(ClonableEvent*)
class Dispatcher : public QObject {
Q_OBJECT
QMap<int, QSet<QObject*>> m_handlers;
public:
Q_SLOT void dispatch(ClonableEvent * ev) {
auto it = m_handlers.find(ev->type());
if (it == m_handlers.end()) return;
for (auto object : *it) {
if (obj->thread() == QThread::currentThread())
QCoreApplication::sendEvent(obj, ev);
else
QCoreApplication::postEvent(obj, ev.clone());
}
}
void addMapping(QClonableEvent * ev, QObject * obj) {
addMapping(ev->type(), obj);
}
void addMapping(int type, QObject * obj) {
QSet<QObject*> & handlers = m_handlers[type];
auto it = handlers.find(obj);
if (it != handlers.end()) return;
handlers.insert(obj);
QObject::connect(obj, &QObject::destroyed, [this, type, obj]{
unregister(type, obj);
});
m_handlers[type].insert(obj);
}
void removeMapping(int type, QObject * obj) {
auto it = m_handlers.find(type);
if (it == m_handlers.end()) return;
it->remove(obj);
}
}
class EventDisplay : public QObject {
bool event(QEvent * ev) {
qDebug() << objectName() << "got event" << ev.type();
return QObject::event(ev);
}
public:
EventDisplay() {}
};
class EventSource : public QObject {
Q_OBJECT
public:
Q_SIGNAL void indication(ClonableEvent *);
}
#define NAMED(x) x; x.setObjectName(#x)
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
ClonableEvent ev1(QEvent::User + 1);
ClonableEvent ev2(QEvent::User + 2);
EventDisplay NAMED(dp1);
EventDisplay NAMED(dp12);
EventDisplay NAMED(dp2);
Dispatcher d;
d.addMapping(ev1, dp1); // dp1 handles only ev1
d.addMapping(ev1, dp12); // dp12 handles both ev1 and ev2
d.addMapping(ev2, dp12);
d.addMapping(ev2, dp2); // dp2 handles only ev2
EventSource s;
QObject::connect(&s, &EventSource::indication, &d, &Dispatcher::dispatch);
emit s.indication(&ev1);
emit s.indication(&ev2);
return 0;
}
#include "main.moc"
【讨论】:
如果连接在一个线程中,我认为您可以抛出异常。但在这种情况下,您应该在发出信号期间捕获任何异常:
try {
emit someSignal();
} catch(...) {
qDebug() << "catched";
}
但我认为这是个坏主意。我将为此使用事件调度。
【讨论】: