在Visual Basic(以下简称VB)程序设计过程中,如何轻松地处理众多的随机事件,往往是制作大型系统首先要考虑的问题之一。例如,多个窗口同时打开同一个表(Table),一个窗口中对数据进行了修改,而其它窗口也能够随之进行数据更新,这时就需要有一条说明数据改变了的消息在所有窗口间进行广播。在C中,只需要定义一条用户消息即可实现这一点;而用VB编程就不那么简单。 早期实现方法及局限性
对于以上问题,笔者早期的实现方法是,自定义一个消息结构(VbMsg),并在程序的主窗体内建立一个消息广播引擎,主要由一个消息队列和一个定时消息广播器所组成。消息广播器每隔一固定时间检查一次消息队列,如果有消息存在,就将其发送给所有打开的窗口,并将该消息从队列中删除。如此再定义一个全局的消息发送过程(SendMsg),将要发送的消息(VbMsg)送入消息队列。这样当需要广播消息时,只需填充好消息结构,调用SendMsg过程即可。
这里较为复杂的是消息广播器如何将消息发送到各窗口。这需要作个硬性规定,即每一个窗体都必须定义一个形式完全相同的消息接收函数(RecMsg),在这个函数中对接收到的消息进行处理,当然也可以什么都不做。有了这样的规定之后,消息广播器在进行广播时,就可以利用VB系统定义的全局变量Forms,遍历所有的窗体,并调用一遍每个窗体的消息接收函数。其主要代码如下:
Public Sub SendMsgToForms(msg as VbMsg)Dim frm as FormFor Each frm In Formsfrm.RecMsg msgNext frmEnd Sub
通过上面的这些过程,就可以实现在独立的程序中,对随机事件进行异步处理。这一方法的效果基本令人满意。但是它有几个较大的局限性:
? 定时检查消息队列需要利用Timer控件进行触发,这在程序运行时,就必然要牺牲一部分效率;
? 消息广播的范围限定在一个程序模块内,如果整个系统分成多个大的模块,那么存在于动态链接模块(.DLL)中的窗体将不能直接接收到广播消息,更无法实现进程间的消息传递;
? 消息的接收者只能是窗体,而作为真正的基础单元类却无法直接接收消息。
VB5.0的新特性及实现方法VB 5.0 企业版增添了 嗲坑辛Φ奶匦裕可以解决以上难题。?/p
1、 用户自定义事件
在类模块中,可以使用Event关键字来定义用户自定义事件,使用 RaiseEvent 语句来产生该事件,这一机制给处理随机事件带来了很大方便。本文中的消息广播引擎,就可以不再使用Timer控件做支持,而是当收到需要广播的消息时,产生一个预定义的事件;而需要处理消息的客体对象,只需截获该事件。
2、 ActiveX EXE组件
利用VB,可以方便地将共享代码封装在ActiveX组件之中,从而可以实现跨进程间的消息传递。因为ActiveX组件有内部(DLL)、外部(EXE)两种,外部组件可以对模块内的全局数据实现共享。
3、 远程自动化连接
ActiveX组件是一种标准的客户机/服务器结构,利用Windows平台的COM模型,VB能够方便地将这种结构扩展到整个网络的范围。所以,消息广播设计既可实现进程间的消息传递,也可实现网络上的消息传递。
根据以上思想,笔者通过四个模块之间的相互协作,完成了消息的发送、广播及接收,并将这四个模块封装在一个ActiveX EXE组件之中。下面就是这四个类模块的简单介绍及源代码。
类模块之一:Msg.cls
该模块定义了消息数据结构VbMsg类,它是消息传递中的载体。这里只是一个简单的例子,如果想实现更多的功能,如建立两点间的数据通道,而不是单纯的广播消息,则要对该结构进行一些扩充。
BEGIN
MultiUse = -1 True
END
Attribute VB_Name = VbMsg"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
消息类:定义全局的消息结构
Public iType As Long 消息类型编号
Public iName As String 消息名
Public iSource As String 消息源说明
Public iDescription As String 消息说明
Dim iT As Date 消息发生时间
返回日期型时间
Public Property Get iTime() As Date
iTime = iT
End Property
返回字符型时间
Public Property Get iTimeStr() As String
iTimeStr = Format(iT, yyyy.mm.dd hh:mm:ss")
End Property
在对象被建立时,设置消息发生时间
Private Sub Class_Initialize()
iT = Now()
End Sub
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)类模块之二:MsgCli.cls
本模块是对客户接收端MsgClient类的定义,这相当于一个消息接收器。在这个类中定义的一个RecMsg事件,当接收器收到消息时(过程SetMsg被调用),就产生这一事件;接收器的建立者截获这一事件,并处理消息。为了避免接收不必要的消息,声明了minMsg、maxMsg两个变量,以便对VbMsg中的iType属性进行过滤。
BEGIN
MultiUse = -1 True
END
Attribute VB_Name = MsgClient
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
客户消息接收类
定义接收消息事件,该对象的宿主类应截获该事件,并处
理接收到的消息
Public Event RecMsg(ByVal msg As VbMsg)
通过设置消息的接收范围,过滤掉不需要的消息
Public minMsg As Long
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)Public maxMsg As Long
该对象的标志编号,使用时不应修改该值
Public ID As Long
事件产生过程,只应由消息服务器(MsgServer)调用
Public Sub SetMsg(msg As VbMsg)
If msg.iType >= minMsg And msg.iType <= maxMsg Then
RaiseEvent RecMsg(msg)
nd If
nd Sub
根据ID返回对象的关键字,只应由消息服务器调用
Public Property Get Key() As String
Key = ID:& ID
End Property
类模块之三:Global.bas
本模块声明了两个全局变量,一个是接收器(MsgClient)列表(Clients),一个是接收器计数器,为每个接收器分配一个唯一的ID标志。把变量放在单独的模块中,是为了实现数据在进程间的共享,是跨进程间消息传递的关键所在(应保证在编译时工程是单线程的,否则不能实现数据共享)。
Attribute VB_Name = modGlobal
Option Explicit
消息服务器全局变量,消息接收客户列表
Public Clients As New Collection
消息接收客户ID计数器
Public CliCount As Long
类模块之四:MsgSrv.cls
本模块定义了消息服务器类MsgServer,该类是消息广播引擎的主体,它主要管理维护消息接收器列表(Clients),将发送来的消息(调用SendMsg过程)依次发送给列表中的所有接收器。请注意:为了方便使用,该类被声明为公共全局类。
BEGINMultiUse = -1 TrueENDAttribute VB_Name = MsgServerAttribute VB_GlobalNameSpace = TrueAttribute VB_Creatable = TrueAttribute VB_PredeclaredId = FalseAttribute VB_Exposed = TrueOption Explicit
消息服务器类,发送消息
Public Sub SendMsg(msg As VbMsg)Dim c As MsgClientFor Each c In Clientsc.SetMsg msgDoEventsNext cEnd Sub
增删消息接收客户
Public Sub AddMsgClient(c As MsgClient)CliCount = CliCount + 1c.Id = CliCountClients.Add c, c.KeyEnd SubPublic Sub DelMsgClient(c As MsgClient)Clients.Remove c.KeyIf Clients.Count = 0 Then CliCount = 0End Sub
应用举例
至此,一个小巧灵活的消息广播引擎就完成了,它的使用范围很广,用起来也很方便,只需在工程中引入编译过的ActiveX组件,就可以直接调用SendMsg发送消息。但可能在安装消息接收器(MsgClient)时会稍许有点麻烦,下面举例说明其应用。
在设计Windows程序时,往往希望调试时能看到程序运行时后台的一些情况。利用VB的单步执行或Debug命令,会受到一些限制。利用消息广播引擎,制作一个通用的实时消息事件查看程序,就可以很好地解决这一问题。查看程序的主要工作是捕捉一组事先定义好的消息事件,并将消息的内容显示在列表框内。本应用可以只用一个窗体完成,主要代码如下。
Const MsgInfoID=101Private WithEvents mClient As MsgClientPrivate Sub Form_Load()Set mClient = New MsgClientMClient.minMsg= MsgInfoIDMClient.maxMsg= MsgInfoIDAddMsgClient mClientEnd SubPrivate Sub Form_Unload(Cancel As Integer)DelMsgClient mClientEnd SubPrivate Sub mClient _RecMsg(ByVal msg As VbMsgSrv.VbMsg)List1.AddItem msg.iTimeStr & Chr(9) & msg.iName & Chr(9) &sg.iDescriptionEnd Sub
在被调试的程序中,为了调用方便,可以编写一个全局过程:
Const MsgInfoID=101Public Sub MsgInfo(iName As String,iDes As String)Dim msg As New MsgClientWith msg.iName = iName. iDescription = iDesEnd WithSendMsg msgEnd Sub
在程序的重点环节插入MsgInfo过程,运行时有关信息就会在事件查看程序的窗口中显示出来。这种方法尤其适合调试多程序协作的软件系统。当软件系统正式交给用户时,插入的MsgInfo过程也不一定要全部删掉,只要将实时查看改为写入日志文件,作为日后软件维护的资料。