Система свойств
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 | REVISION(int[, int])]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[BINDABLE bindableProperty]
[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. Обратите внимание, что необходимо указать сигнал NOTIFY, чтобы разрешить привязки свойств QML.
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 имеет функциюWRITEQWidget::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или макрос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виджета. - Атрибут
BINDABLE bindablePropertyуказывает, что свойство поддерживает привязки, и что можно установить и проверить привязки к этому свойству через систему метаобъектов (QMetaProperty).bindablePropertyназначает член класса типа QBindable<T>, где T — тип свойства. Этот атрибут был введен в Qt 6.0. - Наличие атрибута
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 = nullptr);
~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(), он регистрирует тип перечисления, но помечает тип как набор флагов, т.е. значений, которые могут быть объединены с помощью операции OR. Класс ввода/вывода может иметь значения перечисления 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-6.0/properties.html