Excel中的图表事件
Excel中的许多对象都可以响应事件,这其中包括了Excel程序自身的事件,也包括了我们在Excel中开发VBA应用程序时在对象上所附加的事件处理程序,如按钮的响应事件、单元格被选中的事件、工作表被激活的事件等。大多数的事件处理程序我们都耳熟能详,本文在此重点介绍一下Excel中的图表事件。
在Excel中如何快速地创建图表
虽然Excel可以接受任何类型和格式的数据,但是为了方便创建图表,我们通常都会创建一个相对连续的数据区域,并给定一些有意义的值,好的数据组织将有利于生成更加完美的图表。这里有一个例子,分别对A、B、C、D、E五个栏目按Alpha和Beta两种类别进行统计。
在Excel中生成图表非常简单,选中上述单元格区域,选择Insert选项卡中Charts部分的图表类型,Excel会自动按照你所选的图表类型为你生成图表,如下图。
选择Design选项卡,在Chart Layouts部分选择不同的布局,可以丰富图表的内容,如添加图表的名称、设置图例的显示位置等。通过Excel提供的图表功能,我们可以给图表添加许多元素,按照 Excel自带的说明文档上的介绍,一个相对较完整的图表应该包含7个单元。
1. 图表显示区域。 2. 数据透视图区域。 3. 数据透视图中用于显示数据图表的数据点。 4. 数据透视图中的坐标值。 5. 图例区域。 6. 图表的标题。 7. 用于作为tooltip的数据标签,对数据的解释。 |
按照上述方法创建的图表默认是嵌套在Excel工作表中的,我们也可以创建一个独立的图表在Excel中指定的地方显示。在Design选项卡中找到Location部分,选择Move Chart,弹出的对话框如下图所示,选择New sheet,并定义一个有意义的名称,点击OK,此时Excel会在一个新的sheet中创建图表。这里有一个快速创建图表的方法,选中要创建图表的单元格区域,直接按F11,Excel会按照默认的选项在新的sheet中生成图表。在新 sheet中生成的图表可以在VBA中作为对象来进行访问,同时也可以编写事件处理程序,稍后会介绍。
为什么要使用图表事件
使用图表事件可以更加方便用户使用我们编写的VBA应用程序。例如,我们可以给图表添加一个select事件,当用户点击图表数据透视图中的数据点时,为用户做这样一些事情:
- 显示一个提示信息
- 提取图表中的信息并放置到工作表中。
- 激活另外一个图表或工作表。
- 识别一个数据点进行数据分析。
在接下来的内容中,我会向大家介绍Excel中图表对象的一些常用事件的使用方法,并会给出相应的示例。
如何添加图表事件
与其它的VBA控件类似,要想为图表对象添加事件,必须首先进入到Excel的Visual Basic 编辑器。在上图中,右键点击Chart4选项卡(在Excel窗体的下端),选择“View Code”,即可打开 Visual Basic编辑器。或者在“开发工具”选项卡中直接点击Visual Basic按钮。如果是从当前图表所在的标签进入的Visual Basic编辑器,则Visual Basic编辑器窗口中默认打开的是当前图表的Code窗体,在Code窗体的顶部有两个下拉列表,在左边的下拉列表中选择Chart,右边的下拉列表中即显示了Chart对象支持的所有事件名称,默认是Active事件,即Chart被激活时所触发的事件。
Chart对象支持以下这些事件:
- Active:当Chart对象被激活时触发。
- BeforeDoubleClick:鼠标双击前触发。
- BeforeRightClick:鼠标右键单击前触发。
- Calculate:使用公式运算时触发。
- Deactivate:当Chart对象释放激活状态时触发。
- DragOver:当Chart对象被拖动时触发。
- DragPlot:当Chart对象中的数据透视图被拖动时触发。
- MouseDown:鼠标按下时触发。
- MouseMove:鼠标移动时触发。
- MouseUp:鼠标按下,然后松开按键时触发。
- Resize:调整Chart的大小时触发。
- Select:Chart中的对象被选择时触发。
- SeriesChange:改变Chart中的图标系列时触发。
下面我着重介绍几个常用事件的使用方法。
常用图表事件的使用
1. Activate事件
Activate事件在Chart对象被激活时触发。如果为Chart对象的Activate事件编写了如下代码,则在前面所举的示例中,当我们选择Chart4选项卡时,该事件会被触发。
MsgBox "Welcome to my Chart!"
End Sub
2. Deactivate事件
Deactivate事件在Chart对象被释放激活状态时触发。为Chart对象的Deactivate事件编写如下代码,然后释放Chart4选项卡被激活的状态(如选择其它的Sheet选项卡),事件即被触发。
msg = "Thanks for viewing the chart."
MsgBox msg, , ActiveWorkbook.Name
End Sub
3. Select事件
使用Select事件可以知道Chart对象的哪一部分被选择了。该事件有三个参数,ElementID 被用来告知Chart对象的哪一部分被选择了,后两个参数Arg1和Arg2则会根据ElementID的不同值传递一些额外的信息,如当Chart对象中的series被选中,则Arg1被用来告知哪一个series被选中了,而Arg2被用来告知被选中的 series中被选中的具体位置。当整个series被选中时,Arg1的值为-1,如果Chart的其它对象被选中,Arg1和Arg2的值为0,此时没有任何附加的信息。下表列出了该事件中三个参数可能的所有值。
ElementID |
Arg1 |
Arg2 |
xlChartArea |
None |
None |
xlChartTitle |
None |
None |
xlPlotArea |
None |
None |
xlLegend |
None |
None |
xlFloor |
None |
None |
xlWalls |
None |
None |
xlCorners |
None |
None |
xlDataTable |
None |
None |
xlSeries |
SeriesIndex |
PointIndex |
xlDataLabel |
SeriesIndex |
PointIndex |
xlTrendline |
SeriesIndex |
TrendLineIndex |
xlErrorBars |
SeriesIndex |
None |
xlXErrorBars |
SeriesIndex |
None |
xlYErrorBars |
SeriesIndex |
None |
xlLegendEntry |
SeriesIndex |
None |
xlLegendKey |
SeriesIndex |
None |
xlAxis |
AxisIndex |
AxisType |
xlMajorGridlines |
AxisIndex |
AxisType |
xlMinorGridlines |
AxisIndex |
AxisType |
xlAxisTitle |
AxisIndex |
AxisType |
xlDisplayUnitLabel |
AxisIndex |
AxisType |
xlUpBars |
GroupIndex |
None |
xlDownBars |
GroupIndex |
None |
xlSeriesLines |
GroupIndex |
None |
xlHiLoLines |
GroupIndex |
None |
xlDropLines |
GroupIndex |
None |
xlRadarAxisLabels |
GroupIndex |
None |
xlShape |
ShapeIndex |
None |
xlPivotChartDropZone |
DropZoneType |
None |
xlPivotChartFieldButton |
DropZoneType |
PivotFieldIndex |
xlNothing |
None |
None |
MsgBox "Element: " & ElementID & vbCrLf & " Arg 1: " & Arg1 _
& vbCrLf & " Arg 2: " & Arg2
End Sub
Dim sElement As String
Dim sArg As String
Select Case ElementID
Case xlChartArea
sElement = "Chart Area"
Case xlChartTitle
sElement = "Chart Title"
Case xlPlotArea
sElement = "Plot Area"
Case xlLegend
sElement = "Legend"
Case xlFloor
sElement = "Floor"
Case xlWalls
sElement = "Walls"
Case xlCorners
sElement = "Corners"
Case xlDataTable
sElement = "Data Table"
Case xlSeries
sElement = "Series " & Arg1
If Arg2 > 0 Then sArg = "Point " & Arg2
Case xlDataLabel
sElement = "Data Label"
sArg = "Series " & Arg1
If Arg2 > 0 Then sArg = sArg & ", Point " & Arg2
Case xlTrendline
sElement = "Trendline"
sArg = "Series " & Arg1 & ", Trendline " & Arg2
Case xlErrorBars
sElement = "Error Bars"
sArg = "Series " & Arg1
Case xlXErrorBars
sElement = "X Error Bars"
sArg = "Series " & Arg1
Case xlYErrorBars
sElement = "Y Error Bars"
sArg = "Series " & Arg1
Case xlLegendEntry
sElement = "Legend Entry"
sArg = "Series " & Arg1
Case xlLegendKey
sElement = "Legend Key"
sArg = "Series " & Arg1
Case xlAxis
sElement = IIf(Arg1 = 1, "Primary ", "Secondary ")
sElement = sElement & IIf(Arg2 = 1, "Category ", "Value ")
sElement = sElement & "Axis"
Case xlMajorGridlines
sElement = IIf(Arg1 = 1, "Primary ", "Secondary ")
sElement = sElement & IIf(Arg2 = 1, "Category ", "Value ")
sElement = sElement & "Major Gridlines"
Case xlMinorGridlines
sElement = IIf(Arg1 = 1, "Primary ", "Secondary ")
sElement = sElement & IIf(Arg2 = 1, "Category ", "Value ")
sElement = sElement & "Minor Gridlines"
Case xlAxisTitle
sElement = IIf(Arg1 = 1, "Primary ", "Secondary ")
sElement = sElement & IIf(Arg2 = 1, "Category ", "Value ")
sElement = sElement & "Axis Title"
Case xlDisplayUnitLabel
sElement = IIf(Arg1 = 1, "Primary ", "Secondary ")
sElement = sElement & IIf(Arg2 = 1, "Category ", "Value ")
sElement = sElement & "Axis Display Unit Label"
Case xlUpBars
sElement = "Up Bars"
sArg = "Group Index " & Arg1
Case xlDownBars
sElement = "Down Bars"
sArg = "Group Index " & Arg1
Case xlSeriesLines
sElement = "Series Lines"
sArg = "Group Index " & Arg1
Case xlHiLoLines
sElement = "High-Low Lines"
sArg = "Group Index " & Arg1
Case xlDropLines
sElement = "Drop Lines"
sArg = "Group Index " & Arg1
Case xlRadarAxisLabels
sElement = "Radar Axis Labels"
sArg = "Group Index " & Arg1
Case xlShape
sElement = "Shape"
sArg = "Shape Number " & Arg1
Case xlNothing
sElement = "Nothing"
End Select
MsgBox sElement & IIf (Len(sArg) > 0, vbCrLf & sArg, "")
End Sub
; ; ByVal Arg2 As Long)
If ElementID = 3 And Arg2 > 0 Then
With ActiveChart.SeriesCollection(Arg1).Points(Arg2)
.ApplyDataLabels Type:=xlShowValue
End With
End If
End Sub
4. MouseUp和MouseDown事件
Select事件在使用的时候有一个缺点,那就是当你重复选择同一个对象时,只有第一次的 Select事件会被触发,而MouseUp事件则是当鼠标按键按下之后在弹起的过程中被响应,它与MouseDown事件的唯一区别就在于事件触发的时间不同,MouseDwon事件是在鼠标按键按下时马上就触发。在有的时候使用MouseUp(或 MouseDown)事件比Select事件更奏效。
先来看看MouseUp事件的参数(MouseDown事件的参数与之相同)。Button参数被用来告知鼠标的哪个按键导致了事件的触发,xlNoButton-无按键,xlPrimaryButton-主按键,xlSecondaryButton-次按键, xlMiddleButton-中间的按键;Shift参数被用来告知当事件被触发时使用了哪些键盘组合键,它有4个基础值,0-没有使用组合键,1-Shift键,2-Ctrl键,4-Alt键,1、2、4三个值任意相加的结果即表示了多种不同的组合键,如 Shift的值为5则表示Shift+Alt的组合键;最后两个参数x和y用于表示当事件触发时鼠标所处的坐标值。
一个示例程序用于显示用户点击Chart对象中的series的具体信息。
Dim ElementID As Long, Arg1 As Long, Arg2 As Long
Dim myX As Variant, myY As Double
With ActiveChart
' Pass x & y, return ElementID and Args
.GetChartElement x, y, ElementID, Arg1, Arg2
' Did we click over a point or data label?
If ElementID = xlSeries Or ElementID = xlDataLabel Then
If Arg2 > 0 Then
' Extract x value from array of x values
myX = WorksheetFunction.Index _
; ; (.SeriesCollection(Arg1).XValues, Arg2)
' Extract y value from array of y values
myY = WorksheetFunction.Index _
; ; (.SeriesCollection(Arg1).Values, Arg2)
' Display message box with point information
MsgBox "Series " & Arg1 & vbCrLf _
; ; & """" & .SeriesCollection(Arg1).Name & """" & vbCrLf _
; ; & "Point " & Arg2 & vbCrLf _
; ; & "X = " & myX & vbCrLf _
; ; & "Y = " & myY
End If
End If
End With
End Sub
修改此程序可以实现很多在Excel图表中与用户的交互功能,如弹出提示框要求用户反复尝试某种操作,或结合MouseMove事件给出图表的自定义提示信息等。
另外还有一种很有用的应用,我们可以通过MouseUp事件来实现图表的详细内容查看功能。即Excel中存在多张主-次关系的图表,当点击主图表中的某项内容时,自动转到相应的次表,在次表中点击Return 区域又回到主表,让用户体验通过选择主表中的不同区域可以查看图表中详细内容的功能。下面是一个示例程序。
ByVal x As Long, ByVal y As Long)
Dim ElementID As Long, Arg1 As Long, Arg2 As Long
Dim myX As Variant
With ActiveChart
' Pass x & y, return ElementID and Args
.GetChartElement x, y, ElementID, Arg1, Arg2
' Did we click over a point or data label?
If ElementID = xlSeries Or ElementID = xlDataLabel Then
If Arg2 > 0 Then
' Extract x value from array of x values
myX = WorksheetFunction.Index _
; ; (.SeriesCollection(Arg1).XValues, Arg2)
' Don't crash if chart doesn't exist
On Error Resume Next
' Activate the appropriate chart
ThisWorkbook.Charts("Chart " & myX).Select
On Error GoTo 0
End If
End If
End With
End Sub
其中myX变量保存了当前主表中被选中的series的值,然后通过ThisWorkbook.Charts ("Chart " & myX).Select方法在整个Excel工作簿中查找名称与之相对应的Chart对象,并选中它。你完全可以自己编写名称匹配的算法,然后自动选中要查看的图表,或者采用ThisWorkbook,Sheets().Select方法选中要查看的工作表等等。
其它图表事件读者可以自己去尝试,它们在实际应用中使用得不是很多,而且相对来说使用都比较简单,这里我就不再一一给出例子了。下面重点说一下在Excel中如何使用内嵌的图表事件。
Excel中的内嵌图表事件
其实不单单指图表对象的内嵌事件,与单纯的VB语言一样,Excel中的VBA也是支持事件内嵌应用的,它支持所有VBA对象的内嵌事件编程。
什么是事件的内嵌?举个简单的例子来说明这一点。在VBA应用中,我们为工程里所有需要的对象一一编写事件处理程序,例如按钮的单击事件、下拉列表的选择事件,以及图表控件的激活、选择、鼠标单击事件等,但是在很多情况下,这些对象的事件处理程序完成的都是同一件工作,特别是在图表控件的MouseUp事件中,当事件触发时我们只想给用户一个包含详细内容的提示信息,这样的话如果给工程里所有的图表对象都添加一样的事件处理程序就显得有些过于繁琐了,而且更新和维护代码不方便。有没有一个简单的办法来实现这一功能呢?内嵌事件可以实现这一点。
Excel的VB编辑器中列出了已有的对象目录,包括Chart对象、Sheet对象、ThisWorkbook等,如果含有Moudle,则会包含Moudle对象,这些对象在VBE(Visual Basic Editor)的工程资源管理器窗体中被列出来,该窗体在VBE的右侧,如果没有显示,可以通过“视图-工程资源管理器”菜单来显示它。在对象的属性窗口中可以修改对象的名称,属性窗体默认在工程资源管理器窗体的下方,可以通过“视图-属性窗口 ”菜单来显示它。双击对象名称可以打开代码窗体并给该对象编写相应的代码。这些操作与VB 6的IDE编辑器类似,如果读者对VB很熟悉的话,这一部分对你来说应该不会陌生。除此之外,我们还可以通过“插入 ”菜单在工程中加入用户窗体、模块和类模块。使用类模块来定义全局对象,从而实现内嵌事件。
先介绍一下Option Explicit语句。
VBA是一种脚本语言,与其它许多的脚本语言类似(如常见的javascript语言),它是一种解释性的语言,在执行前不需要进行编译,而且变量或对象在使用前不必严格进行类型定义,这是一种弱类型的语言。然而一种好的编程习惯是要求变量或对象在使用前是经过类型确认的,这样在今后的代码维护中也会方便一些,其他人在阅读你的代码时也会显得思路清晰。VBA默认不要求严格定义变量或对象,但是我们可以通过Option Explicit语句来改变这一点,在模块或类模块代码的第一行写上这行语句,则以后的代码必须遵循变量或对象先定义后使用的规则。可以通过在VBE中选择“工具-选项”,勾选“要求变量声明”来让Excel自动为你加上这行代码。
我们要求的内嵌事件程序都在Option Explicit语句之后编写代码,这样可以避免对象过多时混淆定义,减少编程时不必要的错误。
接下来开始编写内嵌事件处理程序。
1. 在工程中添加新的类模块,取名为CEventChart,编写如下代码。
' Declare object of type "Chart" with events
Public WithEvents EvtChart As Chart
Private Sub EvtChart_Select (ByVal ElementID As Long, _
ByVal Arg1 As Long, ByVal Arg2 As Long)
MsgBox "Element: " & ElementID & vbCrLf & " Arg 1: " & Arg1 _
& vbCrLf & " Arg 2: " & Arg2
End Sub
2. 在模块1中添加下面的代码,或者新建一个模块加入下面的代码。
Dim clsEventChart As New CEventChart
Dim clsEventCharts() As New CEventChart
Sub Set_All_Charts()
' Enable events for active sheet if sheet is a&n bsp;chart sheet
If TypeName(ActiveSheet) = "Chart" Then
Set clsEventChart.EvtChart = ActiveSheet
End If
' Enable events for all charts embedded on a she et
' Works for embedded charts on a worksheet or ch art sheet
If ActiveSheet.ChartObjects.Count > 0 Then
ReDim clsEventCharts(1 To ActiveSheet.ChartObjects.Count)
Dim chtObj As ChartObject
Dim chtnum As Integer
chtnum = 1
For Each chtObj In ActiveSheet.ChartObjects
' Debug.Print chtObj.Name, chtObj.Parent.Name
Set clsEventCharts (chtnum).EvtChart = chtObj.Chart
chtnum = chtnum + 1
Next ' chtObj
End If
End Sub
Sub Reset_All_Charts()
' Disable events for all charts previously enabled to gether
Dim chtnum As Integer
On Error Resume Next
Set clsEventChart.EvtChart = Nothing
For chtnum = 1 To UBound (clsEventCharts)
Set clsEventCharts(chtnum).EvtChart = Nothing
Next ' chtnum
End Sub
3. 在thisWorkbook中加入下面事件以触发内嵌事件。
Private Sub Workbook_SheetActivate (ByVal Sh As Object)
Set_All_Charts
End Sub
Private Sub Workbook_SheetDeactivate (ByVal Sh As Object)
Reset_All_Charts
End Sub
这样,该Excel中的所有Chart对象都会自动关联上Select事件,并且当事件触发时显示相应的提示信息。
为Chart对象添加内嵌事件时存在四种不同的情况:
- 为同一Worksheet中的所有Chart对象添加内嵌事件,在Worksheet对象所在的code中添加下面两个事件:
Option Explicit
Private Sub Worksheet_Activate()
Set_All_Charts
End Sub
Private Sub Worksheet_Deactivate()
Reset_All_Charts
End Sub - 为同一Chart标签中的所有Chart对象添加内嵌事件,在Chart标签所在的对象的code中添加下面两个事件:
Option Explicit
Private Sub Chart_Activate()
Set_All_Charts
End Sub
Private Sub Chart_Deactivate()
Reset_All_Charts
End Sub - 为同一工作簿中的所有Chart对象添加内嵌事件,在当前工作簿的thisWorkbook对象的code中添加下面两个事件。
Option Explicit
Private Sub Workbook_SheetActivate (ByVal Sh As Object)
Set_All_Charts
End Sub
Private Sub Workbook_SheetDeactivate (ByVal Sh As Object)
Reset_All_Charts
End Sub - 为当前内存中所有的Workbook中的所有Chart对象添加内嵌事件。这个稍微有些麻烦,可按照下面的步骤添加代码:
Public WithEvents EventApp As Excel.Application
Private Sub EventApp_SheetActivate (ByVal Sh As Object)
Set_All_Charts
End Sub
Private Sub EventApp_SheetDeactivate (ByVal Sh As Object)
Reset_All_Charts
End Sub
Private Sub EventApp_WorkbookActivate (ByVal Wb As Workbook)
Set_All_Charts
End Sub
Private Sub EventApp_WorkbookDeactivate (ByVal Wb As Workbook)
Reset_All_Charts
End Sub
Sub InitializeAppEvents ()
Set clsAppEvent.EventApp = Application
Set_All_Charts
End Sub
Sub TerminateAppEvents ()
Set clsAppEvent.EventApp = Nothing
Reset_All_Charts
End Sub
InitializeAppEvents
End Sub
Private Sub Workbook_BeforeClose (Cancel As Boolean)
TerminateAppEvents
End Sub
InitializeAppEvents
End Sub
Private Sub Workbook_AddinUninstall()
TerminateAppEvents
End Sub
当新的工作簿被打开或外接程序被加载时,CAppEvent类被实例化并将相应的事件附加到所有的Chart对象中;当工作簿被关闭或外接程序别卸载时,应用程序被终止,图表事件便不再被触发。
实际应用中存在很多意想不到的情况导致我们所设置的内嵌事件处理程序不能成功地将相应的事件附加到Chart对象上,甚至还会引发Excel的异常,这很常见!此时你应该考虑InitializeAppEvents过程执行的时机,从而将该过程的执行代码移到其它的地方,这要视具体情况来定。
Excel的图表事件在实际应用中还是非常有用的,它可以扩展用户对Excel图表的使用,从而使操作更加简便。另一方面,我们可以借助于Excel图表对象的事件,通过编写少量的代码来使我们的应用程序完成更加强大的功能。例如在图表中依据鼠标所点的两个点来生成一个曲线,将该曲线作为图表中series的一部分;或者通过鼠标的滚轮来放大或缩小图表的现实区域等。读者如果感兴趣,可以试着自己定义不同的图表事件处理程序来实现更加丰富的功能。