6

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

Public WithEvents MyObject As MyClass

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

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

4

4 回答 4

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
于 2012-08-28T11:52:06.777 回答
3

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

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

但!!这是非常重要的!!您必须确保在应用程序关闭之前及时清除此引用,以防万一您对表单(或其他任何内容)有任何循环引用。在这种情况下,Form_Unload 事件将是理想的。

于 2012-08-28T09:20:55.260 回答
1

一种简单方法的案例

有时,对于一个类甚至是用户控件(实际上只是一种特殊的类)来说,在整个程序中具有广泛通用的属性和方法是非常有用的。

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

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

但有时您有一个相当复杂的 UserControl 或 Class,您不想通过重构为单独的模块或以其他方式更改来自定义它们。也许您维护一个通用的标准版本以用于不同的项目。也许它是一个你甚至没有来源的第 3 方库。任何。

需要考虑的事情

这给我们带来了另一种可能对你有用的技术,尽管用 Irving Mainway 的话来说它“不适合盲童”。如果您有足够的经验来使用它,那么您应该已经想到了 - 但我们没有一个人有完美的记忆(如果我们确实时间阅读那本精美的手册)。

因此,也许由于某种原因这对您不起作用,并且您已经考虑并放弃了这个想法。

您的容器对象(Form、UserControl、父类等)可以在初始化时仔细设置对相关对象实例的全局引用,并在其终止时删除该引用。这应该小心避免循环或孤立引用,这些引用会阻止您的其他对象甚至整个程序卸载。

Johnny GoTo 不应该采取这种做法或粗心大意地使用它。但是,如果您知道自己在 VB 中做什么,并且已经阅读并理解了手册中讨论这种技术及其缺陷的部分,那么它会很有用。

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

在静态模块中,您可以简单地声明:

Public gWebWiz As WebWiz

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

Set gWebWiz = WebWiz1

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

Set gWebWiz = Nothing

然后你的程序有一个全局引用,它可以用于其他目的。

看起来很容易对吧?

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

  • 为了避免程序“挂起”,还有更多工作要做,
  • 可能不太适合使用这种技术。
于 2012-08-28T13:36:03.957 回答
1

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

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

其次,在现在为空的模块(我们称之为 modGlobal)中,声明Public Property Get Application() As clsApplication. 继续添加一个二传手。

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

现在您已经用一个全局类替换了您的模块,该类可以侦听在应用程序范围内发生的事件。在您的应用程序的其余部分,您可以像这样访问您的全局状态,Application.Config或者Application.GetUser()您在全局级别保留的任何其他内容。

您当然可以将其应用于您希望使用 WithEvents 的变量,但无论如何您都应该真正考虑将代码从模块中取出。


刚刚看到@Mark 的评论。最小的方法是我坚持使用的方法。如果您的事件类是 MyEventSource,则创建一个名为 MyEventSourceListener 的类,该类具有一个名为的属性Target,您将对象传递到该属性中,并私下声明 WithEvents。然后 MyEventSourceListener 可以接收事件并将它们转发回您的模块。

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

于 2012-08-28T11:43:11.963 回答