企業級應用常需將數據匯出為 Word 報表。本文基於 MFC/ATL COleDispatchDriver 封裝類,詳細講解模板驅動、表格動態構建、宏調用、多線程異步生成等工程化實戰技巧。
原則:固定版式 (頁眉/頁腳/表頭/樣式) 預製為 .dot 模板,程序僅負責填充數據。優勢:
// 頭文件包含
#include "msword9.h" // 或對應版本的類向導生成頭文件
// 成員變量
_Application m_app; // Word.Application
_Document m_doc; // 當前文檔
Documents m_docs; // 文檔集合
bool InitWord(const CString& strTemplatePath) {
// 1. 啟動 Word (若未運行)
if (!m_app.m_lpDispatch) {
HRESULT hr = m_app.CreateDispatch("Word.Application");
if (FAILED(hr)) return false;
}
m_app.put_Visible(FALSE); // 後台運行
// 2. 獲取 Documents 集合
m_docs.AttachDispatch(m_app.GetDocuments(), TRUE);
// 3. 基於模板創建新文檔
COleVariant vFalse((long)0), vTrue((long)1);
COleVariant vTemplate(strTemplatePath);
m_doc.AttachDispatch(m_docs.Add(vTemplate, vFalse, vFalse, vTrue), TRUE);
return true;
}
模板中預置書籤 (如 CompanyName, Date, TotalAmount):
void SetBookmarkText(const CString& name, const CString& text) {
COleVariant vName(name);
COleVariant vText(text);
try {
// Bookmarks 集合 Item 方法取得 Range
Range range = m_doc.GetBookmarks().Item(vName).GetRange();
range.PutText(text); // 直接替換書籤內容
} catch (COleException* e) {
TRACE("書籤 %s 不存在: %s\n", name, e->m_strDescription);
e->Delete();
}
}
策略:模板預置 一行數據行 (含樣式、合併單元格),程序複製該行並填充數據。
void FillTable(const std::vector& data, int tableIndex = 1, int templateRow = 2) {
// 1. 獲取目標表格
Table table = m_doc.GetTables().Item(COleVariant((long)tableIndex));
Rows rows = table.GetRows();
// 2. 獲取模板行 Range (用於複製格式)
Row templateRowObj = rows.Item(COleVariant((long)templateRow));
Range templateRange = templateRowObj.GetRange();
// 3. 遍歷數據,複製行並填充
for (size_t i = 0; i < data.size(); ++i) {
// 在模板行之前插入新行 (保持模板行作為格式源)
Row newRow = rows.Add(COleVariant(&templateRowObj), COleVariant((long)1)); // wdInsertBefore = 1? 驗證常量
// 實際常量: wdInsertBefore = 0, wdInsertAfter = 1
// 更安全:rows.Add(&vtMissing, &vtMissing) 在末尾添加,再用模板行格式刷新
// 填充單元格 (假設 4 列)
for (int col = 1; col <= 4; ++col) {
Cell cell = table.Cell(COleVariant((long)(templateRow + i)), COleVariant((long)col));
Range cellRange = cell.GetRange();
CString text = GetCellText(data[i], col); // 業務邏輯轉換
cellRange.PutText(text);
}
}
// 4. 可選:刪除模板行
// templateRowObj.Delete();
}
避坑指南:
Selection 對象移動光標 (MoveUp/Down) 定位單元格,**極其不穩定**,直接用 Table.Cell(row, col) 索引。m_app.put_ScreenUpdating(FALSE) 關閉屏幕刷新,完成後恢復 TRUE,速度提升 10x+。void SetupHeadersFooters() {
Sections sections = m_doc.GetSections();
long count = sections.GetCount();
for (long i = 1; i <= count; ++i) {
Section sec = sections.Item(COleVariant(i));
// 奇偶頁不同、首頁不同
sec.GetPageSetup().PutOddAndEvenPagesHeaderFooter(TRUE);
sec.GetPageSetup().PutDifferentFirstPageHeaderFooter(TRUE);
// 設置奇數頁頁眉
HeaderFooter hdrOdd = sec.GetHeaders(COleVariant(wdHeaderFooterPrimary)); // wdHeaderFooterPrimary = 1
Range hdrRange = hdrOdd.GetRange();
hdrRange.PutText(_T("機密報表 - 奇數頁"));
// 設置頁碼 (頁腳)
HeaderFooter ftr = sec.GetFooters(COleVariant(wdHeaderFooterPrimary));
Range ftrRange = ftr.GetRange();
// 插入頁碼域碼: PAGE / NUMPAGES
ftrRange.Fields.Add(ftrRange, COleVariant(wdFieldPage), COleVariant(), COleVariant());
ftrRange.InsertAfter(_T(" / "));
ftrRange.Fields.Add(ftrRange, COleVariant(wdFieldNumPages), COleVariant(), COleVariant());
}
}
void RunMacro(const CString& macroName) {
COleVariant vtMissing; vtMissing.vt = VT_ERROR; vtMissing.scode = DISP_E_PARAMNOTFOUND;
// Run 方法最多 30 參數,未用填 vtMissing
m_app.Run(COleVariant(macroName),
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing);
}
Word COM 操作耗時長 (秒級),必須放入工作線程:
// 工作線程函數
UINT GenerateReportThread(LPVOID pParam) {
CMyDoc* pDoc = (CMyDoc*)pParam;
// 1. 初始化 COM (線程必須)
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// 2. 執行生成邏輯 (同上述代碼)
pDoc->DoGenerateReport();
// 3. 發送完成消息給主線程
::PostMessage(AfxGetMainWnd()->m_hWnd, WM_USER_REPORT_DONE, 0, (LPARAM)pDoc);
CoUninitialize();
return 0;
}
// 主線程啟動
void CMyView::OnGenerateReport() {
AfxBeginThread(GenerateReportThread, GetDocument());
// UI 顯示「正在生成...」進度條
}
| 優化點 | 效果 | 實現 |
|---|---|---|
| 模板預製 | 減少 90%+ COM 調用 | .dot 包含所有靜態樣式、表頭、頁眉腳 |
| ScreenUpdating = FALSE | 速度提升 10x | 生成開始/結束前後設置 |
| 避免 Selection 對象 | 穩定性質變 | 直接用 Range/Table.Cell 索引 |
| 批量屬性設置 | 減少跨進程調用 | 用 Style 應用格式,而非逐單元格設置 Font/Paragraph |
| 異步生成 | UI 響應不阻塞 | Worker Thread + PostMessage |
| 及時釋放 COM | 防止 Word 殭屍進程 | 智能指針 _DocumentPtr / 手動 ReleaseDispatch |
COleDispatchDriver 派生對象在析構前調用 ReleaseDispatch(),或使用 _ApplicationPtr 智能指針 (需 #import)。GetModuleFileName 定位程式目錄下的 Templates 文件夾。#import "msword.olb" rename_namespace("Word") 晚綁定/智能指針方式。COleVariant 時自動處理 BSTR 轉換。VC++ 自動化 Word 的核心哲學是「模板為王,代碼為輔」。將版式固化至 .dot,代碼專注於數據綁定、表格行複製、域碼插入,配合異步線程與 COM 資源嚴格管理,即可構建穩定、高效、可維護的企業級報表生成系統。若項目允許,遷移至 OpenXML SDK (純 .NET 操作 .docx) 可徹底擺脫 COM 依賴,實現無 Office 環境的服務端部署。