Home · All Classes · Main Classes · Grouped Classes · Modules · Functions |
Сигналы и слоты используются для связи между объектами. Механизм сигналов и слотов - это основная особенность Qt и, вероятно, основная часть Qt, которая больше всего отличается от особенностей других структур.
При GUI-программировании, мы часто хотим сообщать одним элементам об изменении других элементов управления. Более обобщенно можно сказать, что мы хотим обеспечить связь между объектами любых видов. Например, если пользователь нажимает кнопку Close, мы, вероятно, хотим, чтобы была вызвана функция окна close().
Более старые инструментарии обеспечивают подобную связь с помощью отзывов. Обратный вызов, это указатель на функцию. Если Вы хотите чтобы функция обработки уведомила Вас о некотором событии, Вы передаете ей указатель на другую функцию (отзыв). Функция обработки вызовет функцию отзыва, когда это будет уместно. Отзыва имеют два фундаментальных недостатка: Во-первых, они не типобезопасны. Мы некогда не можем проверить, что функция обработки вызывает отзыв с правильными аргументами. Во-вторых, отзыв жестко связан с функцией обработки, так как функция обработки должна знать, какой отзыв вызывать.
В Qt мы ввели технику, альтернативную отзывам: Мы используем сигналы и слоты. Сигнал испускается, когда происходит определенное событие. Виджеты Qt имеют множество предопределенных сигналов, и Вы всегда можете создать их подклассы, чтобы добавить свои сигналы. Слот - это функция, вызываемая в ответ на определенный сигнал. Виджеты Qt имеют множество предопределенных слотов, но Вы, и это стало общеиспользуемой практикой, можете создавать подклассы виджетов и добавлять свои слоты для того, чтобы обрабатывать поступающие сигналы, как Вы того хотите.
Механизм сигналов и слотов типобезопасен: Сигнатура сигнала должна соответствовать сигнатуре получающего сигнал слота. (Фактически слот может иметь более короткую сигнатуру, чем сигнал, который он получает, поскольку может игнорировать лишние аргументы.) Так как сигнатуры совместимы, компилятор может помочь в обнаружении несоответсвия типов. Вы вольны в соединении сигналов и слотов: Класс, испускающий сигналы, не знает, и не интересуется, который из слотов получит сигнал. Механизм сигналов и слотов Qt гарантирует, что, если Вы соединили сигнал со слотом, слот будет вызываться с параметрами сигнала в нужный момент. Сигналы и слоты могут иметь любое количество аргументов любых типов. Они полностью типобезопасны.
Все классы, наследующие QObject или одни из его подклассов (например, QWidget) могут содержать сигналы и слоты. Сигналы испускаются при изменении объектом своего состояния, если это изменение может быть интересно другим объектам. Все объекты делают это для связи с другими объектами. Их не заботит, получает-ли кто-нибудь испускаемые ими сигналы. Это истинная инкапсуляция информации, и такая инкапсуляция гарантирует, что объекты могут использоваться как компоненты программного обеспечения.
Слоты могут получать сигнал, но также они являются обыкновенными функциями-членами. Также, как объект не знает, получает-ли кто-нибудь сигналы, испускаемые им, слоты не знают, существуют-ли сигналы с ними связанные. Это гарантирует, что можно создать полностью независимые компоненты Qt.
Вы можете присоединять к одному слоту столько сигналов, сколько Вам будет нужно, и один сигнал может быть соединен со столькими слотами, сколько Вам требуется. Даже возможно соединять сигнал непосредственно с другим сигналом. (Второй сигнал будет испускаться немедленно всякий раз, когда испускается первый.)
Вместе, сигналы и слоты представляют собой мощный механизм компонентного программирования.
Минимальная декларация класса C++ может выглядеть следующим образом:
class Counter { public: Counter() { m_value = 0; } int value() const { return m_value; } void setValue(int value); private: int m_value; };
Небольшой класс, основанный на QObject, может выглядеть так:
#include <QObject> class Counter : public QObject { Q_OBJECT public: Counter() { m_value = 0; } int value() const { return m_value; } public slots: void setValue(int value); signals: void valueChanged(int newValue); private: int m_value; };
Версия класса, основанная на QObject, имеет то-же самое внутреннее состояние, и предоставляет открытые методы для доступа к состоянию, но, в дополнение, она поддерживает компонентное программирование с использованием сигналов и слотов. Этот класс, испустив сигнал valueChanged(), может сообщать во вне, что его состояние изменилось, и имеет слот, которому другие объекты могут посылать сигналы.
Все классы, содержащие сигналы и слоты, должны упомянуть макрос Q_OBJECT наверху декларации. Также они должны происходить (прямо или косвенно) от QObject.
Слоты реализуются прикладным программистом. Вот возможная реализация слота Counter::setValue():
void Counter::setValue(int value) { if (value != m_value) { m_value = value; emit valueChanged(value); } }
Строка, содержащая emit, заставляет объект испустить сигнал valueChanged() с новым значением, переданным в аргументе.
В следующем отрывке кода мы, используя QObject::connect() создаем два объекта Counter и соединяем, используя QObject::connect(), сигнал valueChanged() первого объекта со слотом setValue() второго объекта:
Counter a, b; QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); a.setValue(12); // a.value() == 12, b.value() == 12 b.setValue(48); // a.value() == 12, b.value() == 48
Вызов a.setValue(12) заставляет a испустить сигнал valueChanged(12), который будет получен слотом setValue() объекта b, т.е. будет вызвано b.setValue(12). Затем b сам испустит сигнал valueChanged(), но с сигналом valueChanged() объекта b не связан ни один слот, и сигнал будет проигнорирован.
Обратите внимание на то, что функция setValue() устанавливает значение и испускается только в том случае, если value != m_value. Это уберегает от зацикливаний при циклических соединениях (например, если бы b.valueChanged() был соединен с a.setValue()).
Сигнал испускается для каждого соединения, которое было создано; если сигнал соединен с двумя слотами, то он будет испущен дважды. Также Вы можете разорвать соединение с помощью QObject::disconnect().
Данный пример иллюстрирует совместную работу объектов, которые ничего не знают друг о друге. Для ее достижения объекты должны быть соединены с помощью вызова простой функции QObject::connect(), или с помощью возможности автоматического связывания uic.
Препроцессор C++ заменяет или удаляет ключевые слова signals, slots и emit для того, чтобы компилятору был предоставлен стандартный код на C++.
moc обрабатывает определения классов, содержащих сигналы и слоты и генерирует файлы реализации C++, которые будут скомпилированы и связаны с другими объектными файлами приложения. Если Вы используете qmake, то в make-файл будет автоматически добавлен вызов moc.
Сигналы испускаются объектом, когда изменяется его внутреннее состояние, и если это может быть интересно его клиентам или владельцу. Только классы, содержащие определения сигналов, и их подклассы могут испускать сигналы.
При испускании сигнала, слоты с ним связанные исполняются немедленно, также, как при обычном вызове функции. Когда это случается, механизм сигналов и слотов полностью независим от цикла обработки сообщений GUI. Выполнение кода, следующего за выражением emit, продолжится, как только завершится выполнение всех слотов. При использовании связи с очередями ситуация несколько отлична; в этом случае, исполнение кода, следующего за ключевым словом emit, продолжится немеленно, а слоты будут исполнены несколько позже.
Если несколько слотов связаны с одним сигналом, то, при испускании сигнала, они будут выполенны один за другим в произвольном порядке.
Сигналы автоматически генерируются moc, и Вы не должны включать их реализацию в .cpp-файлы. Они не должны иметь возвращаемых типов (т.е. использовать void).
Примечание о сигналах: Наш опыт показывает, что сигналы и слоты более широко используются, если они не используют специальных типов. Если сигнал QScrollBar::valueChanged() должен использовать специальный тип, такой как гипотетический QScrollBar::Range, он может быть соединен только со слотами, которые работают QScrollBar. Что-либо столь-же простое, как программа Tutorial 5, в этом случае было-бы невозможно.
Слот вызывается как только испускается соединенный с ним сигнал. Слоты - это обычные функции C++, и могут вызываться обычным образом; их единственная особенность - это то, что к ним могут быть присоединены сигналы. Слоты не могут иметь значений аргументов по умолчанию и использование в качестве типов аргументов Ваших собственных типов редко является мудрым решением.
Так как слоты являются обычными функциями-членами лишь с небольшой особенностью, они имеют права доступа, подобные обычным функциям-членам. Права доступа слота определяют, кто может с ним быть соединен:
Вы также можете определять виртуальные слоты, что мы находим очень полезным на практике.
По сравнению с отзывами, сигналы и слоты немного медленнее в связи с большей гибкостью, которую они предоставляют, но для реальных приложений это различие незначительно. Вообще, испускание сигнала, связанного с некоторыми слотами, примерно в десять раз медленнее, чем вызов невиртуальной функции приемника непосредственно. Так присходит потому, что требуется безопасно перебрать все соединения (т.е. проверить, чтобы поледующие приемники не были разрушени во время испускания сигнала) и передать параметры положенным образом. Хотя "десять вызовов невиртуальных функций" кажется долгим, это меньше чем, например, операция new или delete. Если Вы обрабатываете строку, вектор или список операция, которые требуют вызова new или delete, обработка сигналов и слотов становятся не самыми активными потребителями времени.
То-же самое происходит, когда система вызывает слот или косвенно вызываются более десятка функций. На i586-500 Вы можете генерировать около 2,000,000 сигналов, связанных с одним слотом, в секунду, или около 1,200,000 сигналов, связанных с двумя слотами, в секунду. Простой и гибкий механизм сигналов и слотов является хорошей оболочкой для внутренней реализации, которую пользователи даже не будут замечать.
Обратите внимание на то, что другие библиотеки, определяющие переменные с именем signals или slots, могут вызвать предупреждения и ошибки при компиляции с приложением, созданным на основе Qt. Решить эту проблему может директива препроцессора #undef.
Метаобъектный компилятор (moc) просматривает декларацию класса в файле C++ и генерирует код C++ инициализирующий метаобъект. Метаобъект содержит имена всех сигналов и слотов и указатели на их функции.
Метаобъект содержит дополнительную информация, такую как имя класса объекта. Также Вы можете проверить, является-ли объект наследником определенного класса, например:
if (widget->inherits("QAbstractButton")) { QAbstractButton *button = static_cast<QAbstractButton *>(widget); button->toggle(); }
Информация метаобъекта также используется qobject_cast<T>(), который подобен QObject::inherits(), но менее подвержен ошибкам:
if (QAbstractButton *button = qobject_cast<QAbstractButton *>(widget)) button->toggle();
Для получения более подробной информации см. Метаобъектная Система.
Далее приведен простой пример виджета с комментариями.
#ifndef LCDNUMBER_H #define LCDNUMBER_H #include <QFrame> class LcdNumber : public QFrame { Q_OBJECT
LcdNumber наследует QObject, который имеет понятие о сигналах и слотах, через QFrame и QWidget. Он немного похож на встроенный виджет QLCDNumber.
При расширении препроцессором, макрос Q_OBJECT декларирует несколько функций-членов, которые реализуются moc; если Вы получили сообщения об ошибках компилятора, подобные "undefined reference to vtable for LcdNumber", Вы, вероятно, забыли запустить moc или включить продукцию moc в команду link.
public: LcdNumber(QWidget *parent = 0);
moc явно не требует этого, но, если Вы наследуете QWidget, Вы, почти наверняка, захотите иметь аргумент parent в Вашем конструкторе, и передать его в конструктор базового класса.
Некоторые деструкторы и функции-члены здесь опущены; moc игнорирует функции-члены.
signals: void overflow();
LcdNumber испускае сигнал, когда его просят показать неверное значение.
Если Вы не заботитесь о том, выходит-ли значение за установленные пределы, или считаете, что оно не может за них выйти, Вы можете игнорировать сигнал overflow(), т.е. не соединять с ним ни какой слот.
Если Вы напротив, хотите вызвать две функции при выходе значения за пределы диапазона, содините сигнал с двумя слотами. Qt вызовет оба их (в произвольном порядке).
public slots: void display(int num); void display(double num); void display(const QString &str); void setHexMode(); void setDecMode(); void setOctMode(); void setBinMode(); void setSmallDecimalPoint(bool point); }; #endif
Слот обычно используется для получения информации об изменении состояния других виджетов. LcdNumber использует эту возможность, код, приведенный выше, показывает, как отобразить измененное значение. Так как display() является частью интерфейса между классом и остальной частью программы, то он является открытым слотом (public).
В некоторых из примеров программ сигнал valueChanged() соединяется со слотом display() объекта QScrollBar, в результате LCD-номер непрерывно отображает значение полосы прокрутки.
Обратите внимание на то, что display() перегружена; Qt выберет соответствующую версию во время соединения сигнала со слотом. При использовании отзывов Вы должны были бы завести пять различных названий и отслеживать используемые типы самостоятельно.
Некоторые несущественные функции-члены в данном примере были опущены.
См. также Метаобъектная Система и Система Свойств Qt.
Copyright © 2005 Trolltech | Trademarks | Qt 4.1.0 |