QCefView + QWebChannel + Vue 项目开发

2023-11-09

       看到标题,你大概能猜到这篇我想讲述的是什么了。对的,将要同大家分享的是一种目前PC软件常见的开发方案。

前言

1、桌面应用为什么要使用Vue等框架开发,而不再是Qt UI开发?       

     界面开发我们使用java语言,通过Vue框架快速开发,好处不言而喻,开发速度快,界面美观,同时兼具跨平台特性。其次,网页端与PC应用可同步开发和维护,同步更新,上线快,后期维护成本低。

    有朋友会问,Qt的QML和QSS也完全可以做出极其精美和出彩的界面来呀,不可否认,但是这样做还有一个不可比拟的优势:可将项目打包发布到云服务器,后期界面更新、维护更加方便,不必因为界面布局的修改而被动升级更新桌面应用程序。就拿前4.6的全国哀悼日来说,我们的桌面应用也可以变灰,也可以即时做出样式风格的调整。

2、网页加载为什么用QCefView(或者CEF),而不是Qt自带的 QWebEngineView或者QWebKit组件?

     不再使用QWebEngineVIew 和QWebKit,实在是因为笔者在实际的项目开发中遇到了太多的坑。

     QWebKit : 如果是出学Qt,需要用到Qt加载和渲染网页的朋友,Qt5以下的版本,你可以尝试QWebKit套件,真心讲,如果不做Qt与JS之间的频繁通讯,webkit组件够用,而且系统和平台的兼容性也比较好,使用简单,开发速度也很快。前期我在使用Qt4.8.3的版本开发过的一个项目中,亦使用webkit加载百度地图,实现锚点、动态信息展示等,效果还可以,但是不得不否认他存在严重的内存泄露,这也是为什么Qt5以后,QWebKit组件被放弃的原因。但是细心的朋友可以看到WPS实际上也是使用了Qt4+webkit的方案,但是内存上泄漏并不严重,WPS做了某些处理就不得而知了。

     QWebEngineView:不使用QWebEngineView的原因很简单,一个是只支持MSVC编译工具链,接口不丰富,无法和QFrame进行交互;其次就是系统兼容性真的很不友好。如果系统自带的.netframe版本过低,QWebengineView 编译的程序在windows7无法运行;

    笔者曾在windows7下部署 运行调试环境,在QWebEngineView实例化对象时总是崩溃,后来发现直接和根本原因是跟计算机显卡驱动有关系。

(1)QWebEngineView在运行之前需要检查本地硬件环境,硬件必须要支持OpenGL2.0以上的版本,否则无法运行。

(2)机器的显卡和系统所带的显卡驱动不匹配,导致QtWebEngine在渲染时出现了崩溃。用户需要手动更新显卡驱动来解决这个问题。

   QCefView : 基于CEF的封装,对硬件要求低,性能好(XP、windowNT和其他Unix、MacOS都可以支持)。CEF提供了很多复杂精细的功能,相对比较难理解,但是QCefView做了封装之后,难度减少很多 。爱打Dota1的朋友可以看看 09电竞平台使用的也是Qt+libcef的方案。

3、为什么使用QWebChannel来实现Qt与JS的通讯,而不是我们在上一篇(QCefView(2)—— 实现Qt与JS之间的通讯)中向大家介绍的方法?

     首先说明,上一篇讲诉的方法还是很不错的,参照demo,如果是加载本地的HTML页面,值得推荐:但是在我们使用Vue框架时,会报出QCefClient未定义的问题,原因是Vue中并没有加载此模块,自然无法识得;不需要使用QCefClient的方法也有,如果你有足够的能力,可以在CEF的基础上自己进行封装,看到简书上有大神的教程过,可以去做参考下。所以QWebChannel才成为了备选方案,一个是因为Qt已经为QWebChannel做了足够的完善,在Vue中我们也可以很方便的通过 

import { QWebChannel } from "@calvinscofield/qwebchannel"

进行导入,其次就是其通讯基于本地的websocket进行,实时性也很高,操作起来简单明了,故而成了明智之选。还是说到 09电竞平台,你可以看到其通讯部分实际上也是采用了QWebChannel组件。

 

项目开发

1、在Vue中进行通讯配置

伪代码如下:
import { QWebChannel } from "@calvinscofield/qwebchannel"

...

 /// 譬如在某个按钮点击时调用add()发送消息给Qt
 add() {
    this.websocketsend()   
 },

    // websocket连接
 initWebpack() {
      //初始化websocket
     if ("WebSocket" in window) {
        const baseUrl = "ws://localhost:32145";
        this.websock = new WebSocket(baseUrl); //这里面的this都指向vue
        this.websock.onopen = this.websocketopen;
        this.websock.onmessage = this.websocketonmessage;
        this.websock.onclose = this.websocketclose;
        this.websock.onerror = this.websocketerror;
     } else {
        // 浏览器不支持 WebSocket
        alert("您的浏览器不支持 WebSocket!");
     }
  },

 websocketopen() {
      //打开
      console.log("WebSocket连接成功");
          new QWebChannel(this.websock, function(channel) {
            window.bound = channel.objects.CPPJSInterface //连接qt信号
             window.bound.CPPToJSFunc.connect(function(returnValue, val) {
          //通过信号接收返回值
          alert("returnValue=" + returnValue + val)
      })
      })
    },
    
    websocketsend() { // 数据发送

      var str1 = 80
      var str2 = "world"
      window.bound.CPPJSInterfaceFunc(str1, str2)
    },
    websocketonmessage(e) {
      //数据接收
     
    },
    websocketclose(e) {
      //关闭
      console.log("connection closed (" + e.code + ")");
      console.log("WebSocket关闭");
    },
    websocketerror() {
      //失败
      console.log("WebSocket连接失败");
    },

2、在Qt中进行页面加载和通讯模块编写

2.1  通过QCefView加载网页

        QHBoxLayout* layout = new QHBoxLayout();
	layout->setContentsMargins(2, 2, 2, 2);
	layout->setSpacing(3);

#if defined(LOCAL_TEST)  // test local html
	QDir dir = QCoreApplication::applicationDirPath();
	QString uri = QDir::toNativeSeparators(dir.filePath("QCefViewTestPage.html"));
	cefview = new CustomCefView(uri, this);
#else  // load webpage
	cefview = new CustomCefView(CUR_IOT_URL, this); // www.unimatiot.com
#endif //LOCAL_TEST
 	
	layout->addWidget(cefview);
	centralWidget()->setLayout(layout)

2.2 通过QWebChannel实现通讯

(1)在Qt上,创建一个本地websocket服务器端,网页端则是对应的websocket客户端,注意端口号一致。

(2)注意启动顺序,应先启动websocket服务器,然后再实例化窗体对象(窗体中包含了QCefView的子对象,会在窗体对象实例化过程中加载网页,即启动websocket客户端程序),否则会导致网页上websocket客户端无法连接到本地websocket服务器。

(3)注册对象:

channel.registerObject(QStringLiteral("CPPJSInterface"), &core);

那么,在JS中便可以通过”CPPJSInterface“ 对象来调用 Qt中 core class中的槽函数实现 JS发送消息给Qt了。

int main(){
    QApplication a(argc, argv);
    QFileInfo jsFileInfo(QDir::currentPath() + "/qwebchannel.js");

	if (!jsFileInfo.exists()) QFile::copy(":/qtwebchannel/qwebchannel.js", jsFileInfo.absoluteFilePath());

	// setup the QWebSocketServer
	QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode);
	if (!server.listen(QHostAddress::LocalHost, 32145)) {
		qFatal("Failed to open web socket server.");
		return 1;
	}

	// wrap WebSocket clients in QWebChannelAbstractTransport objects
	WebSocketClientWrapper clientWrapper(&server);

	// setup the channel
	QWebChannel channel;
	QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
		&channel, &QWebChannel::connectTo);

	UManager w;
	
	// setup the core and publish it to the QWebChannel
	Core core(&w);
    // 注册通讯对象==》QWebChannle进行通讯很重要的一点
	channel.registerObject(QStringLiteral("CPPJSInterface"), &core);

    w.show();
    return a.exec();
}

  在core.h中我们做消息的收发处理:

#ifndef CORE_H
#define CORE_H

#include "umanager.h"
#include <QObject>

/*
    An instance of this class gets published over the WebChannel and is then accessible to HTML clients.
*/
class Core : public QObject
{
    Q_OBJECT

public:
    Core(UManager *dialog, QObject *parent = nullptr)
        : QObject(parent), m_dialog(dialog)
    {
        connect(dialog, &UManager::sendText, this, &Core::sendText);// Qt send msg to js ,connect signal A with signal B
		connect(dialog, &UManager::CPPToJSFunc, this, &Core::CPPToJSFunc);

		connect(this, &Core::textRecived, dialog, &UManager::CPPJSInterfaceFunc); // 接收到的消息将转发到UManager进行分发处理
    }

signals:
    /*
        This signal is emitted from the C++ side and the text displayed on the HTML client side.
    */
    void sendText(const QString &text);

    /****************************************************************************/
	void CPPToJSFunc(int index, QString paramters); // send message from cpp to js.
    /****************************************************************************/

	void textRecived(int index, QString paramters); // core对象接收到JS端发送的消息会触发此信号

public slots:

    /*
        This slot is invoked from the HTML client side and the text displayed on the server side.
    */
    void receiveText(const QString &text)
    {
        m_dialog->displayMessage(UManager::tr("Received message: %1").arg(text));
		sendText(text.toUpper());

    }

    /*******************************************************/
	void CPPJSInterfaceFunc(int index, QString paramters) {
		emit textRecived(index, paramters);
	}
    /*******************************************************/

private:
	UManager *m_dialog;
};

#endif // CORE_H

本篇先暂时到这儿,有疑问或者有好的建议的请留言不吝指教。谢谢。后面接着讲项目开发。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QCefView + QWebChannel + Vue 项目开发 的相关文章

随机推荐

  • 【Hadoop全分布式环境搭建】

    文章目录 准备工作 可选配置 创建用户组 关闭防火墙 节点之间免密登录 保证每个节点时间是一致的 时间服务器配置ntp 所有非ntpd时间服务器节点关闭ntpd服务及关闭开机自启 所有非ntpd时间服务器节点 在master节点上进行had
  • org.springframework.amqp.AmqpIOException: java.io.IOException Caused by RabbitMQ异常

    错误如下 org springframework amqp AmqpIOException java io IOException at org springframework amqp rabbit support RabbitExcep
  • python-问题解决

    can t multiply sequence by non int of type str invalid decimal literal ValueError math domain error missing whitespace a
  • spring boot 项目从配置文件中读取maven 的pom.xml 文件标签的内容。

    需求 将pom xml 文件中的版本号读取到配置文件并打印到日志中 第一步 在pom xml 中添加以下标签 第二步 将version 标签的值读取到配置文件中 这里使用 而不是 是因为spring boot 无法识别配置文件中的 符号会导
  • 区块链倪老师:《区块链思维》第一章

    上回我在 区块链思维 序章中提到 从 零维思维 晋升到 一维思维 的过程就像是区块链接成链的过程 区块链由一串使用密码学算法产生的区块连接而成 每一个区块上写满了交易记录 区块按顺序相连形成链状结构 也就是区块链大账本 以比特币为例 矿工在
  • CCF CSP 201803-1 跳一跳 C++ (100分)

    include
  • 机器学习之过拟合欠拟合的理解

    过拟合与欠拟合的理解 一 什么是过拟合 欠拟合 1 过拟合 学习器把训练样本学得 太好了 很可能已经把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质 这样就会导致泛化能力下降 这就是过拟合 换一种说法就是模型过度拟合 在训练集
  • 什么是JSX?

    JSX JavaScriptXML jsx是一种基于Ecmascript的一种新特性 是一种定义带属性树结构的语法 树结构就是我们的dom结构 属性就是dom节点中的属性 jsx不是xml或者Html 不是一种限制 在react中 我们可以
  • [docker]学习笔记-安装

    1 首先修改yum源为国内源 以阿里云为例 下载速度快 尤其时下载docker镜像的时候默认用国外源会非常慢 删除原有自带的yum源 下载阿里云yum源并移动到 etc yum repo目录下 root localhost etc wget
  • TP5-数据库查询左连接

    左连接 展示主表的所有数据 包括不匹配的哈 右连接 只展示左表匹配得上的数据 Db name test1 gt alias a gt join test2 b a id b id LEFT gt select 以上代码会展示出 test1
  • 5个困难的Python谜题

    5个困难的Python谜题 没有多少人能够解决 我挑战你 证明我是错的 这里有一个挑战 尝试解决以下5个Python编程谜题 而不需要在谷歌上寻找答案 魔术方块 魔术方格是一个包含数字1到9的3x3网格 每3个连续的数字 行 列或对角线 必
  • 【STM32】标准库 菜鸟入门教程(1)初识最小系统

    前言 本教程突出两个字 细 和 简 也就是详细跟简洁 所用知识涉及到的原理会详细讲明 再而 在排版以及描述上会采取简约风格 进行实例讲解的时候 包含图片过程以及文字过程两部分 因此适合小菜鸟入门 本次使用的是stm32f103c8t6也就是
  • VIM 快捷键(转)

    VIM快捷键 ctags 文件名 做索引 光标移动 四个方向 k h 0 l j ctrl f ctrl b 向下翻页 向上翻页 ctrl d ctrl u 向下半翻页 向上半翻页 移动行尾 0 移动行首 w 移动下一个词 b 移动到上一个
  • 树莓派Raspbian系统安装tesseract-ocr实现OCR

    第一步安装依赖 启动终端依次敲入命令 sudo apt get install g or clang presumably sudo apt get install autoconf automake libtool sudo apt ge
  • 基于matlab的dbn算法实现

    下载deeplearningtoolbox或者本人提交的zip文件包即可直接运行 command window运行以下程序 function test example DBN load mnist uint8 train x double
  • java毕业设计开题报告javaweb敬老院管理系统的设计和实现

    文末获取联系 一 项目介绍 javaweb基于Java的敬老院管理系统的设计和实现 该项目采用技术jsp servlet jdbc tomcat服务器 mysql数据库 项目含有源码 论文 配套开发软件 软件安装教程 项目发布教程 一 选题
  • Vue插槽

    Vue插槽 Vue的组件如何接受模板内容呢 在某些场景中 我们可能想要为子组件传递一些模板片段 让子组件在它们的组件中渲染这些片段 这时我们可以使用vue提供的插槽来实现 例子 父元素提供了一个模板
  • USB基本知识

    USB协议版本有USB1 0 USB1 1 USB2 0 USB3 0等 目前用的比较多的是USB2 0和USB3 0 1 USB2 0总线 USB2 0总线采用4芯的屏蔽线 一对差分线 D D 传输信号 另一对 VBUS 电源线 传输 5
  • Spring全家桶知识概括

    Spring全家桶知识概括 Springmvc与JS 拦截器与过滤器的区别 spring容器 spring MVC容器 web容器的区别 Filter与Servlet路径映射问题 Spring与JUnit SpringBoot测试与Mock
  • QCefView + QWebChannel + Vue 项目开发

    看到标题 你大概能猜到这篇我想讲述的是什么了 对的 将要同大家分享的是一种目前PC软件常见的开发方案 前言 1 桌面应用为什么要使用Vue等框架开发 而不再是Qt UI开发 界面开发我们使用java语言 通过Vue框架快速开发 好处不言而喻