C++ не предоставляет поддержки для программирования передачи событий. Реализация графического интерфейса с помощью концепции функций обратного вызова(callback functions)1 значительно усложняла восприятие исходного кода и замедляла разработку приложений. Средства разработки Qt расширяют язык C++ механизмом связывания независимых компонентов ПО.
Рассмотрим этот механизм на примере:
#include <QApplication> #include <QPushButton> int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton btn; QObject::connect(&btn, SIGNAL(clicked()), &app, SLOT(quit())); btn.show(); return app.exec(); }
Функция QObject::connect2 выглядит так:
QObject::connect(отправитель, SIGNAL(сигнал), получатель, SLOT(слот));
где отправитель и получатель — указатели на объекты QObject, а сигнал и слот — сигнатуры функций без имен параметров.
В методе connect можно указать тип соединения:
QObject::connect (QObject *sender, char *signal, QObject *receiver, char *slot, Qt::ConnectionType type = Qt::AutoConnection)
В частности, тип соединения определяет будет ли сигнал доставлен немедленно, либо поставлен в очередь.
Типы соединений:
- Qt::DirectConnection - при генерации слот получает сигнал незамедлительно.
- Qt::QueuedConnection - при генерации сигнал помещается в очередь обработки событий.
- Qt::BlockingQueuedConnection - при генерации сигнал помещается в очередь обработки событий. Пока слот не получит сигнал текущий поток блокируется. Данный тип соединения следует применять только для получателя в другом потоке.3
- Qt::AutoConnection - если отправитель и получатель находятся в одном потоке, то будет использован Qt::DirectConnection, иначе используется Qt::QueuedConnection.
Механизм этого расширения был назван метаобъектной системой. Он обеспечивает следующие служебные функции:
- Взаимодействие сигналов и слотов
- Получение информации о подклассах QObject во время выполнения программы(список сигналов и слотов объекта, имена их классов).
Расширение языка C++ реализовано с помощью специального препроцессора MOC (Meta Object Compiler). Он анализирует классы на наличие макроса Q_OBJECT и создает дополнительные файлы с кодом на "чистом" C++, содержащие необходимую функциональность. Так как эта работа выполняется полностью автоматически, то вспоминать об этом механизме вам не придется. Если у вас все же возникла необходимость углубиться в детали реализации,то вы всегда можете просмотреть файлы, сгенерированные метаобъектным компилятором.
Варианты подключения слотов к сигналам:
- Один слот к нескольким сигналам:
connect(somelinedit, SIGNAL(textChanged (const QString &)), mytextedit, SLOT(setText (const QString &))); connect(anotherwidget, SIGNAL(textLoaded(const QString &)), mytextedit, SLOT(setText (const QString &)));
- К одному сигналу несколько слотов:
connect(somebutton, SIGNAL(clicked()), this, SLOT(setChecked())); connect(somebutton, SIGNAL(clicked()), mylabel , SLOT(clear()));
- К одному сигналу один или несколько сигналов(в этом случае генерация первого сигнала приведет к генерации второго):
connect(mainslider, SIGNAL(valueChanged(int)), mydatabase, SIGNAL(addRecord(int)));
При создании соединения необходимо помнить о том, что параметры сигнала и слота должны задаваться в одинаковом порядке и иметь одинаковый тип. У сигнала может быть больше параметров, чем у слота, но обратное неверно. То есть
connect(btn, SIGNAL(clicked()), sometextedit, SLOT(addtext(const QString &)));
Скомпилируется без ошибок, но связь установлена не будет.
Отправку сигналов можно заблокировать(те посланные сигналы не будут доходить до адресата):
bool QObject::blockSignals(bool)
Так же имеется возможность разорвать связь сигнал-слот(ну или сигнал-сигнал)4:
bool QObject::disconnect(отправитель, SIGNAL(сигнал), получатель, SLOT(слот));
Можно использовать 0, что будет означать "все", например:
disconnect(отправитель, SIGNAL(сигнал), 0, 0);
аннулирует все связи в которых присутствует данный сигнал данного объекта. Есть только одно исключение - вместо отправителя не может стоять ноль. Вы не можете разорвать сигналы более одного отправителя за один вызов.
Сигналы определяются в классе, как и обычные методы. Основные отличия - отсутствие реализации и невозможность возвращать значения. Пример определения сигнала:
class MyClass : public QObject { Q_OBJECT public: MyClass(); void dosignal() { emit sendMyString("10"); } signals: void sendMyString(const QString&); };
Слоты можно рассматривать как обычные методы класса. Т.е. при объявлении класса мы можем использовать ключевые слова public slots:, private slots: и protected slots:. Слоты также могут быть виртуальными. Главное отличие слотов от обычных методов - невозможность использования параметров по умолчанию. Пример реализации слота:
class MyClass : public QObject { Q_OBJECT public: MyClass(); public slots: void MySlot(int value); ... }; void MyClass::MySlot(int value) { ... }
Недостатки использования механизма сигнал-слот:
- Необходимость наследования класса QObject.
- Сигналы и слоты несколько медленнее, чем вызовы функций при использовании механизма функций обратного вызова. Однако производительность механизма сигнал-слот все же весьма высока. Так, по информации Trolltech, на i586-500 за одну секунду может быть послано около 2000000 сигналов с одним получателем, либо около 1200000 с двумя.
- В процессе компиляции не ведется проверка совместимости сигнала со слотом или наличия данного сигнала или слота в данном классе. Сообщение об ошибке будет получено на консоль во время исполнения программы.
Существует возможность использования Qt со сторонним механизмом сигнал-слот, например с реализацией из набора библиотек Boost. Для отключения ключевых слов signals, slots и emit добавьте нижеприведенную строку в .pro файл.
CONFIG += no_keywords
Для для задействования механизма сигнал-слот Qt при использовании сторонней реализации используйте макросы Q_SIGNALS (или Q_SIGNAL), Q_SLOTS (или Q_SLOT), и Q_EMIT.





