在 VB6 主应用程序中处理 .NET 窗体及其事件






3.87/5 (10投票s)
2005 年 10 月 8 日
5分钟阅读

78335

855
如何在旧的 VB6 应用程序中处理用作对象的 .NET 窗体(及其事件)。
我一直想在 CodeProject 发表文章。我一直在“索取”,而没有“回馈”这个社区,这让我感到内疚。我认为是时候“回馈”一些有用的东西给这个社区了。
引言
在我对 COM 和 .NET 互操作性(让我们将其缩小到 VB6,再到 COM 客户端和 .NET 对象!)的微薄经验中,我发现关于如何在两种技术之间处理**窗体**的资源非常少……而不是方法和属性。例如,通过不同的窗体访问数据库……在 VB6 主窗体关闭时关闭 .NET 窗体(在普通情况下,.NET 窗体不会在 VB6 主窗体关闭时关闭!)……以及在 VB6 方法中处理 .NET 窗体中引发的事件……所有这些都耗费了大量的时间和精力,并出现了一些令人恼火的错误!
在本文中,我将尝试解释如何处理最后两个问题,并将第一个问题留给读者(也许只建议让 .NET 窗体拥有自己的数据库连接,而不是将数据从 VB6 传递到 .NET 窗体)。
首先,我们需要了解如何将 VB6 对象(此处为窗体)精确地挂接到 .NET 窗体中引发的事件,然后我们就可以解决关闭 .NET 窗体的问题,当关闭 VB6 主窗体时。
将 VB6 窗体挂接到 .NET 窗体中引发的事件
主要构想
注意:以这种方式处理事件的功劳归功于 Adam Nathan 的书《.NET and COM the Complete Interoperability Guide》。我们将不使用 VB6 提供的连接点进行自动事件处理。要以这种方式处理事件,我们需要在 VB6 代码中使用“WithEvents
”关键字声明 .NET 窗体。众所周知,此关键字不能在方法中使用,只能在声明部分使用,而且我敢打赌,你们中的许多人需要在运行时在方法内声明和创建 .NET 窗体,而不是在 VB6 的声明部分拥有固定数量的 .NET 窗体。
主要思想是在每个 .NET 窗体中创建一个 Hashtable
。这个 Hashtable
将持有你希望处理事件的每个 VB6 对象(特别是窗体)的引用。现在,当 .NET 事件被引发时,.NET 窗体中的一个方法(事件处理程序)将遍历 Hashtable
中的每个项目(代表 VB6 窗体的引用),并通过引用调用适当的方法(VB6 处理程序)。
一个包含事件处理程序的接口
当然,到目前为止,您会得出结论,Hashtable
中的每个 COM 对象(在我们的例子中是 VB6 窗体)都应该有一些方法,并且这些方法应该与 Hashtable
中的每个对象具有相同的名称和相同的签名。好的,现在我们可以创建一个包含 .NET 窗体事件处理程序的接口(一些方法),每个 COM 对象都希望处理这些事件。
//Interface with "event handlers" for COM objects to implement
public interface IDotNetEventsHandler
{
void NewFormCreated( ref DotNetForm sender );
}
正如事件处理程序的名称所示,VB6 窗体(COM 客户端,主窗体)希望收到通知,有关每个新创建的 DotNetForm
(当然,我们可以将其称为“Form
”,而不是“DotNetForm
”,但那时我们需要在 VB6 应用程序中引用 .NET Framework 主 DLL。为了简单起见,我们将坚持使用 DotNetForm
)。参数是对新创建的 DotNetForm
对象的引用。
将 VB6 窗体挂接到事件处理程序
现在,要将 VB6 窗体挂接到 .NET 窗体事件,我们为 .NET 窗体定义一个要实现的接口。该接口将包含两个方法:Add
和 Remove
。
public interface IDotNetEventsHookup
{
int Add(IDotNetEventsHandler comObject);
void Remove(int ComObjectIndex);
}
Add
方法将接受一个实现 IDotNetEventHandler
接口的对象,在我们的例子中,它将是 VB6 窗体。在此方法中,VB6 窗体的引用将被添加到 Hashtable
中,以便接收有关此 .NET 窗体中创建的任何新 DotNetForm
的通知。该引用将被分配给一个键以区分它。Remove
方法仅从 Hashtable
中删除该引用。
VB6 应用程序中的这段代码显示了这些方法的使用方式
Dim netForm As NetEvents.DotNetForm
Set netForm = New NetEvents.DotNetForm
Dim Ihookup As NetEvents.IDotNetEventsHookup
Set Ihookup = netForm
Dim cookie As Long
cookie = Ihookup.Add(Me)
现在,我们已将 VB6 窗体挂接到 DotNetForm
事件(它已被引用到 Hashtable
中)。
当 .NET 中引发事件时,将执行此代码;其主要目标是遍历 Hashtable
中的所有 VB6 窗体引用,以便为该事件(事件处理程序)调用适当的方法
private void OnNewFormCreated()
{
for(int counter=1; counter<=this.comObjects.Count; counter++)
{
IDotNetEventsHandler obj =
(IDotNetEventsHandler)comObjects[counter];
DotNetForm form = new DotNetForm();
form.Show();
obj.NewFormCreated( ref form );
}
}
注意:当然,新的 DotNetForm
应该在事件处理程序之外创建,然后传递给事件处理程序,但如果我们这样做,VB6 事件处理程序会使读者感到困惑,所以我们将保持原样,并将修改过程留给读者。
通过关闭主 VB6 窗体来关闭 .NET 窗体
现在,在(希望)学习了事件是如何处理之后,其余的就很简单了。我们所需要的是 VB6 窗体中的一个动态数组,用于保存传递给事件处理程序(新创建的)的 DotNetForm
引用,然后将此对象挂接到传递的 DotNetForm
。
Private Sub IDotNetEventsHandler_NewFormCreated(_
sender As NetEvents.DotNetForm)
FormsCounter = FormsCounter + 1 'FormsCounter is declared
'previously in the declaration
'part
ReDim Preserve DotNetForms(FormsCounter)
Set DotNetForms(FormsCounter) = sender
Dim Ihookup As NetEvents.IDotNetEventsHookup
Set Ihookup = sender
Dim cookie As Long
cookie = Ihookup.Add(Me)
MsgBox ("Created " + sender.Text)
End Sub
然后,遍历这些引用以根据需要关闭它们(通常在 VB6 窗体的 QueryUnload
方法中)
Dim tempForm As NetEvents.DotNetForm
While FormsCounter >= 1
Set tempForm = DotNetForms(FormsCounter)
If Not tempForm Is Nothing Then
tempForm.Close
End If
FormsCounter = FormsCounter - 1
Wend
结论
在每个 .NET 窗体中,我们都持有所有希望处理 .NET 窗体引发的特定事件的 COM 对象的引用。然后,当在私有事件处理程序(未公开)中引发事件时,我们会遍历 COM 对象的引用并调用适当的方法。正如您所见,我们没有将 .NET 事件公开给 COM 对象;它仅使用这些事件来调用适当的方法。
通过这种方式,我们可以通知父 VB6 窗体所有新创建的 .NET 窗体,并将这些窗体的引用存储在动态数组中,因此当父 VB6 窗体关闭时,我们会遍历所有可以使用 Close
方法关闭的 .NET 窗体。