使用了Qt多年,从widget到qml,尽管qml相比widget的确是方便了不少,但是目前仍然缺乏成熟组件库,稍复杂或美观的界面都需要自己造轮子,相对于HTML这边就丰富了很多,其发展已经多年,社区很活跃,有很多成熟的方法和技术可以选择,如vue和react,它们都有其成熟的UI组件库,可拿来即用,要方便很多。
本文将介绍一种基于qml加载vue的方法来构建跨平台应用程序,利用qml作为UI框架的优势,结合vue实现更加灵活和便捷的开发。这种组合将为开发者提供更多的选择和方便。
目录
1 建立qt qml工程
2 qml加载vue
2.1 先准备vue工程
2.2 将vue编译成html
2.3 加载编译后的html到qml
3 qml和vue之间接口的相互调用
3.1 建立通信
3.1.1 qml中添加webchannel
3.1.2 vue中添加webchannel
3.2 vue调用qml接口
3.3 qml调用vue接口
4 源代码
运行效果如下图:
下面介绍构建过程
本文用Qt5.15.2版本
编译工具的时候不能选MingGW,这里我选VS2019 64bit编译工具
建完之后的工程
在资源中加入一个简单的login_test.html登陆作为测试页
Login Page
修改main.qml,加入webengineView,url设置为资源文件中的html页
编译并运行测试
qml已成功拉起了html
接下来我们利用vue工程生成一个html单页应用,然后替换这个测试的html页
运行以下命令创建新的Vue项目:
npm install -g @vue/cli
vue create ui-prj
然后按照提示进行配置,选择需要的特性,其中vue router必须,其他可选。
创建之后,在src/views目录新建两个页
我们用vue的组件库element-ui来构造页面。然后将这两个页挂载到vue router中:
import Vue from 'vue'
import Router from 'vue-router'
import userInfoReg from '@/views/userInfoReg'
import datamgr from '@/views/dataMgr'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/userInfoReg',
name: 'userInfoReg',
component: userInfoReg
},
{
path: '/dataMgr',
name: 'dataMgr',
component: dataMgr
}
]
})
其中userInfoReg.vue界面部分如下:
姓名:
检查时间:
查询
历史检查
继续检查
回首页
详细的代码可在本文底部下载链接中获取。在编写前端之后,用webpack去编译,将vue转化成html
运行如下命令
npm run install
npm run build
这将使用Webpack等构建工具将Vue应用程序转换为HTML、CSS和JavaScript文件,并将这些文件放置在一个名为“dist”的目录中。
将dist目录中的文件全部拷贝至qt qml工程中的html目录下,替换之前的html文件,并将这些文件加入qt资源文件中。
现在qml和vue之间还是相互独立的,qml无法调用vue的接口,vue也无法调用qml中的函数。为了实现互调功能,需要实现两点,一,建立qml和vue之间的通信;二,约定调用的接口规则。
通讯通道可以有一条,也可以是多条,我们这里只建立一条共享通道,供vue页面和qml之间共享使用。所有接口消息都走共享通道,用消息路由做分离,区分何种消息路由到何处做处理,我们这里用hash表做消息分离,用接口名做key,查找到对应接口处理函数即转发调用。
建立通信的方案有一些,比如有:1)websocket、2)webchannel,这里我们采用qt自己提供的webchannel。
我们只建一个webchannel通道,让所有vue页面和qml共享,提高复用减少开销。
添加webchannel,并让webengine引用
import "webChFunc.js" as WebChFunc
QtObject {
id: qtJS
WebChannel.id: "QTJS"
//供qml调用vue的共享接口
signal callhtml(string func, string args)
//vue->qml共享处理,将对应接口消息路由到qml对应接口
function callqml(func, args) {
//用nameToFunc哈希表做消息分离
WebChFunc.nameToFunc[func](args);
}
}
WebChannel {
id:webch
registeredObjects: [qtJS]
}
WebEngineView {
id:webview
z:100
anchors.fill: parent
webChannel: webch
url:"qrc:/html/index.html"
}
在qt自带的webchannel html例子下找到qwebchannel.js,本文用例是在C:\Qt\Examples\Qt-5.15.2\webchannel\shared\qwebchannel.js,将此文件拷贝至qml vue工程src/目录,并更名qt5.15webchannel.js(因为各版本的webchannel略有不同,如果你换了qt版本比如qt5.12,需要取这个版本中的js文件),直接使用会报错:
Uncaught TypeError: Cannot assign to read only property 'exports' of object '#
需要修改一下导出方式:
//注释掉这块
//required for use with nodejs
// if (typeof module === 'object') {
// module.exports = {
// QWebChannel: QWebChannel
// };
// }
//改成这种导出方式
export default {
QWebChannel
};
然后在vue中引用webchannel,并使用,修改main.js,加入如下代码:
// 导入 qwebchannel.js
import qwebchannel from './qt5.15webchannel.js';
//定义一个全局共享的qtCall,供所有页面调用qml的接口
export var qtCall = null;
//定义一个全局共享qtHandle哈希表,用来路由qml->vue到对应页面处理
export var qtHandle = {};
if (process.env.NODE_ENV === 'production')
{
new qwebchannel.QWebChannel(qt.webChannelTransport, (channel) => {
qtCall = channel.objects.QTJS.callqml;
channel.objects.QTJS.callhtml.connect((funcname, args) => {
qtHandle[funcname](args);
});
});
}
根据事先设计的方式,按如下规则定义和调用接口:
将定义写入文件webChFunc.js中,如添加一条数据库记录
nameToFunc["addCheckTb"] = function(args) {
var argobj = JSON.parse(args);
var cid, checktime, name, sex, birthday, height, weight, ts = 0, zs = 0, sos = 0, reportdir = '', conclusion = '', checkpicdir = '', sfz = '';
cid = argobj.cid;
checktime = argobj.checktime;
name = argobj.name;
sex = argobj.sex;
birthday = argobj.birthday;
height = argobj.height;
weight = argobj.weight;
console.log("addCheckTb", cid, checktime, name, sex, birthday, height, weight, ts, zs, sos, reportdir, conclusion, checkpicdir, sfz);
DB.insertCheckTb(cid, checktime, name, sex, birthday, height, weight, ts, zs, sos, reportdir, conclusion, checkpicdir, sfz);
}
addCheckTb就是接口名,参数是JSON格式的字符串
注意:如果这个处理函数非常消耗CPU,可能阻塞UI线程,影响UI的响应,出现假死现象。因此某些耗资源的操作需要借助qt C++里的线程,要用qml调用C++的接口方式处理
在vue某页需要调用的地方,写如下形式代码,例如调用上例的接口:
if (qtCall != null) {
qtCall("addCheckTb", JSON.stringify(qtarg));
}
qtCall第一个参数是qml接口名,第二个参数是接口的,JSON字符串。
按如下规则定义和调用接口:
首先在vue的methods下定义方法,例如列表的返回结果的处理
methods: {
getCheckListRsp(rsp) {
console.log("getCheckListRsp: " + rsp);
let rspobj = JSON.parse(rsp);
this.tableData = rspobj.data;
this.listLoading = false;
},
}
然后在初始时将getCheckListRsp挂载到消息路由表中
created() {
//挂载getCheckListRsp到消息路由表中
qtHandle["getCheckListRsp"] = this.getCheckListRsp;
}
在qml中调用vue接口的方式如下:
qtJS.callhtml("getCheckListRsp", JSON.stringify(rsp));
在需要返回数据的地方调用qtJS.callhtml,第一个参数是vue中的接口名,第二个参数是JSON字符串格式的参数。
https://download.csdn.net/download/chyly/87781328
本文介绍的方法是通过使用HTML来完全接管Qt QML的前端。这种方法允许您在Qt应用程序中利用HTML和CSS来构建用户界面,从而提供更大的灵活性和自定义性。接下来的文章将探讨一种更加灵活的方法,即让Qt QML接管部分前端,并与Qt自带的组件进行混合显示。这种混合显示的方式可以在需要结合Qt的强大功能和自定义UI的场景中发挥作用,为应用程序带来更广泛的应用范围。
技术交流微信:shangfengkj
长沙尚峰https://www.sungfun.com