本文介绍Qt+vue实现一个windows桌面应用程序,效果图如下:
全文分Qt部分和Vue部分两章,本文先介绍Qt部分
1.1. 涉及Qt模块:
注意:Qt版本5.15,编译器要选MSVC xxxx(本文选:MSVC2019 64bit)。Qt web相关模块在windows系统不支持MinGw编译器。
QWebEngineView:加载和显示html页面
QWebChannel:负责html和Qt交互
QWidget:显示窗口页面
QThread:创建线程异步处理页面请求
QJson…:json数据处理
1.2. 系统的详细开发过程
1.2.1. 用Qt Creator 4.9.0创建项目
项目创建完成后运行如下图:
1.2.2. 创建资源文件
由于页面是由Vue实现的,页面需要作为资源文件加载,我们创建一个资源文件src.qrc,如图
把前端vue打包生成的html添加到src.qrc里面,
1.2.3. 添加项目需要的模块
创建好项目后自带模块,如图:
添加web开发相关模块,如图:
1.2.4. 创建项目文件和目录
项目目录结构如上图,文件和目录的说明如下:
Common:Qt类,单例类,注册元类型和定义交互数据结构
CWorker:Qt类,在线程中运行,异步处理html页面请求
main.cpp:项目入口文件
widget.cpp:QML文件,登录页面
Forms:Qt界面文件
Resources:Qt资源文件目录
页面操作
处理结果
页面操作
处理结果
HTML页面
widget/core
Worker
下面我们来创建上面的列表类文件
1.2.4.1. 创建Common类
Common类是一个单例类,有以下功能:
- 定义系统常量和数据结构
- 注册自定义数据类型
头文件
//解决QT+VS中文乱码问题
#ifdef WIN32
#pragma execution_character_set("utf-8")
#endif
enum RET_CODE {
RET_OK = 0,
RET_DBERR_OPEN,
RET_DBERR_RUN,
RET_PARAMERR,
RET_NOFUNC,
RET_NOWORKTYPE
};
extern QStringList RET_MSG;
typedef struct _CmdData {
QString func;
QMap<QString, QString> params;
} CmdData;
typedef struct _RstData {
int retCode;
QString func;
QString msg;
QVector< QVector<QString> > result;
} RstData;
class MyCommon : public QObject
{
Q_OBJECT
public:
explicit MyCommon(QObject *parent = nullptr);
~MyCommon();
static MyCommon *instance();
static QString GetJsonData(const RstData &rstData);
static QString QJson2QString(const QJsonObject &dataObj);
static QString QJson2QString(const QJsonArray &dataObj);
private:
static MyCommon *self;//单例模式
static QTime mTime;
};
#endif // CCOMMON_H
源文件
#include "ccommon.h"
#include <QDateTime>
#include <QDir>
MyCommon *MyCommon::self = nullptr;
QTime MyCommon::mTime;
QStringList RET_MSG = QStringList() << "成功" << "数据库查询打开失败" << "SQL执行失败" << "参数错误"
<< "方法不存在" << "处理类型不存在";
MyCommon::MyCommon(QObject *parent) : QObject(parent)
{
//注册元类型:主要是在定义信号槽的时候,传递的参数类型不一定是QT所识别的
qRegisterMetaType<CmdData>("CmdData");
qRegisterMetaType<RstData>("RstData");
}
MyCommon::~MyCommon()
{
if (self != nullptr)
{
delete self;
}
}
MyCommon *MyCommon::instance()
{
if(!self)
{
self = new MyCommon();
}
return self;
}
QString MyCommon::GetJsonData(const RstData &rstData)
{
mTime.start();
QJsonObject dataObj;
QJsonObject jsObjChild;
QJsonArray dataArray;
dataObj.insert("code", rstData.retCode);
dataObj.insert("msg", rstData.msg);
dataObj.insert("func", rstData.func);
int row = rstData.result.size();
for (int i = 0; i<row; ++i)
{
dataArray.append(QJsonArray::fromStringList(rstData.result[i].toList()));
}
dataObj.insert("data", dataArray);
qDebug() << "-----------elapsed: " << mTime.elapsed();
qDebug() << rstData.result << QJson2QString(dataObj);
return QJson2QString(dataObj);
}
QString MyCommon::QJson2QString(const QJsonObject &dataObj)
{
QJsonDocument document(dataObj);
QByteArray byteArray =document.toJson(QJsonDocument::Compact);
QString strJson(byteArray);
return strJson;
}
QString MyCommon::QJson2QString(const QJsonArray &dataObj)
{
QJsonDocument document(dataObj);
QByteArray byteArray =document.toJson(QJsonDocument::Compact);
QString strJson(byteArray);
return strJson;
}
1.2.4.2. 创建widget类
Widget是继承QWidget,有以下功能:
- 定义数据处理类Core,在类中定义函数供html调用
- 定义QWebEngineView加载和显示html页面
- 定义QWebChannel实现Qt和html交互
头文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QObject>
#include <QThread>
#include "Common/ccommon.h"
class Core : public QObject
{
Q_OBJECT
public:
explicit Core(QObject *parent = nullptr);
~Core()
{
mWorkerThread.quit();
mWorkerThread.wait();
}
// 定义供html页面调用的函数时,需要加上Q_INVOKABLE,否则html页面会找不到定义的函数
Q_INVOKABLE void handleCmd(const QString &func, const QStringList &keys,
const QStringList &values);
signals:
void operate(const int type, const QString &func, const QString &cmd);
void operateResult(const QString &result);
public slots:
void handleResults(const RstData &rstData);
private:
QThread mWorkerThread;
};
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
Core mCore; // 定义全局的,否则程序运行会出错,js里面也找不到Core里面定义的函数
};
#endif // WIDGET_H
源文件
#include "widget.h"
#include "ui_widget.h"
#include <QWebChannel>
#include <QFileDialog>
#include "CWorker/worker.h"
Core::Core(QObject *parent) : QObject(parent)
{
Worker *worker = new Worker;
worker->moveToThread(&mWorkerThread);
connect(&mWorkerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Core::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Core::handleResults); // Qt接口
mWorkerThread.start();
}
void Core::handleCmd(const QString &func, const QStringList &keys, const QStringList &values)
{
qDebug() << func << keys << values;
QString cmd = "0";
if (func == "selectFile")
{
QFileDialog dialog;
QString fileName = dialog.getOpenFileName(NULL,
tr("selectFile"), "/home/jana", tr("Image Files (*.png *.jpg *.bmp)"));
}
else if (func == "selectDir")
{
QFileDialog dialog;
dialog.setFileMode(QFileDialog::Directory);
QString fileName = dialog.getOpenFileName(NULL,
tr("selectDir"), "/home/jana", tr("Image Files (*.png *.jpg *.bmp)"));
}
else
{
RstData rstData;
if (keys.size() != values.size() || values.size() != 2)
{
rstData.retCode = RET_PARAMERR;
rstData.msg = RET_MSG[rstData.retCode];
emit operateResult(MyCommon::GetJsonData(rstData));
return;
}
if (values[0] == "admin" && values[1] == "admin")
{
cmd = "1";
}
}
emit operate(WORK_DB_QUERY, func, cmd);
}
void Core::handleResults(const RstData &rstData)
{
qDebug() << "[handleResults]result.size()=" << rstData.func << ","
<< rstData.retCode << "," << rstData.msg;
emit operateResult(MyCommon::GetJsonData(rstData));
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QString title = "QWebEngineView+vue开发桌面应用程序";
setWindowTitle(title);
ui->preview->setContextMenuPolicy(Qt::NoContextMenu);
// 定义交互类,通过channel与html交互
QWebChannel *channel = new QWebChannel(this);
channel->registerObject(QStringLiteral("core"), &mCore);
ui->preview->page()->setWebChannel(channel);
ui->preview->setUrl(QUrl("qrc:/sources/html/index.html"));
}
Widget::~Widget()
{
delete ui;
}
1.2.4.3. 创建CWorker类
CWorker是Qt类,有以下功能:
- 定义页面操作函数
- 定义接收页面命令槽函数
- 定义发送处理结果信号,把从数据返回到页面
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include "Common/ccommon.h"
enum WORK_TYPE {
WORK_DB_QUERY = 0,
WORK_DB_RUN
};
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
signals:
void resultReady(const RstData &rstData);
public slots:
void doWork(const int type, const QString &func, const QString &cmd);
private:
};
#endif // WORKER_H
源文件
#include "worker.h"
#include <QThread>
Worker::Worker(QObject *parent) : QObject(parent)
{
}
void Worker::doWork(const int type, const QString &func, const QString &cmd)
{
qDebug() << "[Worker::doWork]type=" << type << "func=" << func;
RstData rstData;
rstData.func = func;
rstData.retCode = RET_OK;
rstData.msg = RET_MSG[rstData.retCode];
if (type == WORK_DB_QUERY)
{
if (cmd == "0")
{
rstData.retCode = RET_PARAMERR;
rstData.msg = "用户名或密码错误";
}
}
else if (type == WORK_DB_RUN)
{
}
else
{
rstData.retCode = RET_NOWORKTYPE;
rstData.msg = RET_MSG[rstData.retCode];
}
emit resultReady(rstData); // Qt接口
}
1.2.4.4. 创建main.cpp
main.cpp是项目的入口文件,有以下功能:
- 初始化单例类
源文件
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("丁爸webenginewidgets测试例子");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication a(argc, argv);
MyCommon::instance();
Widget w;
w.show();
return a.exec();
}