QML进阶(十)动态加载QML元素 |
您所在的位置:网站首页 › qml文件加载方式 › QML进阶(十)动态加载QML元素 |
通过搭配QML和JavaScript我们可以动态的对QML元素的生命周期进行管理。实现动态加载元素、动态实例化元素、动态销毁元素。同时我们还可以将动态创建的元素持久化到本地,并在需要的时候进行恢复使用。 通过Loader元素实现动态加载通过QML提供的Loader元素实现动态加载是动态加载最简单的方式。Loader相当于提供了一个元素的加载容器,通过指定该元素的source属性,我们可以加载固定URL地址的元素,通过指定该元素的sourceComponent属性,我们可以实例化一个组件。在动态加载组件的时候,如果我们指定了Loader元素的尺寸,那么加载的组件的显示尺寸将和Loader的尺寸保持一致,如果没有指定Loader的尺寸,那么Loader的尺寸将取决于加载的元素。由于在动态加载元素之前,元素是不存在的,所以我们无法通过信号变化事件(onSignalNam)进行信号绑定。对于动态元素的信号绑定,我们可以使用Connection元素.通过动态指定Connection元素的target目标,我们可以动态的关联信号和对应的处理操作。 下面介绍一下Loader元素和Connection元素的用法。 首先定义两个控件作为需要加载的动态元素,代码如下: 1234567891011121314151617181920212223242526//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") } } } } 1234567891011121314151617181920212223242526//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信号,用来捕获控件的点击事件。 将两个控件文件和调用文件放在同一个目录下,这样我们就可以通过相对路径找到对应的控件了。 主界面的调用方法如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192//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属性来访问动态加载的控件。用例的效果如下所示: GIF![]() ![]() 通过Connections我们实现了动态控件的数据的输出,但是如何向动态加载的控件输入外部的数据呢,这时候就需要用到Binding元素了,使用Binding我们可以将外部的属性值和内部的属性值进行绑定,从而实现外部数据向动态控件输入。使用方法如下: 两个动态控件中声明了一个新属性btnText用来表示控件文本。 123456789101112131415161718192021222324252627//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") } } } } 123456789101112131415161718192021222324252627//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") } } } }主界面的调用逻辑如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109import 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信号绑定元素对应的目标。然后通过修改外部属性实现对动态控件内部属性的修改。显示效果如下: GIF![]() ![]() 通过使用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脚本动态创建新对象使用方法如下: 首先在动态控件里面添加可移动属性: 1234567891011121314151617181920212223242526272829303132333435363738394041import 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创建新元素,调用方法如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859import 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脚本中,通过脚本进行加载,对应的脚本内容如下: 12345678910111213141516171819202122//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()); } }在主界面中的导入对应的脚本对象。 1234567891011121314151617181920212223242526272829303132333435363738import 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: "点击加载不同的控件" } } } } }这里有一点一定要注意,一定要将脚本添加到工程资源文件中,要不会报错的。 显示效果如下: GIF![]() ![]() CreateObject方法有两个参数 123//@1指定对象的父对象 //@2以key-value的形式指定对象的属性(@2是选填参数) var image = component.createObject(root, {"x": 50, "y": 50});createObject方法创建组件的过程是同步阻塞的,这也就是说,如果创建一个复杂耗时的组件的话,那么主线程就会被阻塞住。解决这个问题的方法有两种: 方法一 将复杂组件拆分成几个小组件,然后通过Loader元素进行梯度加载 方法二 可以使用incubateObject方法进行实例化,该方法不仅可以像createObject那样立即实例化,也可以通过回调的方式进行异步实例化。 创建方法如下: 123456789101112131415161718192021222324252627//动态的创建新的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(). 该接口的介绍如下: 123//@1:qml字符串的内容 @2:创建对象的父对象 @3:错误信息输出的文件地址 //如果创建成功返回对象,失败的话返回null Qt.createQmlObject("", root, "dynamicItem");使用QML字符串创建QML对象的话,有一点需要注意,那就是在调用之前一定要确保该控件依赖的模块已经被加载了,如果字符串中引用了一个没有加载的库对象,那么创建过程会失败的。 调用方法如下: 123function createItem() { Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 100; width: 100; height: 100; color: "blue" }", root, "dynamicItem"); } 管理动态对象的生命周期我们可以动态的创建元素,也可以动态的销毁元素,但有一点需要注意,一定不要试图销毁静态对象和没有创建的对象。我们可以通过destroy函数,来实现对象的销毁操作,如果我们不想立即销毁的话,还可以指定延迟参数。使用方法如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758import 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"); } }显示效果如下: GIF![]() ![]() |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |