Система свойств
Qt предоставляет сложную систему свойств, аналогичную системам, предоставляемым некоторыми поставщиками компиляторов. Однако, как независимая от компилятора и платформы библиотека, Qt не полагается на нестандартные возможности компилятора, такие как __property или [property]. Решение Qt работает с любым стандартным компилятором C++ на каждой платформе, поддерживаемой Qt. Оно основано на Системе метаобъектов, которая также обеспечивает межобъектное взаимодействие через сигналы и слоты.
Требования к объявлению свойств
Для объявления свойства используйте макрос Q_PROPERTY() в классе, унаследованном от QObject.
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
[REQUIRED] Вот несколько типичных примеров объявлений свойств, взятых из класса QWidget.
Q_PROPERTY(bool focus READ hasFocus) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
Вот пример, демонстрирующий, как экспортировать переменные-члены как свойства Qt, используя ключевое слово MEMBER. Обратите внимание, что для разрешения привязок свойств QML необходимо указать сигнал NOTIFY.
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged)
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
...
signals:
void colorChanged();
void spacingChanged();
void textChanged(const QString &newText);
private:
QColor m_color;
qreal m_spacing;
QString m_text; Свойство ведет себя как член данных класса, но обладает дополнительными функциями, доступными через Систему метаобъектов.
- Функция доступа
READнеобходима, если не была указана переменнаяMEMBER. Она предназначена для чтения значения свойства. В идеале для этой цели используется константная функция, которая должна возвращать либо тип свойства, либо константную ссылку на этот тип. Например, QWidget::focus — это свойство только для чтения, с функциейREAD— QWidget::hasFocus(). - Функция доступа
WRITEнеобязательна. Она предназначена для установки значения свойства. Она должна возвращать void и принимать ровно один аргумент, либо тип свойства, либо указатель или ссылку на этот тип. Например, QWidget::enabled имеет функциюWRITE— QWidget::setEnabled(). Для свойств только для чтения функцииWRITEне нужны. Например, QWidget::focus не имеет функцииWRITE. - Связывание с переменной
MEMBERнеобходимо, если не указана функция доступаREAD. Это делает данную переменную-член доступной для чтения и записи без необходимости создания функций доступаREADиWRITE. По-прежнему можно использовать функции доступаREADилиWRITEв дополнение к связыванию с переменнойMEMBER(но не оба вместе), если вам нужно контролировать доступ к переменной. - Функция
RESETнеобязательна. Она предназначена для сброса свойства до значения по умолчанию, специфичного для контекста. Например, QWidget::cursor имеет типичные функцииREADиWRITE— QWidget::cursor() и QWidget::setCursor(), а также функциюRESET— QWidget::unsetCursor(), так как любой вызов QWidget::setCursor() может означать сброс до курсора, специфичного для контекста. ФункцияRESETдолжна возвращать void и не принимать параметров. - Сигнал
NOTIFYнеобязателен. Если он определен, он должен указывать на существующий сигнал в этом классе, который испускается всякий раз, когда значение свойства изменяется. СигналыNOTIFYдля переменныхMEMBERдолжны принимать ноль или один параметр, который должен быть того же типа, что и свойство. Параметр будет содержать новое значение свойства. СигналNOTIFYдолжен испускаться только при фактическом изменении свойства, чтобы избежать ненужной повторной оценки привязок в QML, например. Qt автоматически испускает этот сигнал при необходимости для свойств MEMBER, у которых нет явного установщика. - Число
REVISIONнеобязательно. Если оно указано, оно определяет свойство и его сигналы уведомлений для использования в определенной версии API (обычно для экспонирования в QML). Если не указано, по умолчанию оно равно 0. - Атрибут
DESIGNABLEуказывает, должно ли свойство отображаться в редакторе свойств инструмента разработки графического интерфейса (например, Qt Designer). Большинство свойствDESIGNABLE(по умолчанию true). Вместо true или false можно указать булеву функцию-член. - Атрибут
SCRIPTABLEуказывает, должен ли этот параметр быть доступен скриптовому движку (по умолчанию true). Вместо true или false можно указать булеву функцию-член. - Атрибут
STOREDуказывает, рассматривается ли свойство как самостоятельное или зависящее от других значений. Он также указывает, должно ли значение свойства сохраняться при сохранении состояния объекта. Большинство свойствSTORED(по умолчанию true), но, например, QWidget::minimumWidth() имеетSTOREDfalse, потому что его значение просто берётся из компонента ширины свойства QWidget::minimumSize(), которое является QSize. - Атрибут
USERуказывает, является ли свойство свойством, предназначенным для работы или редактирования пользователем, для класса. Обычно существует только одноUSERсвойство на класс (по умолчанию false). Например, QAbstractButton::checked — свойство, редактируемое пользователем, для (переключаемых) кнопок. Обратите внимание, что QItemDelegate получает и устанавливает свойствоUSERвиджета. - Наличие атрибута
CONSTANTуказывает, что значение свойства является постоянным. Для заданного экземпляра объекта метод READ постоянного свойства должен возвращать одно и то же значение каждый раз, когда он вызывается. Это постоянное значение может быть разным для разных экземпляров объекта. Постоянное свойство не может иметь метод WRITE или сигнал NOTIFY. - Наличие атрибута
FINALуказывает, что свойство не будет переопределено производным классом. Это может быть использовано для оптимизации производительности в некоторых случаях, но moc не навязывает это. Следует позаботиться о том, чтобы никогда не переопределятьFINALсвойство. - Наличие атрибута
REQUIREDуказывает, что свойство должно устанавливаться пользователем класса. Это не навязывается moc и в основном полезно для классов, доступных для QML. В QML классы с REQUIRED свойствами не могут быть созданы, пока все REQUIRED свойства не будут установлены.
Функции READ, WRITE, и RESET могут быть унаследованы. Они также могут быть виртуальными. При наследовании в классах с множественным наследованием они должны происходить от первого унаследованного класса.
Тип свойства может быть любым типом, поддерживаемым QVariant, или это может быть пользовательский тип. В этом примере класс QDate считается пользовательским типом.
Q_PROPERTY(QDate date READ getDate WRITE setDate)
Поскольку QDate — пользовательский тип, вы должны включить заголовочный файл <QDate> вместе с объявлением свойства.
По историческим причинам типы свойств QMap и QList являются синонимами QVariantMap и QVariantList.
Чтение и запись свойств с помощью системы метаобъектов
Свойство можно читать и записывать с помощью общих функций QObject::property() и QObject::setProperty(), ничего не зная о владеющем классе, кроме имени свойства. В фрагменте кода ниже вызов QAbstractButton::setDown() и вызов QObject::setProperty() оба устанавливают свойство "down".
QPushButton *button = new QPushButton;
QObject *object = button;
button->setDown(true);
object->setProperty("down", true); Обращение к свойству через его функцию доступа WRITE предпочтительнее, поскольку это быстрее и дает лучшие диагностические сообщения на этапе компиляции, но установка свойства таким способом требует знания класса на этапе компиляции. Доступ к свойствам по имени позволяет получать доступ к классам, о которых вы не знаете на этапе компиляции. Вы можете распознать свойства класса во время выполнения, запросив его QObject, QMetaObject и QMetaProperties.
QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
...
} В приведенном фрагменте используется QMetaObject::property() для получения метаданных о каждом свойстве, определенном в каком-то неизвестном классе. Имя свойства извлекается из метаданных и передается в QObject::property() для получения значения свойства в текущем объекте.
Простой пример
Предположим, что у нас есть класс MyClass, который унаследован от QObject и который использует макрос Q_OBJECT в своем закрытом разделе. Мы хотим объявить свойство в MyClass для отслеживания значения приоритета. Название свойства будет priority, а его тип — тип перечисления с именем Priority, который определен в MyClass.
Мы объявляем свойство с помощью макроса Q_PROPERTY() в закрытом разделе класса. Требуемая функция READ называется priority, и мы включаем функцию WRITE с именем setPriority. Тип перечисления должен быть зарегистрирован в Системе метаобъектов с помощью макроса Q_ENUM(). Регистрация типа перечисления делает имена перечислителей доступными для использования в вызовах QObject::setProperty(). Мы также должны предоставить собственные объявления для функций READ и WRITE. Объявление MyClass может выглядеть следующим образом:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
Q_ENUM(Priority)
void setPriority(Priority priority)
{
m_priority = priority;
emit priorityChanged(priority);
}
Priority priority() const
{ return m_priority; }
signals:
void priorityChanged(Priority);
private:
Priority m_priority;
}; Функция READ является константной и возвращает тип свойства. Функция WRITE возвращает void и имеет ровно один параметр типа свойства. Компилятор метаобъектов контролирует эти требования.
Учитывая указатель на экземпляр MyClass или указатель на QObject, который является экземпляром MyClass, у нас есть два способа установить свойство priority:
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh"); В примере тип перечисления, являющийся типом свойства, объявляется в MyClass и регистрируется в Системе метаобъектов с помощью макроса Q_ENUM(). Это делает значения перечисления доступными в виде строк для использования в вызове setProperty(). Если бы тип перечисления был объявлен в другом классе, потребовалось бы его полное имя (например, OtherClass::Priority), а этот другой класс также должен был бы унаследовать QObject и зарегистрировать тип перечисления там с помощью макроса Q_ENUM().
Аналогичный макрос, Q_FLAG(), также доступен. Как и Q_ENUM(), он регистрирует тип перечисления, но отмечает тип как набор флагов, то есть значений, которые могут быть объединены с помощью операции «ИЛИ». Класс ввода/вывода может иметь значения перечисления Read и Write, а затем QObject::setProperty() может принимать Read | Write. Q_FLAG() следует использовать для регистрации этого типа перечисления.
Динамические свойства
QObject::setProperty() также можно использовать для добавления новых свойств к экземпляру класса во время выполнения. При вызове с именем и значением, если свойство с заданным именем существует в QObject, и если заданное значение совместимо с типом свойства, значение сохраняется в свойстве, и возвращается true. Если значение не совместимо с типом свойства, свойство не изменяется, и возвращается false. Но если свойство с заданным именем не существует в QObject (т.е., если оно не было объявлено с помощью Q_PROPERTY()), новое свойство с заданным именем и значением автоматически добавляется к QObject, но по-прежнему возвращается false. Это означает, что возвращение false нельзя использовать для определения того, было ли фактически установлено конкретное свойство, если вы не знаете заранее, что свойство уже существует в QObject.
Обратите внимание, что динамические свойства добавляются на основе каждого экземпляра, т.е. они добавляются к QObject, а не к QMetaObject. Свойство можно удалить из экземпляра, передав имя свойства и недействительное значение QVariant в QObject::setProperty(). Конструктор по умолчанию для QVariant создает недействительное QVariant.
Динамические свойства можно запросить с помощью QObject::property(), точно так же, как свойства, объявленные во время компиляции с помощью Q_PROPERTY().
Свойства и пользовательские типы
Пользовательские типы, используемые свойствами, необходимо регистрировать с помощью макроса Q_DECLARE_METATYPE(), чтобы их значения можно было хранить в объектах QVariant. Это делает их подходящими для использования как со статическими свойствами, объявленными с помощью макроса Q_PROPERTY() в определениях классов, так и с динамическими свойствами, созданными во время выполнения.
Добавление дополнительной информации в класс
Связанный с системой свойств дополнительный макрос Q_CLASSINFO(), который можно использовать для добавления дополнительных пар имя--значение к метаобъекту класса, например:
Q_CLASSINFO("Version", "3.0.0") Как и другие метаданные, информация о классе доступна во время выполнения через метаобъект; см. QMetaObject::classInfo() для получения подробностей.
См. также Система метаобъектов, Сигналы и слоты, Q_DECLARE_METATYPE(), QMetaType и QVariant.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.15/properties.html