在word2003的时代,word中图表的实现,还是通过MSChart(如图1)来实现的,该图表控件在word中是OLE COM对象,是以out-process的方式进行工作。在VSTO中可以对其进行直接操作。但MSChart的图表对象确实在样式和美观上远不及word2007之后的excel图表。如图2,MSChart图表的样式。
figure 1. 添加MSChart对象
figure 2. MSChart图表样式
当然要是word2007中的图表,就必须要求office的版本在2007或更高。但是安装了office2007后,使用VS2008创建word2007插件,貌似还不能实现excel图表的操作,无论怎么找也找不到Chart相关的接口。 原来PIA 2007中Microsoft.Office.Interop.Word.dll好像没有这部分接口(如图3)。是否通过VSTO就无法操作excel图表来吗?默认在VS2008中新建的WORD2007插件项目中,引用的Microsoft.Office.Interop.Word.dll是微软官方PIA提供的dll文件,如图4.
figure 3.PIA 2007 中Microsoft.Office.Interop.Word.dll部分接口
figure 4. 默认引用pia的dll文件
PIA是Primary Interop Assemblies的简称,它是由微软提供的接口组件,其中包含了COM接口的类型定义,方法签名等。它是.net操作COM的一种实现方式--—COM Interop。而PIA是微软官方提供了一套操作WORD的COM接口,只是貌似漏了一些接口而已。无论是官方还是非官方提供的接口,要想实现COM接口,客户机必须依据注册了该COM对象,否则也无法使用。一般只有客户机安装了office,那么就已经注册了Office的相关COM对象,如Excel,Word等。我们可以通过引用COM的方式,来让VS2008帮我们生成COM的接口签名,如图添加Microsoft Word 14.0 Object Library,由于本机安装的是office2010,因此就没有Microsoft Word 12.0 Object Library。
figure 5. 添加Microsoft Word 14.0 Object Library COM 引用
添加完COM引用后,在项目的引用列表中出现了Microsoft.Office.Interop.Word.dll和默认的PIA引用名称是一样的,但属性却有很大不同。如图6
figure 6. Microsoft Word 14.0 Object Library COM 引用的属性
这是因为VS2008生成的Interop是放在GAC中的,并且版本号也与PIA有很大不同。最重要的是,该dll中包含了Chart相关接口。如图7.
figure 7. Microsoft Word 14.0 Object Library 接口
有了这些接口,操作Excel图表就没有问题了,因为由于操作的是excel图表,图表的数据是存储在excel中的,因此也需要将excel的com或PIA引用进WORD插件项目中,这样就可以方便的操作excel中的数据了。另外如果您觉得引用COM不方便,或者没有安装office2007而安装的是office2010的话,您可以自己编写Interop,并指需要实现Chart相关的接口接口。如果您觉得还是比较麻烦,那么你可以使用项目默认的pia,然后下载我编写好的Interop(http://code.google.com/p/interopword/),该Interop只包含了Chart相关的接口。
Demo
新建ChartDemo,Word2007插件项目,添加Interop.Word.dll引用和Excel,PIA引用。添加菜单中添加一个按钮,用于触发更新图表事件。项目如图8.
figure 8. Solution.
在ThisAddIn.cs文件中添加代码,用于构建菜单:
protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject() {
return new Ribbon();
}
核心代码,用于更新图表数据:
public void btnUpdate_Click(Office.IRibbonControl sender) { UpdateCharts(); } private void UpdateCharts() { ExcelProcessManager pm = new ExcelProcessManager(); pm.Lock(); foreach (Word.Bookmark mark in Globals.ThisAddIn.Application.ActiveDocument.Bookmarks) { // must cast // the inlineshape does not contain the chart property in pia. Interop.Word.InlineShapes inlineShapes = (Interop.Word.InlineShapes)mark.Range.InlineShapes; if (inlineShapes.Count > 0) { try { object Name = new object(); Name = mark.Name; Object[,] obj = new Object[11, 3]; // fill data to obj /* ------------------------------------ * | | 收入 | 支出 | * ------------------------------------ * | 2012/03/01 | 1023.2 | 304.23 | * ------------------------------------- * | 2012/03/02 | 1123.2 | 314.23 | * ------------------------------------- * | 2012/03/03 | 1223.2 | 674.23 | * ------------------------------------- * | 2012/03/04 | 1043.2 | 124.23 | * ------------------------------------- * | 2012/03/05 | 1083.2 | 384.23 | * ------------------------------------- * | 2012/03/06 | 923.2 | 404.23 | * ------------------------------------- * | 2012/03/07 | 2023.2 | 305.23 | * ------------------------------------- * | 2012/03/08 | 1623.2 | 303 | * ------------------------------------- * | 2012/03/09 | 1403.2 | 314.23 | * ------------------------------------- * | 2012/03/10 | 1003.2 | 340 | * ------------------------------------- */ //添加横向第一列作为标题 obj[0, 0] = string.Empty; obj[0, 1] = "收入"; obj[0, 2] = "支出"; DateTime dt = new DateTime(2012, 3, 1); Random rnd = new Random(); int last = 1000; // fill data for (int i = 1; i < 11; i++) { obj[i, 0] = dt.ToString("yyyy/MM/dd"); obj[i, 1] = last + rnd.Next(i, 100); obj[i, 2] = last - rnd.Next(i, 100); last = (int)obj[i, 1]; dt = dt.AddDays(1); } Interop.Word.Chart chart = inlineShapes[1].Chart; ChartUpdate(chart, Name.ToString(), obj); } catch (Exception ex) { //logger.Warn("Chart update failed!", ex); } } } pm.Release(); } protected void ChartUpdate(Interop.Word.Chart chart, string bookMark, Object[,] dataArr) { if (chart == null) { //logger.Warn("Word.Chart is null!"); return; } if (dataArr == null) { //logger.Warn("Data is null!"); return; } Object oMissing = System.Reflection.Missing.Value; Interop.Word.Chart chrt; Microsoft.Office.Interop.Excel.Workbook wb = null; Object bk = bookMark; try { //xlApp.ScreenUpdating = false; chrt = chart; if (chrt.ChartData == null) { //logger.Warn("chart.ChartData is null!"); return; } chrt.ChartData.Activate(); wb = (Excel.Workbook)chrt.ChartData.Workbook; if (wb == null) { //logger.Warn("chart.ChartData.Workbook is null!"); return; } wb.Application.ScreenUpdating = false; wb.Application.WindowState = Microsoft.Office.Interop.Excel.XlWindowState.xlMinimized; Microsoft.Office.Interop.Excel.Worksheet wSh = (Excel.Worksheet)wb.Worksheets[1]; if (wSh == null) { //logger.Warn("chart.ChartData.Workbook.Worksheets[1] is null!"); return; } // save formats wSh.Cells.ClearContents(); Microsoft.Office.Interop.Excel.Range Rng = wSh.get_Range("A1", "A1"); Rng.get_Resize(dataArr.GetUpperBound(0) + 1, dataArr.GetUpperBound(1) + 1).Value2 = dataArr; chrt.SetSourceData("'Sheet1'!" + Rng.get_Resize(dataArr.GetUpperBound(0) + 1, dataArr.GetUpperBound(1) + 1).get_Address(Type.Missing, Type.Missing, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing), Type.Missing); chrt.Refresh(); wb.Application.ScreenUpdating = true; wb.Close(Type.Missing, Type.Missing, Type.Missing); } catch (System.Runtime.InteropServices.COMException ex) { //logger.Warn("Update chart failed!", ex); //xlApp.ScreenUpdating = true; } finally { if (wb != null) { try { Marshal.ReleaseComObject(wb); } catch { } } } } private class ExcelProcessManager { private bool _exits; public ExcelProcessManager() { } public void Lock() { _exits = Process.GetProcessesByName("EXCEL").Length > 0; } public void Release() { if (_exits) return; Process[] excelproc = Process.GetProcessesByName("EXCEL"); if (excelproc.Length > 0) excelproc[0].Kill(); } }
其中UpdateCharts和ChartUpdate方法用于生成数据,更新图表。ExcelProcessManager类用于管理Excel进程。
下载:
Interop.Word:http://code.google.com/p/interopword/
Demo:chartdemo, chart demo word.docx