Последнее обновление:
August 9, 2018

Есть мысль... Жми, напиши!
Что имеем: Постов : 178 Авторов: 1 Категорий: 38

Использование интерфейсов классов в Qt и QML

Привет!

Порою удобнее в QML работать именно с интерфейсом класса, а так же иметь возможность засунуть его в QVariant.  

Разумеется простым способом в «лоб» не получится, т.к. Qt в QML работает с QObject, а мы от него не унаследовались и никакой информации для метасистемы не дали.

Долго я копался в недрах метасистемы Qt, уж собирался делать костыли, но наткнулся на макрос Q_DECLARE_INTERFACE.

И так, допустим мы хотим сделать интерфейс класса для работы с одним свойством myProperty, тем самым заставив определить функции получения, установки значения и сигнала (который сам по себе то же функция) об изменении значения. 

Пишем:

#ifndef IINTERFACE_H
#define IINTERFACE_H

#include <QObject>


class IInterface {
public:
    virtual QString myProperty() const =0;
    virtual void setMyProperty(QString myProperty) =0;
    virtual void myPropertyChanged(QString myProperty) =0;
};

Q_DECLARE_INTERFACE(IInterface, "pavelk.iinterface")


#endif // IINTERFACE_H

Обратите внимание на строку с «Q_DECLARE_INTERFACE» — тем самым мы даём понять метаобъектной системе Qt, что это интерфейс, что бы он прописал необходимые функции по получению исходного класса.

Ну и сам класс:

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

#include "iinterface.h"

class MyClass : public QObject, public IInterface
{
    Q_OBJECT
    Q_INTERFACES(IInterface)
    Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
    explicit MyClass(QObject *parent = nullptr);

    QString myProperty() const;

signals:
    void myPropertyChanged(QString myProperty);

public slots:
    void setMyProperty(QString myProperty);

private:
    QString m_myProperty;
};

#endif // MYCLASS_H

Обратите внимание на строку с «Q_INTERFACES(IInterface)» — тем самым мы даём знать Qt как именно преобразовывать этот класс к указанному интерфейсу. Кстати, можно наследоваться сразу от нескольких интерфейсов — просто пропишите через пробел их все.

Ну и теперь самое интересное. Преобразовываем класс к интерфейсу и передаём этот интерфейс в QML

   MyClass * myClass = new MyClass(0);
   IInterface * interface = myClass;
   engine.rootObjects().at(0)->setProperty("myClassInterface", qVariantFromValue( dynamic_cast<QObject*>(interface) ));

Напрямую в QVariant интерфейс засовывать нельзя, т.к. он не знает необходимой информации, поэтому преобразовываем сначала в QObject*, а благодаря тому, что мы прописал Q_DECLARE_INTERFACE метасистема знает как работать с этим интерфейсом.

Полный код примера тут:  https://github.com/Riflio/QMLInterfaces

Вот как-то так =)

Views :

214

Layout.fillWidth: true и Layout.preferredWidth/Layout.minimumWidth зависимость (очередная хитрость)

Сталкиваюсь иногда с некоторыми хитростями в QML, о которых, по всей видимости, приходится только догадываться, ибо то ли я проглядел это в документации, то ли этого действительно в ней не указано.

Так вот, задача: нужно три колонки одинаковой ширины.

Делаем:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QMLTips&Tricks24")

    RowLayout {
        anchors.fill: parent
        spacing: 0

        Rectangle {
            color: "blue"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

        Rectangle {
            color: "orange"
            Layout.fillHeight: true
            Layout.fillWidth: true
        }


        Rectangle{
            color: "green"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

    }
}

Соответственно на выходе получаем:

Получилось как и задумывали.

Обновим задачу:  Оранжевая колонка должна иметь предпочтительную ширину 200 пикселей.
Делаем:

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

Что-то не то, да..? 

Вот тут начинается самое интересное.

Дело в том, что Layout.prefferedWidth (а так же Layout.minimumWidth) управляет пропорционально шириной относительно соседей, когда задано Layout.fillWidth: true

Соответственно что бы поведение пришло в норму, мы должны у соседей  — синего и зелёного прямоугольника то же прописать желаемую ширину.

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

То есть как и задумывали, три одинаковые колонки.

Теперь наглядный пример насчёт пропорционально соседям изменяемости размеров. Если мы у синего прямоугольника зададим желаемую ширину в 50, а у зелёного 100, то соответственно синий будет в 4 раза меньше оранжевого (200/50=4), а зелёный буде в два раза меньше оранжевого (200/100=2). 

То есть родитель Layout (RowLayout/ColumnLayout/GridLayout) берёт от потомков наибольший желаемый размер и относительно него пропорционально выставляет ширину всем потомкам.
Повторюсь, что так QML себя ведёт, только когда задано Layout.fillWidth: true

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 50
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 100
}

Получаем:

Короче, считаем, что при указании preferredWidth, minimumWidth мы работаем с пропорциями относительно остальных в одном контейнере и всё. 

Почему именно так, а не иначе?  

По-моему как раз для того, что бы можно было задать поведение при растягивании/сжимании…

 

Аналогичная ситуация произойдёт, если мы оранжевой колонке захотим указать minimumWidth: 100 при fillWidth:true

Что делать, если нужно, что бы все колонки имели одинаковую ширину и заполняли всё пространство (т.е. fillWidth: true), но при этом у них (у всех или у некоторых) должна быть задана разная минимальная ширина? 

Я в таком случае прописываю у всех:  preferredWidth: 1000;  что бы желательная ширина у всех была одинаковая и обязательно больше, чем минимальная, иначе учитываться не будет (логично, да?)

Надеюсь, теперь больше с этим проблем не возникнет.

Кстати, такое же поведение будет если у компонента задано свойство implicitWidth или implicitHeight, потому что если мы не задали явно Layout.preferredWidth или  Layout.fillHeight, то берутся как раз они. Наверное вы спрашиваете: как быть, если это не просто прямоугольник, а какой-то компонент? То всё просто — оберните его в Item или Rectangle или задайте явно Layout.preferredWidth

Кстати, вот документация на лэйауты: тынц.

Вот как-то так =)

Views :

90