AUI Framework
master
Cross-platform module-based framework for developing C++20 desktop applications
|
Signal-slots is an object messaging mechanism that creates seamless relations between objects. More...
Signal-slots is an object messaging mechanism that creates seamless relations between objects.
Signal-slots were originally implemented in Qt and it has proven themselves as an object messaging mechanism making it easy to implement the observer pattern without boilerplate code. Signal-slot tracks object existence on the both ends so destruction of either sender or receiver object breaks the link between them. When the sender or receiver object is destroyed slot execution is never possible even in a multithreaded environment. Almost any signal can be connected to almost any slot (even lambda) by any code.
All classes that inherit from AObject or one of its subclasses (e.g., AView) can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to other objects. This is all the object does to communicate. It does not know or care whether anything is receiving the signals it emits. This is true information encapsulation, and ensures that the object can be used as a software component.
Signal declarations are special public fields of any class that inherit from AObject.
They can specify arguments:
Any member function of a class can be used as a slot.
You can connect as many signals as you want to a single slot, and a signal can be connected to as many slots as you need.
Signals are publicly accessible fields that notify an object's client when its internal state has changed in some way that might be interesting or significant. These signals can be emitted from various locations, but it is generally recommended to only emit them from within the class that defines the signal and its subclasses.
The slots connected to a signal are generaly executed immediately when the signal is emitted like a regular function call. emit
returns control after all slots are called. If receiver has AObject::setSlotsCallsOnlyOnMyThread set to true and emit
was called on a different thread, emit
queues slot execution to the AEventLoop associated with receiver's thread. All AView have this behaviour enabled by default.
A slot is triggered by the emission of a related signal. Slot is no more than a regular class method with a signal connected to it.
When called directly, slots follow standard C++ rules and syntax. However, through a signal-slot connection, any component can invoke a slot regardless of its accessibility level. Visibility scope matters only when creating connection (AObject::connect). This means a signal from one class can call a private slot in another unrelated class if connection is created within class itself (with access to its private/protected methods).
Furthermore, slots can be defined as virtual functions, which have been found to be beneficial in practical application development.
Compared to callbacks, the signals and slots system has some performance overhead due to its increased flexibility. The difference is typically insignificant for real-world applications. In general, emitting a signal connected to various slots can result in an execution time roughly ten times slower than direct function calls. This delay comes from locating the connection object and safely iterating over connections.
The trade-off between the signals and slots mechanism's simplicity and flexibility compared to pure function calls or callbacks is well-justified for most applications, given its minimal performance cost that users won't perceive.
Let's use Counter
from our previous example:
If connect(mCounter->valueChanged, this, &MyApp::printCounter);
looks too long for you, you can use slot macro:
Furthermore, when connecting to this
, slot(this) can be replaced with me:
Lambda can be used as a slot either:
As with methods, this
can be omitted:
Knowing basics of signal slots, you can now utilize UI signals:
Let's take our previous example with Counter
and make an UI app. Signal slot reveals it's power when your objects have small handy functions, so lets add increase
method to our counter:
This way, by clicking on "Increase", it would increase the counter and immediately display value via label.
Let's make things more declarative and use let syntax to set up connections:
See also ADataBinding for making reactive UI's on trivial data.
If signal declares arguments (i.e, like AView::keyPressed), you can accept them:
The signals and slots mechanism is type safe: The signature of a signal must match the signature of the receiving slot. Also, a slot may have a shorter signature than the signal it receives because it can ignore extra arguments:
Suppose we want to emit statusChanged
signal with a string argument and connect it with showMessage
slot:
Qt | AUI | |
---|---|---|
Signal declaration | signals:
void statusChanged(QString str);
| signals:
emits<AString> statusChanged;
|
Slot declaration | slots:
void showMessage(QString str);
| |
Connect from this to this | connect(this, SIGNAL(statusChanged(QString), this, SLOT(showMessage(QString)));
| connect(statusChanged, me::showMessage);
|
Connect from the emitter object to the sender object | QObject::connect(emitter, SIGNAL(statusChanged(QString), receiver, SLOT(showMessage(QString)));
| AObject::connect(emitter->statusChanged, slot(receiver)::showMessage);
|
Classes | |
class | AAbstractSignal |
Base class for signal. More... | |
class | AObject |
A base object class. More... | |
Macros | |
#define | emit (*this) ^ |
emits the specified signal in context of this object. More... | |
#define | AUI_EMIT_FOREIGN(object, signal, ...) (*object) ^ object->signal(__VA_ARGS__) |
emits the specified signal in context of specified object. More... | |
#define | me this, &std::remove_reference_t<decltype(*this)> |
Passes the current class and type of the current class separated by comma. It's convenient to use with the connect function: More... | |
#define | slot(v) v, &aui::impl::slot::decode_type_t<std::decay_t<decltype(v)>> |
Passes some variable and type of the variable separated by comma. It's convenient to use with the connect function (see examples). More... | |
Typedefs | |
template<typename... Args> | |
using | emits = ASignal< Args... > |
A signal declaration. More... | |
#define AUI_EMIT_FOREIGN | ( | object, | |
signal, | |||
... | |||
) | (*object) ^ object->signal(__VA_ARGS__) |
emits the specified signal in context of specified object.
object | pointer-like reference to object to emit signal from. |
signal | signal field name |
... | arguments to the signal |
Unlike emit, this macro allows to emit signal of other object. It's recommended to use AUI_EMIT_FOREIGN only if there's no way to use emit.
Basic example: (in context of member function of AView):
This code calls slots connected to clicked
signal.
If signal declaration has arguments, you have to specify them:
See signal-slot system for more info.
#define emit (*this) ^ |
emits the specified signal in context of this
object.
Unlike Qt's emit, AUI's emit is not just a syntax sugar; it's required to actually perform a signal call.
Basic example: (in context of member function of AView):
This code calls slots connected to clicked
signal.
If signal declaration has arguments, you have to specify them in braces:
See signal-slot system for more info.
#define me this, &std::remove_reference_t<decltype(*this)> |
Passes the current class and type of the current class separated by comma. It's convenient to use with the connect function:
without | with |
connect(clicked, this, &MyObject::handleClicked);
| connect(clicked, me::handleClicked);
|
#define slot | ( | v | ) | v, &aui::impl::slot::decode_type_t<std::decay_t<decltype(v)>> |
Passes some variable and type of the variable separated by comma. It's convenient to use with the connect function (see examples).
Quick example:
without | with |
connect(clicked, myObject, &MyObject::handleClicked);
| connect(clicked, slot(myObject)::handleClicked);
|
A signal declaration.
Args | signal arguments |
See signal-slot system for more info.