Spec-Zone.ru › Qt

Система свойств

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 есть функция 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 число или REVISION() макрос необязательны. Если они включены, они определяют свойство и его сигналы-уведомления, которые будут использоваться в определенной версии API (обычно для экспонирования в QML). Если они не включены, они по умолчанию равны 0.
  • Атрибут DESIGNABLE указывает, должно ли свойство отображаться в редакторе свойств инструмента разработки графического интерфейса (например, Qt Designer). Большинство свойств являются DESIGNABLE (по умолчанию true). Допустимые значения — true и false.
  • Атрибут SCRIPTABLE указывает, должен ли этот параметр быть доступен интерпретатору скриптов (по умолчанию true). Допустимые значения — true и false.
  • Атрибут STORED указывает, следует ли рассматривать свойство как существующее самостоятельно или зависящее от других значений. Также он указывает, нужно ли сохранять значение свойства при сохранении состояния объекта. Большинство свойств являются STORED (по умолчанию true), но, например, QWidget::minimumWidth() имеет STORED false, потому что его значение берется из компонента ширины свойства 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) не могут быть созданы, пока не будут установлены все необходимые свойства.

Функции 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(), он регистрирует тип перечисления, но помечает тип как набор флагов, т.е. значений, которые можно объединять операцией ИЛИ. Класс ввода/вывода может иметь значения перечисления 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() для получения подробностей.

Использование привязываемых свойств

Для реализации привязываемых свойств можно использовать три разных типа:

  • QProperty
  • QObjectBindableProperty
  • QObjectComputedProperty.

Первый — это общий класс для привязываемых свойств. Последние два могут использоваться только внутри QObject.

Дополнительную информацию, включая примеры, см. в упомянутых выше классах и общие рекомендации по реализации и использованию привязываемых свойств.

См. также Система метаобъектов, Сигналы и слоты, Q_DECLARE_METATYPE(), QMetaType, QVariant и Привязываемые свойства Qt.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/properties.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API