有個需求是,程式匯出一份word報告,報告中有各種各樣的表格,匯出時還需要插入圖片。
腦海中迅速閃過好幾種元件,openxml元件,com元件,npoi。為了減少程式畫複雜表格,我們選用了com元件+word模板的方式,程式只需要對word中的書籤進行賦值即可。
不知道這幾種元件的(或者還有其他寫入word的元件可以推薦)優缺點各是什麼,還請各路大拿評論區指點一二。
com元件唯一讓人不爽的就是他過於依賴word,因為版本帶來的不相容問題,及各種會生成WORD半途會崩潰的問題.而且很難解決。
不說這麼悲傷的事情了,反正坑都踩了,文章後面我會附上各種深坑的解決方案,今天主要分享的是com元件寫入word的各種操作。
1.書籤賦值
/// <summary>
/// 給特定書籤賦值
/// </summary>
/// <param name="bookMarkName"></param>
/// <param name="value"></param>
public void EditTable(string bookMarkName, string value, Document doc)
{
try
{
if (!doc.Bookmarks.Exists(bookMarkName))
return;
object s = bookMarkName;
Range rng = doc.Bookmarks.get_Item(ref s).Range;
rng.Text = value;
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
2.獲取指定書籤的範圍
/// <summary>
/// 獲取指定書籤的Range
/// </summary>
/// <param name="bookMarkName">書籤名稱</param>
public Range GetBookMarkRange(string bookMarkName, Document doc)
{
object oBookMarkName = bookMarkName;
return doc.Bookmarks.get_Item(ref oBookMarkName).Range;
}
3.新增書籤
/// <summary>
/// 新增書籤
/// </summary>
/// <param name="bookMarkName">書籤名</param>
/// <param name="activeRange">要新增書籤的範圍</param>
public void AddBookMark(string bookMarkName, Range activeRange, Document doc)
{
object oActiveRange = activeRange;
doc.Bookmarks.Add(bookMarkName, ref oActiveRange);
}
4.複製書籤內容到另一個書籤
/// <summary>
/// 複製書籤內容至另一書籤
/// </summary>
/// <param name="sourceBookMarkName">源書籤</param>
/// <param name="toBookMarkName">目標書籤</param>
public void CopyRange(string sourceBookMarkName, string toBookMarkName, Document doc)
{
object oSourceBookMarkName = sourceBookMarkName;
object oToBookMarkName = toBookMarkName;
Range roRange = CopyRange(doc.Bookmarks.get_Item(ref oSourceBookMarkName).Range, doc.Bookmarks.get_Item(ref oToBookMarkName).Range);
doc.Bookmarks.get_Item(ref oToBookMarkName).Delete();
AddBookMark(toBookMarkName, roRange, doc);
}
/// <summary>
/// 複製選定範圍內容至另一範圍
/// </summary>
/// <param name="sourceRange">源範圍</param>
/// <param name="activeRange">目標範圍</param>
public Range CopyRange(Range sourceRange, Range activeRange)
{
try
{
lock (copyLock)
{
sourceRange.Copy();
activeRange.Paste();
}
return activeRange;
}
catch
{
KillwordProcess();
throw;
}
}
5.開啟word檔案
/// <summary>
/// 開啟檔案
/// </summary>
/// <param name="Path">檔案路徑及檔名</param>
/// <param name="IsVisible">是否可見</param>
public void OpenWord(string Path, bool IsVisible, Document doc)
{
object oMissing = System.Reflection.Missing.Value;
GUIDCaption = Guid.NewGuid().ToString();
app.Visible = IsVisible;//是否實時預覽
app.Caption = GUIDCaption;
object oPath = Path;
doc = app.Documents.Open(ref oPath, 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);
}
6.表格縱向合併
/// <summary>
/// 縱向合併
/// </summary>
/// <param name="bookMarkName"></param>
/// <param name="tableIndex"></param>
/// <param name="row_a">開始行號</param>
/// <param name="col_a">開始列號</param>
/// <param name="row_b">結束行號</param>
/// <param name="col_b">結束列號</param>
public void MergeColumnCell(string bookMarkName, int tableIndex, int row_a, int col_a, int row_b, int col_b, Document doc)
{
try
{
object oBookMarkName = bookMarkName;
Range activeRange = doc.Bookmarks.get_Item(ref oBookMarkName).Range;
var newTable = activeRange.Tables[tableIndex];
newTable.Cell(row_a, col_a).Merge(newTable.Cell(row_b, col_b));
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
7.插入圖片
/// <summary>
/// 插入圖片
/// </summary>
/// <param name="bookMarkName"></param>
/// <param name="fileName">圖片路徑</param>
/// <param name="type">圖片版式:四周型,嵌入型,環繞型</param>
public void InsertPicture(string bookMarkName, string fileName, WdWrapType type)
{
try
{
if (!doc.Bookmarks.Exists(bookMarkName))
{
return;
}
object oBookMarkName = bookMarkName;
Microsoft.Office.Interop.Word.Range activeRange = doc.Bookmarks.get_Item(ref oBookMarkName).Range;
object linkToFile = false;
object saveWithDocument = true;
InlineShape inlineShape = doc.InlineShapes.AddPicture(fileName, ref linkToFile, ref saveWithDocument, activeRange);
inlineShape.ConvertToShape().WrapFormat.Type = type;
}
catch (Exception ex)
{
KillwordProcess();
//記錄日誌
AppCommon.AppLogger.WriteLog("插入圖片,錯誤為:" + ex.ToString(), ((int)AppCommon.Unitity.AppChannelEnmu.SS).ToString());
}
}
8.設定圖片大小
/// <summary>
/// 設定圖片大小
/// </summary>
/// <param name="filePath"></param>
/// <param name="width"></param>
/// <param name="height"></param>
public string SetPicture(string filePath, int width, int height)
{
Bitmap bm = new Bitmap(filePath);
Bitmap thumb = new Bitmap(width, height);
Graphics g = Graphics.FromImage(thumb);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(bm, new System.Drawing.Rectangle(0, 0, width, height), new System.Drawing.Rectangle(0, 0, bm.Width, bm.Height), GraphicsUnit.Pixel);
g.Dispose();
string path = ConfigurationManager.AppSettings["MapPath"].ToString() + "/Assets/internal/img/new.png";
thumb.Save(path, System.Drawing.Imaging.ImageFormat.Png);
bm.Dispose();
thumb.Dispose();
return path;
}
9.書籤範圍內新增一行
/// <summary>
/// 在書籤範圍內插入一行到指定表中
/// </summary>
/// <param name="BookMarkName">書籤名稱</param>
/// <param name="tableIndex">表的索引</param>
/// <param name="rowIndex">插入行的位置</param>
public void InsertRow(string bookMarkName, int tableIndex, int rowIndex)
{
try
{
if (!doc.Bookmarks.Exists(bookMarkName))
{
return;
}
object oBookMarkName = bookMarkName;
Microsoft.Office.Interop.Word.Range activeRange = doc.Bookmarks.get_Item(ref oBookMarkName).Range;
InsertRow(activeRange, tableIndex, rowIndex);
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
/// <summary>
/// 在所選範圍內插入一行到指定表中
/// </summary>
/// <param name="activeRange">範圍物件</param>
/// <param name="tableIndex">所選範圍內表的索引</param>
/// <param name="rowIndex">插入行的位置</param>
public void InsertRow(Microsoft.Office.Interop.Word.Range activeRange, int tableIndex, int rowIndex)
{
try
{
object NumRows = 1;
activeRange.Tables[tableIndex].Rows[rowIndex].Select();
doc.ActiveWindow.Panes[1].Selection.InsertRowsBelow(ref NumRows);
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
10.刪除書籤範圍內容
/// <summary>
/// 刪除書籤範圍內容
/// </summary>
/// <param name="bookMarkName">書籤名</param>
public void DeleteBookMarkRange(string bookMarkName)
{
try
{
object oBookMarkName = bookMarkName;
if (doc.Bookmarks.Exists(bookMarkName))
{
DeleteRange(doc.Bookmarks.get_Item(ref oBookMarkName).Range);
}
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
/// <summary>
/// 刪除所選範圍內容
/// </summary>
/// <param name="activeRange">所選範圍物件</param>
public void DeleteRange(Range activeRange)
{
try
{
if (null != activeRange)
{
object oMissing = Missing.Value;
activeRange.Delete(ref oMissing, ref oMissing);
foreach (Table dTable in activeRange.Tables)
{
dTable.Delete();
}
}
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
11.刪除書籤範圍的行
/// <summary>
/// 在書籤範圍內指定表刪除行
/// </summary>
/// <param name="bookMarkName">書籤名稱</param>
/// <param name="tableIndex">表索引</param>
/// <param name="rowIndex">行號</param>
public void DeleteRow(string bookMarkName, int tableIndex, int rowIndex)
{
try
{
object oBookMarkName = bookMarkName;
if (doc.Bookmarks.Exists(bookMarkName))
{
Microsoft.Office.Interop.Word.Range activeRange = doc.Bookmarks.get_Item(ref oBookMarkName).Range;
DeleteRow(activeRange, tableIndex, rowIndex);
}
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
/// <summary>
/// 在所選範圍內指定表刪除行
/// </summary>
/// <param name="activeRange">所選範圍物件</param>
/// <param name="tableIndex">表索引</param>
/// <param name="rowIndex">行號</param>
public void DeleteRow(Microsoft.Office.Interop.Word.Range activeRange, int tableIndex, int rowIndex)
{
try
{
if (activeRange.Tables[tableIndex].Rows.Count >= rowIndex)
{
activeRange.Tables[tableIndex].Rows[rowIndex].Delete();
}
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
12.插入分頁符
public static void InsertBreak(Document doc, string bookmark)
{
object oBookMarkName = bookmark;
if (doc.Bookmarks.Exists(bookmark))
{
object pBreak = (int)WdBreakType.wdPageBreak;
doc.Bookmarks.get_Item(ref oBookMarkName).Range.InsertBreak(ref pBreak);
}
//object oBookMarkName = bookmark;
//Range perRange = doc.Bookmarks.get_Item(ref oBookMarkName).Range;
//object oEnd = perRange.End;
//object oWdBreak = (int)WdBreakType.wdPageBreak;
//doc.Range(ref oEnd, ref oEnd).InsertBreak(ref oWdBreak);
//doc.Range(ref oEnd, ref oEnd).InsertParagraph();
}
13.結束word程式
異常的時候一定要執行此方法,否則,word程式就會被遺留在記憶體中,後續如果繼續執行,會積累更多的程式,導致程式卡死,記憶體飆升。
/// <summary>
/// 結束word程式
/// </summary>
private void KillwordProcess()
{
try
{
Process[] myProcesses;
//DateTime startTime;
myProcesses = Process.GetProcessesByName("WINWORD");
//通過程式視窗標題判斷
foreach (Process myProcess in myProcesses)
{
if (null != GUIDCaption && GUIDCaption.Length > 0 && myProcess.MainWindowTitle.Equals(GUIDCaption))
{
myProcess.Kill();
}
}
}
catch (Exception e)
{
AppCommon.AppLogger.WriteLog("結束Word,錯誤為:" + e.ToString(), ((int)AppCommon.Unitity.AppChannelEnmu.SS).ToString());
throw e;
}
}
14.刪除空行
這段程式碼厲害了,是用來精確控制的,你可以用它獲取到word的每一寸區域,這樣你想在哪裡寫值就在哪裡寫。下面這段程式碼是用來刪除書籤範圍上方的空行的,程式碼中會判斷上方是空行的區域,
如果上方區域有文字,則
iEnd為文字所在行-1,
range.Start表示該書籤區域開頭所在的行
titleRange就是要刪除的空行區域
/// <summary>
/// 刪除Range 上面的空行
/// </summary>
/// <param name="range"></param>
/// <param name="holdblank">下面保留幾個空行,預設保留一個</param>
public void DeleteRangeUpBlankRow(Document doc, Range range, int holdblank = 1)
{
try
{
int iStart = range.Start - holdblank;
int iEnd = range.Start - holdblank;
object oStart = range.Start - holdblank;
object oEnd = range.Start - holdblank;
Range titleRange = doc.Range(ref oStart, ref oEnd);
//獲取標題範圍 從表格範圍起始位置開始 每次迴圈範圍向前增加1
while (true)
{
iStart = iStart - 1;
titleRange.SetRange(iStart, iEnd);
if (iStart < 0 || IsTitleRangeOver(titleRange))
{
titleRange.SetRange(iStart + 1, iEnd);
break;
}
}
//刪除空行
object oMissing = Missing.Value;
titleRange.Delete(ref oMissing, ref oMissing);
}
catch (Exception e)
{
KillwordProcess();
throw e;
}
}
/// <summary>
/// 判斷要刪除表的標題範圍是否超出
/// </summary>
/// <param name="titleRange"></param>
/// <returns></returns>
public bool IsTitleRangeOver(Range titleRange)
{
if (titleRange.Tables.Count > 0)//範圍內包含表格
{
return true;
}
if (titleRange.Paragraphs[1].PageBreakBefore == 1)//有分頁符
{
return true;
}
if (!string.IsNullOrEmpty(titleRange.Text.Trim()))//內容不為空了
{
return true;
}
return false;
}
常見錯誤及處理方案:
1.報告生成期間異常:System.Runtime.InteropServices.COMException (0x8001010A): 訊息篩選器顯示應用程式正在使用中。 (異常來自 HRESULT:0x8001010A (RPC_E_SERVERCALL_RETRYLATER))
解決方案:出現這種錯誤有限檢查COM元件許可權是否正常,檢查步驟:
(1)開啟元件服務
(2)開啟DCOM配置
(3)找到word97-2003
(4)按照如圖依次配置
(5)一般這裡我們選擇使用者名稱密碼 的方式,這個密碼必須在web.Config中進行配置
<identity impersonate="true" userName="伺服器登入名" password="伺服器登陸密碼" />
2.System.Runtime.InteropServices.COMException (0x800A1735): 集合所要求的成員不存在。
解決方案:這個是程式錯誤,只要除錯程式碼即可發現錯誤。
3.System.Runtime.InteropServices.COMException (0x800A1710): 無法編輯 Range。
解決方案:這個說明編輯某個區域的時候,超出了範圍,或者找不到這個區域範圍,這個也是程式碼的問題,需要仔細查詢,不是很好定位。
-------------------------------------------(正文完)-----------------------------------------------------
還有一些其他的問題沒有收集,大家遇到了可以在評論區發出來,我能幫大家解答的一定盡我所能!
好啦,今天的分享就到這裡……
向著高階,進發!