Введение
Помимо базового использования Q_PROPERTY для экспорта свойств в QML (рассмотренного в отдельной статье про связку QML и C++), система свойств Qt полезна и в чисто C++-коде Qt Widgets: она позволяет анимировать произвольные свойства через QPropertyAnimation, сериализовать состояние объекта без написания индивидуального кода для каждого поля, и единообразно обращаться к разным типам объектов через общий интерфейс setProperty()/property(). В статье разберём практическое применение Q_PROPERTY за пределами QML.
Концепция
QPropertyAnimation — класс для анимации значения произвольного Q_PROPERTY от начального к конечному значению за заданное время, с поддержкой кривых сглаживания (easing curves) — он работает с любым свойством, удовлетворяющим минимальным требованиям (наличие READ/WRITE, тип, поддерживаемый QVariant), без необходимости писать собственный код анимации для каждого конкретного случая. Это превращает Q_PROPERTY в универсальный «крюк» для анимационной инфраструктуры Qt, применимый одинаково и к встроенным свойствам виджетов (geometry, windowOpacity), и к собственным пользовательским свойствам.
Пример кода: Q_PROPERTY на практике
// fadingwidget.h
#pragma once
#include <QWidget>
#include <QPainter>
class FadingWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
public:
explicit FadingWidget(QWidget *parent = nullptr) : QWidget(parent) {}
qreal opacity() const { return m_opacity; }
void setOpacity(qreal opacity)
{
m_opacity = opacity;
update();
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setOpacity(m_opacity);
painter.fillRect(rect(), Qt::blue);
}
private:
qreal m_opacity = 1.0;
};
// Использование QPropertyAnimation для анимации кастомного свойства
#include "fadingwidget.h"
#include <QPropertyAnimation>
#include <QSequentialAnimationGroup>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
FadingWidget widget;
widget.resize(300, 200);
widget.show();
auto *fadeOut = new QPropertyAnimation(&widget, "opacity");
fadeOut->setDuration(800);
fadeOut->setStartValue(1.0);
fadeOut->setEndValue(0.1);
fadeOut->setEasingCurve(QEasingCurve::OutQuad);
auto *fadeIn = new QPropertyAnimation(&widget, "opacity");
fadeIn->setDuration(800);
fadeIn->setStartValue(0.1);
fadeIn->setEndValue(1.0);
fadeIn->setEasingCurve(QEasingCurve::InQuad);
auto *group = new QSequentialAnimationGroup(&widget);
group->addAnimation(fadeOut);
group->addAnimation(fadeIn);
group->setLoopCount(-1); // бесконечное повторение
group->start();
return app.exec();
}
Пояснения к коду
FadingWidget объявляет кастомное свойство opacity через Q_PROPERTY, не имеющее ничего общего со встроенным QWidget::windowOpacity — это полностью пользовательское свойство, влияющее на прозрачность отрисовки внутри собственного paintEvent(). Конструктор QPropertyAnimation(&widget, "opacity") принимает целевой объект и имя свойства как строку — именно через эту строку и метаобъектную систему (описанную в одной из предыдущих статей) QPropertyAnimation находит и вызывает setOpacity() на каждом кадре анимации с промежуточным интерполированным значением.
QSequentialAnimationGroup объединяет две анимации (затухание и появление) в последовательность, а setLoopCount(-1) заставляет всю последовательность бесконечно повторяться — это даёт эффект пульсации без единой строчки кода, вычисляющего кадры вручную или используемого QTimer напрямую.
Подводные камни
QPropertyAnimationмолча не работает при опечатке в имени свойства. Поскольку имя свойства передаётся как строка ("opacity"), опечатка ("opacty") не обнаруживается компилятором — анимация просто не будет иметь видимого эффекта, и единственный способ обнаружить проблему — явная проверка через отладку или вывод предупреждения Qt в рантайме о ненайденном свойстве.- Тип свойства должен поддерживаться
QVariantи быть интерполируемым.QPropertyAnimationумеет интерполировать стандартные числовые и геометрические типы «из коробки», но для пользовательских типов нужна явная регистрация интерполятора черезqRegisterAnimationInterpolator()— без неё анимация для нестандартного типа просто «перескакивает» между значениями без плавного перехода. - Изменение свойства напрямую (не через анимацию) во время выполнения активной анимации того же свойства приводит к конфликту — оба источника пытаются управлять одним и тем же значением, и результат становится непредсказуемым в зависимости от порядка операций в каждом кадре event loop.
- Память, занимаемая объектами анимации, созданными через
newбез явного родителя или владения. Если объект анимации не привязан к родителю через конструктор (как в примере, где&widgetпередаётся как родитель) и не удаляется вручную, он может остаться висеть в памяти после завершения — Qt-анимации сDeleteWhenStopped(флаг, передаваемый вstart()) автоматически удаляют себя по завершении, что часто удобнее ручного управления.