本文介绍Qt+vue实现一个windows桌面应用程序,效果图如下:

vue嵌入java swing_qt

vue嵌入java swing_qt_02


全文分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创建项目

vue嵌入java swing_Qt_03

vue嵌入java swing_vue嵌入java swing_04

项目创建完成后运行如下图:

vue嵌入java swing_Qt_05

1.2.2. 创建资源文件

由于页面是由Vue实现的,页面需要作为资源文件加载,我们创建一个资源文件src.qrc,如图

vue嵌入java swing_qt_06


把前端vue打包生成的html添加到src.qrc里面,

vue嵌入java swing_vue嵌入java swing_07

1.2.3. 添加项目需要的模块

创建好项目后自带模块,如图:

vue嵌入java swing_Qt_08

添加web开发相关模块,如图:

vue嵌入java swing_vue嵌入java swing_09

1.2.4. 创建项目文件和目录

vue嵌入java swing_qt_10

项目目录结构如上图,文件和目录的说明如下:
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();
}