在第 4 个示例中,我们将不使用 Visual Objects 中的标准示例,而是在 Visual Objects 中创建一个新的示例应用程序,用于远程控制 Excel 并将数据写入 Excel 表。
我在 Comp.Lang.Clipper.Visual-Objects 新闻组中找到了下面的示例(我为这个示例稍作了修改)。
•在 Visual Objects 中创建一个新的终端应用程序,然后将下面的代码复制并粘贴到应用程序中。
•打开应用程序属性,添加 Ole 库。
•同时将应用程序重命名为 “ExcelTest”。
•现在打开 Star 模块并复制代码。
•编译并运行后,你会在 C:\ExcelTest 文件夹中找到一个 xls 文件。
FUNCTION Start()
LOCAL oExcel AS OBJECT
LOCAL oWorkBooks AS OBJECT
LOCAL oWorksheet AS OBJECT
LOCAL oRange AS OBJECT
LOCAL cFile AS STRING
cFile := "C:\ExcelTest\example.xls"
DirMake("C:\ExcelTest")
oExcel:=OLEAutoObject{"Excel.Application"}
oExcel:Visible:=FALSE // 不显示 EXCEL 执行结果
oExcel:DisplayAlerts:=FALSE // 不显示消息
oWorkBooks:=oExcel:Workbooks
oWorkBooks:add() //打开一个新的 worksheet
oWorkSheet:=oExcel:ActiveSheet // 激活第一个 sheet
oRange:=oWorkSheet:[Range,"A1","A1"] // A1 单元格
oRange:SELECT()
oRange:FormulaR1C1:="Hello my text"
oExcel:ActiveWorkBook:SaveAs(cFile,56,"","",;
FALSE,FALSE) //“56" 在 EXCEL 97-2003 工作簿中保存文件(Excel 8)
oWorkBooks:Close()
oExcel:Quit()
WAIT
RETURN NIL
•将 AEF 导出到 “C:\ExcelTest\ExcelTest.AEF”。
•运行 VOExporter 并导出代码。
•在 Visual Studio 中打开解决方案后,您还会得到一个带有源文件(Start.prg)的应用程序。源文件几乎完全相同,只有一处不同。这一行 :
oRange:=oWorkSheet:[Range,"A1","A1"] // A1 单元格
已更改为
oRange:=oWorkSheet:Range["A1","A1"] // A1 单元格
•注意,Range 现在位于方括号前面。
Range 是工作表的所谓 “索引属性”。Visual Objects 为此使用了一种 “有趣 ”的语法。
X# 使用与大多数其他语言相同的语法。属性看起来像一个数组(方括号)。
•现在用 X# 编译应用程序。您将收到以下错误:
让我们来看看这些错误:
•列表中的最后一个错误表明 Start 方法不正确。这是因为在 .Net 中,Start 函数必须是一个 VOID 函数或一个返回 INT 的函数。本例中没有声明返回类型,因此 X# 认为您要创建一个返回 USUAL 的函数。将 start 函数的原型改为
FUNCTION Start() AS VOID
并删除 RETURN 语句中的 NIL 返回值。
•现在仍有 2 个错误。这表明 X# 不知道如何将数组索引从字符串转换为 int。这是在范围赋值的那一行。
上述代码使用了 “后期绑定”,因此在编译时并不知道类型。X# 不知道工作表对象的 Range 属性是索引属性还是返回数组。它假定返回的是一个数组,并希望指定数组索引,而数组索引是数字(并减去 1,因为 Visual Objects 使用基于 1 的数组,而 .Net 使用基于 0 的数组)。
•解决这个问题的最佳方法是使用强类型并使用 Excel 的生成类包装器。
在 Visual Objects 中,您可以使用 “Automation Server ”工具生成这样的类包装器。在 .Net 中也有类似的工具。最简单的方法是在应用程序的引用中添加对 Excel 的引用:
•右键单击解决方案资源管理器中的 “引用”,然后选择 "添加引用"
•在 “添加引用 ”对话框中,选择 COM 标签页
•找到 “Microsoft Excel nn.m 对象库”。在我的机器上是 Excel 16.0。
•点击 Ok.
•这将在您的引用列表中添加一个名为 “Microsoft.Office.Interop.Excel ”的条目。这是一个生成的类型库,包含 Excel 中所有类型的类定义。
•现在进入代码,为 Excel 类的命名空间添加 Using 语句,并将 “AS OBJECT ”改为正确的类型:
USING Microsoft.Office.Interop.Excel
FUNCTION Start() AS VOID
LOCAL oExcel AS Application
LOCAL oWorkBooks AS Workbooks
LOCAL oWorksheet AS Worksheet
LOCAL oRange AS Range
•同时更改创建 Excel 主对象的调用:
oExcel:=ApplicationClass{}
•现在,您的代码是强类型的,因此,如果您尝试从这些对象中选择一个成员(如 oExcel:Workbooks),您也应该获得 intellisense。
•编译并运行代码。运行结果符合预期。
•您可能需要将 “SaveAs ”行中的值 56 更改为适当的枚举值:xlFileFormat.xlExcel8
•现在再次运行应用程序,一切正常。
•如果查看生成 EXE 的文件夹,就会看到 ExcelTest.Exe 和 Microsoft.Office.Interop.Excel.dll 类型的库
如果你想知道为什么我们将 oExcel 声明为 Application,却使用 ApplicationClass 来实例化对象,原因就在这里:
Application 是接口,ApplicationClass 是实现 Application 接口的实际类。这是大多数自动化服务器的模型。
许多人都在询问实现 OLE 事件的方法。有了 X# 代码和生成的类型库,这个问题就很容易解决了。
在 start 方法中添加以下代码,定义 BeforeSave 和 AfterSave 事件
oExcel:WorkbookBeforeSave += OnBeforeSave
oExcel:WorkbookAfterSave += OnAfterSave
并添加以下功能
FUNCTION OnBeforeSave (oWb AS Workbook, SaveAsUI AS LOGIC, Cancel REF Logic) AS VOID
? "OnBeforeSave", oWb:Path, oWb:Name, SaveAsUI
RETURN
FUNCTION OnAfterSave (oWb AS Workbook, Success AS LOGIC) AS VOID
? "OnAfterSave", oWb:Path, oWb:Name, Success
RETURN
然后再次运行示例。你会发现两个函数都被调用了。保存前,路径和名称没有正确设置,保存后,它们被设置为代码中指定的值
某些版本的 Excel 不支持 WorkbookAfterSave 事件。在这种情况下,编译时会出现错误
注意: | 如果在调试器中运行这段代码,并在 OnBeforeSaveAs 中设置断点,就会发现这些事件的调用堆栈有点奇怪: 该调用栈中没有 Start() 函数(我禁用了 “Just My code option”,否则你只能看到 OnBeforeSave() 这一行)。 |
这是因为这些事件是在单独的线程上调用的。如果查看 VS 中的线程窗口(调试 - 窗口 - 线程),就会发现这一点:
如果仔细观察 “添加引用 ”对话框,还可以发现 Excel 库的其他位置(在 .Net 选项卡上)。在我的机器上,它们是
这些是所谓的 “Primary Interop Assemblies”(PIA),即预编译的程序集,适用于不同的 Excel 版本。您也可以使用这些程序集。这些程序集与 Visual Studio 的 Office 开发工具一起安装。在我的机器上,它们位于 “c:\Program Files (x86)\Microsoft Visual Studio 14.0\Visual Studio Tools for Office/PIA ”的子文件夹中。
您可以在 XSharp 示例文件夹中找到 “Code before ”和 “Code after”。