qml\qt quick на практике
TRANSCRIPT
QML\Qt Quickна практике
Хомяков Сергей
Какой UI требует рынок?
Требуется тесное взаимодействие между разработчиком и дизайнером
horizontalLayoutWidget->setGeometry(QRect(10, 0, 501, 51)); horizontalLayout = new QHBoxLayout(horizontalLayoutWidget); horizontalLayout->setSpacing(6); horizontalLayout->setContentsMargins(11, 11, 11, 11); horizontalLayout->setContentsMargins(0, 0, 0, 0); pushButton_2 = new QPushButton(horizontalLayoutWidget); horizontalLayout->addWidget(pushButton_2); pushButton_4 = new QPushButton(horizontalLayoutWidget); horizontalLayout->addWidget(pushButton_4); pushButton_3 = new QPushButton(horizontalLayoutWidget); horizontalLayout->addWidget(pushButton_3); pushButton = new QPushButton(horizontalLayoutWidget); horizontalLayout->addWidget(pushButton); horizontalSlider = new QSlider(centralWidget); horizontalSlider->setGeometry(QRect(10, 60, 501, 16)); horizontalSlider->setOrientation(Qt::Horizontal); textEdit = new QTextEdit(centralWidget); textEdit->setGeometry(QRect(20, 90, 481, 201)); MainWindow->setCentralWidget(centralWidget);
RowLayout { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 18 Button { text: qsTr("Button 1") } Button { text: qsTr("Button 2") } Button { text: qsTr("Button 3") } Button { text: qsTr("Button 4") } }
Slider { x: 143; y: 57 width: 355; height: 28 }
TextArea { x: 142; y: 105 width: 355; height: 150 }
Что такое QML?
QML это JSON-подобный декларативный язык программирования, основанный на JavaScript, использующий С++ API для интеграции с Qt
Qt Quick это scenegraph-based UI framework, использующий в качестве языка программирования QML и позиционирующий себя как инструмент для быстрой разработки и прототипирования
Где уместно использовать Qt Quick?
Там где вы уже используете Qt
Там где требуется не стандартный (вычурный) UI
Там где необходим кросплатформенный “look and feel”
Там где есть постоянно меняющиеся требования к дизайну и бизнес-
логике
Hello World
Hello World
Hello World
Hello World
import QtQuick 2.0
Rectangle { id: root
width: 120; height: 240 color: "#D8D8D8"
Image { id: world x: (parent.width - width)/2 y: 40 source: 'assets/world.png' }
Text { y: world.y + world.height + 20 width: root.width horizontalAlignment: Text.AlignHCenter text: 'Hello World' }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
import QtQuick 2.2
Rectangle { id: photo objectName: "photo" property bool thumbnail: false property alias image: photoImage.source
signal clicked(); color: "gray" x: 20; y: 20 height: 150 width: { if(photoImage.width > 200) { photoImage.width; } else { 200; } } function doSomething(dx) { return dx + photoImage.width; } onHeightChanged: {
var tmp = doSomething(photo.height);console.log( tmp );
}
QtObject { id: p_attr readonly property color borderSelectedColor: "red" readonly property color borderUnSelectedColor: "black" property int animationDuration: 200 }
Rectangle { id: border anchors.centerIn: parent Image { id: photoImage; anchors.centerIn: parent } } MouseArea { anchors.fill: parent onClicked: photo.clicked(); } states:[ State { name: "Selected" PropertyChanges { target: border; color: p_attr.borderSelectedColor } }, State { name: "UnSelected" PropertyChanges { target: border; color: p_attr.borderUnSelectedColor } } ] transitions: Transition { to: "Selected" ColorAnimation { target: border; duration: p_attr.animationDuration } }}
// Circle.qml
import QtQuick 2.0
Rectangle { property real diameter : 30 width: diameter height: diameter radius: diameter}
import QtQuick 2.0
Rectangle { width: 120; height: 240 Column { anchors.fill: parent Repeater { model: 3
Circle { color: "blue" diameter: 90 }
} }}
Позиционирование и выравнивание
Якоря бывают двух видов:-ссылающиеся на элемент (centerIn, fill)-ссылающиеся на другой якорь ( left, right, top, bottom, …. )
import QtQuick 2.0
Rectangle { id: root width: 120; height: 240 color: "#D8D8D8"
Image { id: world anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 40 source: 'assets/world.png' }
Text { anchors.top: world.bottom anchors.topMargin: 20 anchors.horizontalCenter: world.horizontalCenter text: 'Hello World' }}
Hello World
Причём тут С++?
С++ API позволяет:
– экспортировать в QML C++ обьекты наследованные от QObject
– экспортировать в QML не визуальные типы
– классы на основе QQuickPaintedItem для визуальных элементов с
поддержкой QPainter
– классы на основе QQuickItem для визуальных элементов сцены
Экспорт С++ объекта
class User : public QObject{
Q_OBJECTQ_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
public:User(const QString &name, int age, QObject *parent = 0);
...}
void main( int argc, char* argv[] ) {...User *currentUser = new User("Alice", 29);QQuickView *view = new QQuickView;QQmlContext *context = view->engine()->rootContext();context->setContextProperty("currentUser", currentUser);...
}
void main( int argc, char* argv[] ) {...User *currentUser = new User("Alice", 29);QQuickView *view = new QQuickView;QQmlContext *context = view->engine()->rootContext();context->setContextProperty("currentUser", currentUser);...
}
Text { text : currentUser.name }
Экспорт С++ типа
#include <QObject>class CustomTimer : public QObject{ Q_OBJECT Q_PROPERTY( int interval READ interval WRITE setInterval NOTIFY intervalChanged )public: CustomTimer(QObject *parent = 0); int interval() const; public slots: void start(); void stop(); void setInterval(int arg);signals: void intervalChanged(int arg); void timeout();private: QTimer* m_timer; int m_interval;};
#include <QGuiApplication>#include <QQuickView>#include "CustomTimer.h"int main(int argc, char *argv[]){ QGuiApplication app(argc, argv); qmlRegisterType<CustomTimer>("CustomComponents", 1, 0, "CustomTimer"); QQuickView view; view.setSource(QUrl("qrc:///main.qml")); view.show(); return app.exec();}
import CustomComponents 1.0
Rectangle { id: root Component.onCompleted: { timer.start(); } …….. CustomTimer { id: timer interval: 3000 onTimeout: { console.log( "Timer timeout!" ); root.color = "red"; } }
}
Экспорт QQuickPaintedltem
#include <QQuickPaintedItem>class EllipseItem : public QQuickPaintedItem{ Q_OBJECTpublic: EllipseItem(QQuickItem *parent = 0); void paint(QPainter *painter);};
void EllipseItem::paint(QPainter *painter){ const qreal halfPenWidth = qMax(painter->pen().width() / 2.0, 1.0); QRectF rect = boundingRect(); rect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth); painter->drawEllipse(rect);}
#include <QGuiApplication>#include <QQuickView>#include "EllipseItem.h"int main(int argc, char *argv[]){ QGuiApplication app(argc, argv); qmlRegisterType<EllipseItem>("CustomComponents", 1, 0, "Ellipse"); QQuickView view; view.setSource(QUrl("qrc:///main.qml")); view.show(); return app.exec();}
import CustomComponents 1.0
Rectangle { id: root EllipseItem {
anchors.centerIn: parent width: 64 height: 48
} }
Экспорт QQuickltem
#include <QQuickItem>#include <QSGGeometry>#include <QSGFlatColorMaterial>class TriangleItem : public QQuickItem{ Q_OBJECTpublic: TriangleItem(QQuickItem *parent = 0);protected: QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data);private: QSGGeometry m_geometry; QSGFlatColorMaterial m_material;};
QSGNode *TriangleItem::updatePaintNode(QSGNode *n, UpdatePaintNodeData *){ QSGGeometryNode *node = static_cast<QSGGeometryNode *>(n); if (!node) node = new QSGGeometryNode(); QSGGeometry::Point2D *v = m_geometry.vertexDataAsPoint2D(); const QRectF rect = boundingRect(); v[0].x = rect.left(); v[0].y = rect.bottom(); v[1].x = rect.left() + rect.width()/2; v[1].y = rect.top(); v[2].x = rect.right(); v[2].y = rect.bottom(); node->setGeometry(&m_geometry); node->setMaterial(&m_material); return node;}
QSGNode * QQuickCustomItem::updatePaintNode(QSGNode * node, UpdatePaintNodeData * nodedata){
TexureHolderNode * texture_node = static_cast<TexureHolderNode *>(node);if (!texture_node) texture_node = new TexureHolderNode();if (texture_node->fbo_ && (texture_node->fbo_->width() != width() || texture_node->fbo_->height() != height())){
texture_node->fbo_.reset();}if (texture_node->fbo_.isNull()){
QSize fboSize(qMax<int>(1, int(width())), qMax<int>(1, int(height())));QOpenGLFramebufferObjectFormat format;texture_node->fbo_.reset(new QOpenGLFramebufferObject(fboSize, format));texture_node->setTexture( window()->createTextureFromId( texture_node->fbo_->texture(), texture_node->fbo_->size(), 0) );texture_node->setRect(0, 0, width(), height());redraw_texture_needed_ = true;
}if (redraw_texture_needed_){
redraw_texture_needed_ = false;texture_node->fbo_->bind();{
paintGL();}texture_node->fbo_->bindDefault();texture_node->markDirty(QSGNode::DirtyMaterial);
}
return texture_node;}
class TexureHolderNode: public QSGSimpleTextureNode
{ public:
TexureHolderNode() {}QScopedPointer<QOpenGLFramebufferObject> fbo_;
};
https://github.com/2gis/qtandroidextensions
Что нужно оставлять в плюсах, что лучше перенести в QML?
Инструменты разработки и отладки
Autopilot-Qt5QtCreator GammaRay Squish
– qt.io/ru/download-open-source
– kdab.com/gammaray
– froglogic.com/squish
– wiki.ubuntu.com/Touch/Testing/Autopilot
Вопросы?
Хомяков Сергей[email protected]