RibbonX:自定义Office 2007功能区——VBA基础(二)
RibbonX:自定义Office 2007功能区
第4章 介绍VBA阅读笔记
对于有一定VBA基础的人来说,本章较简单。因此,我的笔记相对也较简单,只是对内容进行粗略地概述,或者记下需要注意的事项及关键点。
—————————————-
编写自已的代码
1、命名约定
使用约定的规则命名,将使他人和您更容易阅读和理解代码;降低了使用预留字或特定字符的风险;避免对象之间的命名冲突;更容易向他人解释代码。
此外,当多人共同为一个项目工作或者需要与他人共享代码时,遵守标准的命名约定是有利的。
本书为XML代码采用的命名约定为:
(1)前缀:为功能区定制代码使用rx以区分其他VBA代码。
(2)标记:采用RVBA标记系统来标记功能区控件。例如,在VBA中label控件的标记为lbl,而在这里的功能区命名约定中使用rxlbl。这样,让我们知道该标签来自于功能区而不是VBA工程内的标签控件。
(3)基本的名称:即控件的描述。例如,有一个功能区控件并定义其前缀和标记为rxbtn,然而该控件做什么呢?如果是一个演示按钮,则将其命名为rxbtnDemo,使之更有意义。
(4)事件后缀:已经有一个按钮,但当用户单击该按钮后会发生什么?将触发事件,此时使用通用的VBA事件后缀来描述该行为。因此,如果Demo按钮附加了onAction属性,那么过程名为rxbtnDemo_click。
(5)共享的事件:如上例,Demo按钮有单击事件。然而,如果希望与其他按钮共享该事件,或者其他控件有onAction属性。此时使用一般的标记来指明onAction属性在许多不同的控件中共享。例如:rxshared_click。现在,应该知道单击被许多具有onAction属性的其他控件共享。
(6)Repurpose后缀:前面已提到使用事件后缀来匹配VBA的事件。然而,当使用内置命令时,可能希望使用onAction属性重新使用其行为。由于onAction返回click后缀,不能让代码阅读者知道其是一个被重新使用的内置命令。此时,在rx前缀后面跟随控件的idMso属性作为基本的名称,然后加上下划线和单词“Repurpose”表示其为重新使用的内置命令。例如:rxFileSave_Repurpose表示重新FileSave命令来执行某些其他操作。
2、数据类型
包括Byte、Boolean、Integer、Long、Single、Double、Currency、Decimal、Date、Object、String、Variant、User-Defined。
3、处理事件
Excel、Access和Word都能控制许多不同类型的事件。事件通常与常使用的对象相关联。
(1)工作簿事件
例如,工作簿的SheetBeforeRightClick事件:
Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, _
ByVal Targe As Range,Cancel As Boolean)
If Union(Target.Range(“A1”),Range(“A1:C25”)).Address= _
Range(“A1:C25”).Address Then Cancel=True
End Sub
其中,参数sh引用发生右击的工作表,参数Target引用右击的目标区域,参数Cancel决定是否取消右击的结果。正常情况下,右击时将获得一个弹出菜单,但当Cancel设置为True后,将取消该弹出菜单。
上述代码对工作簿中所有工作表都有效。
一些有用的工作簿事件:BeforeClose、BeforeSave、NewSheet、Open、SheetActivate、SheetBeforeRightClick、SheetChange。
(2)工作表事件
当必须处理某工作表上的事件时,使用该工作表自身的事件。下面举一个实用的解决方案。
假设需要验证A列接受不重复的值,只要用户在每个单元格中输入数据而不在单元格区域中复制和粘贴,那么一切都正常。关键是,如果用户复制并粘贴数据,那么所有的验证都是徒劳。此时,应该使用事件过程。
使用Change事件来检查在列中发生了什么,如果用户粘贴数据,则该过程验证粘贴值以便确定撤销在该列中的粘贴操作。如下面的代码:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngSelected As Range
Dim rngValue As Range
Dim lngCount As Long
‘当检查发生时禁用事件
Application.EnableEvents = False
‘监控的单元格区域
If Union(Target.Range(”A1″), Range(”A1:A1048576″)).Address = _
Range(”A1:A1048576″).Address Then
On Error Resume Next
‘设置所选择的区域
Set rngSelected = Selection
‘统计所有粘贴的值的重复数
For Each rngValue In rngSelected
lngCount = WorksheetFunction.CountIf(Range(”A1:A1048576″), _
rngValue)
‘如果重复数大于1
If lngCount > 1 Then
‘显示错误消息
MsgBox “列A仅接受唯一值.” _
& “这些值您已经输入.”, vbCritical, _
“在列A复制的值”
‘撤销粘贴操作
Application.Undo
Application.CutCopyMode = xlCut
Exit For
End If
Next rngValue
End If
‘启用事件以使继续监控
Application.EnableEvents = True
End Sub
注意,因为代码也导致工作表中的变化,所以必须临时禁用事件。此外,在过程结束前,必须启用事件。
一些有用的工作表事件:Activate、BeforeDoubleClick、BeforeRightClick、Change、SelectionChange。
(3)在Access中的窗体和报表事件
假设用户打开某报表,但什么也没看到,即在报表中没有放置数据。空的报表将给人在过程中出现错误的印象,而不是报表中没有数据。此时,使用NoData事件来确定是否取消报表,来避免这些问题和混淆
Private Sub Report_NoData(cancel As Integer)
MsgBox “目前没有可用的数据。报表将被删除…”
cancel=-1 ‘也可以使用True代替数字1
End Sub
另一个有用的事件是Open事件,使用该事件确保显示报表前装载了重要的元素。示例略。
一些有用的报表和窗体级事件:Click、Close、Current、Load、NoData、OnFormat、Open。
(4)在Word中的文档级事件
在Word中,需要为特定的文档创建类模块来处理事件。也能应用该方法到全局模板或者制作加载项来控制应用程序级的事件。
使用WithEvents关键词声明能在工程中使用的公共的Word对象。
首先,需要在工程中添加一个类模块并将其命名,本例中为clsEvents。然后,打开代码窗口,输入下面的代码:
Public WithEvents appWrd As Word.Application
Private Sub appWrd_DocumentChange()
End Sub
第一行代码指定Word应监控的事件。下面的过程表明应该监控文档变化。
事实上,此时并不会发生什么。然而,可以使用文档级的Open事件来为Word设置应用程序级事件。
Dim mappWrd As New clsEvents
Private Sub Document_Open()
Set mappWrd.appWrd = Application
End Sub
一旦这样处理后,准备使用在类模块clsEvents中的事件。记住,必须首先编写所有的事件,否则类将被中止,因为没有监控的事件。
下面添加一段实用的事件代码,用来追踪某文档的打印:
Private Sub appWrd_DocumentBeforePrint(ByVal Doc As Document, _
Cancel As Boolean)
If Doc = ThisDocument Then
‘code
Else
‘code
End If
End Sub
注意,WithEvents变量appWrd将监控整个Word应用程序,因此当试图只监控包含代码的文档时,应该创建代码使之仅在正确的文档中执行。在示例中,使用ThisDocument对象来确保仅在包含代码的文档中有效。
(5)应用程序级事件
正如在Word中所见,如果打算配置应用程序级的事件,则需要使用类模块。因此,这里所有的示例都使用类模块。并且,使用标准的名称clsEvents。
第一个示例演示如何监控特定的工作簿的打印,并且仅允许打印指定的工作簿。
(1)打开Excel,在VBE中添加一个新的类模块。
(2)在类模块的代码窗口中,输入下面的代码:
Public WithEvents appXL As Excel.Application
Private Sub appXL_WorkbookBeforePrint(ByVal Wb As Workbook, _
Cancel As Boolean)
If Not Wb.Name = “AllowedWorkbook.xlsm” Then
MsgBox “不允许打印,除非是工作簿AllowedWorkbook.xlsm”, vbCritical
Cancel = True
End If
End Sub
(3)在应用程序的Open事件中,输入下面的代码:
Dim mappXL As New clsEvents
Private Sub Workbook_Open()
Set mappXL.appXL = Application
End Sub
此时,将只允许指定的工作簿被打印。
第二个示例在Access中,在数据输入到表之前提供一种验证数据的方式。假设一个文本框用于输入数据到某字段,并且仅包含正值,因此,如果该文本框出现在多个窗体中时,可能试图为每个文本框编写验证代码。然而,可以在类模块中编写简单的过程然后将其在工程中使用,从而节省时间。代码如下:
Public WithEvents clsTextBox As TextBox
Private Sub clsTextBox_AfterUpdate()
With Me
If .clsTextBox.Value<0 Then
MsgBox “该字段不允许输入负值……”,vbCritical
.clsTextBox.Value=0
End If
End With
End Sub
创建类模块后,需要从使用文本框的任何窗体调用该模块。注意,在每个窗体中文本框必须有相同的名称。从设计视图中打开一个可用的窗体并输入下面的代码:
Dim mtxtbox As New clsTextBoxEvents
Private Sub Form_Load()
Set mtxtbox.clsTextBox=Me.txtValue
End Sub
Private Sub txtValue_AfterUpdate()
‘该过程没有使用,仅确保执行类
End Sub
现在,应该能够在工程中的任何地方重复使用该类而无须重复编写相同的代码。这是一项非常有用的技术,能应用到许多解决方案中。
4、对象浏览器
(1)找到对象及其属性、方法和事件的最好方式是使用对象浏览器。
(2)在VBE中,按F2键即可快速打开对象浏览器。
5、引用库
一旦引用相应的库之后,就能够查看其对象模型获得帮助,也能够使用该库的对象及其属性和方法。
使用VBE菜单“工具”─—“引用”,打开“引用”对话框,其中列出了所有可引用的库。
(1)介绍早期绑定和后期绑定
当使用外部对象时,也就是说使用不属于正处理的对象模型中的对象时,必须创建该外部对象的实例。即使用早期绑定或后期绑定。
当首先安装对其他库的引用时,使用的是早期绑定。早期绑定不仅加速了编写代码的过程(现在所有关于该库的对象包括其属性和方法都公开),而且改善了代码的性能。缺点是使用该程序代码的用户也需要已经注册了所引用的库。如下面的代码:
Dim olEmail As Outlook.MailItem
后期绑定不依赖于先引用库,而且首先简单地声明普通的对象并使用其属性和方法,在运行时再调用相关的库。例如下面的代码:
Dim appOL As Object
Dim Email As Object
Set appOL=CreateObject(“Outlook.Application”)
Set Email=appOL.CreateItem(olMailItem)
CreatObject函数总是创建对象的新实例,如果已经打开了该对象的一个实例,那么将创建另一个实例。如果不希望由于创建应用程序对象新实例而耗尽资源,可以使用GetObject函数:
Set appWrd=GetObject(,”Word.Application”)
如果已经打开则该代码将获取Word的application对象,因而不需要创建该应用程序的另一个实例。当然,如果该应用程序的实例不存在,那么代码将返回错误。可以使用下面的代码进行处理并避免错误:
On Error Resume Next
Set appWrd=GetObject(,”Word.Application”)
If appWrd Is Nothing Then Set appWrd=CreateObject(“Word.Application”)
使用On Error Resume Next忽略可能没有已经运行的实例而产生的错误。如果发生错误,则不会设置该对象并且仍然是Nothing。因此,随后的语句检查是否是nothing,如果是则创建该对象的一个新实例。
调试代码
(1)Debug.Print和Debug.Assert
Debug对象用于向立即窗口输出结果,它有两个方法:
Print:用于在立即窗口打印输出结果。
Assert:当预料的结果应该评估为True时,用于断言在运行时评估为False的条件。当感到代码应该返回true值或者基于true的假设,但事实上返回false值,则当其评估为false时可以断言该代码,并停留在该断言行,如下例所示:
Sub debugAssert()
Dim lngRow As Long
Dim blnAssertNotEmpty As Boolean
lngRow = 1
Do Until IsEmpty(Cells(lngRow, 1))
blnAssertNotEmpty = Not (IsEmpty(Cells(lngRow + 1, 1)))
lngRow = lngRow + 1
Debug.Assert blnAssertNotEmpty
Loop
End Sub
本例中,将在工作表指定的单元格中循环,检查是否下一个单元格是空。当遇到空单元格时,Boolean值将为False,代码停止在断言行。
(2)Stop语句
Stop语句用于执行代码时在特定的点挂起。假设希望在打开工作簿、文档、报表或窗体时停止代码。可以添加MsgBox并输入在显示消息之后的代码。然而,这会导致不期望的中断,在每次执行该行代码时不得不处理消息框。此时,一种更好的选择是使用Stop语句,避免消息框。
使用Stop语句将延缓执行,能使您分析代码。例如,下面的代码包括Stop语句,在设置Ribbon对象之前中止过程。如果在这里停止执行,那么UI仍然会装载,但Ribbon对象将不可用:
Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI)
Stop
Set grxIRibbonUI = ribbon
End Sub
注意,使用Stop与在代码中添加断点有相似的效果。然而,当文件关闭后,断点也失效了。
(3)立即窗口
立即窗口是一个有价值的工具,可以:1)测试所编写的代码或者调试代码中的问题;2)查询或更改变量的值;3)当中止执行时,查询或更改变量的值;4)为变量赋新值;5)查询或更改属性值;6)运行子过程或函数;7)查看调试输出。
(4)本地窗口
本地窗口用于显示正执行过程的变量值和对象。如果需要监控多个变量、值或对象,那么使用本地窗口可能是最好的方式之一。
(5)监视窗口
监视窗口是调试代码的另一个极好的工具,能够指定监视某对象的值的指令,例如变量、表达式、函数调用,等等。当条件满足或者正监视的变量改变时,监视窗口将暂停代码的执行。
错误处理
有一些简单的方式来处理未解决的或不可预知的问题,错误处理技术就是一种,能够帮助您建立更健壮的代码。
(1)On Error Resume Next
这是最简单且最频繁使用的错语处理语句。该语句将简单地忽略发生错误的语句并继续执行下面的代码。但是,如果其他代码行依赖于该行出错的语句,那么程序将会出现一连锁的错误反应,因而得不到正确的结果。
建议保守地使用这种错误处理的方法。
(2)On Error GoTo
这种形式的错误处理有:
On Error GoTo 0或On Error GoTo <label>,label代表选择的标记,如ErrHandler或Err_Handler。
Sub OnErrorGoTO()
On Error GoTo Err_Handler
‘这里是过程/函数的主体
Exit Sub ‘或Exit Function
Err_Handler:
‘这里是错误处理代码
Resume Next
End Sub
处理数组
数组基本上是一组被索引的数据,VBA将其作为单个的变量。例如:
Dim InflationRate(2000 To 2007) As Double
索引值是2000至2007之间的年份。
(1)确定数组的边界
使用LBound和UBound函数确定数组的上边界和下边界(上限和下限)。
(2)调整数组的大小
即创建动态数组。此时,使用ReDim关键字来调整数组的大小。
但是,如果数组已经包含值,那么使用ReDim调整数组的大小将会清除数组内容。如果想保留数组中以前的信息,则可以在ReDim关键字后再使用Preserve关键字,例如:
ReDim Preserve InflationRate(StartingYear To EndingYear)
Dim InflationRate() As Double
Dim varArray As Variant
Sub RunExampleArray()
Call ExampleArray(1990, 2000)
Call ExampleArray2(1980, 2007)
End Sub
Sub ExampleArray(ByVal StartingYear As Long, _
ByVal EndingYear As Long)
ReDim InflationRate(StartingYear To EndingYear)
InflationRate(1990) = 2.4
InflationRate(2000) = 1.9
varArray = InflationRate()
End Sub
Sub ExampleArray2(ByVal StartingYear As Long, _
ByVal EndingYear As Long)
ReDim InflationRate(StartingYear To EndingYear)
InflationRate(1990) = varArray(1990)
InflationRate(2000) = varArray(2000)
Debug.Print LBound(InflationRate)
Debug.Print UBound(InflationRate)
Debug.Print InflationRate(1990)
Debug.Print InflationRate(2000)
End Sub
下一章:回调的具体细节。

发表评论