Система свойств
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]) Вот несколько типичных примеров объявлений свойств, взятых из класса 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определяет свойство и сигнал уведомления, используемые в определенной версии API (обычно для экспонирования в QML). Если не указано, по умолчанию используется 0. - Атрибут
DESIGNABLEуказывает, должно ли свойство отображаться в редакторе свойств инструмента разработки графического интерфейса (например, Qt Designer). Большинство свойств имеют значениеDESIGNABLE(по умолчанию true). Вместо true или false можно указать булеву функцию-член. - Атрибут
SCRIPTABLEуказывает, должно ли это свойство быть доступно скриптовому движку (по умолчанию true). Вместо true или false можно указать булеву функцию-член. - Атрибут
STOREDуказывает, должно ли свойство рассматриваться как существующее само по себе или зависящее от других значений. Он также указывает, должно ли значение свойства сохраняться при сохранении состояния объекта. Большинство свойств имеют значениеSTORED(по умолчанию true), но, например, QWidget::minimumWidth() имеетSTOREDfalse, потому что его значение просто взято из компонента width свойства QWidget::minimumSize(), который является QSize. - Атрибут
USERуказывает, является ли свойство свойством, предназначенным для пользователя или редактируемым пользователем в классе. Обычно в классе существует только одно свойствоUSER(по умолчанию false). Например, QAbstractButton::checked — это редактируемое пользователем свойство для (переключаемых) кнопок. Обратите внимание, что QItemDelegate получает и устанавливает свойствоUSERвиджета. - Наличие атрибута
CONSTANTуказывает, что значение свойства является постоянным. Для данного экземпляра объекта метод READ постоянного свойства должен каждый раз возвращать одно и то же значение. Это постоянное значение может отличаться для разных экземпляров объекта. Постоянное свойство не может иметь метод WRITE или сигнал NOTIFY. - Наличие атрибута
FINALуказывает, что свойство не будет переопределено производным классом. Это может быть использовано для повышения производительности в некоторых случаях, но moc не проверяет это. Следует тщательно следить, чтобы не переопределять свойствоFINAL.
Функции 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 в разделе private. Мы хотим объявить свойство в MyClass для отслеживания значения приоритета. Имя свойства будет priority, а его тип — перечисление с именем Priority, которое определено в MyClass.
Мы объявляем свойство с помощью макроса Q_PROPERTY() в разделе private класса. Необходимая функция 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().
END_OF_DOCUMENT_MARKERАналогичный макрос, 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/archives/qt-5.6/properties.html