我试图在VB6模块中声明一个变量,如下所示:
Public WithEvents MyObject As MyClass
帮助文件说WithEvents
只能在类模块中使用。为什么不能在.bas
模块中使用它?
我正在使用的遗留代码有一个在模块中全局声明的对象。我想在此声明中添加WithEvents
,但我需要将对象保持为全局,因为许多其他形式等都引用了该对象。如何以最小的代码中断实现这一目标?
答案 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.Config
或Application.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
或主表单开始的适当“代码树”,您通常不必格外小心。但是,如果你是那些像弹球向导一样编码的人之一,并发现自己会问“我如何找到所有打开的表格并卸载它们?”之类的问题。那么你: