Reputation:
I'm trying to understand signals and slots in Qt, and there seems to be a huge lack of signals for most Qt gui objects. A lot of objects have "*Event" methods that you can override to deal with certain events, but do I have to make an entire class just to handle these? Why aren't there more signals so these things can be dealt with in a parent class?
Upvotes: 1
Views: 3167
Reputation: 98545
Signals and slots don't really let you easily reimplement an event behavior. Modification of event behavior is a very common pattern in GUI design. The OOP polymorphism and virtual methods map rather cleanly onto this pattern. Implementing polymorphism using signals and slots is cumbersome, because you have to manually track the previous slot handling given signal, and there are no guarantees that only one slot is connected to a signal.
In a virtual method, say QWidget::closeEvent
, there's always exactly one implementation "connected" to the source of the event (an invocation within QWidget::event
).
So, the way it's currently done is:
bool QWidget::event(QEvent * ev) {
switch (ev->type()) {
...
case QEvent::CloseEvent:
closeEvent(static_cast<QCloseEvent*>(ev));
return true;
...
}
return QObject::event(ev);
}
Suppose you were to use a closeEvent
signal instead. The widget would need to connect a slot to its signal, for example:
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);
}
};
The signal and slot need to be both protected, since you're not supposed to use those methods from outside of the widget. Now a widget reimplementing the closeEvent
would have to do the following gymnastics:
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*)));
}
};
You still need to subclass to reimplement the event, but it's now harder, and instead of the small overhead of a virtual method call you now have the overhead of a direct signal slot call.
It'd be a very bad idea to make the event signals and slots public, because it's very easy to break a class's internal state by messing with its event processing. To make you pay attention to this, reimplementing event processing needs subclassing. An intimate bond exists between the reimplementation and the base class that depends on such reimplementation to work correctly.
How would one break the state, you ask? By doing exactly what you intended to do, of course. Namely, "handling" the close event outside of the class.
Suppose that the closeEvent
signal was public in QWidget
. Originally, it's only connected to the handler slot within the class, and derived classes that wish to override it know what signal to disconnect, and what slot to call if the original implementation is to be fallen back on.
Now we add an external "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*)));
}
};
Now suppose you have two such handlers. With subclassing, it was easy: the more-derived handler always knew how to get to the inner handler. The handlers, through inheritance, formed a directed acyclic graph (DAG). Now, instead, we have the newest handler preempting all the other handlers except for the handler within the class itself. If the external handler didn't disconnect the existing handler, it would be a tree, and the internal handler would end up getting called multiple times!
Recall that there's no way to enumerate the connection list. There are good reasons for that - it'd make thread safety of signal-slot connections have much larger overhead (of course not if Jeff Preshing would get his hands dirty on it ;)
The best you could do is to detect if there's a third party handler - even though at that point you can merely abort, as you've broken things.
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*)));
}
The installable event filter machinery is there to let you intercept events delivered to a given QObject
in cases where it makes sense. You're free to use it to do what you wish:
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) {}
};
Upvotes: 1
Reputation: 21258
You do not need to handle events in order to manage scroll area's bars. This happens automatically in QScrollArea
when you resize the containing widget. If widget's size is bigger than the size of scroll area's viewport, scroll bars appear automatically (default behavior).
Upvotes: 0
Reputation: 3982
You can use installEventFilter to avoid overriding the event methods. As for the differences between events and signals/slots, read this question
Upvotes: 2
Reputation: 22920
Most of the graphical interfaces (Windows, Linux, Mac OS ) use an Event-driven architecture. It is the historical and standard way to do. When writing a multiplatform adapter like the Qt Gui module it make sense to use the common denominator of the underlying architectures, which is event-based. And the design event mechanism which consist of propagating events to your "children" objects is very powerful for handling user inputs (keyboard, mouse, etc...).
Aside of that, you should probably look at the excellent replies to the question Qt Events and Signal/Slots
Upvotes: 1