【问题标题】:VBA - How to temporarily disable the Worksheet.Activate Event?VBA - 如何暂时禁用 Worksheet.Activate 事件?
【发布时间】:2018-09-28 10:52:07
【问题描述】:

我正在编写一个使用 SAP 的 BusinessObjects Analysis 插件的 Excel 工作簿。每当工作簿打开时,都会触发变量选择提示,并允许用户输入值,然后用于从后端选择数据并在 Excel 中以表格格式显示(Excel 中 SAP BW 查询的表示形式)。我面临的问题是以下... 每当提示选择完成并且正在处理选择时,如果您单击工作表选项卡发送垃圾邮件,则会触发Worksheet.Activate 事件。问题是Worksheet.Activate 处理程序试图使本身尚未设置的功能区对象无效。我想防止这种情况发生,并且我希望仅在已设置功能区后才使功能区无效。问题是在更改工作表时必须使功能区无效,以便它加载功能区中每个工作表唯一的特定按钮。在激活工作表时不失效不是一种选择。最好在我们的自定义功能区加载之前消除更改工作表的可能性。

这是我尝试过但没有成功的方法:

  1. 我尝试在提示出现后立即禁用与Application.Interactive = False 的交互模式,并在执行AOCust_OnLoad(ribbon As IRibbonUI) 功能区onLoad 回调并设置功能区时重新启用它。

  2. 我尝试在提示出现后立即使用 Application.EnableEvents = False 禁用所有事件,并在执行功能区 onLoad 回调并设置功能区时重新启用它们。

  3. 我尝试将以下内容添加到 Worksheet.Activate 回调中,但它永远不会退出可能预期的无限循环,因为功能区 onLoad 不是系统事件,因此永远不会触发。

    while ribbon is Nothing
      'waiting for ribbon onload to fire and set the value
      DoEvents
    wend
    

第二种情况令人惊讶的是,当事件被禁用时,Worksheet.Activate 事件是如何被触发的。

您知道如何解决此问题,以便用户在设置功能区之前无法更改工作表吗?

如果您希望我在此描述中添加代码 sn-ps,请告诉我。

谢谢

编辑 - 添加代码

在 ThisWorkbook 中的 Microsoft Excel 对象中

Private Sub Workbook_SheetActivate(ByVal Sh As Object)
  If EnableEvents = True Then
      Debug.Print "invalidate"
      Call AOCust_Callbacks.AOCust_InvalidateRibbon
  End If
End Sub

在“Custom_Ribbon”模块中

'Callback for Selections data onAction
 Sub PROMPT(control As IRibbonControl)
   EnableEvents = False
   'more prompt-related code
 End Sub

在模块“AOCust_Callbacks”中

Public EnableEvents As Boolean

'Callback for Ribbon OnLoad
Sub AOCust_OnLoad(ribbon As IRibbonUI)
  Debug.Print "setting the ribbon"
  'Start Highlighting for Workbook
  StartHighlighting
  Set AOCustRibbon = ribbon
  Debug.Print "the ribbon is now set"
  EnableEvents = True
End Sub

当我在提示选择完成并且选择仍在处理时立即单击工作表选项卡时,我不再遇到由于 if 语句而尝试使功能区无效时未设置变量的问题,但问题是没有执行加载功能区功能。

【问题讨论】:

  • 我没有在您的代码中看到您设置Application.EnableEvents = False 的位置。拥有一个全局变量EnableEvents 不是一回事。
  • 是的,我知道。在发布 sn-ps 之前,我已经删除了该代码。

标签: vba excel business-objects


【解决方案1】:

这可能不是最好的解决方案,但可以解决。

你可以使用一个全局变量来处理它。

在模块中放置如下内容

Public EventsEnabled As Boolean
Public Sub ModuleWithDisabledEvents()
    EventsEnabled = False
    Debug.Print EventsEnabled
    EventsEnabled = True
    Debug.Print EventsEnabled
End Sub

然后在您的工作表激活中将您的 Activate 代码包装在一个查看此变量的 if 块中

Private Sub Worksheet_Activate()
    If Not EventsEnabled Then
        ' Do activate code here
    End If
End Sub

通过更改 EventsEnabled 变量,即使启用了事件,您的代码也可以忽略您的 Activate 过程。


更新: 你用丝带做什么?这就是我过去实现自定义功能区的方式并且没有遇到您的问题:

在我的 XML 文件中,我有以下内容:

<customUI onLoad="RibbonOnLoad" xmlns="http://schemas.microsoft.com/office/2009/07/customui">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="customTab" label="Custom Tab">
                <group id="customGroup" label="Custom Group">
                    <button id="customButton" label="Custom Button" imageMso="HappyFace" size="large" onAction="Callback" getVisible="GetVisible" />
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>

然后在我的文件中,我有一个名为“功能区”的模块,其中包含:

Option Explicit
Option Private Module
Dim rib As IRibbonUI

Private Declare Function ShellExecute _
                         Lib "shell32.dll" Alias "ShellExecuteA" ( _
                         ByVal hWnd As Long, _
                         ByVal Operation As String, _
                         ByVal Filename As String, _
                         Optional ByVal Parameters As String, _
                         Optional ByVal Directory As String, _
                         Optional ByVal WindowStyle As Long = vbMinimizedFocus _
                         ) As Long

#If VBA7 Then
    Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
#Else
    Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
#End If
#If VBA7 Then
Function GetRibbon(ByVal lRibbonPointer As LongPtr) As Object
#Else
Function GetRibbon(ByVal lRibbonPointer As Long) As Object
#End If
Dim objRibbon As Object
CopyMemory objRibbon, lRibbonPointer, LenB(lRibbonPointer)
Set GetRibbon = objRibbon
Set objRibbon = Nothing
End Function
Public Sub RibbonOnLoad(ribbon As IRibbonUI)
    Set rib = ribbon
    Debug.Print "ribbon:-", ObjPtr(ribbon)
    Sheet2.Cells(1, 1).Value = ObjPtr(ribbon)
End Sub
Public Sub RefreshRibbon()
    On Error GoTo RibbonError
    If rib Is Nothing Then
        Set rib = GetRibbon(Sheet2.Cells(1, 1).Value)
        If rib Is Nothing Then GoTo RibbonError
    Else
        rib.Invalidate
        Exit Sub
    End If
    Exit Sub
RibbonError:
    Debug.Print "There is an issue with the menu bar. Please restart the tool"
End Sub

Public Sub GetVisible(control As IRibbonControl, ByRef visible)
    visible = True
End Sub

加载功能区时,它将其内存值设置为 Sheet2,然后在刷新运行的功能区 RefreshRibbon 时使用该值重新加载

【讨论】:

  • 这是我最初想到的一个很好的解决方法!我希望通过设置 VBA 事件属性来实现更强大的实现,但这可以解决问题。如果没有其他更强大的解决方案的答案,我会将其标记为正确答案。谢谢
  • 我以这样的方式实现了这一点,即一旦出现提示,我将 EventsEnabled 设置为 False,并在功能区 onLoad 中设置功能区后将其更改为 True。现在的问题是,当我点击垃圾邮件时,功能区 onLoad 根本没有执行。
  • 您可以发布您的任何代码吗?自定义功能区的主要问题之一是它的文档记录非常差。
  • 请查看我编辑的帖子 - 我添加了一些代码。我希望它有所帮助。顺便说一句,感谢您这么快回复。
  • 你能看看我的更新。我认为从您的代码来看,问题是当您使其无效时,您会丢失它的内存分配。调试功能区很困难,因为调试器并不能真正使用它
【解决方案2】:

通过在选择提示加载或第一次加载之前使用 Application.interactive = False 禁用鼠标事件并在功能区加载时重新启用它们来解决问题。问题是 SAP 决定对名为 onBeforeFirstPromptsDisplay 的第一个提示使用不同的回调。因此,我对工作簿中提示回调的更改从未真正触发,因为该回调仅在您触发提示时执行自己通过工作簿。实施onBeforeFirstPromptsDisplay 后一切正常。 感谢您的意见!

【讨论】:

    猜你喜欢
    • 2015-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    • 1970-01-01
    相关资源
    最近更新 更多