Spec-Zone.ru › Qt 5.11

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

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 имеет функцию 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() имеет STORED false, потому что его значение просто взято из компонента ширины свойства 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

Spec-Zone.ru

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