本示例展示了如何迁移使用 ActiveX/OCX 控件的应用程序。
我们使用的是 Visual Objects 的电子邮件示例,您可以在 Examples 文件夹的子文件夹 Email 中找到该示例。
我们在这里遇到的问题是 X# Runtime(以及 Vulcan rumtime)不支持 ActiveX 控件。
因此,让我们尝试解决这个问题。
•首先运行 VOXporter 并从 AEF 创建一个 Visual Studio 解决方案
•在 Visual Studio 中编译并运行。
•我们将收到两条信息:
第一条信息显示了本例中最大的问题。第二条信息是 Xporter 插入的,用来警告我们原始代码向 VO Gui 类中的一个类添加了一个方法。
让我们快速解决这些问题,以便编译应用程序。稍后我们将添加 OCX:
•点击警告。您将看到 XPorter 添加了一个从 Toolbar 继承而来的 CLASS ToolBar_external_class。原始代码试图将 ShowButtonmenu 方法添加到现有的工具栏类中。
•我们可以通过添加扩展方法或子类化工具栏类来解决这个问题,我们在 VOPAD 示例中也看到了这个问题。
•就像在 VOPAD 示例中一样,我更喜欢扩展方法。
•更改类名和方法声明。我们将创建 2 个重载,因为 symTb 参数是可选的:
STATIC CLASS ToolBarExtensions
STATIC METHOD ShowButtonMenu(SELF tbSelf as Toolbar, nButtonID as LONG, oButtonMenu as Menu) AS VOID
tbSelf:ShowButtonMenu(nButtonID, oButtonMenu, #MAINTOOLBAR)
RETURN
STATIC METHOD ShowButtonMenu(SELF tbSelf as Toolbar, nButtonID as LONG, oButtonMenu as Menu,symTb as Symbol) AS VOID
•删除 Default() 一行,并用 tbSelf 替换原始 ShowButtonMenu 主体中的 SELF
•我们还需要对调用该方法的代码进行一些修改。这是因为代码在访问窗口类的工具栏时调用了 ShowButtonMenu。该工具栏访问是无类型的,因此返回 USUAL。
因此,请找到包含 SELF:ToolBar:ShowButtonMenu 的两行,并将其更改为 ((Toolbar) SELF:ToolBar):ShowButtonMenu。您不能使用 Window 类的 oToolbar 字段,因为 DataWindow 类将从其框架窗口返回工具栏,而不是自己的工具栏。
在改进后的 VO SDK 中,我们将通过强类型属性(如 Window:Toolbar)来解决这个问题。
也许您会想把扩展方法添加到 USUAL 类型中,这样您就不必在调用 ShowButtonMenu 的代码中添加转换了。
这样可以编译,但不幸的是,在运行时会出现问题。X# 编译器(就像 Vulcan 和 VO 编译器一样)知道 USUAL 类型是特殊的,因此不会尝试发出方法调用,而会产生调用 Send() 的代码来调用其方法。Vulcan 运行时不会处理 Send() 函数内部的扩展方法。
•稍后,当我们按下工具栏上的 “回复 ”按钮时,就可以确认这是否有效。这将弹出菜单,其中包括 “回复发件人 ”和 “回复所有人”。 现在是修复 ActiveX/OCX 问题的时候了
•单击有关 OleControl 的错误。
•为了快速解决这个问题,我们可以修改代码,让 webbrowser 继承 MultiLineEdit。这样,我们就有了一个可以正常工作的控件。我们稍后将实现 OCX。 为此,请访问 Webbrowser.PRG 类并更改 INHERIT 子句。现在写的是 INHERIT OleControl。将其改为 INHERIT MultiLineEdit。
•再次编译后会出现其他错误。其中 2 个提到了 cOleMethod 类型。双击该错误,转到该代码。
•您将看到 Webbrowser 类的 Quit 方法。这段代码使用了 VO OLE 类中的一个内部类和内部方法。暂时注释掉该方法的内容。
•再次编译后,你会发现只剩下一些错误。其中一些错误与 VOPAD 示例中的错误相同,需要我们将字体属性更改为 ControlFont。纠正这个错误。
•一个错误是 “+”运算符不正确:在这一行中
cTemp += +"; "+ cEntry
•这是原始 VO 代码中的一个明显错误,VO 编译器从未发现。删除双引号前的 +
•最后一个错误来自 Webbrowser 类的构造函数。它调用了 OleControl 中的 CreateEmbedding 方法。这个方法在 MultiLineEdit 类中并不存在,因此我们暂时将其注释掉。我们稍后再处理 Webbrowser。
•注释掉对 SELF:CreateEmbedding() 的调用后,其余代码的编译应该没有问题。
•现在应该可以运行应用程序了。
•如果您尝试打开地址簿,会出现运行时错误,因为它使用了依赖于 Cato3Cnt.dll 的数据库浏览器控件。将 Cavo28\Redist 文件夹中的 cato3*.dll 和 msvcrt.dll 复制到输出文件夹即可解决这个问题。
•重新编译并运行示例。现在,在 Webbrowser 类的 Display 方法(如果您使用的是 VO 2.8 SP4 中的电子邮件示例,则为 DisplayHtml)中将出现错误。
该方法获取电子邮件内容,将其写入磁盘并调用 Webbrowser 控件的 Navigate 方法(后期绑定,使用 VO 的 Send() 函数)。这样做是行不通的。
由于我们已经更改了 Web 浏览器控件,使其成为一个多行编辑器,因此我们可以改变这种行为。我们可以简单地将电子邮件文本赋值给多行编辑器的 TextValue 属性,而不是将其写入磁盘。因此,请注释掉 Display 方法的主体(不要扔掉代码,我们稍后会用到它),并替换为
SELF:TextValue := cText
•之后,示例运行就不会有问题了。您还可以显示电子邮件。当然,它不会正确显示 HTML,但这是下一步的工作。
Vulcan 中与 VO 兼容的图形用户界面类不支持 ActiveX 控件。不过,Windows 窗体对 ActiveX 控件有很好的支持。
我们将使用 Windows 窗体的 ActiveX 支持,在示例中添加 ActiveX 控件。
这里有两种可能性:
1.用 Windows 窗体窗口替换整个电子邮件显示窗口
2.使用一个技巧,使用 Windows 窗体显示 ActiveX 控件,并将该控件合并到我们的 VO GUI 应用程序中
第一种解决方案是目前最容易理解的,但我们必须创建一个全新的窗口,还必须更改调用代码。
您可以根据自己的应用程序做出选择。
在本例中,我们将选择第二种方法。
在此方法中,我们使用 Windows.Forms.Form 窗口作为 ActiveX 控件的 “主机”。
我们将实例化该窗口,并获取该控件的窗口句柄,然后将该窗口句柄与我们的 VO GUI 窗口链接起来。
为此,您必须采取以下步骤:
•右键单击解决方案资源管理器中的项目图标,选择 “添加新项目”。
•这时会出现一个可能的新项目列表。选择 Windows 窗体表单图标,给它起一个有意义的名字,如 “EmailDisplayForm.prg”,然后单击 “添加”。
•这将打开 “表单设计器 ”窗口。
•打开工具箱。网络浏览器控件将不在其中。
•右键单击工具箱中的空白区域,然后选择 “选择项目...”。这将弹出一个对话框,您可以在其中控制工具箱的内容。
•选择 “COM 组件 ”选项卡并向下滚动,直到看到 Microsoft Web 浏览器控件:
•勾选控件前面的复选框,然后按 “确定”。这将把 ActiveX 添加到工具箱中:
•如果对控件的位置不满意,可以将其拖到工具箱中的其他位置。
•现在将控件从工具箱拖到表单上。无需调整控件大小或移动控件。
•Visual Studio 将为我们的项目添加两个引用。它们是
oAxSHDocVw, 包含实际 ActiveX 控件代码的类型库
oSHDocVw, 类型库,包含支持自动化服务器接口和类的代码
•表单编辑器将为表单添加一个名为 axWebBrowser1 的字段。
•转到属性窗口,更改 Modifiers 字段,将其从 “Private”改为 “Public”(Export)。
这将使该字段可以在 webBrowserHost 类之外访问
•保存代码并关闭表单
•现在进入 Webbrowser 类
•在文件顶部添加以下 using 子句:
using Email
using AxShDocVw
using ShDocVw
第一个命名空间是生成 webBrowserHost 窗口的命名空间。第二个命名空间是生成 ActiveX 的命名空间,第三个命名空间是我们需要的其他类型(如枚举和事件)的命名空间。
•添加以下 2 个字段(我想没必要详细说明):
EXPORT oHost as webBrowserHost
EXPORT oWebBrowser as AxWebBrowser
•转到 Webbrowser 类的构造函数,添加以下代码行(代替之前注释掉的 CreateEmbedding()
SELF:oHost := webBrowserHost{} // 创建主机窗口,不显示 .NET 窗口!
SELF:oWebBrowser := SELF:oHost:axWebBrowser1 // 获取表单上的 ActiveX
SetParent(oWebBrowser:Handle, self:Handle()) // 使用 Windows API “窃取 ”其句柄并链接到 MLE
SELF:oWebBrowser:Visible := TRUE // 使浏览器可见
SELF:Okay := TRUE
•然后添加以下方法,以确保 ActiveX 与其所有者 MultiLineEdit 具有相同的高度,并确保其被正确销毁、
METHOD Resize(oEvent)
LOCAL oDim as Dimension
SUPER:Resize(oEvent)
oDim := SELF:Size
IF oDim:Width > 0
SELF:oWebBrowser:SuspendLayout()
SELF:oWebBrowser:Location := System.Drawing.Point{0,0}
SELF:oWebBrowser:Size := System.Drawing.Size{oDim:Width,oDim:Height}
SELF:oWebBrowser:ResumeLayout()
ENDIF
RETURN NIL
METHOD Destroy()
SUPER:Destroy()
SELF:oWebBrowser:Dispose()
SELF:oHost:Dispose()
RETURN NIL
•我们需要 “恢复 ”以前的行为,在浏览器窗口中显示 HTML,而我们之前已经注释掉了。
因此,进入 WebBrowser:Display() 方法(VO 2.8 SP4 的 DisplayHtml),恢复旧代码并更改
Send(SELF, #Navigate, cFileName)
into
SELF:oWebBrowser:Navigate(cFileName)
因此,您可以将其改为早期绑定方法调用
•要完成这项工作,请浏览 Webbrowser 类的源代码,找到调用 Navigate 的行,例如
Send( SELF, #Navigate, "#top" )
并将其改为早期绑定方法调用:
SELF:oWebBrowser:Navigate("#top" )
并查找类似的行:
Send( SELF, #ExecWB, OLECMDID_PRINT, OLECMDEXECOPT_DODEFAULT, NIL, NIL )
并将其更改为使用类型库中的枚举进行早期绑定方法调用。同时删除 NIL 值:
SELF:oWebBrowser:ExecWB(OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT )
•这样就结束了。现在一切正常,包括打印预览和打印功能。
•当然,您现在也可以使用 ActiveX 事件并对其做出响应。
你必须用 .Net 的方法来做。类似这样
SELF:oWebBrowser:NavigateComplete2 += NavigateComplete2
然后执行
METHOD NavigateComplete2(sender AS OBJECT, e AS DWebBrowserEvents2_NavigateComplete2Event) AS VOID
SELF:Owner:StatusBar:SetText("Showing file:" +e:uRL:ToString())
您可以在 XSharp 示例文件夹中找到 “Code before ”和 “Code after”。