在我的程式開發生涯中,套版輸出指定格式的報表/表單一直是揮之不去的煩人差事,沒什麼營養,偏偏在每個案子裡幾乎都像小強一樣冒出來。

面對這類需求,轉成網頁是下策,因為列印時排版格式常會亂到一塌糊塗,鮮少讓人滿意。在經驗裡,Reporting Service是不錯的選擇(而且免費)。

但有些報表如確認書、通知書,在格式上並非Gird格式,跟Reporting Service最擅長的表格呈現有點差距,數量一多,要將User提供的Word檔一一轉成Reporting Service報表便成了苦差事,尤其某些文件被要求必須模仿到跟原始樣版分毫不差,常為了一兩公釐急死大丈夫。(有時,所謂原有樣版不過是某個User信手捻來的作品,並非官頒公訂,也沒人說過排版必須完全相符,但是你也知道的,各地風俗民情不同,尤其是澳洲...)

當以上情境發生,直接把User提供Word樣版裡文字換一換,照樣生出個Word檔來,在我看來是最簡便直覺省工的解法。

依我所知,動態產生Word要在ASP.NET上實現,有幾種解法,個人評估分析如下:

  1. 利用Office Automation(就如同VBA操作Word/Excel Object Model的做法)
    在Server端(ASP.NET)使用Office Automation技巧有些額外的風險,我自己有過不好的經驗,依MS KB的說法也不建議。
  2. 產出HTML後標註ContentType,讓Word/Excel開啟
    先前曾貼文介紹過這種做法,但逼真度與真實doc/xls有段差距,例如: doc沒法精確分頁、xls不支援多Worksheet... 等等。
  3. 使用3rd Party元件直接產生Office文件
    剛才提到的MS KB裡就有些建議的元件廠商,另外還有一家元件廠商ASPOSE的名字也常被提到。只是,元件的價格通常不便宜。
  4. docx/xlsx OpenXML
    Office 2007在檔案格式上做了大改革,揚離了過去的獨有二進位格式,變成公開的標準,而docx/xlsx,其實是ZIP起來的一堆XML及資源檔。這麼一來,修改XML也比以前搞封閉的二進位檔案格式簡單多了,也因為標準公開,可用的技術資源也多。
    使用docx/xlsx固然便捷,缺點是產出的docx/xlsx Office 2003/2000無法直接開啟,所幸微軟提供了免費的Word Viewer可以用來檢視,另外也有Microsoft Office Word、Excel 及 PowerPoint 2007 檔案格式相容性套件能將docx/xlsx轉存為doc/xls。在我看來,這點是可被容忍的缺陷。因此,我決定設法試做OpenXML的解決方案。

雖然我們可以自行將docx Rename成.zip,解壓縮後取出XML修改再存回壓縮回去,但MS提供了更好的支援服務: (SDK文件裡的範例寫得簡單明瞭,讓我一下子就上手!! 哦哦~~ Open XML Format SDK,我要輕輕為你唱首歌)

因為我想做的套版只是更換特定字串,用1.0的寫法也不算太鳥,加上打算用來Production環境,不想承擔CTP的風險。所以我決定現階段先用1.0 SDK正式版來實作,日後有更複雜運用時再改版。

今天只花了幾個小時,還真的把雛型做出來了:

1.先用Word 2007存一個docx檔案,把其中要動態更換的文字用[$keyName$]的格式標起來。(這種[$...$]的格我還為它取了個名字--Parser Tag,從ASP時代,我就一直用它來玩範本套表的把戲)

2.寫一小段程式,把[$keyName$]要更換的文字轉成Dictionary<string, string>,連同範本的檔案路徑一起傳給套表輔助元件DocxHelper,傳回的byte[]就是套表好的docx檔案內容。

    protected void btnExport_Click(object sender, EventArgs e)
    {
        string template = Server.MapPath("Notice.docx");
 
        Dictionary<string, string> dct = new Dictionary<string, string>()
            { { "today", DateTime.Today.ToString("yyyy-MM-dd") },
              { "name", txtName.Text },
              { "addr", txtAddr.Text },
              { "amount", txtAmount.Text } };
 
        Response.Clear();
        Response.ContentType = "application/octet-stream";
        Response.AddHeader("content-disposition", "attachment;filename=Notice.docx");
        Response.BinaryWrite(
            Darkthread.OpenXml.DocxHelper.MakeDocx(
                Server.MapPath("Notice.docx"), 
                dct)
        );
        Response.End();
    }

3.實際跑起來,按下【匯出DOCX】,TextBox裡的文字就會被填進下載的docx中,很棒吧!!

天哪,我真的中了大樂透了嗎? 不會吧? 莫非有人在耍我? (謎之聲: 不就是你自己嗎? 找時間去看一下精神科好唄?)

我打算在手邊的小案子試行這個新元件,陸續蒐集一些問題跟情境,待元件較為成熟後,到時再推出懶人包跟大家分享。


Comments

# by Kevin Lin

黑大,這篇太晚出來了啦! 在公司處理這種狀況已經很多了,腦細胞不知死了幾次。

# by alan

黑大,我在公司有看到前輩用一樣的方法處理這一類user的報表! 但是確是用office XP搞定的!除了程式碼又臭又長之外!還有一個缺點!就是只能直接輸出到印表機就列印!無法像黑大一樣匯出.docx! 直接匯出的好處有 1.能再改完再印! 2.可以選只印第幾頁, 直接印的壞處就是!浪費紙,我常常聽到的抱怨是 印到一半沒紙!印表機自動跳到回收紙夾印,總數有11頁結果印到第10頁沒紙,第11頁就是用回收紙印(背面不是白的),這樣子就造成11頁全都要重印一次>"<粉累

# by Tim

黑大, 你是我的偶像跟救星!!!

# by ABC

Darkthread.OpenXml.DocxHelper.MakeDocx 這段沒分享出來交大如如何做

# by alan

黑大,請問懶人包何時會出^_^ 我左等右等上等下等,為何對面的懶人包還不出來

# by Jeffrey

to ABC, alan, 因為元件還沒被用力操練過,尚未到"懶人可用"的地步,很有可能一堆地方需要修改調整,而我另外也擔心目前用的做法在某些情境下會出錯,因此打算晚幾個月才會丟出來。 PO這篇文的另一個目的也在分享"用OpenXML實作解決方案"的點子,想要早點動手自己試試的朋友,可以先參考OpenXML SDK 1.0 How to: Replace a Document Part in an Office Open XML Package by Using the Open XML API 的範例試看看,原理還蠻簡單的。

# by James

這種應該只適合用於固定的版型上,當狀況是:依資料內容,版型不同時,還是要思考一下解法XD

# by rockzero(Rz)

codeplex上面有兩套 DocX 原生(不需word、openxml SDK) fleXdoc 套樣(不需word、但需openxml SDK 2.0CTP) 不過都在alpha階段(Flexdoc 還沒釋出) 提供給大家參考看看

# by joper

大大可否釋出範本 最近套版一直很難調整 所以想用套word方式 感激哩

# by Jeffrey

to joper, ABC, alan, 應該是不會出懶人包了,所以我把PoC版的程式碼分享出來: http://blog.darkthread.net/post-2011-08-14-openxml-template-parser.aspx

# by tania

黑大:您好,看到您的範本後覺得很酷,但想請問您,若是表單需要顯示框框跟勾選的樣式,要怎樣處理呢?我實在不知道如何將checkbox 或radiobox的樣子顯示在表單中,但又很想用您的方法,能否請您告訴我好的方法,謝謝您!

# by Jeffrey

to tania, Word可經過設定啟用checkbox、radio物件,但若只是要列印出來不需實際點選操作,我會選擇較簡單(取巧)的做法--用Wingdings2字型中的特殊符號來代替(參考: http://www.brownylin.com/2009/10/tip-word-check-box.html)

# by wei

您好 請教一下 有發生文字無法置換情況該如何解決? 用openxml SDK tool去修改嗎? 還是? word明明輸入的就是[$test$] 但是用tool看中間會多很多符號 感謝您的解答

# by Jeffrey

to wei, 有時會如此,在輸入文字時做了中英切換之類,文字被切成很多段以標示不同設定,才會摻入符號。(有時沒什麼切換也會) 遇到時我的解決方法是試著改用Copy/Paste貼文字取代打字,如果還是有問題,就只能事後用文字編輯器修改(它是種XML格式,修改時注意不要破壞它的結構)。

# by wei

前輩您好 是用哪個文字編輯器呢(試過wordpad or txt都不行)? 另外要怎麼開啟docx檔中的xml來去修改內容呢? openxml sdk tool 看得到xml結構 不能修改 這問題困擾我好久了 還是十分感謝您的回答

# by Jeffrey

to wei, docx其實是一個ZIP檔,將其更名為docx.zip後就可解壓成目錄,在其中就可以找到XML,修改完再按原結構壓回去就可更動內容。但這麼玩已經有點Hacking了,Have Fun!

# by wei

前輩您好 有照您的方法做 , 目前看起來是work的 最後解決方法如下 1.整張word都先打入要替換的文字[$test$] 2.修改副檔名為zip 3.解壓縮 找出document.xml 4.再用XML編輯器(firstobject)把缺少的符號寫進去多餘的符號刪除 5.壓縮回zip , 修改副檔名回docx 6.再開啟程式測試 替換文字便OK 謝謝您的解答 , 也給有相同問題的人一個解決方法

# by Willy

你好~~ 照你的方式,已可轉成WORD 並直接開啟~~感謝你~~ 我想請問一下~~是否可直接利用這個方式存成檔案呢??或是另存PDF檔案呢??

# by Jeffrey

to Willy, 將MakeDocx()傳回的byte[]即為Word檔內容,用File.WriteAllBytes()可另存檔案,至於另存PDF,則需要其他元件或服務協助。

# by Willy

搞定~~謝謝~~ pdf 需要用什麼元件呢?? 我有看過WORD to PDF 那篇,但好像還要套用 WORD 的東西,有些語法會出現錯誤~

# by Jeffrey

to Willy, 你用Word PDF convert當關鍵字應該可以找到不少,但有些是商業元件,要花錢買。 我自己是用Word處理轉PDF的需求,就是Word to PDF那篇的做法,語法錯誤是發生在SaveAs2嗎? 我有接到回報,如果是Word 2007,要改用SaveAs,其餘不變。

# by hsin

To Jeffrey大大 您的Docx套版列印功能程式我已經測試出來試驗成功。 不曉得您是否有套用Excel套版列印功能程式的方式可以供使用! 謝謝您! :D

# by Jeffrey

to hsin, Excel的特性與Word不同,有一格一格的儲存格(Cell)可供定位填入內容,不需要使用自訂置換註記([$$..$$])的技巧,實務上我傾向用EPPlus或ClosedXML等元件處理。

# by will

請教黑大 如果有一表格要套(列數不固定, 如購買物品清單, 榜單), 看起來不能用Replace 那要用什麼方式會比較好呢?

# by Jeffrey

to will, 這種做法不適合置換出複雜的結構(例如: 表格、圖文),用3rd Party元件或Office Automation(參考: http://blog.darkthread.net/post-2013-06-01-word-to-pdf.aspx)較為可行,但仍得先了解在Word建立表格的做法,評估要花點功夫。

# by 被套表紙搞死的人

有什麼好方法可以對付點陣式印表機嗎 普通的印表機還好 點陣式套版到底有什麼好方法可以解決阿....

# by Jeffrey

to 被套表紙搞死的人, 我想到的做法是用Reporting Service或其他報表工具,先掃瞄套表紙當成報表的背景圖檔,各欄位用絕對位置安排在圖檔的對應位置,配置完成後把圖檔移掉再列印在套表紙上。

# by 被套表紙搞死的人

謝謝你 我試看看

# by ping

我有找到一個 透過 JS 套 docx 版的程式 下載位置 : https://github.com/djpate/docxgen DEMO : http://javascript-ninja.fr/docxgenjs/examples/demo.html 可惜我技術不佳...無法轉換中文 可以請黑大看一下嗎??

# by Jeffrey

to ping, 粗略看過,是透過JavaScript呼叫後端的PHP程式產生docx,中文變亂碼應是PHP端未考量非英文字元所致。

# by ping

黑大...我這裡的版本單純就只有JS..與PHP無關囉 測試連結 http://www.net-tw.net/test/Contract.asp 檔案打包 http://www.net-tw.net/test/docx.zip

# by Jeffrey

to ping, 看到了,嘆為觀止! docx本質是一堆ZIP過的XML,居然有神人全部用JavaScript搞定。我想問題根源還是JavaScript在處理字元編碼不正確所致,這點子蠻有趣的,有空來追追程式看是否有解。

# by ping

那有勞黑大幫個小忙了...(跪拜)

# by Jeffrey

to ping, debug完成(撥瀏海),請參考新文: http://blog.darkthread.net/post-2014-03-13-pure-js-docx-template-print.aspx

# by ping

帥呆了~~~拜

# by 翔哥

請問版主我最近用NPOI 來產生word 但是Table 的部分都無法修改文字的字型 所以版主可有建議嗎??? 是繼續try NPOI 還是使用其他的第三方元件呢?? 謝謝!!

# by 翔哥

我也在論壇問過了!! https://social.msdn.microsoft.com/Forums/zh-TW/db2cdfe2-fd59-420b-9167-2d7918ecd9a8/npoi-docx-table-?forum=236

# by Jeffrey

to 翔哥,我建議試試OpenXML SDK(http://www.dotblogs.com.tw/chou/archive/2010/04/30/14954.aspx),至於文件套表,我也試過用Office Automation(http://blog.darkthread.net/post-2013-06-01-word-to-pdf.aspx),但建議不要直接呼叫包成服務較佳,但有一些眉角(http://blog.darkthread.net/post-2013-08-17-word-run-under-windows-service.aspx)

# by 貓鷹鷹

想請教黑大,如果我是十個人中獎要印出十張word檔的話也是如此施作嗎??(因為現在我的狀況是如果塞十筆名字,他就乾脆維持原樣的 [$name$] OWQ想請教如果想要修改要查哪方面的資訊呢

# by Jeffrey

to 貓鷹鷹,若Word格式不複雜許可以考慮用Office內建的合併列印功能:http://www.flag.com.tw/book/cento-5105.asp?bokno=FS006&id=320

# by 貓鷹鷹

哇,大大你回應好快XD,謝謝您,但沒辦法用那個OWQ ,因為是需求功能,那大大對於 ex:十個人中獎要印出十張word檔 這樣的需求,有什麼建議的方向嗎? 如果用大大這隻程式來改的話~

# by Jeffrey

to 貓鷹鷹,文章提到做法的範例程式可在另一篇文章找到: http://blog.darkthread.net/post-2011-08-14-openxml-template-parser.aspx 跑迴圈傳不同的人名呼叫MakeDocx10就可以得到10個Word檔。

# by 貓鷹鷹

大大您真的好耐心喔,謝謝你,我試看看,真的是萬分感謝 v

# by Edison

不好意思,請我在我自己電腦的本機上可以套版產出 但是放上 server 會有問題,我想請問的是 是不是需要再 server 安裝 word

# by Jeffrey

to Edison, Server必須要有Open XML Format SDK,不必安裝Word。放上Server有問題,有詳細的錯誤訊息嗎?

# by carlshen

如果是多頁要如何處理

# by Jeffrey

to carlshen, 如果是一份文件有多頁,上述置換邏輯一樣適用。若是同份文件要用套不同內容產生多頁,可參考討論串中中貓頭鷹所提的問題。

# by Eric

請教黑大, 如果要產出明細表word, 即有畫table的明細資料表,且有每頁小計列,及最後有合計列,這類word報表,要如何使用open xml撰寫? 以vb.net開發, 謝謝

# by Jeffrey

to Eric, 可參考MSDN上的範例 https://msdn.microsoft.com/en-us/library/office/gg490656%28v=office.14%29.aspx ,有點費工,表格需求用Excel做較省工。另一個思考方向是用Reporting Service做,再匯出成Word。

# by

我今天也在套表功能,但我是呼叫WORD 套表功能,參考:https://support.microsoft.com/zh-tw/kb/301659 但我一直卡在一個問題,就是我想要畫表格讓參數自動放進去如 FillRowCell(wrdDoc, 1, "測試編號", wrdMergeFields.Add(wrdSelection.Range, "測試編號")); FillRowCell(wrdDoc, 2, "轉換編號/總數", wrdMergeFields.Add(wrdSelection.Range, "轉換編號總數")); 但是,wrdMergeFields.Add(wrdSelection.Range, "測試編號")這個不管我怎麼試,它永遠都無法打在表格內.....

# by

即使我使用了 FillRowCell(wrdDoc, 1, "測試編號", wrdMergeFields.Add(wrdSelection.Range, "測試編號").ToString()); 呈現的方式 也會把所有的欲顯示的值都放在第一格裡,而其它格是顯示System.__ComObject @~@ 1.PGMPFactories_DomesticDealer(工廠經銷商) 0T-01-09-011/129工廠經銷商ETL2016/05/04/00時00分01秒00測試編號 System.__ComObject 轉換編號/總數 System.__ComObject 轉入資料表名稱 System.__ComObject 資料轉換方式 System.__ComObject 轉換日期/所需時間 System.__ComObject 資料確認日期 2016-08-04 來源資料筆數 目地資料筆數 System.__ComObject System.__ComObject 請放來源圖片 請放目的圖片

# by

把語法全貼上來(麻煩黑大了) using Microsoft.Office.Interop.Word; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Word = Microsoft.Office.Interop.Word; using Excel = Microsoft.Office.Interop.Excel; using ofd=System.Windows.Forms.OpenFileDialog; using DataTable = System.Data.DataTable; using StreamReader = System.IO.StreamReader; using System.Data.OleDb; namespace 測試報告產製 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } Word.Application wrdApp; Word._Document wrdDoc; Object oMissing = System.Reflection.Missing.Value; Object oFalse = false; private void InsertLines(int LineNum) { // Insert "LineNum" blank lines. for(iCount = 1; iCount<=LineNum; iCount++) { wrdApp.Selection.TypeParagraph(); } } private void FillRow(Word._Document oDoc, int Row, string Text1, string Text2,string Text2) { // Insert the data into the specific cell. oDoc.Tables[1].Cell(Row, 1).Range.InsertAfter(Text1); oDoc.Tables[1].Cell(Row, 2).Range.InsertAfter(Text2); oDoc.Tables[1].Cell(Row, 3).Range.InsertAfter(Text3); } private void FillRowCell(Word._Document oDoc, int Row, string Text1,string Text2) { // Insert the data into the specific cell. oDoc.Tables[1].Cell(Row, 1).Range.InsertAfter(Text1); oDoc.Tables[1].Cell(Row, 2).Range.InsertAfter(Text2); } public DataTable GetCSVData(string filePathExt, string fileNameExt) { string strCon = "Provider='Microsoft.Jet.OLEDB.4.0';" + "Data Source='" + filePathExt + "';Extended Properties='Text;IMEX=1;HDR=Yes;'"; // string strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + filePathExt + ";Extended Properties='Text;HDR=NO;IMEX=1'"; //csv路徑及相關資訊匯入 OleDbConnection GetCSV = new OleDbConnection(strCon); DataTable Table = new DataTable(); //打開檔案 GetCSV.Open(); OleDbDataAdapter daCSV = new OleDbDataAdapter("SELECT * FROM " + fileNameExt, GetCSV); DataSet ds = new DataSet(); daCSV.Fill(ds); DataTable dt = ds.Tables[0]; GetCSV.Close(); return dt; } private void CreateMailMergeDataFile(DataTable dt) { Word._Document oDataDoc; int iCount; Object oName = "C:\\DataDoc.doc"; Object oHeader = "序號, 步驟名稱,轉換編號總數"; wrdDoc.MailMerge.CreateDataSource(ref oName, ref oMissing, ref oMissing, ref oHeader, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing); // Open the file to insert data. oDataDoc = wrdApp.Documents.Open(ref oName, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing/*, ref oMissing */); for (iCount = 1; iCount <= dt.Rows.Count-1; iCount++) { oDataDoc.Tables[1].Rows.Add(ref oMissing); } // Fill in the data. int i; for (i=0;i<dt.Rows.Count;i++) { //FillRow(oDataDoc, i+2, dt.Rows[i]["序號"].ToString(), dt.Rows[i]["步驟名稱"].ToString(), dt.Rows[i]["轉換編號總數"].ToString()); } // Save and close the file. oDataDoc.Save(); oDataDoc.Close(ref oFalse, ref oMissing, ref oMissing); //確保Document COM+釋放 // if (oDataDoc != null) // Marshal.FinalReleaseComObject(oDataDoc); // oDataDoc = null; } private void button1_Click(object sender, System.EventArgs e) { Word.Selection wrdSelection; Word.MailMerge wrdMailMerge; Word.MailMergeFields wrdMergeFields; Word.Table wrdTable; DataTable dt; //string StrToAdd; try { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Comma-Delimited Files (*.csv)|*.csv"; DialogResult result = ofd.ShowDialog(); // 顯示檔案對話方框並回傳狀態(DialogResult.OK、DialogResult.Cancel) if (ofd.ShowDialog() == DialogResult.OK) { string fn = ofd.FileName.ToString(); //檔名 string fileNameExt = fn.Substring(fn.LastIndexOf("\\") + 1); string filePathExt = fn.Substring(0, fn.LastIndexOf("\\")); dt = GetCSVData(filePathExt, fileNameExt); // Create an instance of Word and make it visible. wrdApp = new Word.Application(); wrdApp.Visible = true; // Add a new document. wrdDoc = wrdApp.Documents.Add(ref oMissing, ref oMissing, ref oMissing, ref oMissing); wrdDoc.Select(); wrdSelection = wrdApp.Selection; wrdMailMerge = wrdDoc.MailMerge; // Create a MailMerge Data file. CreateMailMergeDataFile(dt); InsertLines(4); // Insert merge data. wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft; wrdMergeFields = wrdMailMerge.Fields; wrdMergeFields.Add(wrdSelection.Range, "序號"); wrdSelection.TypeText("."); wrdMergeFields.Add(wrdSelection.Range, "步驟名稱"); wrdSelection.TypeParagraph(); InsertLines(2); wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphJustify; // Insert a new table with 9 rows and 2 columns. wrdTable = wrdDoc.Tables.Add(wrdSelection.Range, 9, 2, ref oMissing, ref oMissing); // Set the column widths. wrdTable.Columns[1].SetWidth(180, Word.WdRulerStyle.wdAdjustNone); wrdTable.Columns[2].SetWidth(180, Word.WdRulerStyle.wdAdjustNone); // Set the shading on the first row to light gray. wrdTable.Columns[1].Cells[1].Shading.BackgroundPatternColorIndex = Word.WdColorIndex.wdGray25; wrdTable.Columns[1].Cells[2].Shading.BackgroundPatternColorIndex = Word.WdColorIndex.wdGray25; // Bold the first row. wrdTable.Rows[1].Range.Bold = 1; // Center the text in Cell (1,1). wrdTable.Cell(1, 1).Range.Paragraphs.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter; // F ill each row of the table with data. // int i = 0; // for (i = 0; i <= dt.Rows.Count; i++) // { FillRowCell(wrdDoc, 1, "測試編號", wrdMergeFields.Add(wrdSelection.Range, "序號").ToString()); FillRowCell(wrdDoc, 2, "轉換編號/總數", wrdMergeFields.Add(wrdSelection.Range, "轉換編號總數").ToString()); // Go to the end of the document. Object oConst1 = Word.WdGoToItem.wdGoToLine; Object oConst2 = Word.WdGoToDirection.wdGoToLast; wrdApp.Selection.GoTo(ref oConst1, ref oConst2, ref oMissing, ref oMissing); InsertLines(2); // Perform mail merge. wrdMailMerge.Destination = Word.WdMailMergeDestination.wdSendToNewDocument; wrdMailMerge.Execute(ref oFalse); // Close the original form document. wrdDoc.Saved = true; wrdDoc.Close(ref oFalse, ref oMissing, ref oMissing); // Release References. wrdSelection = null; wrdMailMerge = null; wrdMergeFields = null; wrdDoc = null; wrdApp = null; System.IO.File.Delete("C:\\DataDoc.doc"); } else // 操作檔案 openFileDialog1.FileName txtbSourceFile.FileName = string.Empty; } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { // Clean up temp file. System.IO.File.Delete("C:\\DataDoc.doc"); Console.WriteLine("Done"); Console.ReadLine(); } } } }

# by Jeffrey

to 云,此處怪怪的,FillRowCell(Word._Document oDoc, int Row, string Text1,string Text2),Text2要接收字串參數,而 FillRowCell(wrdDoc, 1, "測試編號", wrdMergeFields.Add(wrdSelection.Range, "測試編號").ToString());中的wrdMergeFields.Add(wrdSelection.Range, "測試編號")是一個COM+物件,故ToString()變成"System.__ComObject"是合理的,這應不是你預期的結果。

# by

黑大.真想貼圖給你看...其實我想要標籤能打在格子裡面,但是他的 wrdMergeFields 一定會先打在 第一格裡 然後連續打完 我要的字...我創建FillRowCell() 是希望他能幫我把所有欄位都存在同張表格裡, 所以我卡在..要如何把我真正要 標註的資料 能夠明確放入 格子裡 請看以下,他都把資料 都打在左上角...其實我是想打在System.__ComObject, T-01-09-01 1/129 工廠經銷商ETL2016/05/04/00時00分01秒00測試編號 System.__ComObject,MERGEFIELD 這裡原本的位置...不知道還能調什麼? 轉換編號/總數 MERGEFIELD 轉換編號總數 轉入資料表名稱 MERGEFIELD 轉入資料表名稱 資料轉換方式 MERGEFIELD 資料轉換方式 轉換日期/所需時間 MERGEFIELD 轉換所需時間 資料確認日期 2016-08-05 來源資料筆數 目地資料筆數 MERGEFIELD 資料來源筆數 MERGEFIELD 資料轉換筆數 請放來源圖片 請放目的圖片

# by

預期希望他能長這樣子 測試編號 T-01-09-01 轉換編號/總數 1/129 轉入資料表名稱 工廠經銷商 資料轉換方式 ETL 轉換日期/所需時間 2016/05/04/00時00分01秒 資料確認日期 2016-08-05 來源資料筆數 目地資料筆數 0 0

# by

解決了...哈~因為我先作標誌了..沒有改位置 在FillRowCell(wrdDoc,wrdMergeFields, 1, "測試編號","測試編號"); 再加了參數及加了換全值的方法...如果有人要使用可以參考一下 黑大 ,你要不要把我一些多餘留言先刪除..不然很佔空間 XDDDD

# by Jeffrey

to 云,恭喜破關。未來分享程式碼可考慮用Gist:https://solinariwu.blogspot.tw/2016/08/github-gist-gist.html

# by Galaxy

Hi, 黑大 我和Edison有同樣的問題 在自己電腦的本機上可以套版產出,但是放上server時,可以存出docx,但是在開啟時word會報說"無法開啟,因為發現其內容有問題" 在Server也裝上了OpenXMLSDKv2.msi 是否還有其他的注意事項呢?謝謝。

# by Jeffrey

to Galaxy, 有點棘手,感覺是套版過程破壞到XML結構,我想到的解法是解開docx比對套版前後XML的差異找原因。另外,OpenXML現在可用NuGet安裝,能排除在不同機器上版本有別的因素,也可試試。

# by Galaxy

黑大 進度報告: 我在DocxHelper.cs中的這段程式: string tempFile = Path.GetTempPath() + "temp.docx"; 原本是".docx" 改為 "temp.docx" 匯出後開啟,雖然Word仍會報內容有問題,但已經能開啟,不再是空白一片,同時內容也正確替換。 只是有點像開temp文件的感覺,檔名變為"文件1",需要使用者重新存檔一次,就不會再出現錯誤訊息。 感覺是原始Template檔案就有格式問題,只是Word能容錯,在套表時又爆出來,重新存檔後,Word又補好洞了 XDDD

# by Ting

黑大 各位程式高手大大 大家好 小弟想請問一下此做法是否能運用在圖檔上 在word上插入一張圖檔

# by Jeffrey

to Ting, 插入圖檔我建議用OpenXML SDK,參考:http://blog.darkthread.net/post-2017-11-06-insert-image-to-docx.aspx

# by Ting

黑大 謝謝您 我來好好研究...

# by Orson

請教黑大 我試用你的方法成功docx 套版完成,真的十分驚豔, 但我有想到一例,想請教您會怎麼做或是要用excel來解決? 以你中獎通知書來舉例,假如這通知書可以一次把多筆中獎金額一一列入,該怎麼套入? 因為我們不會知道最多中幾筆,也許一筆也許100筆,而中獎金額格式一律照您的圖例是完整四邊黑線,100筆就會產生100列,這時您建議有何妙方?謝謝

# by Jeffrey

to Orson, 基本上還是要靠 OpenXML,至於怎麼設定成用範本重複產生需要花點心思,之前曾試做過半成品,未來有機會再整理分享。至於 OpenXML 產生表格可以參考:https://docs.microsoft.com/en-us/office/open-xml/how-to-insert-a-table-into-a-word-processing-document

Post a comment