有个需求是,程序导出一份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。
解决方案:这个说明编辑某个区域的时候,超出了范围,或者找不到这个区域范围,这个也是代码的问题,需要仔细查找,不是很好定位。
-------------------------------------------(正文完)-----------------------------------------------------
还有一些其他的问题没有收集,大家遇到了可以在评论区发出来,我能帮大家解答的一定尽我所能!
好啦,今天的分享就到这里……
向着高级,进发!