• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

采用Qt + QML + Vue构建应用程序附源码

武飞扬头像
长沙尚峰
帮助1

使用了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 源代码


运行效果如下图:

学新通

学新通

下面介绍构建过程

1 建立qt qml工程

本文用Qt5.15.2版本

学新通

学新通

编译工具的时候不能选MingGW,这里我选VS2019 64bit编译工具

学新通

 建完之后的工程

学新通

在资源中加入一个简单的login_test.html登陆作为测试页

  1.  
    <!DOCTYPE html>
  2.  
    <html lang="en">
  3.  
    <head>
  4.  
    <meta charset="UTF-8">
  5.  
    <title>Login Page</title>
  6.  
    <style>
  7.  
    /* 添加一些基本的样式 */
  8.  
    body {
  9.  
    font-family: Arial, sans-serif;
  10.  
    background-color: #f4f4f4;
  11.  
    }
  12.  
    form {
  13.  
    background-color: #fff;
  14.  
    padding: 20px;
  15.  
    margin: 20px auto;
  16.  
    max-width: 500px;
  17.  
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  18.  
    }
  19.  
    label {
  20.  
    display: block;
  21.  
    margin-bottom: 10px;
  22.  
    }
  23.  
    input[type="text"],
  24.  
    input[type="password"] {
  25.  
    display: block;
  26.  
    width: 100%;
  27.  
    padding: 10px;
  28.  
    margin-bottom: 20px;
  29.  
    border: 1px solid #ccc;
  30.  
    border-radius: 4px;
  31.  
    box-sizing: border-box;
  32.  
    }
  33.  
    button[type="submit"] {
  34.  
    background-color: #4CAF50;
  35.  
    color: #fff;
  36.  
    padding: 10px 20px;
  37.  
    border: none;
  38.  
    border-radius: 4px;
  39.  
    cursor: pointer;
  40.  
    }
  41.  
    button[type="submit"]:hover {
  42.  
    background-color: #3e8e41;
  43.  
    }
  44.  
    </style>
  45.  
    </head>
  46.  
    <body>
  47.  
    <form>
  48.  
    <h1>Login</h1>
  49.  
    <label for="username">用户名:</label>
  50.  
    <input type="text" id="username" name="username">
  51.  
     
  52.  
    <label for="password">密码:</label>
  53.  
    <input type="password" id="password" name="password">
  54.  
     
  55.  
    <button type="submit">登陆</button>
  56.  
    </form>
  57.  
    </body>
  58.  
    </html>
学新通

 修改main.qml,加入webengineView,url设置为资源文件中的html页

学新通

编译并运行测试

学新通

qml已成功拉起了html

接下来我们利用vue工程生成一个html单页应用,然后替换这个测试的html页

2 qml加载vue

2.1 先准备vue工程

运行以下命令创建新的Vue项目:

  1.  
    npm install -g @vue/cli
  2.  
    vue create ui-prj

然后按照提示进行配置,选择需要的特性,其中vue router必须,其他可选。

创建之后,在src/views目录新建两个页

  • userInfoReg.vue:用户注册
  • dataMgr.vue:数据管理

我们用vue的组件库element-ui来构造页面。然后将这两个页挂载到vue router中:

  1.  
    import Vue from 'vue'
  2.  
    import Router from 'vue-router'
  3.  
    import userInfoReg from '@/views/userInfoReg'
  4.  
    import datamgr from '@/views/dataMgr'
  5.  
     
  6.  
    Vue.use(Router)
  7.  
     
  8.  
    export default new Router({
  9.  
    routes: [
  10.  
    {
  11.  
    path: '/userInfoReg',
  12.  
    name: 'userInfoReg',
  13.  
    component: userInfoReg
  14.  
    },
  15.  
    {
  16.  
    path: '/dataMgr',
  17.  
    name: 'dataMgr',
  18.  
    component: dataMgr
  19.  
    }
  20.  
    ]
  21.  
    })
学新通

其中userInfoReg.vue界面部分如下:

  1.  
    <template>
  2.  
    <ContainerExt title="数据管理">
  3.  
    <div class="main_c">
  4.  
    <div class="headinfo_c">
  5.  
    <div class="searchinput_c">
  6.  
    <div class="searchname_c">
  7.  
    <span>
  8.  
    姓名:
  9.  
    </span>
  10.  
    <el-input
  11.  
    placeholder="请输入姓名"
  12.  
    prefix-icon="el-icon-search"
  13.  
    v-model="searchname"
  14.  
    style="width: 150px;"
  15.  
    clearable>
  16.  
    </el-input>
  17.  
    </div>
  18.  
     
  19.  
    <div class="searchdate_c">
  20.  
    <span>
  21.  
    检查时间:
  22.  
    </span>
  23.  
    <el-date-picker
  24.  
    v-model="searchdate"
  25.  
    type="daterange"
  26.  
    range-separator="至"
  27.  
    start-placeholder="开始日期"
  28.  
    end-placeholder="结束日期">
  29.  
    </el-date-picker>
  30.  
    </div>
  31.  
     
  32.  
    </div>
  33.  
    <div class="searchbotton_c">
  34.  
    <el-button type="primary" @click="handleSearch">查询</el-button>
  35.  
    </div>
  36.  
    </div>
  37.  
    <div class="databoard_c">
  38.  
    <el-table
  39.  
    :data="tableData"
  40.  
    v-loading="listLoading"
  41.  
    highlight-current-row
  42.  
    border
  43.  
    style="width: 100%" height="100%"
  44.  
    @row-click="handleRowClick"
  45.  
    :header-cell-style="{background:'#f5f7fa'}">
  46.  
    <el-table-column
  47.  
    prop="cid"
  48.  
    label="ID"
  49.  
    align="center">
  50.  
    </el-table-column>
  51.  
    <el-table-column
  52.  
    prop="name"
  53.  
    label="姓名"
  54.  
     
  55.  
    align="center">
  56.  
    </el-table-column>
  57.  
     
  58.  
    <el-table-column
  59.  
    prop="birthday"
  60.  
    label="生日"
  61.  
     
  62.  
    align="center">
  63.  
    </el-table-column>
  64.  
     
  65.  
    <el-table-column
  66.  
    prop="sex"
  67.  
    label="性别"
  68.  
    align="center">
  69.  
    </el-table-column>
  70.  
     
  71.  
    <el-table-column
  72.  
    prop="zs"
  73.  
    label="检测值"
  74.  
    align="center">
  75.  
    </el-table-column>
  76.  
     
  77.  
    <el-table-column
  78.  
    prop="checktime"
  79.  
    label="检查时间"
  80.  
     
  81.  
    align="center">
  82.  
    </el-table-column>
  83.  
     
  84.  
    <template slot="empty">
  85.  
    <el-empty :image-size="100" description="空空的"></el-empty>
  86.  
    </template>
  87.  
    </el-table>
  88.  
    </div>
  89.  
     
  90.  
    <div class="tail_c">
  91.  
    <div class="tail2_c">
  92.  
    <div>
  93.  
    <el-button type="primary" style="width:113px;height: 55px;font-size: 18px;" @click="handlehistory">历史检查</el-button>
  94.  
    </div>
  95.  
    <div>
  96.  
    <el-button type="primary" style="width:113px;height: 55px;font-size: 18px;" @click="handlegoon">继续检查</el-button>
  97.  
    </div>
  98.  
    <div>
  99.  
    <el-button type="primary" style="width:113px;height: 55px;font-size: 18px;" @click="handleback">回首页</el-button>
  100.  
    </div>
  101.  
    </div>
  102.  
    </div>
  103.  
    </div>
  104.  
    </ContainerExt>
  105.  
     
  106.  
    </template>
学新通

详细的代码可在本文底部下载链接中获取。在编写前端之后,用webpack去编译,将vue转化成html

2.2 将vue编译成html

运行如下命令

  1.  
    npm run install
  2.  
    npm run build

这将使用Webpack等构建工具将Vue应用程序转换为HTML、CSS和JavaScript文件,并将这些文件放置在一个名为“dist”的目录中。

2.3 加载编译后的html到qml

将dist目录中的文件全部拷贝至qt qml工程中的html目录下,替换之前的html文件,并将这些文件加入qt资源文件中。

学新通

3 qml和vue之间接口的相互调用

现在qml和vue之间还是相互独立的,qml无法调用vue的接口,vue也无法调用qml中的函数。为了实现互调功能,需要实现两点,一,建立qml和vue之间的通信;二,约定调用的接口规则。

通讯通道可以有一条,也可以是多条,我们这里只建立一条共享通道,供vue页面和qml之间共享使用。所有接口消息都走共享通道,用消息路由做分离,区分何种消息路由到何处做处理,我们这里用hash表做消息分离,用接口名做key,查找到对应接口处理函数即转发调用。

3.1 建立通信

建立通信的方案有一些,比如有:1)websocket、2)webchannel,这里我们采用qt自己提供的webchannel。

我们只建一个webchannel通道,让所有vue页面和qml共享,提高复用减少开销。

3.1.1 qml中添加webchannel

添加webchannel,并让webengine引用

  1.  
    import "webChFunc.js" as WebChFunc
  2.  
     
  3.  
    QtObject {
  4.  
    id: qtJS
  5.  
    WebChannel.id: "QTJS"
  6.  
     
  7.  
    //供qml调用vue的共享接口
  8.  
    signal callhtml(string func, string args)
  9.  
     
  10.  
    //vue->qml共享处理,将对应接口消息路由到qml对应接口
  11.  
    function callqml(func, args) {
  12.  
    //用nameToFunc哈希表做消息分离
  13.  
    WebChFunc.nameToFunc[func](args);
  14.  
    }
  15.  
    }
  16.  
     
  17.  
    WebChannel {
  18.  
    id:webch
  19.  
    registeredObjects: [qtJS]
  20.  
    }
  21.  
     
  22.  
    WebEngineView {
  23.  
    id:webview
  24.  
    z:100
  25.  
    anchors.fill: parent
  26.  
    webChannel: webch
  27.  
     
  28.  
    url:"qrc:/html/index.html"
  29.  
    }
学新通

3.1.2 vue中添加webchannel

在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 '#<Object>'

需要修改一下导出方式:

  1.  
    //注释掉这块
  2.  
    //required for use with nodejs
  3.  
    // if (typeof module === 'object') {
  4.  
    // module.exports = {
  5.  
    // QWebChannel: QWebChannel
  6.  
    // };
  7.  
    // }
  8.  
     
  9.  
    //改成这种导出方式
  10.  
    export default {
  11.  
    QWebChannel
  12.  
    };

然后在vue中引用webchannel,并使用,修改main.js,加入如下代码:

  1.  
    // 导入 qwebchannel.js
  2.  
    import qwebchannel from './qt5.15webchannel.js';
  3.  
     
  4.  
    //定义一个全局共享的qtCall,供所有页面调用qml的接口
  5.  
    export var qtCall = null;
  6.  
    //定义一个全局共享qtHandle哈希表,用来路由qml->vue到对应页面处理
  7.  
    export var qtHandle = {};
  8.  
     
  9.  
    if (process.env.NODE_ENV === 'production')
  10.  
    {
  11.  
    new qwebchannel.QWebChannel(qt.webChannelTransport, (channel) => {
  12.  
    qtCall = channel.objects.QTJS.callqml;
  13.  
     
  14.  
    channel.objects.QTJS.callhtml.connect((funcname, args) => {
  15.  
    qtHandle[funcname](args);
  16.  
    });
  17.  
    });
  18.  
    }
学新通

3.2 vue调用qml接口

根据事先设计的方式,按如下规则定义和调用接口:

  • 定义qml的接口

将定义写入文件webChFunc.js中,如添加一条数据库记录

  1.  
    nameToFunc["addCheckTb"] = function(args) {
  2.  
    var argobj = JSON.parse(args);
  3.  
    var cid, checktime, name, sex, birthday, height, weight, ts = 0, zs = 0, sos = 0, reportdir = '', conclusion = '', checkpicdir = '', sfz = '';
  4.  
     
  5.  
    cid = argobj.cid;
  6.  
    checktime = argobj.checktime;
  7.  
    name = argobj.name;
  8.  
    sex = argobj.sex;
  9.  
    birthday = argobj.birthday;
  10.  
    height = argobj.height;
  11.  
    weight = argobj.weight;
  12.  
     
  13.  
    console.log("addCheckTb", cid, checktime, name, sex, birthday, height, weight, ts, zs, sos, reportdir, conclusion, checkpicdir, sfz);
  14.  
     
  15.  
    DB.insertCheckTb(cid, checktime, name, sex, birthday, height, weight, ts, zs, sos, reportdir, conclusion, checkpicdir, sfz);
  16.  
    }
学新通

addCheckTb就是接口名,参数是JSON格式的字符串

注意:如果这个处理函数非常消耗CPU,可能阻塞UI线程,影响UI的响应,出现假死现象。因此某些耗资源的操作需要借助qt C 里的线程,要用qml调用C 的接口方式处理

  • vue调用qml某接口

在vue某页需要调用的地方,写如下形式代码,例如调用上例的接口:

  1.  
    if (qtCall != null) {
  2.  
    qtCall("addCheckTb", JSON.stringify(qtarg));
  3.  
    }

qtCall第一个参数是qml接口名,第二个参数是接口的,JSON字符串。

3.3 qml调用vue接口

按如下规则定义和调用接口:

  • 定义vue的接口

首先在vue的methods下定义方法,例如列表的返回结果的处理

  1.  
    methods: {
  2.  
    getCheckListRsp(rsp) {
  3.  
    console.log("getCheckListRsp: " rsp);
  4.  
    let rspobj = JSON.parse(rsp);
  5.  
    this.tableData = rspobj.data;
  6.  
    this.listLoading = false;
  7.  
    },
  8.  
     
  9.  
    }

然后在初始时将getCheckListRsp挂载到消息路由表中

  1.  
    created() {
  2.  
    //挂载getCheckListRsp到消息路由表中
  3.  
    qtHandle["getCheckListRsp"] = this.getCheckListRsp;
  4.  
     
  5.  
    }
  • qml调用vue的接口

在qml中调用vue接口的方式如下:

    qtJS.callhtml("getCheckListRsp", JSON.stringify(rsp));

在需要返回数据的地方调用qtJS.callhtml,第一个参数是vue中的接口名,第二个参数是JSON字符串格式的参数。

4 源代码

https://download.csdn.net/download/chyly/87781328

本文介绍的方法是通过使用HTML来完全接管Qt QML的前端。这种方法允许您在Qt应用程序中利用HTML和CSS来构建用户界面,从而提供更大的灵活性和自定义性。接下来的文章将探讨一种更加灵活的方法,即让Qt QML接管部分前端,并与Qt自带的组件进行混合显示。这种混合显示的方式可以在需要结合Qt的强大功能和自定义UI的场景中发挥作用,为应用程序带来更广泛的应用范围。

长沙尚峰https://www.sungfun.com

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgebjib
系列文章
更多 icon
同类精品
更多 icon
继续加载