Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)

发布于:2024-12-21 ⋅ 阅读:(12) ⋅ 点赞:(0)

关于QT Widget 其它文章请点击这里:     QT Widget

国际站点 GitHub:     https://github.com/chenchuhan
国内站点 Gitee :      https://gitee.com/chuck_chee

姊妹篇:     

Qt WORD/PDF(一)使用 QtPdfium库实现 PDF 操作
Qt WORD/PDF(二)使用 QtPdfium库实现 PDF 预览、打印等
Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)
Qt WORD/PDF(四)使用 QAxObject 对 Word 替换(QWidget)


一、QAxObject 简介

QAxObject 是 Qt 提供的一个类,它用于与 COM(Component Object Model)对象进行交互。COM 是一种微软的技术,广泛用于各种应用程序之间的通信,尤其在 Windows 平台上,很多软件和系统组件都是基于 COM 构建的。QAxObject 类提供了一个 Qt 风格的接口,简化了与这些 COM 对象的交互。

本文主要使用 QAxObject 操作 word 文档,使用键值对,对模板文件进行替换操作,导出相应的文档,特别适合输出报告。

本文采用 QML + C++ 分离的方式:

QML 界面:用户通过 QML 界面输入键值对,选择模板文件并选择保存路径。
C++ 后端:当用户确认选择后,C++ 后端通过 QAxObject 与 Word 应用进行交互,打开模板文件并进行占位符替换,生成的新 Word 文档被保存到用户选择的位置。

环境:

QT5.15.2 + MSVC2019 + QML

二、演示

在这里插入图片描述

基本流程:

输入替换键值对——>选择模板文件——>选择输出文件夹——>开始替换并导出;

三、代码与分析

完整代码

OfficeExporter.cpp:

#include "OfficeExporter.h"
#include <QFile>
#include <QTextStream>
#include <QAxObject>
#include <QDebug>

OfficeExporter::OfficeExporter(QObject *parent)
    : QObject(parent) {}

bool OfficeExporter::exportWord(const QString &filePath, const QString &content) {
    // 使用 Qt-Office 或 QTextDocument API 生成文档
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }

    QTextStream stream(&file);
    stream << content;
    file.close();
    return true;
}

//测试:创造和保存
bool OfficeExporter::createAndSaveWordDocument(const QString &filePath, const QString &content)
{
    // 创建 Word 应用程序对象
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (!wordApp->isNull()) {
        wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

        // 创建一个新的文档
        QAxObject *documents = wordApp->querySubObject("Documents");
        QAxObject *document = documents->querySubObject("Add()");

        // 获取文档中的选择区域(光标位置)
        QAxObject *selection = wordApp->querySubObject("Selection");

        // 插入内容
        selection->dynamicCall("TypeText(const QString&)", content);
        selection->dynamicCall("TypeParagraph()");  // 换行

        // 保存文件
        document->dynamicCall("SaveAs(const QString&)", filePath);

        // 关闭文档
        document->dynamicCall("Close()");
        wordApp->dynamicCall("Quit()");
        delete wordApp;

        return true;
    }

    delete wordApp;
    return false;
}

//测试:替换
bool OfficeExporter::createFromTemplate(const QString &templatePath, const QString &outputPath, const QVariantMap &data)  //const QMap<QString, QString> &placeholder)
{
    qDebug() << "Received data:" << data;

    // 转换 QVariantMap 为 QMap<QString, QString>
    QMap<QString, QString> placeholders;
    for (auto it = data.begin(); it != data.end(); ++it) {
        placeholders[it.key()] = it.value().toString();
    }

    qDebug() << "Template Path:" << templatePath;
    qDebug() << "Output Path:" << outputPath;


    // 检查文件路径
    if (!QFile::exists(templatePath)) {
        qDebug() << "Template file does not exist:" << templatePath;
        return false;
    }

    qDebug() << "QFile::exists ok" ;

    // 创建 Word 应用程序对象
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (wordApp->isNull()) {
        qDebug() << "Failed to initialize Word.Application.";
        delete wordApp;
        return false;
    }

    qDebug() << "wordApp ok" ;
    wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

    // 打开模板文件
    QAxObject *documents = wordApp->querySubObject("Documents");
    QAxObject *document = documents->querySubObject("Open(const QString&)", templatePath);

    // 查找占位符并替换
    //使用 Find.Execute 查找占位符,使用 TypeText 方法替换为新内容
    QAxObject *selection = wordApp->querySubObject("Selection");

    qDebug() << "selection ok" ;

    // 获取 Find 对象
    QAxObject *find = selection->querySubObject("Find");

    qDebug() << "start placeholde";
    // 遍历占位符键值对, 替换未成功,则有问题
    for(auto it = placeholders.begin(); it != placeholders.end(); ++it) {
        QString placeholder = it.key();
        QString newContent = it.value();

        // 重置光标到文档开头
//        selection->dynamicCall("HomeKey(Unit:=6)"); // Move to the start of the document
        //适应于多目标的查找,全部替换
        bool isFound = true;

        //可替换多个,且重复的
        while (isFound) {
            // 查找目标文本并替换
//            isFound = find->dynamicCall("Execute(const QString&)", placeholder).toBool();
            isFound = find->dynamicCall("Execute(QString, bool, bool, bool, bool, bool, bool, int)",
                                        placeholder,  // 要查找的字符串
                                        false,        // 区分大小写
                                        false,        // 完整单词
                                        false,        // 使用通配符
                                        false,        // 忽略标点符号
                                        false,        // 忽略空格
                                        true,         // 向前查找
                                        1).toBool();   // 查找范围:整个文档

            if (isFound) {
                // 替换文本
                selection->dynamicCall("TypeText(const QString&)", newContent);
            }
        }
    }

    qDebug() << "All Find operation succeed!";

    document->dynamicCall("SaveAs(const QString&)", outputPath);
    // 关闭文档
    document->dynamicCall("Close()");
    wordApp->dynamicCall("Quit()");

    delete wordApp;
    return true;
}

OfficeExporter.h:

#ifndef OFFICEEXPORTER_H
#define OFFICEEXPORTER_H

#include <QObject>
#include <QString>

class OfficeExporter : public QObject {
    Q_OBJECT
public:
    explicit OfficeExporter(QObject *parent = nullptr);

    Q_INVOKABLE bool exportWord(const QString &filePath, const QString &content);

    // 创建并保存 Word 文档
    Q_INVOKABLE bool createAndSaveWordDocument(const QString &filePath, const QString &content);

    Q_INVOKABLE bool createFromTemplate(const QString &templatePath, const QString &outputPath,  const QVariantMap &data);

    bool replaceMultiple(const QString &templatePath, const QString &outputPath, const QMap<QString, QString> &placeholders);

};
#endif // OFFICEEXPORTER_H

pro 中需要增加对 QAxObject 的支持

QT       += core gui axcontainer 

简要分析

这段代码展示了如何使用 Qt 和 QAxObject 类与 Word 进行交互,主要分为三个功能:导出 Word 文档创建并保存 Word 文档、以及从模板生成并替换占位符内容

1. exportWord 函数

该函数将内容保存为纯文本文件。它并不直接涉及 Word 的操作,而是将内容以纯文本形式写入文件。它通过 QFileQTextStream 写入文本内容。

bool OfficeExporter::exportWord(const QString &filePath, const QString &content) {
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }
    QTextStream stream(&file);
    stream << content;
    file.close();
    return true;
}

2. createAndSaveWordDocument 函数

此函数使用 QAxObject 创建一个新的 Word 文档,插入给定内容,并保存为指定路径。通过 QAxObject 创建 Word 应用程序并与之交互:

  • Word.Application 通过 QAxObject 实现。
  • 插入文本通过 Selection 对象的 TypeText 方法进行。
  • 保存文件通过 SaveAs 方法,关闭文档并退出 Word。
bool OfficeExporter::createAndSaveWordDocument(const QString &filePath, const QString &content) {
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (!wordApp->isNull()) {
        wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

        QAxObject *documents = wordApp->querySubObject("Documents");
        QAxObject *document = documents->querySubObject("Add()");

        QAxObject *selection = wordApp->querySubObject("Selection");
        selection->dynamicCall("TypeText(const QString&)", content);
        selection->dynamicCall("TypeParagraph()");

        document->dynamicCall("SaveAs(const QString&)", filePath);
        document->dynamicCall("Close()");
        wordApp->dynamicCall("Quit()");
        delete wordApp;

        return true;
    }

    delete wordApp;
    return false;
}

3. createFromTemplate 函数

此函数通过加载一个现有的 Word 模板文件,并在文档中查找并替换占位符。主要步骤包括:

  • 通过 Find 对象查找占位符。
  • 使用 TypeText 方法替换占位符为给定数据。
  • 使用 Execute 方法执行查找,并通过 for 循环迭代器确保替换所有出现的占位符。
bool OfficeExporter::createFromTemplate(const QString &templatePath, const QString &outputPath, const QVariantMap &data) {
    // Convert QVariantMap to QMap<QString, QString>
    QMap<QString, QString> placeholders;
    for (auto it = data.begin(); it != data.end(); ++it) {
        placeholders[it.key()] = it.value().toString();
    }

    // Open template file
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (wordApp->isNull()) {
        delete wordApp;
        return false;
    }

    wordApp->setProperty("Visible", false);
    QAxObject *documents = wordApp->querySubObject("Documents");
    QAxObject *document = documents->querySubObject("Open(const QString&)", templatePath);

    QAxObject *selection = wordApp->querySubObject("Selection");
    QAxObject *find = selection->querySubObject("Find");

    for (auto it = placeholders.begin(); it != placeholders.end(); ++it) {
        QString placeholder = it.key();
        QString newContent = it.value();

        bool isFound = true;
        while (isFound) {
            isFound = find->dynamicCall("Execute(QString, bool, bool, bool, bool, bool, bool, int)",
                                        placeholder, false, false, false, false, false, true, 1).toBool();
            if (isFound) {
                selection->dynamicCall("TypeText(const QString&)", newContent);
            }
        }
    }

    document->dynamicCall("SaveAs(const QString&)", outputPath);
    document->dynamicCall("Close()");
    wordApp->dynamicCall("Quit()");
    delete wordApp;

    return true;
}

流程:

  1. 创建 Word 应用程序:每个函数中都通过 QAxObject("Word.Application") 来创建 Word 应用程序的 COM 对象,随后可以通过该对象访问文档、光标(Selection)等。
  2. 插入和替换文本
    • 插入文本是通过 TypeText 方法来实现的。这个方法会在光标位置插入指定的文本。
    • createFromTemplate 函数中,使用 Find 对象查找占位符,替换文本的过程是循环执行的,直到文档中的所有占位符都被替换为新内容。
  3. 操作文档
    • SaveAs 方法用于保存文档,Close 方法用于关闭文档,Quit 方法退出 Word 应用程序。
    • Word.ApplicationVisible 属性设置为 false 使得 Word 在后台运行,用户看不到界面。

main.qml:

import QtQuick.Window 2.12
import QtQuick.Dialogs 1.3
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import com.example.OfficeExporter 1.0

Window {
    visible: true
    width: 640
    height: 480

    title: qsTr("Word Export Demo")

    OfficeExporter {
        id: exporter
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 10
        anchors.margins: 10

        // 表单标题
        Text {
            text: "请输入键值对:"
            font.pixelSize: 20
            anchors.horizontalCenter:  parent.horizontalCenter
        }

        // 键值对输入区
        ListView {
            id: listView
            anchors.horizontalCenter:  parent.horizontalCenter
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: ListModel {
                    ListElement { key: "[A]"; value: "柯布" }
                    ListElement { key: "[B]"; value: "阿瑟" }
                    ListElement { key: "[C]"; value: "杜拉" }
                    ListElement { key: "[D]"; value: "伊姆斯" }
                }

            delegate: RowLayout {
                spacing: 10
                TextField {
                    id: keyField
                    placeholderText: "键 (Key)"
                    text: model.key
                    Layout.fillWidth: true
                    onTextChanged: model.key = text
                }
                TextField {
                    id: valueField
                    placeholderText: "值 (Value)"
                    text: model.value
                    Layout.fillWidth: true
                    onTextChanged: model.value = text
                }
                Button {
                    text: "删除"
                    onClicked: model.remove(index)
                }
            }
        }

        // 添加键值对按钮
        Button {
            text: "添加键值对"
            Layout.alignment: Qt.AlignHCenter
            onClicked: listView.model.append({key: "", value: ""})
        }

        // 生成文档按钮
        Button {
            text: "选择模板并导出 Word 文档"
            Layout.alignment: Qt.AlignHCenter
            onClicked: {
                dialog.open();
            }
        }

        Item {
            width: 1
            height: 1
        }
    }

    FileDialog {
        id:     dialog
        title:  "选择 Word 模板文件"
        selectExisting: true
        nameFilters: [ "Word 文档 (*.docx)", "All files (*)" ]
        onAccepted: {
            fileDialog.open();
        }
    }

    //templatePath.slice(8), 通常是为了去掉路径中的前缀部分,比如 file:///
    FileDialog {
        id: fileDialog
        title: "选择保存路径"
        selectExisting: false
        nameFilters: [ "Word 文档 (*.docx)", "All files (*)" ]
        onAccepted: {

            let data = {};
            for (let i = 0; i < listView.model.count; i++) {
                let item = listView.model.get(i);
                data[item.key] = item.value;
            }
            console.log("用户输入的键值对: ", JSON.stringify(data));

            var templatePath = dialog.fileUrl.toString();
            var filePath = fileUrl.toString();
            if (!filePath.endsWith(".docx")) {
                filePath += ".docx";
            }
            if ( exporter.createFromTemplate(templatePath.slice(8), filePath.slice(8), data)) {
                console.log("Word 文档已导出到:" + filePath);
            } else {
                console.log("导出失败");
            }
        }
    }
}

这段代码结合了 Qt 的 QMLC++ 后端,目的是让用户通过图形界面操作生成 Word 文档。它允许用户通过键值对形式的输入替换 Word 模板中的占位符,并将替换后的内容保存为 Word 文件。

QML 界面结构

(1) OfficeExporter 实例

在 QML 中,OfficeExporter 是一个 C++ 类的 QML 组件

OfficeExporter {    id: exporter}

(2) 输入区:键值对

通过 ListViewTextField,用户可以输入一组键值对。model 用于管理键值对的列表,delegate 负责定义如何显示每一对键值。用户可以修改键值对的内容,并且可以删除不需要的项。

ListView {
    model: ListModel {
        ListElement { key: "[A]"; value: "柯布" }
        ListElement { key: "[B]"; value: "阿瑟" }
        ListElement { key: "[C]"; value: "杜拉" }
        ListElement { key: "[D]"; value: "伊姆斯" }
    }
    delegate: RowLayout {
        TextField {
            text: model.key
            onTextChanged: model.key = text
        }
        TextField {
            text: model.value
            onTextChanged: model.value = text
        }
        Button {
            text: "删除"
            onClicked: model.remove(index)
        }
    }
}

(3) 调用c++替换程序

最终通过 createFromTemplate 接口,输入键值对,调用 C++ 后端程序

if ( exporter.createFromTemplate(templatePath.slice(8), filePath.slice(8), data)) {
	···
}

3. 工作流程总结

  1. QML 界面:用户通过 QML 界面输入键值对,选择模板文件并选择保存路径。
  2. C++ 后端:当用户确认选择后,C++ 后端通过 QAxObject 与 Word 应用进行交互,打开模板文件并进行占位符替换。
  3. 生成 Word 文档:生成的新 Word 文档被保存到用户选择的位置。

注意:

  1. pro 工程文件中加入 : QT += axcontainer 模块
  2. c++中注册: qmlRegisterType(“com.example.OfficeExporter”, 1, 0, “OfficeExporter”);
  3. 另外在 Word 中不方便预览和打印,可结合姊妹篇的(一)和(二),输出为PDF后预览和打印会方便很多!

关于QGC地面站其它文章请点击这里:     QT Widget