摘要 :本文从一个具体例子出发阐述了在Visual Basic 5.0环境下进行外接程序(Add-Ins)开发的原理、关键技术和注意事项,并对其相关技术,如ActiveX、多态性与接口、事件变量等VB5新引进的编程概念也做了必要的分析和描述。
关键词:外接程序(Add-Ins),ActiveX,接口,多态,事件变量
一、 概述
Visual Basic下的Add-Ins,即外接程序,是扩展VB编程环境的非常有用的工具。作为一个开发者,我们发现在使用Visual Basic集成开发环境(IDE)时经常需要重复地干同一件简单的工作,如设置所有或一类控件的字体,前景及背景颜色,改变控件的Tab次序等。这些简单、单调而重复性的劳动如果由程序自动完成,将变得非常方便,而Add-Ins正为实现这一功能提供了可能。与Visual Basic的早期版本不同,Visual Basic5.0下的Add-Ins是一种模块化的ActiveX部件,可以作为ActiveX DLL或EXE文件进行编译。另外,跟以前的版本相比,VB5下Add-Ins无论从编程思想还是从其扩展模型的对象与结构来说,变化都比较大,因此VB5下编写Add-Ins程序与早期版本将会有很大不同。在这篇文章中,我们将从一个具体例子出发,阐述VB5下Add-Ins的编程。
二、 问题的提出
在Visual Basic下进行窗体设计时,我们一般希望自己的程序具有风格一致的界面,如同类控件具有相同的外观、相同的字体等。另外,我们在设计时还应该考虑到屏幕分辨率的变化对程序的影响,例如我们经常发现出现这样的问题:即在某一分辨率(如800*600)下看起来比较好的字体,当在另一分辨率下(如640*480)运行时将变得非常难看;或者在中文环境下看起来比较舒服,但在英文环境下却很不清晰。这种种原因都使得我们必须调整窗体控件的字体,以达到最佳效果。但如果表单或控件比较多时,手工调整将会非常繁琐,而且容易出错,因此编写一个Add-Ins以实现字体的自动调整将会非常实用。本文中的例子即是用来实现此功能的。
三、 编程方法
编写Add-Ins的最简单方法是首先利用VB5的编程模板生成一个Add-Ins的框架,然后在此框架上进一步实现自己的代码。具体方法是在VB5的“文件”菜单下选择“新建工程”,然后在弹出的窗口中选择“外接程序”(英文版中是“Add-Ins”)即可。这时VB5将生成一个名为MyAddin的工程,该工程中包括一个窗体frm AddIn,一个模块AddIn,以及一个类模块Connect。下面我们对这些部分分别做一简单分析。
1. 类模块Connect
整个类模块Connect的代码均是由模板自动生成的,大大方便了程序员编程,但其源代码中有一些比较重要的概念与方法,对我们理解VB Add-Ins甚至VB面向对象编程均有很大好处。下面我们对如下几点做一简单介绍(源代码由于是VB自动产生,这里就没有列出了)。
(1)实现IDTExtensibility接口
所有的VB外接程序都必须实现IDTExtensibility接口,该接口包含了当外接程序与Visual Basic连接时Visual Basic调用的一些方法,无论是通过外接程序管理器,还是其它一些手段。接口是VB5引入的新概念,是Visual Basic提供多态性的一种重要手段。我们知道,面向对象的语言一般都提供多态性。如C++语言,通过类的继承关系,子类重载父类的方法以实现其不同的特性,或父类仅提供方法框架,即无函数体的虚函数,而子类具体实现其代码。简单地说,多态意味着许多类可以提供同样的属性或者方法,而且调用者在调用这些属性或方法之前,不必知道某个对象属于什么类。严格来说,Visual Basic不是完整意义上的面向对象编程语言,但它也能实现多态性,不过这种多态性不是通过继承来实现的,而是通过多重ActiveX接口来提供的。多态性具体实现方法简单来说,首先是建立一个类模块,然后在该模块中声明一些子程序(Sub),但不实现其代码,这样该类模块可以说是一个抽象类,也即接口;然后建立其它的类模块,在这些类模块的声明部分中,使用Implements语句表明该类实现了上述接口,接下来就可以实现接口代码(也即接口的方法)了。这种用Implements语句的方法可以说与Java语言比较相似。至于这方面的详细信息,可参看VB的帮助或联机手册。
在类模块Connect中,使用Implements IDTExtensibility语句声明该类实现了IDTExtensibility接口。因此类模块Connect中实现了该接口包括的四个方法:OnAddInsUpdate,OnConnection,OnDisconnection,OnStartupComplete的代码,例如一条语句、过程调用、注释等等。如果过程是空的话,它会被编译器删掉。如果实在没有代码可往这些过程里加的话,就插入注释。这是因为既然实现了接口,就必须提供接口的所有方法的代码。这四种方法中,比较关键的是OnConnection和OnDisconnection方法,其中OnConnection方法当一个外接程序通过“外接程序管理器”对话框或另一个个接程序与Visual BasicIDE连接时被调用。我们一般在该方法中保存当前Visual Basic会话期实例,挂接VB菜单,工具栏,或显示初始窗口,以便用户使用该外接程序。而OnDisconnection方法当外接程序通过编程或“外接程序管理器”对话框与Visual BasicIDE分离时被调用。我们一般在该方法中卸掉菜单、工具栏、程序窗体,以及做保存设置等工作。
(2)挂接Visual BasicIDE菜单
在接口IDTExtensibility的OnConnection方法代码中,一般来说大多数情况下需要挂接Visual BasicIDE菜单(当然也可以是工具栏),以便用户可随时使用该程序。挂接菜单的代码如下:
Set MCBmenuCommandBar = AddToAddinCommandBar ("My Addin")
'sink the event
Set Me.MenuHandler=
VBinst.Events.CommandBarEvents (mcbMenuCommandBar)
其中AddToAddInCommandBar 函数的代码如下:
Function AddToAddInCommandBar (sCaption As String)As-Office.CommandBarControl
Dim cbMenuCommandBar As Office.CommandBarControl
Dim cbMenu As Object
On Error Go To AddToAddInCommandBarErr
'see if we can find the Add-ins menu
Set cbMenu=Vbinstance.CommandBars ("Add-ins")
If cbMenu is Nothing Then
'not available so we fail
Exit Function
End if
'add it to the command bar
Set cbMenuCommandBar=cbMenu.Controls.Add(1)
'set the caption
cbMenuCommandBar.Caption =sCaption
Set AddToAddInCommandBar = cbMenuCommandBar
Exit Function
AddToAddInCommandBarErr:
End Function
写上面的代码中,mcbMenuCommandBar变量定义为:
Dim mcbMenuCommandBar As Office.CommandBarControl
MenuHandler变量定义为:
Public WithEvents MenuHandler As CommandBarEvents
这里需要指出的是,MenuHandler是WithEvents变量,凡是WithEvents变量均表明该变量是一个用来响应由ActiveX对象触发的事件的对象变更。也就是说,该变量是拥有事件的,因此可以编写该变量的事件代码。在上述代码中将事件对象VBInst.Events.Comman-dBarEvents(mcbMenuCommandBar)赋予MenuHandler变量,则表示当发生有关mcbMenuC-ommandBar菜单项的事件时,由该变量的代码进行处理。在类模块中,我们发现,就存在MenuHandler-Click事件代码,该代码用以显示窗体对话。
在AddToAddInCommandBar函数中,VBInstance是在OnConnection方法保存的会话期实例,所有Add-Ins的调用都需要此实例。使用VBInstance. CommandBars(“Add-Ins”)将获得Add-Ins菜单条对象(在中文VB5下是“外接程序”菜单条),请注意:该“Add-Ins”是不会本地化的,因此无论是在中文还是英文VB5下抱歉获得上菜单条对象。代码Set cbMenuCommandBar=cbMenu.Controls.Add(1)在该菜单下添加一新的菜单项“My AddIn”,使用了cbMenu.Controls的Add方法。关于菜单条的层次关系及相应的属性及方法,可以参看VB关于外接程序的帮助或联机手册。但通过笔者的实践认为,最好的方法是使用F2打开VB的浏览器,其类之间的层次关系以及对象事件,方法,属性一目了然。
关于类Connect中的其它方面的代码,一般来说比较容易理解,这里就不叙述了。
3. 模块AddIn
模块AddIn比较简单,仅包含一个子程序:
Sub AddToINI ()
Dim ErrCode As Long
ErrCode=WritePrivateProfileString ("Add-Ins32","My AddIn.Connect","0","vbaddin.ini")
End Sub
该程序可以在立即窗口中运行,以便将关于此外接程序的信息添加到Windows目录下的vbaddin.ini文件中去,这样VB的外接程序管理器就可以认识该程序,并可将其挂接到VB的IDE环境中。该程序所做的工作很简单,只是在vbaddin.ini文件的[Add-Ins32]下添加一行My AddIn.Connect=0,表明My AddIn没有挂接到IDE环境中,若My AddIn.Connect=1,则VB启动时自动挂接该程序。
4. 窗体frm AddIn
窗体frm AddIn中的代码是由用户编写的,模板仅给出了一个框架。针对我们上面提出的具体问题,我们设计了一个简单的用户界面,包括如下控件:
(1)标签Label1:Caption="All controls In SelectedForm:"
(2)列表框List1:用于显示表单中的所有控制。
(3)命令按钮fontButton (0):Caption ="&Select Font",用于将选中的窗体上所有控制的字体设为缺省字体。(注:缺省字体为宋体9号字,这种字体在中文环境不同分辨率下效果都比较好)。
(4)命令按钮fontButton (1):Caption ="&Select Font",选择该按钮后将弹出选择字体,然后程序将选中的窗体上所有控制的字体均设为用户选择的字体。
(5)命令按钮ExitButton:Caption="&Exit",用于退出此程序,实际上的工作只是隐藏了窗体frm AddIn。外接程序的真正退出还是需要外接程序管理器中删去。
(6)通用对话框CommonDialog1:用于弹出选择字体的通用对话框,便于用户从中选择所需字体。
下面是窗体代码:
Public VBInstance As vbide.VBE
Public Connect As Connect
Dim mcmpCurrentForm As VBComponent
Option Explicit
Private Sub ChangeFont (NewFontName As String,NewFontSize As Integer,NewFontBold As Boolean,NewFontltalic As Boolean)
Dim control As VBControl
On Error Resume Next
List1.Clear
Set mcmpCurrentForm =VBInstance.SelectedVBComponent
If (mcmpCurrentForm.Type<>vbext-ct-VBForm) And-
(mcmpCurrentForm.Type<>vbext-ct-UseControl) And-
(mcmpCurrentForm.Type<>vbext-ct-DocObject) And-
(mcmpCurrentForm.Type<>vbext-ct-PropPage) Then
Exit Sub
End if
For Each control in mcmpCurrentForm.Designer.VBControls
List.AddItem control.ControlObject.Name
control.ControlObject.FontName= NewFontName
control.ControlObject.FontSize= NewFontSize
control.ControlObject.FontBole= NewFontBole
control.ControlObject.FontItalic= NewFontItalic
Next
End Sub
Private Sub ExitButton-Click (Index As Integer)
Connect.Hide
End Sub
Private Sub FontButton-Click (Index As Integer)
Select Case Index
Case 0
ChangeFont "宋体",9,False,False
Case 1
With CommonDialog1
.Flags = cdlCFBoth
.ShowFont
ChangeFont.FontName,.FontSize,.FontBold,.FontItalic
End With
End Select
End Sub
上面的代码比较简单,关键的代码在于获取屏幕上的所有控制,在Visual Basic5.0下,与早期版本不同,它利用了VBComponent对象的Designer属性获取屏幕上的设计器,它可以是窗体,也可以是ActiveX控件或ActiveX文档等,然后通过该设计器来获得设计器上的用户控制。上述代码中,首先将mcmpCurrenForm变量被设为当前选中的构件,然后判断它是否是窗体,用户控件,ActiveX文档或属性页,如果是,则获取其设计器即Designer对象,然后通过Designer对象的VBControls属性获得其上所有控制的集合,这样就可以操作该集合中的每一个VBControls控制了。对每一个控制,通过其ControlObject 属性可以获得其相应的屏幕上的控制对象,然后就可以设置该对象的任意属性了。在上述代码中,也可以使用control.Properties!Fontname取代control.ControlObject.FontName,其结果是一样的。另外,为了防止对没有Font属性的控制进行该属性设置将导致错误发生,在程序的开头使用了On Error Resume Next语句以忽略这些错误。下面是该程序的用户界面:
四、 进一步的改进
对上述程序还可做进一步的改进,下面列出几种改进思想及编程方案。
1、 设置控制的其它属性
上述代码主要用来设置控制的字体,但基于同样的原理,我们只要对代码稍加改变就可以设置控制的其它属性,如前景色,背景色,可见或不可见等。
2、 为当前工程中的所有窗体设置控制属性
在上述代码中,是对当前选中的窗体里的控制设置字体,但有时我们需要将工程中的所有窗体中的所有控制一次设置其属性,而不是每次选择一个窗体进行设置。这时可以采用如下方法:首先用VBE(即当前会话期实例)的ActiveVBProject属性获得当前的工程,然后通过其VBComponents属性获得当前工程中的所有组件集合,针对每一组件判断它是否为窗体或 ActiveX控件等,剩下的代码就和上面基本一样了。
3、 仅为选中的控制设置属性
若只是需要为选中的控制设置属性则可以利用VBForm 对象的SelectedVBControls属性获得当前选中的所有控制的集合,然后对其包含的控制设置属性即可。也可利用VBControl的InSelection属性判断该控制是否被选中,然后仅对选中的控制设置属性即可。
4、 当控件被添加到窗体时,自动为它设置属性
要实现当控件被添加到窗体时,自动为它设置属性的功能需要用到VBE的事件对象,即与上面提到的为菜单提供事件代码的方法一样。首先声明一个事件变量:Public WithEvents CtlHandler As VBControlsEvents,然后在接口IDTExtensibility的OnConnection方法代码中挂接控制事件处理:Set Me.CtlHandler=VBInstance.Events.VBControlsEvents (Nothing,Nothi-ng),这样就可以进行事件处理了。而进行事件处理的代码可写在CtlHandler变量的ItemAdded代码中,如下:
Private Sub CtlHandler-ItemAddec (ByVal VBControl As VBIDE.VBControl)
'这里可加入所需要的代码。
End Sub
当然,除上述改进外,我们还可以提出更多的改进方案,以丰富原程序的功能。总之,由于VB显露其IDE包括窗口、工程、控制,甚至事件、代码等各个部分的接口,使得我们能非常简单而有效地控制自己的编程活动和定制自己的界面,从而节省了编程时间,提高了工作效率,也减少了错误的发生。
参考文献
1、[美]鲍(Boyle,D)等著·Visual Basic 4 开发人员指南·薛万鹏等译·机械工业出版社
2、Visual Basic 5联机手册·Microsoft Corporation
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/