✿4-The Basics-Qt Quick and QML

Qt由两个不同的模块组成,用于开发图形用户界面(GUI)应用程序。第一种方法是使用Qt小部件和C++,这是我们在前一章中了解的。第二种方法是使用Qt Quick控件和Qt建模语言(Qt Modeling language:QML),我们将在本章中介绍。

让我们熟悉QML类型系统和各种QML类型。QML文件中的类型可以来自不同的来源。QML文件中使用的不同类型概述如下:

  • 本地提供的QML基础类型,如intboolreallist
  • JS类型,如varDateArray
  • QML对象类型,如ItemRectangleImageComponent
  • QML模块通过C++注册的类型,如BackendLogic
  • 作为QML文件提供的类型,如MyPushButton

基本类型可以包含简单的值,例如int或bool类型。除了本机基本类型之外,Qt Quick模块还提供其他基本类型。QML引擎还支持JS对象和数组。任何标准JS类型都可以使用泛型var类型创建和存储。请注意,变体类型(variant type)已过时,仅用于支持较旧的应用程序。QML对象类型是可以从中创建QML对象的类型。自定义QML对象类型可以通过创建定义该类型的.qml文件来定义。QML对象类型可以具有属性、方法、信号等。

要在QML文件中使用基本QML类型,请使用以下代码行导QtQml模块:

import QtQml

Item是Qt Quick中所有视觉元素的基本类型。Qt Quick中的所有可视项都继承自Item,Item是一个透明的可视元素,可以用作容器。Qt Quick提供Rectangle作为绘制矩形的视觉类型,以及显示图像的Image类型。Item为视觉元素提供一组公共属性。

Qt Quick Controls提供了一组UI元素,可用于使用Qt Quick构建流畅的UI。为了避免与小部件(widgets)的歧义,我们将使用术语控件(controls)来表示UI元素。

可以使用.qml文件中的以下导入语句将QML类型导入到应用程序中:

import QtQuick.Controls

Qt 6引入了自动导入功能,该功能被编写为import <module> auto。这将确保导入的模块和正在导入的模块具有相同的版本号。

要配置Qt快速控制模块以使用qmake进行构建,请在项目的.pro文件中添加以下行:

QT += quickcontrols2

Qt Quick Controls附带一套标准样式。它们列在这里:

  • Basic
  • Fusion
  • Imagine
  • Material
  • Universal

有两种方式在Qt Quick控件中应用样式:

  • 编译时(Compile time)
  • 运行时(Runtime)

你可以通过导入相应的样式模块来应用编译时样式,如下所示:

import QtQuick.Controls.Universal

可以使用以下方法之一应用运行时样式:

创建一个简单的Qt Qucik应用程序:Application->Qt Quick Application

QML运行时在QtQml模块中用C++实现。它包含一个负责执行QML的QML引擎。它还保存QML元素可以访问的上下文和属性。Qt提供了一个QQmlEngine类来实例化QML组件。你还可以使用QQmlApplicationEngine类以方便的方式使用单个QML文件加载应用程序。
main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
    	return -1;
    return app.exec();
}

你还可以使用QQuickView类,它提供了一个显示Qt Quick UI的窗口。这种方法有点过时。QQmlApplicationEngine使用QML提供了方便的中央应用程序功能,而QQuickView通常由C++控制。下面的代码片段显示了如何使用QQuickView加载.qml文件:

#include <QGuiApplication> 
#include <QQuickView> 
int main(int argc, char *argv[]) { 
	QGuiApplication app(argc, argv); 
	QQuickView view; 
	view.setResizeMode(QQuickView::SizeRootObjectToView); 
	view.setSource(QUrl("qrc:/main.qml")); 
	view.show(); 
	return app.exec(); 
}

QQuickView不支持将Window用作根项目。如果你想从QML创建根窗口,那么选择QQmlApplicationEngine。
在使用QQuickView时,可以直接使用任何Qt Quick元素,如以下代码段所示:

import QtQuick
Item {
	width:400
	height:400
	Text {
		anchors.centerIn:parent
		text:"Hello World"
	}
}

运行程序,效果如下:

请记住,在对.pro文件进行更改后,需要运行qmake。

创建一个Qt Quick 2 UI工程:Other Project->Qt Quick UI Prototype

与Qt小部件中的.ui文件类似,你也可以在QML中创建UI文件。该文件具有.ui.qml文件扩展名。QML文件有两种类型:一种扩展名为.qml,另一种扩展名为.ui.qml。QML引擎将其视为标准.qml文件,但它禁止其内部的逻辑实现。它为多个.qml文件创建了一个可重用的UI定义。通过将用户界面定义和逻辑实现分离,增强了QML代码的可维护性。

在QML中定位与布局
下面的代码片段显示了如何将Rectangle项放置在位置(50,50)处:

import QtQuick
Rectangle {
	// Manually positioned at 50,50
	x: 50// x position
	y: 50// y position
	width: 100; height: 80
	color: "blue"
}

Qt Quick提供了一种将控件锚定到另一个控件的方法。每个项目有七条不可见的锚定线:left、right、top、bottom、baseline、horizontalCenter、verticalCenter。你可以为每一侧设置边距或不同的边距。如果一个特定项目有多个锚点,则可以对它们进行分组。
让我们看看下面的例子:

import QtQuick
import QtQuick.Window
Window {
	width: 400; height: 400
	visible: true
	title: qsTr("Anchoring Demo")
	Rectangle {
		id: blueRect
		anchors {
			left: parent.left; leftMargin:10
			right: parent.right; rightMargin: 40
			top: parent.top; topMargin: 50
			bottom: parent.bottom; bottomMargin: 100
		}
		color: "blue"
		Rectangle {
			id: redRect
			anchors.centerIn: blueRect
			color:"red"
			width: 150; height: 100
		}
	}
}


你还可以编写代码,将控件放置在定位器内。如果使用Qt Quick Designer,Qt Creator会自动生成代码。生成的代码可以通过表单编辑器旁边的文本编辑器选项卡查看和修改。代码显示在以下代码片段中:

Row { 
	id: row      
	Rectangle { 
		id: yellowRect 
		width: 150; height: 100 
		color: "yellow" 
		border.color: "black" 
	} 
	Rectangle { 
		id: redRect 
		width: 150; height: 100 
		color: "red" 
		border.color: "black" 
	} 
	Rectangle { 
		id: greenRect 
		width: 150; height: 100 
		color: "green" 
		border.color: "black" 
	}
}

重复器(Repeater)使用提供的模型创建许多可视元素,以及模板中的元素以与定位器一起使用,并使用模型中的数据。在定位器内放置一个中继器,并创建符合定义定位器布置的视觉元素。如果有许多类似的项目,则带有中继器的定位器在按常规布局布置时更易于维护。

import QtQuick 
import QtQuick.Window 
Window { 
	width: 400; height: 200 
	visible: true 
	title: qsTr("Repeater Demo") 
	Row {
		anchors.centerIn: parent 
		spacing: 10 
		Repeater {
			model: 5 
			Rectangle { 
				width: 60; height: 40 
				border {width: 1; color: "black";} 
				color: "green" 
			} 
		} 
	} 
}


Qt Quick布局
可以使用以下导入语句将Qt Quick布局导入QML文件:

import QtQuick.Layouts

在QML中有5种不同类型的布局类型:RowLayout、ColumnLayout、GridLayout、Layout、StackLayout。其中Layout是为推送到ColumnLayout、RowLayout或GridLayout类型上的项目提供附加属性。
让我们看看下面的RowLayout示例:

import QtQuick 
import QtQuick.Window 
import QtQuick.Layouts 
Window { 
	width: 640; height: 480 
	visible: true 
	title: qsTr("Layout Demo") 
	RowLayout { 
		id: layout 
		anchors.fill: parent 
		spacing: 6 
		Rectangle { 
			color: 'yellow' 
			Layout.fillWidth: true 
			Layout.minimumWidth: 50
			Layout.preferredWidth: 150 
			Layout.maximumWidth: 200 
			Layout.minimumHeight: 100 
			Layout.margins: 10 
		} 
		Rectangle { 
			color: 'red' 
			Layout.fillWidth: true 
			Layout.minimumWidth: 50 
			Layout.preferredWidth: 100 
			Layout.preferredHeight: 80 
			Layout.margins: 10 
		} 
	} 
}

请注意,Row类型是定位器,而RowLayout类型是布局。和往常一样,何时使用它们主要取决于你的目标。

QML与C++集成
QML应用程序通常需要在C++中处理更高级、更高性能的任务。最常见、最快捷的方法是将C++类公开给QML运行时,前提是C++实现是从QObject派生的。QML可以很容易地与C++代码集成。QML对象可以从C++加载和操作。QML与Qt的元对象(meta-object)系统的集成允许从QML调用C++功能。这有助于构建混合了C++、QML和JS的混合应用程序。要向QML公开C++数据、属性或方法,它应该从QObject类派生。这是可能的,因为所有QML对象类型都是使用QObject派生类实现的,允许QML引擎通过Qt元对象系统加载和检查对象。可以通过以下方式将QML与C++集成:

  • 使用上下文属性将C++对象嵌入QML
  • 向QML引擎注册类型
  • 创建QML扩展插件

我们导出radius到QML环境:

#include <QGuiApplication> 
#include <QQmlApplicationEngine> 
#include <QQmlContext> 
int main(int argc, char *argv[]) { 
	QGuiApplication app(argc, argv); 
	QQmlApplicationEngine engine; 
	engine.rootContext()->setContextProperty("radius", 50); 
	const QUrl url(QStringLiteral("qrc:/main.qml")); 
	engine.load(url); 
	return app.exec(); 
}

我们现在可以在QML中直接使用导出的值:

import QtQuick 
import QtQuick.Window 
Window { 
	width: 640; height: 480 
	visible: true 
	title: qsTr("QML CPP integration") 
	Text { 
		anchors.centerIn: parent 
		text: "C++ Context Property Value: "+ radius 
	} 
}

用QML引擎注册C++类
这些方法可以是公共插槽,也可以是带有Q_INVOKABLE标记的公共方法。
现在,让我们将C++类导入QML文件。看看下面的C++类:
backend.h

#ifndef BACKENDLOGIC_H
#define BACKENDLOGIC_H
#include <QObject>
class BackendLogic : public QObject {
    Q_OBJECT
public:
    explicit BackendLogic(QObject *parent = nullptr) { Q_UNUSED(parent);}
    Q_INVOKABLE int getData() { return mValue; }
private:
    int mValue = 100;
};
#endif // BACKENDLOGIC_H

可以在main.cpp中注册C++类,使用qmlRegister()函数:
main.cpp

#include "backend.h"
	...
	qmlRegisterType<BackendLogic>("backend.logic", 1, 0, "BackendLogic");
	...

template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);

任何QObject派生的C++类都可以注册为QML对象类型。一旦类在QML类型系统中注册,该类就可以像任何其他QML类型一样使用。现在,C++类已经准备好在.qml文件中实例化。你必须导入模块并创建一个对象,如以下代码段所示:
main.qml

import QtQuick
import QtQuick.Window
import backend.logic
Window {
    width: 640; height: 480
    visible: true
    title: qsTr("QML CPP integration")
    BackendLogic {
        id: backend
    }
    Text {
        anchors.centerIn: parent
        text: "From Backend Logic : " + backend.getData()
    }
}


还可以使用qmlRegisterSingletonType()将C++类公开为QML单例。通过使用QML单例,可以防止全局命名空间中出现重复的对象。

在Qt6中,可以通过使用QML_ELEMENT宏实现C++集成。此宏使用其类或命名空间名称作为QML元素名称,声明封闭类型在QML中可用。要在C++头文件中使用此宏,必须包含头文件#include<QtQml>。
让我们看看下面的例子:

#ifndef USINGELEMENT_H 
#define USINGELEMENT_H 
#include <QObject> 
#include <QtQml> 
class UsingElements : public QObject { 
	Q_OBJECT 
	QML_ELEMENT 
public: 
	explicit UsingElements(QObject *parent = nullptr) { Q_UNUSED(parent); } 
	Q_INVOKABLE int readValue() { return mValue; } 
private: 
	int mValue = 500; 
}; 
#endif // USINGELEMENT_H

.pro文件中,你得添加qmltypes选项到CONFIG变量,同时指定QML_IMPORT_NAME和QML_IMPORT_MAJOR_VERSION:

CONFIG += qmltypes
QML_IMPORT_NAME = backend.element
QML_IMPORT_MAJOR_VERSION = 1

但实测,在.pro文件中添加上述代码,会报错如下(不添加则正常运行):

创建一个QML扩展插件
QML扩展插件提供了与C++集成的最灵活的方式。它允许你在第一个QML文件调用导入标识符时加载的插件中注册类型。你可以跨项目使用插件,这在构建复杂项目时非常方便。

Library->Qt Quick 2 Extension Plugin
plugin类必须从QqmlExtensionPlugin派生,并且应该实现registerTypes()函数。需要Q_PLUGIN_METADATA宏来将插件标识为QML扩展插件:

#include <QQmlExtensionPlugin>
class MyQtQuickPlugin2Plugin: public QQmlExtensionPlugin {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
    void registerTypes(const char *uri) override;
};

这是一个高级Qt主题部分。要创建自己的QML扩展插件,你需要深入了解Qt。如果你是初学者,可以跳过这一节。

在C++类中调用QML函数
所有QML方法都公开给元对象系统,可以使用QMetaObject::invokeMethod()从C++调用。可以为参数指定类型,并在冒号字符后指定返回值,如下一个代码段所示。例如,当你想要将C++中具有特定签名的信号连接到QML定义的方法时,这可能很有用。如果省略类型,C++签名将使用QVariant。让我们来看一个使用QMetaObject::invokeMethod()调用QML方法的应用程序。
在QML文件中,我们添加一个名为qmlMethod()的方法,如下所示:

import QtQuick 
Item { 
	function qmlMethod(msg: string) : string { 
		console.log("Received message:", msg) 
		return "Success" 
	} 
	Component.onCompleted: { 
		console.log("Component created successfully.") 
	} 
}

在main.cpp文件中,调用QMetaObject::invokeMethod()函数:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    QQmlComponent component(&engine, "qrc:/CustomItem.qml");
    QObject *myObject = component.create();

    QString retValue = "";
    QString msg = "Message from C++";

    QMetaObject::invokeMethod(myObject, "qmlMethod", Q_RETURN_ARG(QString, retValue), Q_ARG(QString, msg));

    qDebug() << "QML method returned:" << retValue;
    delete myObject;
    return app.exec();
}

运行程序,控制台打印:

qml: Component created successfully.
qml: Received message: Message from C++
QML method returned: "Success"

如果你不需要返回值,你可以调用只有两个参数的invokeMethod()函数:

QMetaObject::invokeMethod(myObject, "qmlMethod");

向C++公开QML对象指针
MyCustomObject.h

#ifndef CUSTOMOBJECT_H
#define CUSTOMOBJECT_H
#include <QObject>
#include <QVariant>
class CustomObject: public QObject {
	Q_OBJECT
public:
	explicit CustomObject(QObject *parent = nullptr) { Q_UNUSED(parent); };
	Q_INVOKABLE void setObject(QObject* object) {
		object->setProperty("text", QVariant("Clicked!"));
	}
};
#endif

bool QObject::setProperty(const char *name, const QVariant &value)

main.qml

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import MyCustomObject
Window {
	width: 640; height: 480;
	visible: true
	title: qsTr("QML Object in C++")
	CustomObject {
		id: customObject
	}
	Button {
		id: button
		anchors.centerIn: parent
		text: qsTr("Click Me!")
		onClicked: {
			customObject.setObject(button);
		}
	}
}

重要提示:
Qt QML模块提供几种宏去注册非实例化类型。QML_ANONYMOUS注册一个非实例化的C++类型并且可以被QML引用。QML_INTERFACE注册一个存在的Qt接口类型。这个类型是不能从QML实例化的,并且不能用它来声明QML属性。QML_UNCREATABLE注册一个名称化的C++类型,它不能实例化但对于QML类型系统它应该是唯一的。QML_SINGLETON注册一个可以从QML导入的单例类型。

QML与Javascript集成

import QtQuick
import QtQuick.Window
import QtQuick.Controls
Window {
    width: 640; height: 480;
    visible: true
    title: qsTr("QML Object in JS")
    function btnClicked(controlName) {
        controlName.text= "JS called"
    }
    Column {
        anchors.centerIn: parent
        Button {
            text: "Call JS!"
            onClicked:btnClicked(displayText)
        }
        Text {
            id: displayText
        }
    }
}

如果你的逻辑非常长,可以使用单独的JS文件。你可以用下面语句导入JS文件:

import "helloworld.js" as HelloWorld// HelloWorld is an identifier for our JS file

你也可以创建共享库,你得将下面语句包含在JS文件的开头:

.pragma library

在QML中导入目录
通用的导入形式,如下:

import "<DirectoryPath>" [as <Qualifier>]

例如,你的目录名是customqmlelements,那么你可以这样导入:

import ".../customqmlelements"

你也可以将导入的目录作为一个合适的命名空间:

import ".../customqmlelements" as CustomQMLElements

你还可以从资源路径导入文件:

import "qrc:/qml/customqmlelements"

你也可以从远程服务器导入一个目录。

处理鼠标与触摸事件
MouseArea

import QtQuick
import QtQuick.Window
Window {
	width: 640; height: 480
	visible: true
	title: qsTr("Mouse Area Demo")
	Rectangle {
		anchors.centerIn: parent
		width: 100; height: 100
		color: "green"
		MouseArea {
			anchors.fill: parent
			onClicked: { parent.color = 'red' }
		}
	}
}

MultiPointTouchArea

import QtQuick
import QtQuick.Window
Window {
	width: 640; height: 480
	visible: true
	title: qsTr("Multitouch Example")
	MultiPointTouchArea {
		anchors.fill: parent
		touchPoints: [
			TouchPoint {id: tp1},
			TouchPoint {id: tp2}
		]
	}
	Rectangle {
		width: 100; height: 100
		color: "blue"
		x: tp1.x; y: tp1.y
	}
	Rectangle {
		width: 100; height: 100
		color: "red"
		x: tp2.x; y: tp2.y
	}
}

TapHandler
TapHandler是用来在触摸屏上处理鼠标点击事件和触拍事件的。

import QtQuick
import QtQuick.Window
Window {
	width: 640; height: 480
	visible: true
	title: qsTr("Tap Demo")
	Item {
		anchors.fill: parent
		TapHandler {
			acceptedButtons: Qt.LeftButton
			onTapped: console.log("Left Button Clicked")
		}
		TapHandler {
			acceptedButtons: Qt.MiddleButton
			onTapped: console.log("Middle Button Clicked")
		}
		TapHandler {
			acceptedButtons: Qt.RightButton
			onTapped: console.log("Right Button Clicked")
		}
		TapHandler {
			acceptedDevices: PointerDevice.Stylus
			onTapped: console.log("Stylus Tap")
		}
	}
}

SwipView

import QtQuick
import QtQuick.Window
import QtQuick.Controls
Window {
	width: 640; height: 480
	visible: true
	title: qsTr("Swip Demo")
	SwipeView {
		id: swipeView
		currentIndex: 0
		anchors.fill: parent
		Rectangle {id: page1; color: "red"}
		Rectangle {id: page2; color: "green"}
		Rectangle {id: page3; color: "blue"}
	}
	PageIndicator {
		id: pageIndicator
		count: swipeView.count
		currentIndex: swipeView.currentIndex
		anchors {
			bottom: swipeView.bottom
			horizontalCenter: parent.horizontalCenter
		}
	}
}
Qt Quick enables you to build UIs around the behavior of components and how they connect with one another. You create components using Qt Quick and QML types that are available in the Design mode. You can specify values for the properties of a component to change its appearance and behavior. All QML types have a set of predefined properties, some of which control things that are visible to users, while others are used behind the scene. While it is useful to learn the basics of Qt Quick, you can also rely on Qt Design Studio to write the code for you when you drag-and-drop the ready-made components to the working area and change them to your liking by modifying their properties in the Design mode. You can always check up details in the extensive Qt Quick documentation by pressing F1. Creating Qt Quick Projects You can use wizards to create Qt Quick projects. Editing QML Files in Design Mode You can use the Form Editor or the Text Editor in the Design mode to develop Qt Quick applications. Creating Components In addition to your imported artwork, you can use the Design mode to customize ready-made components or design any custom form and shape directly as QML types. You can import visual assets in various formats, such as PNG, JPG, and SVG for use in the components. Managing Item Hierarchy You can manage the items in the current QML file and their relationships in the Navigator. Specifying Item Properties You can specify values for the properties of a component to change its appearance and behavior. All QML types have a set of predefined properties. Some properties, such as position, size, and visibility, are common to all QML types, whereas others are specific to the QML type. You can specify properties for your components in the Properties pane. Creating Animations You can use a timeline and keyframe based editor in the Timeline view to animate the properties of UI components. Animating properties enables their values to move through intermediate values at specified keyframes instead of immediately changing to the target value. Adding Connections You can create connections between the UI components and the application to enable them to communicate with each other. For example, how does the appearance of a button change on a mouse click and which action does the application need to perform in response to it. You can also create connections between UI components by binding their properties together. This way, when the value of a property changes in a parent component, it can be automatically changed in all the child components, for example. Adding States Qt Quick allows you to declare various UI states that describe how component properties change from a base state. Therefore, states can be a useful way of organizing your UI logic. You can associate transitions with items to define how their properties will animate when they change due to a state change. Related Topics Editing PathView Properties You can use a graphical spline editor to specify PathView paths. A path view lays out data provided by data models on a Path. Browsing ISO 7000 Icons You can add ISO 7000 icons from a library delivered with Qt Creator to UIs and change their color. Qt Quick UI Forms Some of the wizards create Qt Quick projects that contain UI forms (.ui.qml files). The forms use a purely declarative subset of the QML language and you can edit them in the Design mode. Using QML Modules with Plugins QML modules may use plugins to expose components defined in C++ to QML applications. Qt Creator cannot load the plugins to determine the details of the contained components, and therefore, the modules must provide extra type information for code completion and the semantic checks to work correctly. Converting UI Projects to Applications Qt Quick UI projects (.qmlproject) are useful for creating user interfaces. To use them for application development, you have to convert them to Qt Quick Application projects that contain .pro, .cpp, and .qrc files. Designing User Interfaces ◦ Creating Qt Quick Projects
最新发布
09-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值