Система свойств
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, потому что его значение просто взято из компонента ширины свойства 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 в своей частной секции. Мы хотим объявить свойство в 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(), он регистрирует тип перечисления, но помечает тип как набор флагов, т.е. значений, которые можно объединять с помощью оператора 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.11/properties.html