通过搭配QML和JavaScript我们可以动态的对QML元素的生命周期进行管理。实现动态加载元素、动态实例化元素、动态销毁元素。同时我们还可以将动态创建的元素持久化到本地,并在需要的时候进行恢复使用。
通过Loader元素实现动态加载通过QML提供的Loader元素实现动态加载是动态加载最简单的方式。Loader相当于提供了一个元素的加载容器,通过指定该元素的source属性,我们可以加载固定URL地址的元素,通过指定该元素的sourceComponent属性,我们可以实例化一个组件。
在动态加载组件的时候,如果我们指定了Loader元素的尺寸,那么加载的组件的显示尺寸将和Loader的尺寸保持一致,如果没有指定Loader的尺寸,那么Loader的尺寸将取决于加载的元素。
由于在动态加载元素之前,元素是不存在的,所以我们无法通过信号变化事件(onSignalNam)进行信号绑定。对于动态元素的信号绑定,我们可以使用Connection元素
通过动态指定Connection元素的target目标,我们可以动态的关联信号和对应的处理 *** 作。
下面介绍一下Loader元素和Connection元素的用法。
首先定义两个控件作为需要加载的动态元素,代码如下:
//button1.qml
import QtQuick 2.5
Item {
id:myItem
signal message(string msg) //声明信号
Rectangle{
width: 290
height: 30
gradient: Gradient{
GradientStop { color: "#ff9a9e" ; position: 0 }
GradientStop { color: "#fad0c4" ; position: 1 }
}
Text{
anchors.centerIn: parent
color: "white"
text: "Button1"
}
MouseArea{
anchors.fill: parent
onClicked: {
myItem.message("button1")
}
}
}
}
//button2.1ml
import QtQuick 2.5
Item {
id:myItem
signal message(string msg) //声明信号
Rectangle{
width: 290
height: 30
gradient: Gradient{
GradientStop { color: "#ff5858" ; position: 0 }
GradientStop { color: "#f09819" ; position: 1 }
}
Text{
anchors.centerIn: parent
color: "white"
text: "Button2"
}
MouseArea{
anchors.fill: parent
onClicked: {
myItem.message("button2")
}
}
}
}
由于外部无法直接访问控件的内部信息,所以我们需要导出我们需要的属性和信号,这里我导出了一个message信号,用来捕获控件的点击事件。
将两个控件文件和调用文件放在同一个目录下,这样我们就可以通过相对路径找到对应的控件了。
主界面的调用方法如下:
//main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: window
width: 640
height: 480
visible: true
Rectangle {
id: root
x:20;y:20
width: 400
height: 600
color: "white"
//用于动态显示元素
Loader{
id:btnLoader
width:400
height: 50
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: stateChanger.top
}
//点击矩形切换状态
Rectangle {
id: stateChanger
height: 60
color: "#4D9CF8"
anchors.top: btnLoader.bottom
anchors.left: parent.left
anchors.right: parent.right
MouseArea {
anchors.fill: parent
onClicked:{
if(root.state == "button1")
{
root.state = "button2"
}
else
{
root.state = "button1"
}}
}
Text {
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
text: "点击加载不同的控件"
}
}
//用来显示接收到的信号
Rectangle{
id:clickInfo
anchors.top: stateChanger.bottom
width: 290
height: 30
Text{
id:clickInfoText
font.pixelSize: 20
anchors.fill: parent
text:"显示结果"
}
}
//用来关联动态元素的信号属性
Connections{
id:connections
target:btnLoader.item
onMessage:{
clickInfoText.text = msg
}
}
state: "button1"
states: [
State {
name: "button1"
PropertyChanges { target: btnLoader; source: "button1.qml"; }
},
State {
name: "button2"
PropertyChanges { target: btnLoader; source: "button2.qml"; }
}
]
}
}
在界面的入口中,我们通过点击按钮切换state状态实现Loader的source属性修改从而加载不同的控件。同时我们通过Connections元素关联动态控件的信号和对应的 *** 作。
这里有一点要注意我们通过Loader的Item属性来访问动态加载的控件。用例的效果如下所示:
除了动态元素外,针对静态元素我们也可以使用Connections来实现信号和对应的处理函数的关联。这里有一点需要注意,当使用一个Connections关联不同元素的不同信号的时候,有时候你会发现由于不同元素的信号不同,Connections运行的时候会报某些信号丢失的错误,我们可以通过将ignoreUnknownSignal属性设置为true来屏蔽这些错误。
通过Connections我们实现了动态控件的数据的输出,但是如何向动态加载的控件输入外部的数据呢,这时候就需要用到Binding元素了,使用Binding我们可以将外部的属性值和内部的属性值进行绑定,从而实现外部数据向动态控件输入。使用方法如下:
两个动态控件中声明了一个新属性btnText用来表示控件文本。
//button1.qml
import QtQuick 2.5
Item {
id:myItem
signal message(string msg) //声明信号
//和外部绑定的属性
property string btnText: "Button1"
Rectangle{
width: 290
height: 30
gradient: Gradient{
GradientStop { color: "#ff9a9e" ; position: 0 }
GradientStop { color: "#fad0c4" ; position: 1 }
}
Text{
anchors.centerIn: parent
color: "white"
text:btnText
}
MouseArea{
anchors.fill: parent
onClicked: {
myItem.message("button1")
}
}
}
}
//button2.qml
import QtQuick 2.5
Item {
id:myItem
signal message(string msg) //声明信号
//和外部绑定的属性
property string btnText: "Button2"
Rectangle{
width: 290
height: 30
gradient: Gradient{
GradientStop { color: "#ff5858" ; position: 0 }
GradientStop { color: "#f09819" ; position: 1 }
}
Text{
anchors.centerIn: parent
color: "white"
text: btnText
}
MouseArea{
anchors.fill: parent
onClicked: {
myItem.message("button2")
}
}
}
}
主界面的调用逻辑如下:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: window
width: 640
height: 480
visible: true
property string inputBtntext;
Rectangle {
id: root
x:20;y:20
width: 400
height: 600
color: "white"
//用于动态显示元素
Loader{
id:btnLoader
width:400
height: 50
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: stateChanger.top
onLoaded: {
buttonTextBinder.target = btnLoader.item
}
}
//绑定外部元素和内部属性
Binding{
id: buttonTextBinder
//动态元素的属性
property: "btnText"
//调用界面中的属性值
value: inputBtntext
when: (inputText.length != 0)
}
//点击矩形切换状态
Rectangle {
id: stateChanger
height: 60
color: "#4D9CF8"
anchors.top: btnLoader.bottom
anchors.left: parent.left
anchors.right: parent.right
MouseArea {
anchors.fill: parent
onClicked:{
if(root.state == "button1")
{
root.state = "button2"
}
else
{
root.state = "button1"
}}
}
Text {
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
text: "点击加载不同的控件"
}
}
//用来显示接收到的信号
Rectangle{
id:clickInfo
anchors.top: stateChanger.bottom
width: 290
height: 30
Text{
id:clickInfoText
font.pixelSize: 20
anchors.fill: parent
text:"显示结果"
}
}
TextEdit{
id:inputText
anchors.top: clickInfo.bottom
width: 290
height: 30
onTextChanged: {
inputBtntext = inputText.text
}
}
//用来关联动态元素的信号属性
Connections{
id:connections
target:btnLoader.item
onMessage:{
clickInfoText.text = msg
}
}
state: "button1"
states: [
State {
name: "button1"
PropertyChanges { target: btnLoader; source: "button1.qml"; }
},
State {
name: "button2"
PropertyChanges { target: btnLoader; source: "button2.qml"; }
}
]
}
}
在Loader加载完毕的时候,我们通过 onLoaded信号绑定元素对应的目标。然后通过修改外部属性实现对动态控件内部属性的修改。显示效果如下:
Binding元素有两个属性需要我们注意一下,分别是when和delayed。通过when属性的设置,我们可以设置绑定的生效时机,从而实现动态的绑定对应的属性值。通过将delayed属性设置为true,我们可以确保只有在事件队列为空的时候绑定才会把对应的数值推送给控件,避免事件队列阻塞,从而提升响应性能。
通过QML API接口实现动态加载通过使用Qt的API我们可以将内部资源文件、本地的QML文件、远程URL地址的文件甚至是内存里面的字符串解析成一个Component,然后实例化成一个QML可以访问的对象。
通过记录Component的status属性,我们可以监测组件的加载进度,加载过程一般分为以下几个阶段:Component.Null, Component.Loading, Component.Ready 和Component.Error.。Comonnet.Null说明还没有开始加载,Componnet.Loading说明正在加载过程中,Component.Ready说明加载完毕,在加载过程中都有可能因为某种错误使得组件进入Component.Error错误,此时我们可以通过Componnet.errorString来查看错误信息。当然我们还可以通过progress属性来进行进度的判断。属性值范围为0.0~1.0,0.0表示还没有加载,1.0表示已经加载完毕。当Component的status的值变成Ready的时候我们就可以拿它来实例化对象了。
通过JaveScript脚本动态创建新对象使用方法如下:
首先在动态控件里面添加可移动属性:
import QtQuick 2.5
Item {
id:myItem
signal message(string msg) //声明信号
//和外部绑定的属性
property string btnText: "Button1"
Rectangle{
width: 290
height: 30
gradient: Gradient{
GradientStop { color: "#ff9a9e" ; position: 0 }
GradientStop { color: "#fad0c4" ; position: 1 }
}
Text{
anchors.centerIn: parent
color: "white"
text:btnText
}
MouseArea{
anchors.fill: parent
onClicked: {
myItem.message("button1")
}
property real lastX: 0
property real lastY: 0
onPressed: {
//鼠标按下时,记录鼠标初始位置
lastX = mouseX
lastY = mouseY
}
onPositionChanged: {
if (pressed) {
//计算新新位置
myItem.x += mouseX - lastX
myItem.y += mouseY - lastY
}
}
}
}
}
然后在主界面里面动态的通过API创建新元素,调用方法如下:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: window
width: 640
height: 480
visible: true
property string inputBtntext;
Rectangle {
id: root
x:20;y:20
width: 400
height: 600
color: "white"
//点击矩形切换状态
Rectangle {
id: stateChanger
x:50;y:100
height: 60
width: 300
color: "#4D9CF8"
MouseArea {
anchors.fill: parent
onClicked:createButton();
Text {
anchors.centerIn: parent
font.pixelSize: 20
color: "white"
text: "点击加载不同的控件"
}
}
}
}
//动态的创建新的UI元素
property var component;
function createButton() {
component = Qt.createComponent("button1.qml");
if (component.status === Component.Ready || component.status === Component.Error) {
finishCreation();
} else {
component.statusChanged.connect(finishCreation);
}
}
function finishCreation() {
if (component.status === Component.Ready) {
var image = component.createObject(root, {"x": 100, "y": 100});
if (image === null) {
console.log("Error creating button");
}
} else if (component.status === Component.Error) {
console.log("Error loading component:", component.errorString());
}
}
}
当然我们也可以将对应的创建过程写入到JavaScript脚本中,通过脚本进行加载,对应的脚本内容如下:
//createObject.js
//动态的创建新的UI元素
var component;
function createButton() {
component = Qt.createComponent("button1.qml");
if (component.status === Component.Ready || component.status === Component.Error) {
finishCreation();
} else {
component.statusChanged.connect(finishCreation);
}
}
function finishCreation() {
if (component.status === Component.Ready) {
var image = component.createObject(root, {"x": 100, "y": 100});
if (image === null) {
console.log("Error creating button");
}
} else if (component.status === Component.Error) {
console.log("Error loading component:", component.errorString());
}
}
在主界面中的导入对应的脚本对象。
import QtQuick 2.5
import QtQuick.Window 2.2
import "createObject.js" as ButtonCreator
Window {
id: window
width: 640
height: 480
visible: true
property string inputBtntext;
Rectangle {
id: root
x:20;y:20
width: 400
height: 600
color: "white"
//点击矩形切换状态
Rectangle {
id: stateChanger
x:50;y:100
height: 60
width: 300
color: "#4D9CF8"
MouseArea {
anchors.fill: parent
onClicked:ButtonCreator.createButton();
Text {
anchors.centerIn: parent
font.pixelSize: 20
color: "white"
text: "点击加载不同的控件"
}
}
}
}
}
这里有一点一定要注意,一定要将脚本添加到工程资源文件中,要不会报错的。
显示效果如下:
CreateObject函数是Compnent元素的对象创建接口,它不仅适用于动态创建的Component元素,静态存在的也可以。因此我们也可以实例化已经存在的组件对象。动态创建的对象和静态对象没有差异,唯一的不同就是动态创建的对象没有ID。
CreateObject方法有两个参数
//@1指定对象的父对象
//@2以key-value的形式指定对象的属性(@2是选填参数)
var image = component.createObject(root, {"x": 50, "y": 50});
createObject方法创建组件的过程是同步阻塞的,这也就是说,如果创建一个复杂耗时的组件的话,那么主线程就会被阻塞住。解决这个问题的方法有两种:
方法一
将复杂组件拆分成几个小组件,然后通过Loader元素进行梯度加载
方法二
可以使用incubateObject方法进行实例化,该方法不仅可以像createObject那样立即实例化,也可以通过回调的方式进行异步实例化。
创建方法如下:
//动态的创建新的UI元素
var component;
function createButton() {
component = Qt.createComponent("button1.qml");
if (component.status === Component.Ready || component.status === Component.Error) {
finishCreationg();
} else {
component.statusChanged.connect(finishCreation);
}
}
function finishCreationg() {
if (component.status === Component.Ready) {
var incubator = component.incubateObject(root, {"x": 100, "y": 100});
if (incubator.status === Component.Ready) {
//立即创建
var image = incubator.object;
} else {
incubator.onStatusChanged = function(status) {
if (status === Component.Ready) {
//异步创建
var image = incubator.object;
}
};
}
}
}
通过字符串动态创建新对象
相比于通过独立的文件创建QML对象,通过字符串创建QML对象更加高效便捷。通过字符串创建对象的话我们需要新接口Qt.createQmlObject().
该接口的介绍如下:
//@1:qml字符串的内容 @2:创建对象的父对象 @3:错误信息输出的文件地址
//如果创建成功返回对象,失败的话返回null
Qt.createQmlObject("", root, "dynamicItem");
使用QML字符串创建QML对象的话,有一点需要注意,那就是在调用之前一定要确保该控件依赖的模块已经被加载了,如果字符串中引用了一个没有加载的库对象,那么创建过程会失败的。
调用方法如下:
function createItem() {
Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 100; width: 100; height: 100; color: \"blue\" }", root, "dynamicItem");
}
管理动态对象的生命周期
我们可以动态的创建元素,也可以动态的销毁元素,但有一点需要注意,一定不要试图销毁静态对象和没有创建的对象。我们可以通过destroy函数,来实现对象的销毁 *** 作,如果我们不想立即销毁的话,还可以指定延迟参数。使用方法如下:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: window
width: 640
height: 480
visible: true
Rectangle {
id: root
x:20;y:20
width: 400
height: 600
color: "white"
//点击矩形切换状态
Rectangle {
id: createor
x:50;y:100
height: 60
width: 300
color: "#4D9CF8"
MouseArea {
anchors.fill: parent
onClicked:createItem()
Text {
anchors.centerIn: parent
font.pixelSize: 20
color: "white"
text: "创建控件"
}
}
}
//点击矩形切换状态
Rectangle {
id: destoryMaker
x:50;y:200
height: 60
width: 300
color: "#4D9CF8"
MouseArea {
anchors.fill: parent
//延时1000毫秒销毁
onClicked:dynamicItem.destroy(1000)
Text {
anchors.centerIn: parent
font.pixelSize: 20
color: "white"
text: "销毁控件"
}
}
}
}
property var dynamicItem;
function createItem() {
dynamicItem = Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 20; width: 200; height: 50; color: \"#4D9CF8\" }", root, "dynamicItem");
}
}
显示效果如下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)