QCefView + QWebChannel + Vue 项目开发

本文介绍了使用QCefView、QWebChannel和Vue进行桌面应用开发的方法。通过QCefView加载Vue页面,利用QWebChannel实现在Qt和JS间的通讯,避免了QWebKit的内存泄漏和QWebEngineView的系统兼容性问题。文章详细阐述了为何选择这些技术,并给出了项目开发的初步步骤。
摘要由CSDN通过智能技术生成

       看到标题,你大概能猜到这篇我想讲述的是什么了。对的,将要同大家分享的是一种目前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

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

Spring Cloud Nacos是一种基于Nacos的微服务架构中的服务发现和配置管理工具。它提供了服务注册与发现、服务健康监测、动态配置管理等功能,可以帮助我们更好地管理前端Vue项目的上线部署。 首先,我们需要在前端Vue项目中引入Spring Cloud Nacos的相关依赖。可以通过Maven或者Gradle方式引入,具体的依赖可以参考Spring Cloud Nacos的官方文档。 接下来,在前端Vue项目的配置文件中,我们需要指定Nacos服务的地址和端口号。这样前端项目就可以通过Nacos注册和发现相关的微服务。配置文件的具体内容如下所示: ``` nacos: server-addr: localhost:8848 ``` 然后,我们需要在前端Vue项目中编写相应的代码来获取Nacos中的配置信息。可以使用Nacos提供的Java SDK或者相关的RESTful接口来实现。通过动态配置管理的功能,我们可以在Nacos中配置前端Vue项目的一些运行参数,并实时获取这些配置信息。 最后,我们需要将前端Vue项目打包,并将打包后的文件部署到服务器上。可以使用Nginx等Web服务器来托管前端静态资源文件。部署过程中需要注意配置Nginx来正确地映射前端Vue项目的访问路径。 总结来说,通过引入Spring Cloud Nacos的依赖,配置Nacos的地址和端口号,编写相关代码来获取Nacos中的配置信息,并将前端Vue项目打包部署到服务器上,我们就可以实现前端Vue项目的上线部署。这样可以更好地管理和配置前端项目,提高项目的可维护性和灵活性。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

键盘会跳舞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值