前言

本文将展示如何使用 QWebChannel 来实现 Web 端与 QT 端之间的交互,同时会通过一个在浏览器端展示文件夹信息的简单例子来展示其具体使用,其功能如下:

  • 获取指定文件夹下的文件信息。
  • 通过使用 QT 的 QFileSystemWatcher 对指定文件夹进行监听,以实现可以获取到文件变更信息并实时展示在浏览器端。

最终实现的效果如下:

动画

本文涉及到的完整代码已上传到GitHub

阅读本文前需要对 QT 的基础知识(比如信号槽)及 QWebChannel 模块有基本的了解。

实现

QT 端

在创建完项目后,首先需要确保引入了WebChannelWebSockets模块,这里以 MSVC 为例:

image-20240131133251712

image-20240131133110870

完成以上操作后,我们首先需要创建一个WebChannelObj类用于管理所有开放给前端调用方法,头文件内容如下(为方便查看,内容有删减,后续文件都会有些许删减,完整代码可在Github查看):

class WebChannelObj : public QObject
{

signals:

    /// <summary>
    /// 信息变更事件
    /// </summary>
    /// <param name="info">文件信息</param>
    void infoChanged(const QJsonDocument& info);

public slots:

    /// <summary>
    /// 获取文件信息
    /// </summary>
    /// <returns>转为 JSON 格式的文件信息</returns>
    QJsonDocument getInfo();

};

这里的getInfo方法是直接提供给 Web 端调用的方法,需要定在slots中,infoChanged是提供给 Web 端监听的事件,需要定义在signals中。

对应的 C++ 文件如下:

// 监听的路径
QString dirPath = "D:\\code\\temp\\";

WebChannelObj* WebChannelObj::getInstance()
{
    // 配置监听
	QFileSystemWatcher* m_FileWatcher = new QFileSystemWatcher;
	m_FileWatcher->addPath(dirPath);
	connect(m_FileWatcher, &QFileSystemWatcher::directoryChanged, obj, &WebChannelObj::directoryChanged);
	return obj;
}

QJsonDocument WebChannelObj::getInfo()
{
	return dirInfo(dirPath);
}

void WebChannelObj::directoryChanged(const QString& path)
{
    // 触发变更事件
	emit infoChanged(dirInfo(path));
}

QJsonDocument WebChannelObj::dirInfo(const QString& path)
{
    // 根据给定的路径获取目录下的文件信息
    // 然后封装信息为 JSON 对象并返回
	QDir dir(path);
	dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
	QFileInfoList list = dir.entryInfoList();
	QJsonArray jsonArray;
	for (auto& file : list)
	{
		QJsonObject obj;
		obj["isDir"] = file.isDir();
		obj["fileName"] = file.fileName();
		obj["fileSize"] = file.size();
		obj["lastModified"] = file.lastModified().toMSecsSinceEpoch();
		jsonArray.append(obj);
	}
	QJsonDocument doc(jsonArray);
	return doc;
}

这里使用了QFileSystemWatcher来实现对指定路径进行监听,这样就可以实时获取路径下的最新文件信息,然后通过连接directoryChangedinfoChanged事件,将信息实时返回给前端。

然后我们需要创建一个 WebSocket 服务端用于 QT 与 Web 端通信的桥梁,并在其中将QWebChannelWebSocket进行连接,内容如下:

WebChannelServer::WebChannelServer()
{
    // 将定义的 WebChannelObj 注册到 QWebChannel 中
    m_webChannel = new QWebChannel(this);
    m_webChannel->registerObject("server", WebChannelObj::getInstance());
    startServer();
}

void WebChannelServer::startServer()
{
    // 启动一个 websocket 服务, 端口为 9999
    m_websocketServer = new QWebSocketServer("Webchannel", QWebSocketServer::NonSecureMode, this);
    if (m_websocketServer->listen(QHostAddress::Any, 9999))
    {
        connect(m_websocketServer, &QWebSocketServer::newConnection, this, &WebChannelServer::onNewConnection);
    }
}

void WebChannelServer::onNewConnection()
{
    // 将 QWebChannel 与 WebSocket 进行连接
    QWebSocket* client = m_websocketServer->nextPendingConnection();
    m_webChannel->connectTo(new WebSocketTransport(client));
}

其中WebSocketTransport类是我们自定义的用于处理QWebChannelWebSocket和 Web 端通信的类,实现自QWebChannelAbstractTransport这个抽象类,其内容较为固定,就不再介绍其内容,可在GitHub中进行查看。

完成以上操作后,只需要在 main 方法中使用即可:

int main(int argc, char *argv[])
{

    QGuiApplication app(argc, argv);

    //...省略内容

    // 使用
    WebChannelServer webChannelServer;

    return app.exec();
}

到此为止, QT 端的工作就算完成了。

Web 端

相比于 QT 端,Web 端的使用就很简单了,首先我们需要先引用一个qwebchannel.js文件,这个文件由官方提供,可以在 QT 安装目录的 Examples 中找到:

image-20240131141033266

然后就可以直接使用了:

<script>
import { QWebChannel } from '@/assets/qwebchannel'

export default {
  name: 'App',
  data() {
    return {
      // 文件列表
      fileList: []
    }
  },
  mounted() {
    const websocket = new WebSocket('ws://localhost:9999')
    websocket.onopen = () => {
      new QWebChannel(websocket, channel => {
        // server 就是前文中通过 m_webChannel->
        //   registerObject("server", WebChannelObj::getInstance()); 注册的名字  
        const webChannelObj = channel.objects.server
        
        // infoChanged 是我们在 signals 中定义的提供给前端的监听事件
        // 只要有文件变更事件, 这里通过 connect 就可以获取到相应的回调事件
        webChannelObj.infoChanged.connect(data => {
          this.fileList = data
        })
        
        // getInfo 是我们在 slots 中定义的提供给前端的方法
        // 返回值需要通过回调获取  
        webChannelObj.getInfo(data => {
          this.fileList = data
        })
      })
    }
  }
}
</script>

关于上述代码的含义参考相应的注释即可,这里只需要知道如果我们想给前端提供监听事件,只需要参考 infoChanged 的定义,在前端通过 xxx.connect(data => {})就可以在回调中获取相关信息。如果想给前端提供可以直接调用的方法,只需要参考 getInfo 的定义,通过需要注意返回值也需要通过回调函数进行获取,如果方法包含参数,则按照正常传参(如果传递了对应,QT 中可以使用 QJsonObject 进行接收),同时把回调函数始终作为最后一个参数即可。

总结

本文通过一个简单的例子展示了如何使用 QWebChannel 及 WebSocket 来实现 QT 与 Web 端的通信,通过使用 QWebChannel ,我们可以很方便地在 Web 端调用 QT 中的定义方法以及实现两端的通信(例如在 Web 端通过 QT 获取摄像头然后展示在 Web 端)。本文如果有错误或不当之处,也欢迎一起交流讨论。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐