如何将WithEvents关键字与全局变量一起使用?

时间:2012-08-28 04:50:17

标签: class events vb6 module

我试图在VB6模块中声明一个变量,如下所示:

Public WithEvents MyObject As MyClass

帮助文件说WithEvents只能在类模块中使用。为什么不能在.bas模块中使用它?

我正在使用的遗留代码有一个在模块中全局声明的对象。我想在此声明中添加WithEvents,但我需要将对象保持为全局,因为许多其他形式等都引用了该对象。如何以最小的代码中断实现这一目标?

4 个答案:

答案 0 :(得分:5)

编写一个接受全局对象作为参数的类并接收其事件。

' Class MySink
Private WithEvents m_oSink As MyClass

Friend Sub frInit(oSink As MyClass)
    Set m_oSink = oSink
End Sub

Private Sub m_oSink_MyEvent()
    '--- implement event
End Sub

.bas模块中创建此类的实例。

Public g_oMyObject AS MyClass
Private m_oMySink As MySink

Sub Main()
    Set g_oMyObject = New MyClass
    Set m_oMySink = New MySink
    m_oMySink.frInit g_oMyObject
End Sub

答案 1 :(得分:3)

VB中的事件是一个相当复杂的COM机制的包装器。基本上在这种机制中,所涉及的一切都必须是实现接口IConnectionPoint或IConnectionPointContainer的COM类。 BAS模块是旧版BASIC的一部分,与VB类不同,它不是作为COM类实现的。我想他们可以在COM中重新实现一个BAS文件作为单例类型对象(就像他们使用Global MultiUse类一样),但他们没有。所以,遗憾的是BAS文件不能使用WithEvents。

至于你问题的最简单的解决方案 - 你需要利用这样一个事实,即对象变量只是对象的引用,而不是对象本身。我想你希望你的事件消息到达必要的最高级别对象,以便你有办法控制你感兴趣的应用程序的位。识别这个地方。例如,您可能有一个主窗体,您确定它将是第一个加载的对象,最后一个是卸载的。作为此对象初始化的一部分,传入对全局对象的引用,并将其设置为WithEvents模块级变量。你现在掌控着。

BUT !!这非常重要!!您必须确保在申请结束前及时清理此引用,以防万一您对表单(或其他)有任何循环引用。在这种情况下,Form_Unload事件将是理想的。

答案 2 :(得分:1)

我不确定您的应用程序有多大,但有几种方法可以做到这一点。这就是我要做的事情,虽然这可能需要15分钟左右来重构您的应用程序才能使用它。

首先将模块中的所有Public和Global方法和成员复制到一个名为(例如)clsApplication的类中。

其次,在now-empty模块中(让我们称之为modGlobal),声明Public Property Get Application() As clsApplication。继续,也添加一个二传手。

第三,在启动程序的Sub Main()中,将其添加为第一行Set modGlobal.Application = New clsApplication

现在,您已将模块替换为可以侦听在应用程序范围内发生的事件的全局类。在您应用的其余部分中,您可以使用此Application.ConfigApplication.GetUser()或您在全球级别保留的其他任何内容来处理您的全局状态。

您当然可以将此应用于您希望使用WithEvents的变量,但是您应该真正考虑从模块中获取代码。


刚看到@Mark的评论。最小化的方法是我改进的方法。如果事件类是MyEventSource,则创建一个名为MyEventSourceListener的类,其中包含一个名为Target的属性,您可以将该对象传递给私有的WithEvents。然后MyEventSourceListener可以接收事件并将它们转发回您的模块。

我不喜欢它,因为它是一个hack并将代码放回模块中,但它可能是最方便的方法。

答案 3 :(得分:1)

一个简单方法的案例

对于Class或者甚至是UserControl(实际上只是一种特殊的类)而言,它有时非常有用,可以在整个程序中广泛使用属性和方法。

如果主要关注的是广泛使用无状态的“实用程序”方法(例如,某些Web相关的方法具有URL编码,实体编码等方法),只需要声明一个额外的全局实例就可以相当便宜。没有“你不会导致加载重内部状态的事件(数组,集合等)。由于您的程序无论如何都需要所有代码,并且只有一个副本位于内存中,因此第二个实例可能相当便宜。

另一种选择是将这些“常见”操作甚至属性值分解为单独的类或静态(BAS)模块。

但有时你有一个相当复杂的UserControl或Class,你不想通过重构到单独的模块或以其他方式改变来自定义。也许您维护一个通用的标准版本,以便在不同的项目中使用。也许这是一个你甚至没有资源的第三方图书馆。不管。

需要考虑的事项

这给我们带来了另一种可能对你有用的技术,即使在欧文主干道不朽的话语中,它“不适合盲童”。如果你有足够的经验可以使用它,那么它应该已经发生在你身上了 - 但我们都没有完美的记忆(如果我们确实 曾经花时间阅读那本精细的手册)。

所以,由于某种原因,这可能对你不起作用,你已经考虑过并放弃了这个想法。

您的容器对象(Form,UserControl,父类等)在初始化时可以小心地设置全局引用到该对象的实例,并在它终止时删除引用。这应该小心谨慎,以避免圆形或孤立的引用,这些引用可以使您的其他对象甚至整个程序无法卸载。

Johnny GoTo不应该采取或不小心使用。但是如果你知道你在VB中做了什么,并且实际上已经阅读并理解了手册中讨论这种技术及其缺陷的部分,那么它将非常有用。

假设您有一些Form1作为您的程序的启动对象。在Form1中,您有一个您编写的UserControl“WebWiz”实例,名为WebWiz1。但是在其他地方(另外三个或十二个表单?)你想调用一个WebWiz方法,它将一组常见的MIME类型字符串值转换为文件扩展名。只有Form1需要处理WebWiz1的事件,而WebWiz在内部状态上相当沉重,因此您不希望仅为此方法创建其他实例。

在静态模块中,您只需声明:

Public gWebWiz As WebWiz

当Form1的Initialize或Load事件运行时(或者对于Class,在创建WithEvents实例后),您可以:

Set gWebWiz = WebWiz1

在Form1的Unload事件处理程序中:

Set gWebWiz = Nothing

然后你的程序有一个全局参考,它可以用于不相关的目的。

看起来很简单吗?

如果您使用此程序的程序被编写为从Sub Main或主表单开始的适当“代码树”,您通常不必格外小心。但是,如果你是那些像弹球向导一样编码的人之一,并发现自己会问“我如何找到所有打开的表格并卸载它们?”之类的问题。那么你:

  • 还有更多工作要做,以避免程序“挂起”,
  • 可能并不真正使用这种技术。