Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)
关于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 的操作,而是将内容以纯文本形式写入文件。它通过 QFile
和 QTextStream
写入文本内容。
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;
}
流程:
- 创建 Word 应用程序:每个函数中都通过
QAxObject("Word.Application")
来创建 Word 应用程序的 COM 对象,随后可以通过该对象访问文档、光标(Selection
)等。 - 插入和替换文本:
- 插入文本是通过
TypeText
方法来实现的。这个方法会在光标位置插入指定的文本。 - 在
createFromTemplate
函数中,使用Find
对象查找占位符,替换文本的过程是循环执行的,直到文档中的所有占位符都被替换为新内容。
- 插入文本是通过
- 操作文档:
SaveAs
方法用于保存文档,Close
方法用于关闭文档,Quit
方法退出 Word 应用程序。Word.Application
的Visible
属性设置为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 的 QML 和 C++ 后端,目的是让用户通过图形界面操作生成 Word 文档。它允许用户通过键值对形式的输入替换 Word 模板中的占位符,并将替换后的内容保存为 Word 文件。
QML 界面结构
(1) OfficeExporter 实例
在 QML 中,OfficeExporter
是一个 C++ 类的 QML 组件
OfficeExporter { id: exporter}
(2) 输入区:键值对
通过 ListView
和 TextField
,用户可以输入一组键值对。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. 工作流程总结
- QML 界面:用户通过 QML 界面输入键值对,选择模板文件并选择保存路径。
- C++ 后端:当用户确认选择后,C++ 后端通过
QAxObject
与 Word 应用进行交互,打开模板文件并进行占位符替换。 - 生成 Word 文档:生成的新 Word 文档被保存到用户选择的位置。
注意:
- pro 工程文件中加入 : QT += axcontainer 模块
- c++中注册: qmlRegisterType(“com.example.OfficeExporter”, 1, 0, “OfficeExporter”);
- 另外在 Word 中不方便预览和打印,可结合姊妹篇的(一)和(二),输出为PDF后预览和打印会方便很多!
关于QGC地面站其它文章请点击这里: QT Widget