分享

细品RibbonX(15):回调(CallBacks)

 yuxinrong 2009-11-10
回调,即使自定义界面能够工作的代码。如果没有回调,那么自定义界面可能看起来漂亮,但只是一个漂亮的功能区。当然,除内置控件不需要自定义回调外。

下面的内容介绍如何使用回调来提供自定义控件所需的功能。在编写XML代码时,需要指定回调,然后在Excel中编写VBA代码来匹配和处理回调。

回调概要

回调是自定义的用户界面使用的子过程和函数,使自定义的用户界面能够工作。回调简单地表现为所提供的指令的动向。例如,当设置某按钮的onAction属性并装载用户界面时,一旦单击该按钮,就产生回调,即在属性中指定的操作。

如果没有找到指定的操作,那么回调将失败,因为代码中发生了例外,即在属性中指定的回调在VBA中不存在,因此失败了。

自定义用户界面一装载就会调用VBA工程,搜寻指定的函数或过程。如果找到,则将其值传回到用户界面。如果没有找到指定的函数或过程,那么将产生错误的结果。

有两种主要的方式来创建回调:

n         直接在VBE的标准模块中输入过程或函数

n         使用工具生成过程,例如Office 2007 CustomUI Editor

使用诸如Office 2007 CustomUI Editor这类工具的主要优势是能够扫视XML代码并返回回调签名,也称作subprocedure stubXML代码中每个属性都有一个需要处理的回调。接受回调作为值的属性包括onActiongetVisiblegetImage

除了节省时间外,使用CustomUI Editor还能避免由于手工编写回调签名而导致的拼写错误。上述每个属性都会产生一个不同的回调签名,必须按顺序使用代码处理,使用户界面有合适的功能。

此外,当工程装载时需要访问某些回调并运行,这意味着如果回调处理(即响应回调的VBA代码)在该工程中不存在,将产生错误消息。当然,下面的内容也会介绍如何缓解这类错误,甚至如何避免错误。

为动态回调建立文件

为了使自定义控件工作,文件必须启用宏,否则不能够添加或运行VBA代码。

1、捕获IRibbonUI对象

IRibbonUI对象引用功能区用户界面,用于控制事物如何响应。

VBA中使用的关键之一是使整个Ribbon对象无效(因而能够改变功能区的某项特征)或者使功能区中指定的控件无效(因而能够改变该控件的某项特征)。

1)调整XML以包括onLoad

为了使用IRibbonUI对象,需要在VBA中对其设置。首先,需要对UIonLoad属性指定值,这能通过为onLoad属性指定回调来实现:

<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui onLoad=rxIRibbonUI_onLoad>

因为onLoad属性的值是一个回调,所以需要在VBA代码中对其进行处理。接着,能够在工程中使用IRibbonUI对象。

2)创建VBA代码处理onLoad事件

可能会注意到,前面将onLoad称作为属性,但现在将其称为事件。这是因为在XML文件中,能为许多属性定义值,例如onActiongetLabelonLoad。一旦为某属性赋值,如果能以某种方式触发,将导致事件发生。这就是属性和事件的区别。

因为IRibbonUI对象用于整个应用程序,所以需要在标准模块的全局声明部分对其声明,如下面的代码所示。

全局的ribbon对象

Dim grxIRibbonUI As IRibbonUI

这里使用标准的格式grxIRibbonUI引用IRibbonUI对象,接着添加回调来设置该对象:

Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI)

    Set grxIRibbonUI = ribbon

End Sub

设置为全局对象之后,该对象可用于整个工程。然而,Ribbon对象对于修改是非常敏感的。这意味着任何时候修改代码,该对象实例将丢失并且任何需要Ribbon对象的操作都将失败。因此,无论何时作出修改,都需要保存、关闭并重新打开工程。

生成第一个回调

知道需要获得回调签名后,还需要知道如何使用回调签名。例如,对于切换按钮(toggleButton)有下列签名:

Sub rxtgl_click(control as IRibbonControl,pressed as boolean)

一个普通按钮的签名如下:

Sub rxbtn_click(control as IRibbonControl)

1、从头开始编写回调

如果知道了回调签名,则不必使用标准的形式声明参数。例如,有一个使用下列XML的切换按钮:

<toggleButton

id=”rxtgl”

label=”Toggle”

size=”large”

onAction=”rxtgl_click”

imageMso=”FormatPainter”/>

编写rxtgl_click回调如下:

Sub rxtgl_click(rxctl As IRibbonControl,toggled As Boolean)

If toggled Then

    MsgBox “我已切换 我的ID “ & rxctl.ID,vbInformation

End If

End Sub

注意到回调的参数名与上面介绍的标准的参数名不一样,但是单击切换按钮时仍触发该回调,像平常一样传递参数,因此签名中的修改不会导致过程失败,只要使用了正确类型的正确参数。

也能修改onLoad回调签名来满足需要,如下所示:

Sub rxIRibbonUI_onLoad(MyRibbon As IRibbonUI)

Set grxIRibbonUI=MyRibbon

End Sub

然而,如果使用标准的签名及其参数名称,那么其他人将更容易理解代码。下面列出了一些回调签名。

属性onLoad,回调签名(ribbon as IRibbonUI)

属性getLabel,getPressed,getEnabled,getImage,getScreentip,getVisible,等,回调签名(control as IRibbonControl,ByRef returnedVal)

属性onAction(切换按钮),回调签名(control as IRibbonControl,pressed as Boolean)

属性onAction(按钮),回调签名(control as IRibbonControl)

2、使用Office CustomUI Editor生成回调

一种很容易生成回调的方法是使用CustomUI Editor。这个工具将所有的返回回调的属性搜寻在一起,然后生成需要的回调。因此,无须追踪XML中所有的回调。对于大段的XML代码来说,这是无价的。

CustomUI Editor中(以Excel为例),可以使用下列步骤自动生成必需的回调:

步骤1 使用CustomUI Editor打开包含XML代码的Excel

步骤2 单击“Generate Callbacks”按钮。

步骤3 出现一个新的Callbacks选项卡。复制产生的代码并粘贴到VBA工程中。

注意,确保验证代码。

3、理解文件打开时事件的顺序

在工程中添加了回调后,该文档打开时将调用某些过程,具体调用的过程取决于工程打开时定制是否获得了焦点。注意,某些过程仅当包含定制的选项卡获取焦点时才调用,而其它过程当鼠标移动到控件之上时调用。

理解过程调用的顺序是复杂的,因为调用顺序受许多可变因素影响。为了帮助预测典型的事件顺序,下表列出了功能区选项卡的事件及相应的顺序。

表:当打开工程后选项卡获取焦点时的事件顺序

事件

选项卡获取焦点

选项卡有焦点

按下ALT

鼠标在其上方

onLoad

最顶级的事件,当装载UI时发生

getVisible

1

1

N/A

N/A

getLabel

2

3

N/A

N/A

getImage

3

4

N/A

N/A

getEnabled

4

2

N/A

N/A

getKeytip

N/A

N/A

1

N/A

getScreentip

N/A

N/A

N/A

1

getSupertip

N/A

N/A

N/A

2

上表仅列出了可以使用的通用属性的一些示例,然而顺序可能受其它引入的属性的影响,例如getDescriptiongetTitle,等等。虽然如此,打算在性能方面以最好的方式解决用户界面问题时,上表仍然可以作为一个通用的向导。

4、能够有具有相同名称但不同签名的两个回调吗?

VBA不允许在相同工程中使用相同名称不同签名的两个回调。然而,如果回调在不同的工程中,则相同的回调名称能够有不同的签名。因此,如果同时打开一个以上的Excel文档,那么可能发现回调返回不可预料的结果。因为如果多个操作(签名)与一个回调名称相联系,那么将运行当前文档的回调。

假设打开一个安装了两个加载项的Excel工作簿,三个项目都有一个带有称为rxbtnnsQaShared的控件的用户界面。当单击用户界面中该控件时,所期望的是添加一个新工作簿,然而将出现一个消息框。检查后,发现为所有三个按钮都使用了相同的回调。此时,可以使用断点调试,看看单击操作调用了哪个回调。

上述表明,只要在不同工程中,虽然允许使用带有不同签名相同名称的回调,但这不是一个好主意。

调用位于不同工作簿中的过程

与上面所讲的情况相似,如果XML代码运行位于不同工作簿中的VBA,也会碰到类似的问题。

假设有两个工作簿:Book.xlsmBook2.xlsm,希望在第一个工作簿中添加一个按钮,运行第二个工作簿中的过程。使用下面的XML代码创建用户界面:

<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui>

    <ribbon>

        <tabs>

            <tab id=rxtabDemo

                  label=My Custom Tab

                  insertBeforeMso=TabHome>

                  <group id=rxgrpDemo

                         label=My Demo Group>

                         <button id=rxbtnDemo

                                 label=My Demo Button

                                 size=large

                                 onAction=Book2.xlsm!rxbtnDemo_click

                                 imageMso=FileStartWorkflow/>

                  </group>

            </tab>

        </tabs>

    </ribbon>

</customUI>

Book2.xlsm的标准模块中,放置下列代码:

Sub rxbtnDemo_click(control As IRibbonControl)

    MsgBox “您调用了位于:” & ThisWorkbook.Name & “中的过程.”

End Sub

注意,必须要打开工作簿才能运行。如果希望onAction属性指向已装载的加载项而不是工作簿,那么简单地使用[add-in name].xlamonAction代码名称前加上前缀,例如:

onAction=”myAddIn.xlam!rxbtnDemo_click”

然后,在加载项的标准模块中放置回调VBA代码:

Book3.xlam!rxbtnDemo_click

此时,必须加上前缀xlam!。注意,如果在用户界面的onAction属性下指定的过程中xlsmxlam有相同的名称,那么该事件会运行包含在活动工作簿中的代码。你可能已注意到,在前面的介绍的内容中我们必须移除xlsm!,但使用加载项时必须有xlam!

组织回调

随着功能区自定义代码技术的不断熟练,你将会采取不同的方式来组织回调,可以有单个的回调处理,或者有全局的回调处理,一次处理多个控件。

1、单个的回调处理

编写XML代码时,可以指定返回回调的多种属性,例如onActiongetLabelgetVisiblegetEnabled,等等。这些属性的每一个都必须处理。下面以Excel为例,通过生成组中的3个按钮来演示这个过程。

<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui>

    <ribbon>

       <tabs>

            <tab id=rxtabDemo

                 label=My Custom Tab

                 insertBeforeMso=TabHome>

                 <group id=rxgrpDemo

                        label=My Demo Group>

                     <button id=rxbtnPaste

                             label=My Paste Button

                             size=normal

                             onAction=rxbtnPaste_click

                             imageMso=Paste

                             tag=Custom Paste Button/>

                     <button id=rxbtnCopy

                             label=My Copy Button

                             size=normal

                             onAction=rxbtnCopy_click

                             imageMso=Copy

                             tag=Custom Copy Button/>

                     <button id=rxbtnCut

                             label=My Cut Button

                             size=normal

                             onAction=rxbtnCut_click

                             imageMso=Cut

                             tag=Custom Cut Button/>

                 </group>

            </tab>

        </tabs>

    </ribbon>

</customUI>

每个按钮都有其onAction属性,因此如果企图对按钮添加功能,则必须处理每个属性:

Sub rxbtnPaste_click(control As IRibbonControl)

    MsgBox “您单击了” & control.Tag, vbInformation

End Sub

Sub rxbtnCopy_click(control As IRibbonControl)

    MsgBox “您单击了” & control.Tag, vbInformation

End Sub

Sub rxbtnCut_click(control As IRibbonControl)

    MsgBox “您单击了” & control.Tag, vbInformation

End Sub

单独处理每个属性非常繁锁且难以维护,而一种可供选择的方法是一次处理多个属性。

2、使用全局回调处理

1)全局回调处理能够用于一次处理几个控件。如果以标准化方式命名全局处理,那么效果将更好,因为可以立即从单个控件中区分全局控件。

2)使用全局回调的原因是因为某些指令可能重复,甚至当指令不重复时,对某种控件为某项操作共享相同的属性仍有好处,例如onAction属性。此外,如果操作相同,那么过程能组合在单个的回调处理中。

3)能利用这种优势减少要处理的回调数,即通过通用的回调处理控件本身。

4)例如,下列对象:tabgroupbutton,都共享一个通用的属性getLabel。如前所述,当需要对每个控件动态应用值时,不需要在XML代码中为每个控件编写getLabel回调。相反,可以在VBA过程中通过共享任务在单个处理内将它们放在一起,而且因为是回调,所以不需要遍历XML中指定的控件,将自动遍历控件直至所有控件有所需的值。

5)仍以上节中的示例,在前面的XML代码中修改每个按钮的onAction属性如下:

onAction=”rxshared_click”

将产生单个的回调,用于处理具有onAction属性和共享该签名的任何控件。

接着,在VBA中为每个按钮处理该回调,使用下面的一小段代码:

Sub rxshared_click(control As IRibbonControl)

    MsgBox “您单击了” & control.Tag, vbInformation

End Sub

也能使用Select Case语句分开每个按钮并赋予其特定的代码:

Sub rxshared_click(control As IRibbonControl)

Select Case control.ID

    Case “rxbtnPaste”

      rxbtnPaste按钮的代码

    Case “rxbtnCopy”

      rxbtnCopy按钮的代码

    Case “rxbtnCut”

     rxbtnCut按钮的代码

End Select

End Sub

因此,无需为用户界面中的每个自定义控件处理回调,可以使用全局的(共享的)回调,然后处理VBA过程里的每个控件。这里,使用了Select Case语句,因为该语句简洁且易于理解。也可以使用If…Then…ElseIf…End If语句。

使用户界面组件无效

Ribbon的一个重要方面是使Ribbon无效或指定的控件无效。

某些操作仅当使整个Ribbon无效才能执行,而另一些操作只需通过使指定控件无效来完成。

1、无效能处理什么?为什么需要?

IRibbonUI对象包含两个方法:InvalidateInvalidateControl

表:IRibbonUI对象模型

方法

用途

Invalidate()

为更新而标记整个功能区(顺序标记其中的每个控件)

InvalidateControl(strControlID)

为更新而标记特定的控件,被更新的控件作为字符串被传递到方法的参数中。(strControlID)

理解Invalidate方法的关键是该方法使用户界面中每个控件都无效,这意味着强制调用在用户界面中定义的所有回调。此外,也将导致更新所有控件,而无论其是否有回调。当使功能区无效时,在功能区中作出调用,并且在用户界面中指定的每个过程都将运行。

注意,“刷新”指在打开工程时简单地更新在用户界面中已经装载的控件,而“重新装载”指卸载并重新装载用户界面中的整个项目。

除非确实需要作用整个功能区,更好的选择是在执行期间的指定时刻使单个控件无效,即仅使必需的控件无效。

要使整个功能区或功能区中指定的控件无效,需要设置代表IRibbonUI对象的全局变量,可通过在onLoad属性中指定回调来实现:

<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui onLoad=rxIRibbonUI_onLoad>

</customUI>

接下来,需要编写VBA代码处理在onLoad属性中指定的回调:

Public grxIRibbonUI As IRibbonUI

Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI)

Set grxIRibbonUI=ribbon

End Sub

注意,代表IRibbonUI对象的变量在标准模块的全局声明区中声明,以便于能被工程的其他部分访问。

2、使整个Ribbon无效

步骤1 创建一个新的Excel文件,并保存为启用宏的工作簿。

步骤2 使用CustomUI Editor,在工作簿文件中添加XML代码:

<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui onLoad=rxIRibbonUI_onLoad>

    <ribbon>

        <tabs>

            <tab id=rxtabDemo

                 label=My Custom Tab

                 insertBeforeMso=TabHome>

                 <group id=rxgrpDemo

                        label=My Demo Group>

                     <button id=rxbtn

                             getLabel=rxshared_getLabel

                             size=normal

                             onAction=rxshared_click

                             imageMso=FillRight/>

                     <button id=rxbtn2

                             getLabel=rxshared_getLabel

                             size=normal

                             onAction=rxshared_click

                             imageMso=FillRight/>

                 </group>

            </tab>

        </tabs>

    </ribbon>

</customUI>

步骤3 保存并验证代码。

步骤4 单击“Generate Callbacks”按钮生成回调。

步骤5 复制回调代码并关闭该文件。

步骤6 Excel中打开该工作簿,添加一个标准模块。(不要担心出现的错误消息)

步骤7 粘贴代码到该模块中,并补充代码。

Public grxIRibbonUI As IRibbonUI

Public glngCount1 As Long

Public glngCount2 As Long

 

Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI)

    Set grxIRibbonUI = ribbon

End Sub

 

Sub rxshared_click(control As IRibbonControl)

    grxIRibbonUI.Invalidate

End Sub

 

Sub rxshared_getLabel(control As IRibbonControl, ByRef returnedVal)

    Select Case control.ID

        Case “rxbtn”

            returnedVal = “功能区无效: ” & glngCount1 & “.”

            glngCount1 = glngCount1 + 1

        Case “rxbtn2″

            returnedVal = “功能区无效: ” & glngCount2 & “.”

            glngCount2 = glngCount2 + 1

    End Select

End Sub

按照代码作用的顺序放置代码,然后保存工作簿,关闭工作簿,接着重新打开该工作簿。

注意,此时单击任一按钮,两个标签均更新,如下图1所示。因为单击按钮时代码使整个功能区无效,所以两个控件都无效,共享的getLabel过程为第一个按钮调用一次,又为第二个按钮所调用。

1:使IRibbonUI对象无效导致其中的所有控件都被验证

3、使单个控件无效

仍然以上面的例子来介绍,现在仅使触发click事件的按钮无效。将共享的click事件和共享的getLabel事件修改如下:

Public grxIRibbonUI As IRibbonUI

Public glngCount1 As Long

Public glngCount2 As Long

 

Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI)

    Set grxIRibbonUI = ribbon

End Sub

 

Sub rxshared_click(control As IRibbonControl)

    Select Case control.ID

        Case “rxbtn”

            glngCount1 = glngCount1 + 1

        Case “rxbtn2″

            glngCount2 = glngCount2 + 1

    End Select

    grxIRibbonUI.InvalidateControl (control.ID)

End Sub

 

Sub rxshared_getLabel(control As IRibbonControl, ByRef returnedVal)

    Select Case control.ID

        Case “rxbtn”

            returnedVal = “功能区无效: ” & glngCount1 & “.”

            glngCount1 = glngCount1 + 1

        Case “rxbtn2″

            returnedVal = “功能区无效: ” & glngCount2 & “.”

            glngCount2 = glngCount2 + 1

    End Select

End Sub

此时,只会使单击的控件无效并统计其数量,如下图2所示。

 

2:仅使单个的控件对象无效

综上,本文主要介绍了自定义功能区的一个重要的内容,即通过回调添加功能。讲述了如何捕获和使用Ribbon对象以及如何通过单独的或共享的回调来组织过程。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章