在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