雖然現代 Office 開發推薦使用 Open XML SDK 或 Office.js 增益集,但透過 COM 自動化模型 (Microsoft.Office.Interop.Word) 仍是快速原型、舊系統維護、伺服器端批次轉檔的實用選擇。本文系統整理 Word Application、Document、Range、Selection 核心物件模型,並提供完整的建立、編輯、存檔、列印、資源釋放最佳實務。
using Word = Microsoft.Office.Interop.Word;public class WordAutomation : IDisposable
{
private Word.Application _app;
private Word.Document _doc;
private bool _disposed;
public WordApplication()
{
_app = new Word.Application();
_app.Visible = false; // 批次處理建議 false,除錯時改 true
_app.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone; // 抑制彈窗
}
// 建立新文件
public void CreateNewDocument()
{
_doc = _app.Documents.Add(
Template: Type.Missing,
NewTemplate: Type.Missing,
DocumentType: Word.WdNewDocumentType.wdNewBlankDocument,
Visible: false);
}
// 開啟現有文件
public void OpenDocument(string path)
{
_doc = _app.Documents.Open(
FileName: path,
ReadOnly: false,
Visible: false);
}
// 存檔並關閉
public void SaveAndClose(string savePath = null)
{
if (_doc != null)
{
object saveChanges = Word.WdSaveOptions.wdSaveChanges;
object originalFormat = Word.WdOriginalFormat.wdWordDocument;
object routeDoc = Type.Missing;
if (!string.IsNullOrEmpty(savePath))
_doc.SaveAs2(FileName: savePath);
else
_doc.Save();
_doc.Close(ref saveChanges, ref originalFormat, ref routeDoc);
ReleaseComObject(_doc);
_doc = null;
}
}
// 結束 Word 進程
public void Quit()
{
if (_app != null)
{
object saveChanges = Word.WdSaveOptions.wdDoNotSaveChanges;
_app.Quit(ref saveChanges, ref Type.Missing, ref Type.Missing);
ReleaseComObject(_app);
_app = null;
}
}
// === 內容操作範例 ===
public void InsertTextAtStart(string text) => _doc.Words.First.InsertBefore(text);
public void InsertTextAtEnd(string text) => _doc.Words.Last.InsertAfter(text);
public int GetCharacterCount() => _doc.Characters.Count;
public void SelectAndCutRange(int start, int end)
{
Word.Range range = _doc.Range(start, end);
range.Select();
range.Cut();
}
public void ApplyNumberingToFirstTwoParagraphs()
{
if (_doc.Paragraphs.Count >= 2)
{
Word.ListTemplate listTemplate = _app.ListGalleries[Word.WdListGalleryType.wdNumberGallery]
.ListTemplates[1]; // 內建編號樣板 1
object continuePrev = true;
_doc.Paragraphs[1].Range.ListFormat.ApplyListTemplate(listTemplate, ref continuePrev);
_doc.Paragraphs[2].Range.ListFormat.ApplyListTemplate(listTemplate, ref continuePrev);
}
}
// === COM 物件釋放 ===
private void ReleaseComObject(object obj)
{
try
{
if (obj != null && Marshal.IsComObject(obj))
Marshal.ReleaseComObject(obj);
}
catch { }
finally { obj = null; }
}
public void Dispose()
{
if (!_disposed)
{
SaveAndClose();
Quit();
GC.Collect();
GC.WaitForPendingFinalizers();
_disposed = true;
}
}
}
| 物件 | 用途 | 常用成員 |
|---|---|---|
| Application | Word 應用程式本身 | Documents, Visible, Quit, ListGalleries, ActiveDocument |
| Document | 單一 .docx 檔案 | Words, Characters, Paragraphs, Sections, Tables, Save(), Close(), Range() |
| Range | 連續文字區域 (不等於選取區) | Start, End, Text, InsertBefore/After, Select(), Cut(), Copy(), Paste(), ListFormat |
| Selection | 使用者目前選取區 (含游標) | TypeText(), TypeParagraph(), MoveLeft/Right/Up/Down(), Font, ParagraphFormat |
| Words 集合 | 文件/範圍/選取區內的單字 (含標點、段落符) | Count, First, Last, Item[index] |
| Characters 集合 | 逐字元集合 | Count, Item[index] |
| Paragraphs 集合 | 段落集合 | Count, Item[index], Range, Format, Style |
| ListFormat | 段落編號/項目符號格式 | ApplyListTemplate(), RemoveNumbers(), ListLevelNumber |
public void ConvertToPdf(string sourcePath, string pdfPath)
{
using (var wa = new WordAutomation())
{
wa.OpenDocument(sourcePath);
wa._doc.SaveAs2(pdfPath, Word.WdSaveFormat.wdFormatPDF);
wa.SaveAndClose();
}
}
public void MailMerge(string templatePath, string outputPath, DataTable data)
{
using (var wa = new WordAutomation())
{
wa.OpenDocument(templatePath);
wa._doc.MailMerge.OpenDataSource(/* 連接字串或暫存 CSV */);
wa._doc.MailMerge.Execute();
wa._doc.SaveAs2(outputPath);
wa.SaveAndClose();
}
}
public void InsertTable(DataTable dt)
{
Word.Range range = _doc.Range(_doc.Content.End - 1, _doc.Content.End - 1);
Word.Table table = _doc.Tables.Add(range, dt.Rows.Count + 1, dt.Columns.Count);
table.Borders.Enable = 1;
// 標題列
for (int c = 0; c < dt.Columns.Count; c++)
table.Cell(1, c + 1).Range.Text = dt.Columns[c].ColumnName;
// 資料列
for (int r = 0; r < dt.Rows.Count; r++)
for (int c = 0; c < dt.Columns.Count; c++)
table.Cell(r + 2, c + 1).Range.Text = dt.Rows[r][c].ToString();
}
Marshal.ReleaseComObject(obj),不可只設 null。_app.ActiveDocument.Paragraphs[1].Range.Text = "x" 會產生兩個隱含 COM 物件 (ActiveDocument、Paragraphs) 無法釋放。應拆解為區域變數並逐一釋放。ReleaseComObject,或使用 for (int i = count; i >= 1; i--) 反向迴圈配合釋放。doc.Close(SaveChanges) 避免殘留 Word 進程。GC.Collect(); GC.WaitForPendingFinalizers(); 強制清理 RCW (Runtime Callable Wrapper)。Word Interop 提供最完整的文件操作能力 (分頁、列印、郵件合併、複雜排版),但伴隨 COM 物件管理複雜度與部署限制。掌握 Range 而非 Selection 進行非 UI 操作、嚴格釋放 COM 物件、評估替代方案,是專業開發者的必修課。