Excel 2010 中发生事件时运行 VBA 代码

**摘要:**了解工作簿中发生事件(例如用户更改所选内容)时如何在 Microsoft Excel 2010 中运行 Visual Basic for Applications (VBA) 代码。根据事件运行 VBA 代码使您能够自动执行任务,而不需要用户单击快捷方式或运行宏。

上次修改时间: 2011年6月5日

适用范围: Excel 2010 | Office 2010 | SharePoint Server 2010 | VBA

本文内容
了解 VBA 中的事件处理程序
突出显示当前行和列
拆分当前行和列的窗格
在单元格值发生更改时添加批注
创建自定义快捷菜单
使用键盘快捷键启动计时器
使用 Page Up 和 Page Down 键逐页浏览工作表
使用事件自动保存工作簿的更改
结论
其他资源

**发布时间:**2011 年 6 月

供稿人:社区成员图标 Gerard M. Verschuuren 博士,Mr. Excel(该链接可能指向英文页面) | Mark Roberts,Microsoft Corporation

目录

  • 了解 VBA 中的事件处理程序

  • 突出显示当前行和列

  • 拆分当前行和列的窗格

  • 在单元格值发生更改时添加批注

  • 创建自定义快捷菜单

  • 使用键盘快捷键启动计时器

  • 使用 Page Up 和 Page Down 键逐页浏览工作表

  • 使用事件自动保存工作簿的更改

  • 结论

  • 其他资源

单击以获取代码下载示例工作簿:发生事件时运行 VBA 代码 (Events.xlsm)(该链接可能指向英文页面)

了解 VBA 中的事件处理程序

通过阅读本文,您会了解到如何在工作簿中发生事件时通过运行 Visual Basic for Applications (VBA) 代码来执行任务,这些事件包括打开工作簿、在工作簿中进行选择或更改所选内容、右键单击或切换工作表等。可与 VBA 代码关联的操作或项目集称为事件,而事件发生时运行的特殊类型的过程称为事件处理程序。

本文中的示例重点介绍与 WorksheetWorkbook 对象关联的事件,以及像事件一样运行的 Application 对象的两个方法(OnKeyOnTime 方法)。要查看与 Microsoft Excel 对象关联的事件列表,请参阅 Excel 2010 开发人员参考的Events, Worksheet Functions, and Shapes一节中的Worksheet Object Events主题。

若要使用示例数据运行本文中所述的代码,请下载事件(该链接可能指向英文页面)示例工作簿。

突出显示当前行和列

事件示例工作簿包含与 1-Cross 工作表关联的事件处理程序过程,该过程根据用户在工作表中的选择自动显示水平和垂直标记,如图 1 所示。

图 1. 突出显示当前行和列

突出显示当前行和列

若要创建此示例,事件处理程序代码与 Worksheet 对象的 SelectionChange 事件关联。顾名思义,SelectionChange 事件在用户更改其在工作表中选择的内容时发生。若要为工作表添加事件处理程序,请使用以下过程。

添加工作表事件的事件处理程序

  1. 在"开发工具"选项卡上,单击"Visual Basic"。

  2. 在"项目资源管理器"中,单击要处理的工作表。

  3. 在代码编辑器上面的左下拉列表中,选择"工作表"。

  4. 在代码编辑器上面的右下拉列表中,选择要为其创建事件处理程序的事件。

    对于下面的示例,这是 SelectionChange 事件,即下拉列表中的默认选择。

备注

如果"开发工具"选项卡未显示在 Excel 功能区中,请依次单击"文件"、"选项"、"自定义功能区",然后选中"主选项卡"下面的"开发工具"复选框。

请注意,SelectionChange 事件的事件处理程序有一个名为 Target 的事件参数,它提供有关用户选择的单元格区域的信息。然而,由于 Target 参数返回的 Range 对象可能包含多个单元格,因此该事件处理程序的代码使用 Application 对象的 ActiveCell 属性代替。ActiveCell 属性返回仅引用一个单元格(当前单元格或单元格区域左上方的单元格)的 Range 对象,事件处理程序用它来突出显示用户所在的行和列。

代码通过将包含所选单元格的行或列的内部 ColorIndex 属性设置为灰色来发挥作用(请参阅 ColorIndex 属性主题以获取默认颜色索引值的列表)。在执行此操作之前,代码使用 Worksheet 对象的 Cells 属性清除工作表内所有单元格的内部颜色。

下面的列表显示 1-Cross 工作表中 SelectionChange 事件处理程序的代码。

' Highlight the entire row and column for the ActiveCell.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    Cells.Interior.ColorIndex = xlColorIndexNone
    ActiveCell.EntireRow.Interior.ColorIndex = 15
    ActiveCell.EntireColumn.Interior.ColorIndex = 15
End Sub

拆分当前行和列的窗格

事件工作簿中的 2-MoveButton 工作表有一个遵循您的选择的命令按钮。单击该按钮将从该处切换屏幕拆分。

若要创建此功能,请首先单击"开发工具"选项卡上的"插入",然后单击"ActiveX 控件"调色板中的"命令按钮"图标,向工作表中添加一个按钮。在工作表中绘制按钮后,右键单击该按钮,然后单击"查看代码"。此操作将打开代码编辑器并创建该按钮的 Click 事件处理程序。

如下面的代码摘录所示,当用户单击该按钮时,ToggleSplit 按钮的事件处理程序代码使用 If/Else 语句检查 Split 属性的值,并根据当前值打开或关闭拆分。此外,代码还更改按钮的 Caption 属性以反映下次单击按钮时发生的操作。

' The button turns the window split on/off.
Private Sub ToggleSplit_Click()
    If ActiveWindow.Split = True Then
        ActiveWindow.Split = False
        ToggleSplit.Caption = "Turn Split ON"
    Else
        ActiveWindow.Split = True
        ToggleSplit.Caption = "Turn Split OFF"
    End If
End Sub

要使按钮遵循用户的选择,需要有与 2-MoveButton 工作表关联的 SelectionChange 事件的事件处理程序。如下面的代码摘录所示,代码根据当前单元格的 TopLeft 坐标值设置按钮的 TopLeft 坐标,然后按照该单元格的 HeightWidth 属性进行偏移。

' Make the button follow the user's navigation.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ToggleSplit.Top = ActiveCell.Top + ActiveCell.Height
    ToggleSplit.Left = ActiveCell.Left + ActiveCell.Width
End Sub

在单元格值发生更改时添加批注

与 3-Comment 工作表关联的事件处理程序代码跟踪用户对工作表中任何单元格所做的所有更改,并将它们添加到批注中,所以每次发生新的更改后,批注都会增大。要创建此功能,需要两个工作表事件的事件处理程序:

  • 记录新选中单元格的当前值的 SelectionChange 事件

  • 发生更改时创建批注或向现有批注中添加新行的 Change 事件

在下面的事件处理程序中,必须相对于工作表的整个代码模块限定变量 OldValue 的范围,以便可以在第一个事件处理程序中设置它的值,然后将该值用于第二个事件处理程序。为此,请在开始该过程之前在工作表模块的顶部声明变量。

下面的列表显示 SelectionChange 事件处理程序过程的代码,该代码将 OldCellValue 变量的值设置为所选单元格的当前值。

' Record the current cell value.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    OldCellValue = ActiveCell.Text
End Sub

这次,Change 事件处理程序使用 Target 参数,因为 ActiveCell 并不总是所选内容中的第一个单元格。首先,它使用下面的代码行确保 Target 参数仅引用一个单元格,如果是这样,那么随即结束该过程,否则我们将需要编写更多的代码。

If Target.Cells.Count > 1 Then Exit Sub

下一行代码创建要添加到批注中的更改历史记录项,该项包括更改时间、做出更改的用户的名称和之前的值。

NewComment = "Changed on " & Now() & " by " & Application.UserName & _
        " from " & OldCellValue

接下来的代码行确定批注是否与单元格关联。如果不关联,代码将创建一个新批注,然后添加新建的更改历史记录项。否则,它将当前批注的文本存储在 OldComment 变量中,添加新的历史记录项,并附加旧的历史记录项。

If Target.Comment Is Nothing Then
    Target.AddComment NewComment
Else
    OldComment = Target.Comment.Text
    Target.Comment.Text NewComment & vbLf & OldComment
End If

下一行代码通过将 AutoSize 属性设置为 True 来将批注框的大小调整为最宽的行。接下来的行显示批注五秒钟。因为 Wait 方法会暂停 Excel 的其他活动,直到等待期结束,然后调用 DoEvents 函数以便操作系统可以处理其他操作。

Target.Comment.Shape.TextFrame.AutoSize = True

Target.Comment.Visible = True
DoEvents
Application.Wait Now + TimeValue("00:00:05")
Target.Comment.Visible = False

最后的代码行询问用户是否删除更改历史记录批注,如果选择删除,则循环访问当前区域中的所有单元格以删除批注。

If MsgBox("Delete all comments?", vbYesNo) = vbYes Then
    For Each objCell In ActiveCell.CurrentRegion.Cells
        If Not objCell.Comment Is Nothing Then objCell.Comment.Delete
    Next objCell
End If

下面的列表显示与 3-Comment 工作表关联的整个 Change 事件处理程序过程的代码。

' Add a comment for each change.
Private Sub Worksheet_Change(ByVal Target As Range)
    Dim OldComment As String, NewComment As String, objCell As Range
    
    If Target.Cells.Count > 1 Then Exit Sub
    
    NewComment = "Changed on " & Now() & " by " & Application.UserName & _
        " from " & OldCellValue
    
    If Target.Comment Is Nothing Then
        Target.AddComment NewComment
    Else
        OldComment = Target.Comment.Text
        Target.Comment.Text NewComment & vbLf & OldComment
    End If
    
    Target.Comment.Shape.TextFrame.AutoSize = True
    
    Target.Comment.Visible = True
    DoEvents
    Application.Wait Now + TimeValue("00:00:05")
    Target.Comment.Visible = False
    
    If MsgBox("Delete all comments?", vbYesNo) = vbYes Then
        For Each objCell In ActiveCell.CurrentRegion.Cells
            If Not objCell.Comment Is Nothing Then objCell.Comment.Delete
        Next objCell
    End If
End Sub

备注

如果您想要记录单元格的所有更改历史记录,此过程还是有几个潜在缺点。SelectionChange 事件处理程序在以下情况下不运行:

  • 如果用户打开文件,然后更改当前活动的单元格。(OldComment 的值保留为""(空字符串),所以未记录历史记录项。)

  • 如果用户更改某个单元格,然后停留在该单元格中(例如,通过按 Ctrl + Enter),随后再次更改该单元格。

创建自定义快捷菜单

与事件工作簿的 5-PopupMenu 工作表关联的示例显示如何创建包含三个命令的右键单击快捷菜单。代码包含在工作表的 BeforeRightClick 事件处理程序中,还有从代码模块调用的三个函数以定义快捷菜单中的命令。

前两行代码创建用于创建快捷菜单上的三个命令的数组。varCaption 数组包含要显示的标题,而 varAction 数组包含对每个命令调用的 Sub 过程的名称。

varCaption = Array("Toggle Gridlines", "Toggle Formulas", "Print Preview")
varAction = Array("Gridlines", "Formulas", "Preview")

下一行代码将配置为快捷菜单的新 CommandBar 对象添加到 CommandBars 集合中,并将 objMenu 变量设置为该对象。

Set objMenu = CommandBars.Add(Position:=msoBarPopup, Temporary:=True)

下面的代码行循环访问两个 varCaption 和 varAction 数组以填充快捷菜单中包含的命令。

For i = 0 To UBound(varAction)
    Set objCommand = objMenu.Controls.Add
    objCommand.Caption = varCaption(i)
    objCommand.OnAction = varAction(i)
Next i

最后两行代码显示快捷菜单,然后将 BeforeRightClick 事件处理程序的 Cancel 事件参数设置为 True 以阻止 Excel 显示其内置快捷菜单。

objMenu.ShowPopup
Cancel = True

下面的列表显示从快捷菜单中调用的 Module1 中三个过程的代码,快捷菜单在 BeforeRightClick 事件处理程序过程中定义。Gridlines 和 Formulas 过程只是简单地切换 DisplayGridlinesDisplayFormulas 属性,而 Preview 过程则在打印预览中显示工作表。

Sub Gridlines()
    ActiveWindow.DisplayGridlines = Not ActiveWindow.DisplayGridlines
End Sub

Sub Formulas()
    ActiveWindow.DisplayFormulas = Not ActiveWindow.DisplayFormulas
End Sub

Sub Preview()
    ActiveSheet.PrintPreview
End Sub

下面的代码显示与 5-PopupMenu 工作表关联的 BeforeRightClick 事件处理程序过程的整个列表。

Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
    Dim varCaption As Variant, varAction As Variant, i As Integer
    Dim objMenu As CommandBar, objCommand As CommandBarControl
    
    ' Create array of shortcut menu captions.
    varCaption = Array("Toggle Gridlines", "Toggle Formulas", "Print Preview")
    
    ' Create array of the Sub procedure names to call from shortcut menu.
    varAction = Array("Gridlines", "Formulas", "Preview")
    
    ' Add shortcut menu to CommandBars collection.
    Set objMenu = CommandBars.Add(Position:=msoBarPopup, Temporary:=True)
    
    ' Loop through arrays to add commands to shortcut menu.
    For i = 0 To UBound(varAction)
        Set objCommand = objMenu.Controls.Add
        objCommand.Caption = varCaption(i)
        objCommand.OnAction = varAction(i)
    Next i
    
    ' Display shortcut menu.
    objMenu.ShowPopup
    
    ' Cancel display of the built-in shortcut menu.
    Cancel = True
End Sub

使用键盘快捷键启动计时器

本节中的示例显示如何使用 Application 对象的 OnKey 方法,当按下某个键或键组合时,该方法会运行一个过程,就像事件一样。本示例中 OnKey 方法调用的过程使用 Application 对象的 OnTime 方法在指定时间段结束后触发警报。

定义要按的键和要调用的过程的代码在工作簿的 Open 事件处理程序中只有一行。以下代码摘录中对 OnKey 方法的调用指定按 End 键将调用定义警报行为的 SetAlarm 过程。

Private Sub Workbook_Open()
    Application.OnKey Key:="{END}", Procedure:="SetAlarm"
End Sub

备注

当您将此行代码放入工作簿的 Open 事件处理程序中之后,您必须关闭然后重新打开工作簿。

下面的列表显示 Module1 中的 SetAlarm 过程,当用户按 End 键时将调用该过程。该过程提示用户指定警报设置,然后使用用户指定的值调用 OnTime 方法以确定何时调用 ShowTime 过程来显示时间。

Sub SetAlarm()
    Dim strAlarm As String
    strAlarm = InputBox(Prompt:="At what time? (24-hour clock)", Title:="Set Alarm")
    If strAlarm = "" Then Exit Sub
    Application.OnTime EarliestTime:=TimeValue(strAlarm), Procedure:="ShowTime"
End Sub

备注

  • OnTime 方法并不像 Wait 方法那样阻止系统,所以无需调用 DoEvents 函数。

  • 如此示例所示,对 OnTime 方法的调用在用户指定的时间(24 小时制)运行 ShowTime 过程。若要创建使用从现在起所经过时间的计时器,请将代码行更改为如下内容:

    Application.OnTime EarliestTime:=Now + TimeValue(strAlarm), Procedure:="ShowTime"

如下面的列表所示,ShowTime 过程的代码显示当前时间,然后再次调用 SetAlarm 过程,直到用户取消 InputBox 提示。

Sub ShowTime()
    MsgBox Now
    SetAlarm
End Sub

使用 Page Up 和 Page Down 键逐页浏览工作表

本节中的示例使用 OnKey 方法通过 Page Up 和 Page Down 键浏览工作簿中的工作表。

与前面的示例一样,对 OnKey 方法的调用位于工作簿的 Open 事件处理程序中,如下面的代码所示。

Private Sub Workbook_Open()
    Application.OnKey Key:="{PgUp}", Procedure:="SheetsUp"
    Application.OnKey Key:="{PgDn}", Procedure:="SheetsDown"
End Sub

下面的代码显示 Module1 中 SheetsUp 和 SheetsDown 函数的列表,从工作簿的 Open 事件处理程序过程的 OnKey 方法调用中调用这些函数。SheetsUp 过程的代码将活动工作表的索引值每次递增一,然后检查该值是否超过工作表的数量;如果没有,该过程将前往下一个工作表。SheetsDown 过程的代码执行相反的操作。

Sub SheetsUp()
    Dim i As Long
    i = ActiveSheet.Index + 1
    If i <= Sheets.Count Then Sheets(i).Select
End Sub

Sub SheetsDown()
    Dim i As Long
    i = ActiveSheet.Index - 1
    If i >= 1 Then Sheets(i).Select
End Sub

备注

由于下面一节中介绍的 Workbook_SheetDeactivate 事件处理程序也位于示例工作簿中,所以在您浏览工作表时,系统会提示您保存工作簿。

使用事件自动保存工作簿的更改

要使用代码在指定间隔后自动保存工作簿,您可以调用工作簿 Open 事件处理程序过程中 Application 对象的 OnTime 方法。

事件示例工作簿的 Open 事件的事件处理程序过程中的代码行在工作簿打开 10 分钟之后调用名为 SaveWB 的过程。

Private Sub Workbook_Open()
    Application.OnTime EarliestTime:=Now + TimeValue("00:10:00"), Procedure:="SaveWB"
End Sub

如下面的代码所示,SaveWB 过程中的第一行代码向用户显示提示。如果用户响应"是",该过程将调用 Save 方法。第二行代码与 Open 事件处理程序过程一样调用同一 OnTime 方法,以创建重新启动计时器的递归操作。

Sub SaveWB()
    If MsgBox("Save workbook?", vbYesNo) = vbYes Then ActiveWorkbook.Save
    Application.OnTime EarliestTime:=Now + TimeValue("00:10:00"), Procedure:="SaveWB"
End Sub

如下面的代码所示,您还可以在用户切换到其他工作表时,提示用户是否保存工作簿,方法是从 SheetDeactivate 事件的事件处理程序过程调用诸如 SaveWB 之类的流程。

Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
    SaveWB
End Sub

结论

本文和事件示例工作簿提供工作簿中发生事件(例如用户更改所选内容或更改单元格值)时如何运行 VBA 代码的示例。

其他资源