В QML предоставляет механизм отделения данных от их представления. За показ отвечают элементы представления и делегаты, а за поставку- элементы моделей.
Модель - предоставляет интерфейс для обращения к данным, может содержать и сами данные, но не обязательно.
Представлена элементом ListModel и содержит последовательность элементов: ListElement{…} ….
ListElement содержит свойства данных, все они определяются пользователем, стандартных нет.
Элементы можно описывать как вручную, так и динамически, поддерживаются функции типа: append(), insert(), remove(), move и т.д.
import QtQuick 2.8 ListModel { ListElement { artist: "Amaranthe" album: "Amaranthe" year: 2011 cover: "qrc:/button-press.png" } ListElement { artist: "Dark Princess" album: "Without" year: 2005 cover: "qrc:/button.png" } ListElement { artist: "Within" album: "The Unfo" year: 2011 cover: "qrc:/atom.png" } }
XmlListModel- тоже модель списка и используется для XML-данных. Задействует для заполнения данными опросы XPath и присваивает данные свойствам.
Для каждого определяемого свойства, определяем XmlRole, что является фактом создания св-ва, задаем ему name, а так же путь откуда извлечь и тип извлекаемых данных.
<?xml version = "1.0"?> <CDs> <CD> <artist>Amaranthe</artist> <album>Amaranthe</album> <year>2011</year> <cover>qrc:/button-press.png</cover> </CD> <CD> <artist>Dark Princess</artist> <album>Without</album> <year>2005</year> <cover>qrc:/button.png</cover> </CD> <CD> <artist>Within</artist> <album>The Unfo</album> <year>2011</year> <cover>qrc:/atom.png</cover> </CD> </CDs>
import QtQuick 2.8; import QtQuick.XmlListModel 2.0 XmlListModel { source: "qrc:/CDs.xml" query: "/CDs/CD" XmlRole { name: "artist"; query: "artist/string()" } XmlRole { name: "album"; query: "album/string()" } XmlRole { name: "year"; query: "year/string()" } XmlRole { name: "cover"; query: "cover/string()" } }
Благодаря поддержке JavaScript, данные JSON можно использовать напрямую, например присвоить переменной и далее работать с ней.
import QtQuick 2.8; var jsonModel = [ { artist: "Amaranthe", album: "Amaranthe", year: 2011, cover: "qrc:/button-press.png", }, { artist: "Dark Princess", album: "Without", year: 2005, cover: "qrc:/button.png", } , { artist: "Within", album: "The Unfo", year: 2011, cover: "qrc:/atom.png", } ]
Отвечает за отображение данных модели в виде столбца или строки.
За отображение каждого элемента списка в отдельности отвечает делегат.
В примере, в роли делегата реализуем элемент Component, со свойством id, что бы можно было на него сослаться, в блоке Item определяем как будет отображаться каждый элемент нашего списка.
Далее определяем собсна саму модель ListView, в ней ставим focus, для навигации клавиатурой, добавляем header и footer, элемент highlight используется для показа текущего элемента. Так же, присваиваем ей определенные ранее модель и делегат.
import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Particles 2.0 import "qrc:/CDs.js" as CDs ApplicationWindow { width: 200; height: 360; visible: true Rectangle { id: mainrect anchors.fill: parent color: "gray" Component { id: delegate Item { width: mainrect.width; height: 70 Row { anchors.verticalCenter: parent.verticalCenter Image { width: 64; height: 64 source: modelData.cover smooth: true } Column { Text { color: "white" text: modelData.artist font.pointSize: 12 } Text { color: "lightblue" text: modelData.album font.pointSize: 10 } Text { color: "yellow" text: modelData.year font.pointSize: 8 } } } } } ListView { focus: true header: Rectangle { width: parent.width; height: 30 gradient: Gradient { GradientStop { position: 0; color: "gray" } GradientStop { position: 0.7; color: "black" } } Text { anchors.centerIn: parent color: "gray"; text: "CDs" font.bold: true; font.pointSize: 20 } } footer: Rectangle { width: parent.width; height: 30 gradient: Gradient { GradientStop { position: 0; color: "gray" } GradientStop { position: 0.7; color: "black" } } } highlight: Rectangle { width: parent.width color: "darkblue" } anchors.fill: parent model: CDs.jsonModel delegate: delegate } } }
GridView автоматически заполняет всю область элементами в табличном порядке, поэтому нет необходимости указывать кол-во строк/столбов.
Использование почти полностью идентично предыдущему примеру, разве что в делегате данные располагаются вертикально.
import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Particles 2.0 import "qrc:/CDs.js" as CDs ApplicationWindow { width: 380; height: 420; visible: true Rectangle { id: mainrect anchors.fill: parent color: "gray" Component { id: delegate Item { width: 120; height: 120 Column { anchors.centerIn: parent Image { anchors.horizontalCenter: parent.horizontalCenter width: 64; height: 64 source: modelData.cover smooth: true } Text { color: "white"; text: modelData.artist; font.pointSize: 12 } Text { color: "lightblue"; text: modelData.album; font.pointSize: 10 } Text { color: "yellow"; text: modelData.year; font.pointSize: 8 } } } } GridView { cellWidth: 120; cellHeight: 120 focus: true header: Rectangle { width: parent.width; height: 30 gradient: Gradient { GradientStop { position: 0; color: "gray" } GradientStop { position: 0.7; color: "black" } } Text { anchors.centerIn: parent color: "gray"; text: "CDs" font.bold: true; font.pointSize: 20 } } footer: Rectangle { width: parent.width; height: 30 gradient: Gradient { GradientStop { position: 0; color: "gray" } GradientStop { position: 0.7; color: "black" } } } highlight: Rectangle { width: parent.width color: "darkblue" } anchors.fill: parent model: CDs.jsonModel delegate: delegate } } }
Показывает элементы в виде замкнутой линии.
Делегат идентичен предыдущим. ключевой момент в создании элемента Path, этот элемент задает форму замкнутой линии, он присваивается свойству path элемента PathView, свойство pathItemCount определяет кол-во одновременно видимых элементов.
import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Particles 2.0 import "qrc:/CDs.js" as CDs ApplicationWindow { width: 450; height: 170; visible: true Rectangle { anchors.fill: parent color: "gray" Component { id: delegate Item { width: item.width; height: item.height Column { id: item Image { width: 90; height: 90 source: modelData.cover smooth: true } Text { color: "white"; text: modelData.artist; font.pointSize: 12 } Text { color: "lightblue"; text: modelData.album; font.pointSize: 10 } Text { color: "yellow"; text: modelData.year; font.pointSize: 8 } } } } Path { id: itemsPath startX: 45; startY: 80 // Не совсем понятны эти значения PathLine {x: 500; y: 80} // И эти тоже } PathView { id: itemsView focus: true anchors.fill: parent model: CDs.jsonModel delegate: delegate path: itemsPath pathItemCount: 4 } } }
Пример прикольной компоновки для предыдущего примера, но работает криво :(
Path { id: itemsPath startX: 150; startY: 150 PathAttribute { name: "iconScale"; value: 1.0 } PathAttribute { name: "iconOpacity"; value: 1.0 } PathQuad { x: 150; y: 25; controlX: 460; controlY: 75 } PathAttribute { name: "iconScale"; value: 0.3 } PathAttribute { name: "iconOpacity"; value: 0.3 } PathQuad { x: 150; y: 150; controlX: -80; controlY: 75 } }
VisualItemModel - модель которая не нуждается в делегате и сама отвечает за представление своих данных, плюс в том, что каждый элемент может быть отображен в индивидуальной манере.
// Отдельный файл с данными import QtQuick 2.12 VisualItemModel { Row { Image { width: 64; height: 64 source: "qrc:/button.png" smooth: true } Column { Text { color: "white"; text: "111"; font.pointSize: 12 } Text { color: "lightblue"; text: "112"; font.pointSize: 10 } Text { color: "yellow"; text: "113"; font.pointSize: 8 } } } Rectangle { width: parent.width; height: 64 color: "yellow" Text { anchors.centerIn: parent color: "red" text: "Blank" } } Row { Image { width: 64; height: 64 source: "qrc:/button-press.png" smooth: true } Column { Text { color: "white"; text: "221"; font.pointSize: 12 } Text { color: "lightblue"; text: "222"; font.pointSize: 10 } Text { color: "yellow"; text: "223"; font.pointSize: 8 } } } } // Основной файл программы import QtQuick 2.12; import QtQuick.Controls 2.2; import QtQuick.Particles 2.0 import "qrc:/CDs.js" as CDs ApplicationWindow { width: 250; height: 250; visible: true Rectangle { anchors.fill: parent color: "DarkSlateGray" Flickable { id: view width: 250; height: 500 contentWidth: 250; contentHeight: column.height anchors.fill: parent Column { id: column anchors.fill: view spacing: 5 Repeater { model: CDs{} } } } } }
В QML интегрирован класс QQuickWidget, который наследуется от QWidget т.е. является обычным виджетом и хорошо подходит для отображения QML-элементов.
Расположен он в модуле quickwidgets, который необходимо добавлять в pro-файл.
pro-файл содержит: «QT += core gui quick qml quickwidgets».
Файл «myWidget.h»
#ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> #include <QQuickWidget> #include <QVBoxLayout> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = nullptr); ~MyWidget(); }; #endif // MYWIDGET_H
Файл «myWidget.cpp»
#include "mywidget.h" MyWidget::MyWidget(QWidget *parent): QWidget(parent) { QQuickWidget* pv= new QQuickWidget(QUrl("qrc:/main.qml")); QVBoxLayout* pvbox= new QVBoxLayout; pvbox->addWidget(pv); setLayout(pvbox); } MyWidget::~MyWidget() { }
Файл элемента QML, main.qml, добавленный в ресурсы
import QtQuick 2.0 Rectangle { color: "lightgreen" width: 100; height: 100 Text { objectName: "text" anchors.centerIn: parent text: "Hello QML" function setFontSize(newSize) { font.pixelSize= newSize return font.family + " Size= "+ newSize } } }
Базовый QML элемент Item реализован в C++ классом QQuickItem, который унаследован от QObject, наравне с QWidget.
Через свойство objectName, указанное в QML, можно получить доступ к объекту класса QQuickItem и с ним можно работать как с обычным объектом QObject.
В следующем примере (на базе предыдущего), с помощью вызова QQuickWidget::rootObject() мы получаем указатель на узловой объект QQuickItem (в данном случае это Rectangle).
Далее у этого объекта, с помощью методов setProperty() устанавливаем/изменяем нужные свойства.
С помощью метода findChild() мы можем получить указатели на дочерние элементы, и так же доступ к их свойствам.
Затем вызываем пользовательскую JavaScript функцию из QML:
Все функции в QML представлены в метаобъектной информации, благодаря этом могут быть вызваны из C++, с помощью метода QMetaObject::invokeMethod().
В этот метод мы передаем указатель на объект, содержащий ф-ю, затем имя функ-ции, в макросе Q_RETURN_ARG передается переменная для результата, последними передаются макросы с аргументами- Q_ARG, равное кол-ву принимаемых аргументов. Тип данных используется QVariant.
На основании предыдущего примера, файл myWidget.cpp
MyWidget::MyWidget(QWidget *parent): QWidget(parent) { QQuickWidget* pv= new QQuickWidget(QUrl("qrc:/main.qml")); QVBoxLayout* pvbox= new QVBoxLayout; pvbox->addWidget(pv); setLayout(pvbox); QQuickItem *pqiRoot= pv->rootObject(); if(pqiRoot) { pqiRoot->setProperty("color", "yellow"); QObject *pObjText= pqiRoot->findChild<QObject*>("text"); if(pObjText) { pObjText->setProperty("text", "C++"); pObjText->setProperty("color", "blue"); QVariant varRet; QMetaObject::invokeMethod(pObjText, "setFontSize", Q_RETURN_ARG(QVariant, varRet), Q_ARG(QVariant, 52)); qDebug() << varRet; } } }
В прошлых примерах мы использовали QML внутри виджетов, теперь мы исключим их и уберем лишние зависимости.
На этот раз вся форма будет QML, на C++ реализованы действия по нажатию кнопок.
В проект добавлены только 2 модуля: «QT += quick qml»
Форма приложения
import QtQuick 2.8; import QtQuick.Controls 2.2; import QtQuick.Window 2.2 ApplicationWindow { visible: true width: 150; height: 150; Column { anchors.centerIn: parent spacing: 5 Button { signal infoClick(string str) objectName: "InfoButton" text: "Info" onClicked: infoClick("Information") } Button { signal quitClick() objectName: "QuitButton" text: "Quit" onClicked: quitClick() } } }
Класс со слотами
#ifndef MYWIDGET_H #define MYWIDGET_H #include <QtCore> class CppConnect : public QObject { Q_OBJECT public: CppConnect(QObject *parent = nullptr) { } public slots: void slotQuit() { qApp->quit(); } void slotInfo(const QString str) { qDebug() << str; } }; #endif // MYWIDGET_H
Соединение сигналов со слотами, ф-я main
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlComponent> #include "mywidget.h" int main(int argc, char** argv) { QGuiApplication app(argc, argv); QQmlApplicationEngine eng; QQmlComponent comp(&eng, QUrl("qrc:/main.qml")); CppConnect cc; QObject *pobj= comp.create(); QObject *pcmdQuitButton= pobj->findChild<QObject*>("QuitButton"); if(pcmdQuitButton) { QObject::connect(pcmdQuitButton, SIGNAL(quitClick()), &cc, SLOT(slotQuit())); } QObject *pcmdInfoButton= pobj->findChild<QObject*>("InfoButton"); if(pcmdInfoButton) { QObject::connect(pcmdInfoButton, SIGNAL(infoClick(QString)), &cc, SLOT(slotInfo(QString))); } return app.exec(); }
В QML реализована возможность расширения при помощи C++, благодаря этому можно QML расширять новыми элементами из C++.
Существующие технологии импортируются директивой import, например «import QtWebEngine 1.5».
Работа происходит через класс контекста QQmlContext, что бы получить доступ к объекту этого класса, нужно из объекта класса QQuickWidget вызвать метод rootContext(), который вернет указатель на корневой контекст.
Используя этот указатель вы можете ввести в дерево контекста новые объекты классов, унаследованных от QObject. Публикация происходит с помощью метода setContextProperty(), он принимает имя объекта и собсна адрес самого объекта.
После этого, свойства класса QObject станут свойствами QML, а слоты и методы (декларированные с Q_INVOKABLE) смогут вызываться из вашего QML-элемента.
Класс QQuickWidget так же содержит объект класса QQmlEngine (вызов engine()), который предоставляет среду для исполнения QML.
Пример экспорта, создаем C++ класс, в конструкторе делаем загрузку и отображение QML формы, виджетом и создаем несколько локальных объектов, которые экспортируются в QML форму, так же экспортируется и объект самого класса (this), определяем слот, все это будет доступно в QML форме.
Файл класса myWidget.cpp
#include "mywidget.h" MyWidget::MyWidget(QWidget *parent): QWidget(parent) { QQuickWidget *pv= new QQuickWidget(this); pv->setSource(QUrl("qrc:/main.qml")); QVBoxLayout *pvbx= new QVBoxLayout(this); pvbx->addWidget(pv); setLayout(pvbx); QQmlContext *pcon= pv->rootContext(); QStringList lst; for(int i= 0; i < 100; i++) lst << "Item"+ QString::number(i); QStringListModel *pmodel= new QStringListModel(this); pmodel->setStringList(lst); pcon->setContextProperty("myModel", pmodel); pcon->setContextProperty("myText", "It's my text"); pcon->setContextProperty("myColor", QColor(Qt::yellow)); pcon->setContextProperty("myWidget", this); } void MyWidget::slotDisplayDialog() { QMessageBox::information(0, "Title Message", "Text in message"); }
Форма QML
import QtQuick 2.8 Rectangle { width: 200; height: 200; color: myColor Text { anchors.centerIn: parent text: myText } ListView { anchors.fill: parent model: myModel delegate: Text { text: model.display } } MouseArea { anchors.fill: parent onPressed: { myWidget.setWindowTitle("Hello from QML"); myWidget.slotDisplayDialog(); } } }
Как я понял, это передача в QML только конкретных свойств и методов, определенных директивами Q_PROPERTY и Q_INVOKABLE соответственно.
Прежде всего производим регистрацию нашего класса, функцией qmlRegisterType<T>(), в нее передается идентификатор модуля (похоже собственный), два параметра с номерами версии, и собсна имя самого класса.
Calculate.h
#ifndef CALCULATION_H #define CALCULATION_H #include <QObject> class Calculation : public QObject { Q_OBJECT private: Q_PROPERTY(qulonglong input WRITE setInputValue READ inputValue NOTIFY inputValueChanged) // Уведомление об изменении Q_PROPERTY(qulonglong result READ resultValue NOTIFY inputValueChanged) qulonglong m_nInput; qulonglong m_nResult; public: explicit Calculation(QObject *parent = nullptr); Q_INVOKABLE qulonglong factorial(const qulonglong &n); qulonglong inputValue() const; void setInputValue(const qulonglong&); qulonglong resultValue() const; signals: void inputValueChanged(qulonglong); void resultValueChanged(qulonglong); }; #endif // CALCULATION_H
Calculate.cpp
#include "calculation.h" Calculation::Calculation(QObject *parent) : QObject(parent), m_nInput(0), m_nResult(1) { } qulonglong Calculation::factorial(const qulonglong &n) { return n ? (n * factorial(n-1)) : 1; } qulonglong Calculation::inputValue() const { return m_nInput; } qulonglong Calculation::resultValue() const { return m_nResult; } void Calculation::setInputValue(const qulonglong &n) { m_nInput= n; m_nResult= factorial(m_nInput); emit inputValueChanged(m_nInput); emit resultValueChanged(m_nResult); }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "calculation.h" int main(int argc, char** argv) { QGuiApplication app(argc, argv); qmlRegisterType<Calculation>("my.class.Calc", 1, 0, "Calculatoin"); QQmlApplicationEngine engine; engine.load(QUrl("qrc:/main.qml")); return app.exec(); }
Два варианта реализации, свойствами и методом
import QtQuick 2.8; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.3; import my.class.Calc 1.0 ApplicationWindow { title: "Factorial Calc"; width: 250; height: 80; visible: true Calculation { id: calc } ColumnLayout { anchors.fill: parent RowLayout // Первый подход, с использованием метода { SpinBox { id: sbx value: 0 } Text { text: "Result: "+ calc.factorial(sbx.value) } } RowLayout // Второй подход, с использованием свойств { SpinBox { value: 0 onValueChanged: calc.input= value } Text { text: "Result: "+ calc.result } } } }
Альтернативный подход с использованием сигналов
import QtQuick 2.8; import QtQuick.Controls 2.2; import QtQuick.Layouts 1.3; import my.class.Calc 1.0 ApplicationWindow { title: "Factorial Calc"; width: 250; height: 80; visible: true Calculatoin { input: sbx.value onResultValueChanged: txt.text= "Result: "+ result } RowLayout { SpinBox { id: sbx value: 0 } Text { id: txt } } }
Все базовые элементы QML (такие как Rectangle или Text), реализованы на C++ (QQuickRectangle, QQuickText), унаследованы они от QQuickItem, для собственной реализации нужно наследовать этот класс, но есть спец подготовленный для этого- QQuickPaintedItem, он уже унаследован.
Ellipse.h
#ifndef ELLIPSE_H #define ELLIPSE_H #include <QQuickPaintedItem> class QPainter; class Ellipse: public QQuickPaintedItem { Q_OBJECT private: Q_PROPERTY(QColor color WRITE setColorValue READ colorValue) QColor m_color; public: Ellipse(QQuickItem *parent= nullptr); // Отвечает рисование элемента, вызывается при обновлении void paint(QPainter *ppainter); QColor colorValue() const; void setColorValue(const QColor&); }; #endif // ELLIPSE_H
Ellipse.hpp
#include "ellipse.h" #include <QPainter> Ellipse::Ellipse(QQuickItem *parent): QQuickPaintedItem(parent), m_color(Qt::black) { } void Ellipse::paint(QPainter *ppainter) { ppainter->setRenderHint(QPainter::Antialiasing, true); ppainter->setBrush(QBrush(this->colorValue())); ppainter->setPen(Qt::NoPen); ppainter->drawEllipse(boundingRect()); } QColor Ellipse::colorValue() const { return this->m_color; } void Ellipse::setColorValue(const QColor &vCol) { this->m_color= vCol; }
main.hpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "ellipse.h" int main(int argc, char** argv) { QGuiApplication app(argc, argv); qmlRegisterType<Ellipse>("my.class.Ellipse", 1, 0, "Ellipse"); QQmlApplicationEngine engine; engine.load(QUrl("qrc:/main.qml")); return app.exec(); }
main.qml
import QtQuick 2.8; import QtQuick.Controls 2.2 import my.class.Ellipse 1.0 ApplicationWindow { title: "Paint Element"; width: 200; height: 100; visible: true Ellipse { anchors.fill: parent color: "blue" } }
Этот класс является неким объектом, который возвращает запрашиваемое изображение (QImage либо QPixmap), запрашивается оно из QML по имени файла (либо какой-нибудь условный идентификатор), зарегистрированный ImageProvider типа папка, а идентификатор имя файла.
Т.к. ImageProvider возвращает (методом request) изображение в как таковом виде (тип данных image/pixmap), его можно всячески модифицировать перед отправкой, или даже вовсе создать в этом методе, либо организовать некую логику, на основании переданных данных например..
ImageProvider.h
#ifndef IMAGEPROVIDER_H #define IMAGEPROVIDER_H #include <QQuickImageProvider> #include <QObject> #include <QImage> class ImageProvider: public QQuickImageProvider { private: QImage brightness(const QImage &imgOrig, int n); public: ImageProvider(); QImage requestImage(const QString&, QSize*, const QSize&); }; #endif // IMAGEPROVIDER_H
ImageProvider.hpp
#include "imageprovider.h" ImageProvider::ImageProvider(): QQuickImageProvider(QQuickImageProvider::Image) { } QImage ImageProvider::brightness(const QImage &imgOrig, int n) { QImage imgTemp= imgOrig; qint32 nHeigt= imgTemp.height(); qint32 nWidth= imgTemp.width(); for(qint32 y= 0; y < nHeigt; ++y) { QRgb *tempLine= reinterpret_cast<QRgb*>(imgTemp.scanLine(y)); for(qint32 x= 0; x < nWidth; ++x) { int r= qRed(*tempLine) + n; int g= qGreen(*tempLine) + n; int b= qBlue(*tempLine) + n; int a= qAlpha(*tempLine); *tempLine++ = qRgba(r > 255 ? 255 : r < 0 ? 0:r, g > 255 ? 255 : g < 0 ? 0:g, b > 255 ? 255 : b < 0 ? 0:b, a); } } return imgTemp; } QImage ImageProvider::requestImage(const QString &strId, QSize *ps, const QSize &requestedSize) { QStringList lst= strId.split(";"); bool bOk= false; int nBrightness= lst.last().toInt(&bOk); QImage img= brightness(QImage(":/"+ lst.first()), nBrightness); if(ps) *ps= img.size(); return img; }
main.hpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "imageprovider.h" int main(int argc, char** argv) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.addImageProvider(QLatin1String("brightness"), new ImageProvider); engine.load(QUrl("qrc:/main.qml")); return app.exec(); }
main.qml
import QtQuick 2.8; import QtQuick.Controls 2.2 ApplicationWindow { title: "Brightness"; width: controls.width; height: controls.height; visible: true Column { id: controls Image { id: img source: "image://brightness/11.jpg;"+ sld.brightnessValue } Slider { id: sld width: img.width value: 0.75 stepSize: 0.01 property int brightnessValue: (value * 255 -127) } Text { width: img.width text: "<h1>Brightness: "+ sld.brightnessValue+ "</h1>" } } }