|
生成智能文档解决方案的涉及到的大多数工作包括建立一个Word或Excel文档,并把XML元素与它关联。实际上,你一般可以把已有的Word或Excel文档作为建立智能文档的基础。因为多数业务文档已经有了一定的结构,当你定义XML元素的时候仅仅需要对它进行形式化(formalize)和命名。 从开发者的角度看,该文档或电子表格是解决方案的基本UI。当用户编辑的时候,智能文档操作处理程序DLL定义了出现在该文档旁边的附加的UI。在可能的情况下,在建立智能文档的时候使用Word和Excel的下层能力,而不是在智能文档DLL中编写自定义代码是明智的。我的Excel进度表工作薄中的公式就演示了这一点。我希望在上一周的星期一得到状态报告,但是人们通常在星期一或星期四添加状态报告;在这以后是本周的星期一了。我不是编写一段C++代码实现这种操作,而是编写了Excel公式来压缩这个计算过程并把它放在工作表的一个单元中:
IF(WEEKDAY(TODAY(),3)<2, TODAY()-(7+WEEKDAY(TODAY(),3)), TODAY()-WEEKDAY(TODAY(),3))
我简单地使用一个自己编写的从Excel工作薄中得到值的函数从该工作表单元中载入计算过的日期值。
开始
现在我们从建立一个Excel电子表格以跟踪软件进度表开始。注意,我假定你已经下载了示例代码,它包含了必要的支持文件。
启动微软Excel 2003并打开Project Schedule (Original).xls。这个电子表格与大多数Excel电子表格一样,已经有了一个结构。现在使用XSD文档(例子中是ScheduleSmartDocument.xsd)把一个XML大纲与这个结构关联。在Excel中,点击"数据"菜单下的"XML"并选择"XML源"。当"XML源"事务面板出现的时候,点击"工作薄映射"按钮并添加ScheduleSmartDocument.xsd文件。接着点击"确定"关闭"浏览"对话框,再次点击"确定"关闭"XML映射"对话框。图1显示了添加大纲后的Excel"XML源"事务面板。
 图1.XML源
为了建立映射,你必须把元素从XML大纲中拖到工作薄上。对于类似Project Name和Developer的元素,这是很简单的事情,只需要把该XML元素名称从"XML源"事务面板上拖动到包含相关数据的工作薄单元上(例如,把ProjectName拖动到A2上)。对于分层显示的重复的进度表数据项,可以通过选择数据项把该组拖动到标题行(A5)上。你可以通过选择"XML源"事务面板中特定的元素(例如Description)来确认映射关系;工作薄中相应的部分应该高亮度显示。把结果工作薄保存为Project Schedule (Mapped).xls。在示例中,我将建立单个电子表格,或者你可以把进度表保存为Excel工作薄模版。这样的话,你就简化了用户建立该文档的新副本的过程,每个新的副本都将成为一个Excel智能文档。 ISmartDocument接口教程
下一步是建立智能文档操作DLL并安装它。在你编写智能文档操作处理程序DLL(它实现了ISmartDocument接口方法)之前,最好先了解一下这个接口。
当用户在文档中移动的时候,你的操作处理程序DLL将建立并管理一组出现在文档操作事务面板中的控件,允许你基于文档中的位置提供自定义的用户界面。映射到文档的XML大纲元素定义了用户在文档中的位置。XML元素应该指定给用户显示哪些控件。图2显示了你可以建立的控件类型。你将使用操作处理程序DLL中实现的ISmartDocument接口的方法建立这些控件。当这些控件被激活的时候(例如进入了文本框或者点击了某个按钮),Office通过ISmartDocument接口方法调用你的自定义操作处理程序DLL代码。
图2.智能文档控件类型
|
符号名称 |
控件类型 |
| C_TYPE_LINK |
超级链接 |
| C_TYPE_HELP |
作为HTM帮助文本提供的,并显示在文档操作事务面板中 |
| C_TYPE_HELPURL |
显示在文档操作事务面板中的URL-to-HTML帮助文本 |
| C_TYPE_SEPARATOR |
仅仅格式化可以使用的分隔线 |
| C_TYPE_BUTTON |
触发操作的命令按钮 |
| C_TYPE_LABEL |
显示的静态文本 |
| C_TYPE_IMAGE |
显示的图像文件,需要提供GIF、BMP、TIFF、JPEG、WMF或其它图像文件的路径 |
| C_TYPE_CHECKBOX |
检查框 |
| C_TYPE_TEXTBOX |
用于输入文本的编辑框 |
| C_TYPE_LISTBOX |
可滚动的列表框 |
| C_TYPE_COMBO |
组合框 |
| C_TYPE_ACTIVEX |
ActiveX 控件 |
| C_TYPE_DOCUMENT- FRAGMENT |
文档片段,只能提供Word XML文本 |
| C_TYPE_DOCUMENT- FRAGMENTURL |
文档片段,只能提供文件的链接 |
| C_TYPE_RADIOGROUP |
单选框 | 把IsmartDocument接口的方法分为几类:配置方法,它为Office描述了建立在文档操作事务面板中的控件(图3);控件绘制时(draw-time)方法,它们作为控件调用并绘制在事务面板上(图4);修改通知方法,Office把它们作为文档操作事务面板控件调用,由用户维护(图5)。
图3. ISmartDocument接口配置方法
|
方法 |
参数 |
描述 |
| SmartDocInitialize |
BSTR ApplicationName, LPDISPATCH Document, BSTR SolutionPath, BSTR SolutionRegKeyRoot |
当用户打开智能文档的时候被Office调用。它可以被用户执行预先部分的初始化。IDispatch 指向被打开的Word或Excel文档。SolutionPath 是安装解决方案的本地计算机的目录名称。 |
| get_SmartDocXmlTypeCount |
int * Count |
为Office提供智能文档中XML元素的数量,它们必须在文档操作面板中拥有内容。每个文档元素和控件都被指定一个从1开始的智能文档类型的唯一ID(在下面的方法中叫做SmartDocID)。 |
| get_SmartDocXmlTypeName |
int SmartDocID, BSTR * bstrElementName |
为Office提供与SmartDocID对应的XML元素名称(从1开始)。 |
| get_SmartDocXmlTypeCaption |
int SmartDocID, int LocaleID, BSTR * Caption |
为Office提供与SmartDocID 对应的元素名称在文档事务面板中显示的本地化说明。你必须在从DLL中的本地字符串表中载入它。 |
| get_ControlCount |
BSTR bstrElementName, int * Count |
给定一个来自智能文档的XML元素名称,告诉Office当用户位于文档中某个位置的时候,在文档操作事务面板中将显示多少个控件。 |
| get_ControlID |
BSTR bstrElementName, int ControlIndex, int * ControlID |
给定一个元素名称和一个控件索引(从1开始),你使用该方法赋予每个控件一个唯一的控件ID。该方法的其它部分用这个ID引用控件。 |
| get_ControlNameFromID |
int ControlID, BSTR * Name |
给定一个控件ID,提供控件的(内部)名称。 |
| get_ControlTypeFromID |
int ControlID, BSTR ApplicationName, int LocaleID, C_TYPE * Type |
给定一个控件ID,提供控件的类型(图2显示了类型列表)。 |
图4. ISmartDocument接口控件绘制方法
|
方法 |
参数 |
描述 |
| get_ControlCaptionFromID |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, BSTR * Caption |
提供给定的控件ID的可视的控件名称(作为输出BSTR)。 |
| PopulateHelpContent |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, BSTR * Content |
供C_TYPE_HELP或C_TYPE_HELPURL类型的控件调用以提供帮助文本。 |
| PopulateCheckbox |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, VARIANT_BOOL * Checked |
供C_TYPE_ CHECKBOX类型的控件调用以提供检查框的状态。 |
| PopulateTextboxContent |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, BSTR * Value |
供C_TYPE_TEXTBOX类型的控件调用以提供文本框的内容(在输出参数的BSTR值中)。 |
| PopulateListOrComboContent |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, SAFEARRAY * * List, int * Count, int * InitialSelected |
供C_TYPE_LISTBOX或C_TYPE_COMBO类型的控件调用以提供下拉框或组合框的内容。其内容作为List输出参数的一个VARIANT数组提供;数组中数据项的数量由Count 输出参数提供;最初的选择(从1开始)由InitialSelected 输出参数提供(如果没有选择返回-1)。 |
| PopulateDocumentFragment |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, BSTR * DocumentFragment |
仅仅被Word使用。供C_TYPE_DOCUMENTFRAGMENT类型的控件调用以提供文档片段的内容(作为HTML形式)。 |
| PopulateActiveXProps |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, ISmartDocProperties * ActiveXPropBag |
供C_TYPE_ACTIVEX类型的控件调用以提供初始化事务面板中的ActiveX 控件需要的信息。这些信息是通过IPropertyBag 提供的。 |
| PopulateImage |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, BSTR * ImageSrc |
供C_TYPE_IMAGE类型的控件调用以提供图像的路径(本地路径、UNC或URL)。图像可以是GIF、 JPEG或WMF。 |
| PopulateRadioGroup |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props, SAFEARRAY * * List, int * Count, int * InitialSelected |
供C_TYPE_RADIOGROUP类型的控件调用以提供单选按钮的内容。这些都作为VARIANT数组中的BSTR字符串、数组中数据项的数量、最初的选择(从1开始)提供的。 |
| PopulateOther |
int ControlID, BSTR ApplicationName, int LocaleID, BSTR Text, BSTR Xml, LPDISPATCH Target, struct ISmartDocProperties * Props |
供 C_TYPE_LINK和C_TYPE_BUTTON类型的控件调用以提供超级链接的内容。这些都作为VARIANT数组中的BSTR字符串、数组中数据项的数量、最初的选择(从1开始)提供的。 |
| OnPaneUpdateComplete |
LPDISPATCH Target |
当控件绘制好后被调用。 |
图5. ISmartDocument接口通知方法
| 方法 |
参数 |
描述 |
| InvokeControl |
int ControlID, BSTR ApplicationName, LPDISPATCH Target, BSTR Text, BSTR Xml, int LocaleID |
当用户按下按钮或点击文档片段中的链接的时候,为C_TYPE_LINK、 C_TYPE_BUTTON、C_TYPE_DOCUMENTFRAGMENT和C_TYPE_DOCUMENTFRAGMENTURL 类型的控件调用它。 |
| OnCheckboxChange |
int ControlID, LPDISPATCH Target, VARIANT_BOOL Checked |
当复选状态改变的时候,为C_TYPE_CHECKBOX类型的控件调用它。 |
| OnTextboxContentChange |
int ControlID, LPDISPATCH Target, BSTR Value |
当文本内容发生改变的时候,为 C_TYPE_TEXTBOX类型的控件调用它。 |
| OnListOrComboSelectChange |
int ControlID, LPDISPATCH Target, int Selected, BSTR Value |
当被选中的数据项发生改变的时候,为C_TYPE_LISTBOX或C_TYPE_COMBO类型的控件调用它。 |
| ImageClick |
int ControlID, BSTR ApplicationName, LPDISPATCH Target, BSTR Text, BSTR Xml, int LocaleID, int XCoordinate, int YCoordinate |
当用户点击图像的时候,为C_TYPE_IMAGE类型的控件调用它。X和Y 都是从0开始的,与图像左上角的相对象素。 |
| OnRadioGroupSelectChange |
int ControlID, LPDISPATCH Target, int Selected, BSTR Value |
当用户改变单选框的选择状态时,为C_TYPE_RADIOGROUP类型的控件调用它。字符串的值是被最新选择的数据项的文本。 |
注意,对于大多数DLL,你不会使用所有的控件类型或实现所有的IsmartDocument方法。例如,因为我的事务面板中没有ActiveX或列表框控件,在例子中就只有这些方法的样板实现。甚至于根本不编写任何操作处理程序DLL也可能利用一些简单的智能文档特性(例如提供超级链接或内容敏感性帮助文本)。作为代替的是你只需要在关联的Word或Excel智能文档中简单地提供一个XML文件,该文件描述了你希望与多种XML元素关联的操作。
图6摘录了ISmartDocument方法的参数。
图6. ISmartDocument通用方法参数
| 参数 |
描述 |
| BSTR ApplicationName |
寄宿智能文档的Office 应用程序的ProgID (例如"Excel.Application.11")。 |
| LPDISPATCH Document |
或Target 指向下层的Word 或Excel文档的IDispatch指针(供SmartDocInitialize和 OnPaneUpdateComplete使用)。对于其它方法,IDispatch指向受到影响的XML元素的Word或Excel范围。 |
| int LocaleID |
用户语言的LCID ;供多语言智能文档使用,以激活从DLL中载入的本地化的字符和其它资源。 |
| ISmartDocProperties * Props |
用于提供控件属性的IPropertyBag。 |
| BSTR Text |
当前文档元素的文本。 |
| BSTR Xml |
文档元素的XML表现方式,不能为NULL。 | 当Office载入你的操作DLL的时候,它首先调用图3所示的配置方法。Office首先调用SmartDocInitialize(允许你执行具体的一次性的初始化)。接着它将通过调用你的get_SmartDocXmlTypeCount实现查询文档中定义的拥有关联控件的XML元素的数量(文档中的XML元素没有关联控件的情况也是可以的)。在例子中,文档操作面板是空的,因此你可能希望为这些元素定义一些标准的帮助内容。你一旦告诉Office你对多少XML文档元素有兴趣后,它将查询每个元素的名称,这些名称是通过调用多个get_SmartDocXmlTypeName获得的。你必须返回XML元素的合格的名称(例如schema#element)。每个元素都有说明,当用户定位到这个元素中的时候,说明会出现在文档操作面板的标题中。这是通过调用get_SmartDocXmlTypeCaption返回的。对get_SmartDocXmlTypeName和 get_SmartDocXmlTypeCaption的调用是成对出现的,每个XML元素执行一次。下一步,Office希望知道每个元素的显示的事务面板中的控件信息。这些信息包括:
· get_ControlCount--给定的某个元素的控件数量。
· get_ControlID--允许你根据某个元素的控件索引(从1开始)给每个控件指定一个唯一的ID号。
· get_ControlNameFromID--在给定控件ID后返回控件的(非固定的)名称。这个名称没有显示,但是用于引用Word或Excel对象模型中的控件。
· get_ControlTypeFromID--给定控件ID后,返回控件的类型(从图2中)。
当用户在文档中移动的时候,Office用适当的控件更新文档操作事务面板,它接着在事务面板中绘制控件的时候调用控件绘制方法(图4所示)。首先它调用get_ControlCaptionFromID,允许你为控件提供一个说明。注意说明不是在配置的时候提供的,而是在显示的时候提供的,因此可以动态地修改它。Office不会绘制有空白的或NULL说明字符串的控件。这可能用于动态地隐藏和显示控件。为了隐藏一个控件,你只需要简单地为该控件从get_ControlCaptionFromID返回一个一个NULL或空字符串。
接着Office为每个控件调用该控件类型特定的填充方法(例如PopulateHelpContent)。你使用这些方法为控件提供内容,例如图像的位置或超级链接的文本。注意与控件关联的文档元素的XML和Text内容被传递到所有的填充方法。同时也要注意,C_TYPE_LABEL和C_TYPE_SEPARATOR类型的控件没有关联的填充方法。在所有的控件绘制了并且事务面板完成了以后,Office将调用OnPaneUpdateComplete。
当用户与事务面板中的控件交互操作的时候,Office调用图5显示的列表中的某个通知方法。标签和分隔符没有关联的通知方法。对于其它控件,通知方法表明要么状态改变了,要么被点击了,这都可能调用一个操作(例如超级链接或图像)。
注意,通知太多比太少更容易造成Office出错。例如,在Excel中的单元选择发生改变的时候(即使XML元素仍然没有变化),Office重新绘制文档操作工作面板并调用你的方法,这样在事务面板上你才拥有真正的动态内容。例如,你能够通过Excel对象模型查询被选择的单元的值并根据这个值更新控件。在示例代码中我利用了这个优点来更新事务面板中被选择的事务的信息,而不需要在文档操作DLL中操作Excel VBA事件。
属性包
大多数控件绘制方法都提供了ISmartDocProperties参数。这是一个名称/值对的属性组(继承自IPropertyBag),提供了控件上的附加信息。这是你指定控件的大小、字体、对齐方式等的地方。图7提供了应用于所有控件的关键的名称。属性值,包括整形属性的值,都是作为BSTR提供给的ISmartDocProperties::Write的。例如,PopulateTextboxContent下述代码行会把文本框控件的宽度设置为50个像素:
Props->Write(CComBSTR("W"), CComBSTR("50"));
图7. 所用控件的ISmartDocProperties
| 属性 |
描述 |
| X |
正的整型值,它指定控件应该离左边多远,按象素计算。 |
| Y |
正的整型值,它指定控件应该离顶部多远,按象素计算。 |
| H |
正的整型值,它指定控件的高度,按象素计算。 |
| W |
正的整型值,它指定控件的宽度,按象素计算。 |
| Align |
对齐方式。 |
拥有关联标签的控件(C_TYPE_TEXTBOX、C_TYPE_CHECKBOX)都有图8所示的附加属性以描述该控件的标签属性。注意这些只能影响该控件的标签,不会影响绘制控件的字体。最后,还有一些其它的属性只能应用于特定类型的控件。这些属性显示在图9中。
图8.控件标签的IsmartDocProperties
| 属性 |
描述 |
| FontFace |
显示控件标签的字体(例如"Arial") |
| FontSize |
显示标签控件的字体的大小,按"点(point)"计算。 |
| FontStyle |
字体格式,"none"(默认值)或"underline"、"italic"、"strikeout" (如果使用多个,用"|"字符隔开) |
| FontWeight |
它的值是 "normal"(default) 或"bold" |
图9. 其它的ISmartDocProperties名称/值对
|
属性 |
应用的控件类型 |
描述 |
| IsMultiLine |
C_TYPE_TEXTBOX |
如果设置为True,就允许文本框接收多行文本,每行直接用回车换行符隔开。 |
| NumberOfLines |
C_TYPE_TEXTBOX, C_TYPE_COMBO, C_TYPE_LISTBOX |
文本框中显示的行数。对于列表框或组合框,它是不滚动时显示的行数。 |
| PasswordCharacter |
C_TYPE_TEXTBOX, C_TYPE_COMBO |
用于掩饰文本框中输入的内容的一个字符,这样文本框就可以用于输入密码。 |
| ExpandHelp |
C_TYPE_HELP, C_TYPE_HELPURL |
如果设置为True,它在最初显示的时候就扩展帮助控件。 |
| Border |
C_TYPE_IMAGE |
如果设置为True,当鼠标进入图像的范围内的时候显示图像的边框。 |
| IsEditable |
C_TYPE_COMBO |
如果设置为True,它就允许编辑组合框中的文本内容。 |
| ControlOnSameLine |
C_TYPE_TEXTBOX, C_TYPE_COMBO, C_TYPE_LISTBOX |
如果设置为True(默认值),它就把控件标签显示在控件周围。如果设置为False,它把控件标签显示在控件上面一行。 |
| Expanded |
C_TYPE_DOCUMENTFRAGMENT, C_TYPE_DOCUMENTFRAGMENTURL |
如果设置为True,它就扩展事务面板中的文档片段内容。 |
现在你已经有了多种接口方法的一个相当快速的教程了,我将使用Visual Studio .NET建立一个智能文档操作处理程序DLL。 建立智能文档操作DLL
我决定在Visual C++中使用活动模版库(ATL)建立智能文档操作DLL。ATL是个很好的选择,因为它的覆盖区域比MFC小一些,但是它仍然提供了操作智能文档需要的大量方便的COM功能。但是,如果你更熟悉MFC,你也可以选择它。
从MSDN Online上下载Office 2003智能文档SDK之后,我启动Visual Studio .NET并建立了一个新的ATL类型的项目,接着使用Add | Class给项目添加一个ATL简单对象。这就是示例代码中的CScheduleSmartDoc。从该对象的类环境菜单(使用View | Class打开类资源管理器)中,我使用Add | Implement Interface让这个类实现ISmartDocument接口。如果在开发计算机上没有安装Office 2003,你必须把有Office 2003的计算机上的类型库复制到你的计算机上。你一般可以在C:\Program Files\Common Files\Microsoft Shared\Smart Tag\mstag.tlb中找到它,但是你要确保得到的是1.2版本(Office 2003版本)。接着你应该把我的示例代码和接口描述作为向导,实现你所需要的方法。
|