C++ объекты и QML, все по полочкам

На Хабре и в Сети достаточно много статей на тему QML, но все они оставляют за кадром некоторые моменты. Сегодня я попытаюсь приподнять занавес над некоторыми очевидными моментами для тех, кто имел дело со связкой QML и C++, и не таких очевидных для тех, кто только начинает вникать в нюансы этой замечательной технологии.
Итак. Допустим, у нас есть интерфейс приложения на QML и C++ класс с логикой работы. Как же нам собрать все это в единое целое? Начнем с нашего C++ класса, самое первое что нам нужно сделать для того что бы он был доступен из QML – это унаследовать его от QObject либо любого другого наследника этого самого QObject.

class TestClass : public QObject
{
    Q_OBJECT
public:
    explicit TestClass(QObject *parent = 0);
signals:
public slots:
};

Теперь сделаем доступным из QML какое-нибудь свойство, для этого существует макрос Q_PROPERTY.
class TestClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int someProperty READ getSomeProperty WRITE setSomeProperty NOTIFY somePropertyChanged)
public:
    explicit TestClass(QObject *parent = 0);
    int getSomeProperty()const;
    void setSomeProperty(const int &);
private:
    int someProperty;
signals:
    void somePropertyChanged();
public slots:
}; 
int TestClass::getSomeProperty()const
{
    qDebug() << "I'm getter";
    return someProperty;
}
void TestClass::setSomeProperty(const int &i)
{
    qDebug() << "I'm setter";
    someProperty = i;
}


Здесь someProperty собственно само наше свойство, getSomeProperty – метод для чтения, setSomeProperty – метод для записи. Если мы собираемся менять наше свойство из C++ то нам необходимо уведомлять об этом интерфейс на QML с помощью сигнала somePropertyChanged. Теперь должны зарегистрировать наш класс в QML, для этого нам нужно вызвать в конструкторе QmlApplicationViewer, который создается QtCreator автоматически при создании нового QtQuick проекта, добавить вызов шаблонной функции qmlRegisterTypes.


qmlRegisterType<TestClass>("ModuleName", 1, 0, "TypeName");


Здесь TestClass – наш класс, ModuleName – имя импортируемого QML модуля, TypeName – имя типа объектов в QML. Теперь в QML файле импортируем наш класс, и создаем экземпляр.
import QtQuick 1.0
import ModuleName 1.0
Rectangle {
    width: 360
    height: 360
    TypeName{
        id: myObj
        someProperty: 10
    }
    Text {
        text: "My property is: " + myObj.someProperty
        anchors.centerIn: parent
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}


Подробно нас интересуют следующие моменты:
1)
import ModuleName 1.0
– так мы говорим QML движку в каком модуле мы будем искать наш тип.
2)
TypeName{
        id: myObj
        someProperty: 10
    }

Собственно создание объекта нашего типа и запись свойства.
3)
text: "My property is: " + myObj.someProperty
– Чтение нашего свойства.

Компилируем, запускаем.
image

Для того что бы иметь возможность вызывать из QML C++ методы их необходимо определять как слоты либо при помощи макроса Q_INVOKABLE.

class TestClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int someProperty READ getSomeProperty WRITE setSomeProperty NOTIFY somePropertyChanged)
public:
    explicit TestClass(QObject *parent = 0);
    int getSomeProperty()const;
    void setSomeProperty(const int &);
    Q_INVOKABLE
    void myMethod();
private:
    int someProperty;
signals:
    void somePropertyChanged();
public slots:
    void mySlot();
};
void TestClass::myMethod()
{
    qDebug() << "I am Method";
    someProperty++;
}
void TestClass::mySlot()
{
    qDebug() << "I am SLOT";
    someProperty--;
}


Модифицируем QML файл.
import QtQuick 1.0
import ModuleName 1.0
Rectangle {
    width: 360
    height: 360
    TypeName{
        id: myObj
        someProperty: 10
    }
    Text {
        text: "My property is: " + myObj.someProperty
        anchors.centerIn: parent
    }
    Rectangle{
        width: 20
        height: 20
        color: "red"
        MouseArea {
            anchors.fill: parent
            onClicked: {
                myObj.mySlot();
            }
        }
    }
    Rectangle{
        anchors.right: parent.right
        width: 20
        height: 20
        color: "blue"
        MouseArea {
            anchors.fill: parent
            onClicked: {
                myObj.myMethod();
            }
        }
    }
}


Компилируем, запускаем, нажимаем на квадраты и по отладочному выводу видим что все работает, вот только изменения нашего свойства никак не отражаются на интерфейсе. Но мы это предвидели и при объявлении свойства указали NOTIFY somePropertyChanged, модифицируем наш метод и слот так что бы при изменении свойства испускался сигнал somePropertyChanged.

void TestClass::myMethod()
{
    qDebug() << "I am Method";
    someProperty++;
    emit somePropertyChanged();
}
void TestClass::mySlot()
{
    qDebug() << "I am SLOT";
    someProperty--;
    emit somePropertyChanged();
}


Снова компилируем, запускаем и наслаждаемся – оно реагирует.
А как же нам быть если сами мы хотим обработать сигнал, испускаемый C++ объектом, из QML кода? Все просто. Добавляем в наш класс сигнал и генерируем его в угодный нам момент.

void TestClass::mySlot()
{
    qDebug() << "I am SLOT";
    someProperty--;
    emit somePropertyChanged();
    if(someProperty < 0) emit someSignal();
}


Для того что бы поймать этот сигнал в QML нашему объекту необходим обработчик события onSomeSignal, имена событий в QML получаются путем преобразования первой буквы сигнала к верхнему регистру и добавления префикса on.
TypeName{
        id: myObj
        someProperty: 10
        onSomeSignal: {
            Qt.quit();
        }
} 


Вот так выглядит создание объекта с обработчиком событий в QML файле.
Вот собственно и все что я хотел рассказать. Спасибо за внимание.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 12

    +8
    Начните уже использовать теги

    <source lang="cpp"> //Для C++
    <source lang="javascript"> // Для QML
    


    Читать же невозможно.

    много статей на тему QML, но все они оставляют за кадром некоторые моменты.


    Какие именно моменты вы осветили, которых нету в других статьях?

    На мой взгляд это очередной стопицотый пост про qml для самых маленьких.
      +1
      А теперь, когда код оформлен, конструктивная критика:
      1. Для Q_PROPERTY setter должен быть объявлен как public slot, иначе из того же QML нельзя будет например написать:
      myObject.myProperty = 10


      2. Changed сигнал должен emit'иться не абы где, а именно в setter'е и, желательно, только в том случае, если пришедшее значение действительно отличается от текущего.

      3. Q_INVOKABLE относится не к группе методов, а к каждому конкретному методу, поэтому есть смысл писать его на одной строчке с объявлением метода.

      4. Менять значение переменной использующейся в Q_PROPERTY напрямую — моветон, а вас же есть setter.

      5. Ну и в общем Qt Creator умеет сам генерировать идейно правильный код для setter & getter если поставить курсор на слово Q_PROPERTY и нажать Alt+Enter
        0
        1. Для Q_PROPERTY setter должен быть объявлен как public slot, иначе из того же QML нельзя будет например написать:
        это неправда
          0
          А доказать?
          Не, я, конечно, согласен это может быть и private slot, но это должен быть слот или Q_INVOKABLE.
            0
            Запустите и проверьте, setter должен просто быть public, и ему не надо быть слотом или invokable
              0
              Проверил, признаю, был не прав, moc уже на основе Q_PROPERTY генерирует код:
                    else if (_c == QMetaObject::ReadProperty) {
                      void *_v = _a[0];
                      switch (_id) {
                      case 0: *reinterpret_cast< int*>(_v) = a(); break;
                      }
                      _id -= 1;
                  } else if (_c == QMetaObject::WriteProperty) {
                      void *_v = _a[0];
                      switch (_id) {
                      case 0: setA(*reinterpret_cast< int*>(_v)); break;
                      }
                      _id -= 1;
              
                0
                Ну да, просто Вы же могли раньше(до QML) объявлять property с методами-не-слотами. QML просто использует существующую инфраструктуру, а не вводит дополнительные ограничения(за исключением NOTIFY)
          0
          1. Для Q_PROPERTY setter должен быть объявлен как public slot, иначе из того же QML нельзя будет например написать:
          myObject.myProperty = 10
          

          private:
              int someProperty;
          

          и
          myObject.myProperty = 10
          

          Взаимоисключающие параграфы, ведь.
          А для случая с public свойством, вы абсолютно правы, сеттер обязательно должен быть слотом либо Q_INVOKABLE

          5. Ну и в общем Qt Creator умеет сам генерировать идейно правильный код для setter & getter если поставить курсор на слово Q_PROPERTY и нажать Alt+Enter

          А за это огромное спасибо, не знал о такой возможности, очень удобно.
            0
            Взаимоисключающие параграфы

            Что Вы этим хотели сказать? С чего вдруг они взаимоисключающие?
            А для случая с public свойством, вы абсолютно правы, сеттер обязательно должен быть слотом либо Q_INVOKABLE

            Нет он не прав, и Вы, как следствие, тоже.
            Вот вам кусок кода из Qt, дабы поставить точку:

            class Q_AUTOTEST_EXPORT QDeclarativeBorderImage : public QDeclarativeImageBase
            {
                Q_OBJECT
                Q_ENUMS(TileMode)
            
                Q_PROPERTY(QDeclarativeScaleGrid *border READ border CONSTANT)
                Q_PROPERTY(TileMode horizontalTileMode READ horizontalTileMode WRITE setHorizontalTileMode NOTIFY horizontalTileModeChanged)
                Q_PROPERTY(TileMode verticalTileMode READ verticalTileMode WRITE setVerticalTileMode NOTIFY verticalTileModeChanged)
            
            public:
                QDeclarativeBorderImage(QDeclarativeItem *parent=0);
                ~QDeclarativeBorderImage();
            
                QDeclarativeScaleGrid *border();
            
                enum TileMode { Stretch = Qt::StretchTile, Repeat = Qt::RepeatTile, Round = Qt::RoundTile };
            
                TileMode horizontalTileMode() const;
                void setHorizontalTileMode(TileMode);
            
            0
            Что Вы этим хотели сказать? С чего вдруг они взаимоисключающие?

            С того что нечего пытаться изменять private свойство извне без прямого вызова сеттера.

            И покажите тогда уже и qml файл где horizontalTileMode используется для записи.
              0
              Это не private свойство, private только поле в котором хранится значение. Вообще сложно представить private свойство мне, могу представить read-only — когда не объявлен сеттре, но оно от этого private'ом не становится.

              Про public slot я был не прав смотрите ответ выше.
                0
                Вы не верно интерпретируете приватность. Если Вы объявили Q_PROPERTY, то это, априори, публичное свойство. Нет такого понятие как private property, Вы можете принять некое соглашение, что считать internal и external(в qml internal свойства де факто имеют префикс __, хотя никакого стандарта на это нет). C++, же, backends(т.е. то, что хранит состояние свойства) как правило находятся в private секции, этого требует инкапсуляция.
                И покажите тогда уже и qml файл где horizontalTileMode используется для записи.

                Целью моего комментария была восполнить пробел в Ваших знаниях, а не в собственных. Будьте добры, убедитесь в этом самостоятельно. Это больше Вам нужно, чем мне.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое