繁体   English   中英

RibbonUI的PowerPoint加载项丢失

[英]PowerPoint Add-In Loss of RibbonUI

我一直在努力找出分布在大约40个最终用户中的PPT加载项中的错误原因。

问题:功能区状态丢失/功能区对象丢失。

对于某些用户,最终Rib对象变为Nothing

用户向我保证,它们不会出现任何运行时错误或脚本错误(来自我们也通过此加载项调用的COM对象)。 如果用户单击End ,则未处理的错误预期会导致状态丢失。

没有用户能够可靠地重现导致观察到的故障的方案。 这就是很难排除故障的原因。 我希望不要有明显的遗失或未曾预料到的东西。

我目前如何处理损失或RibbonUI

为了解决这个问题,我在三个地方存储了指向功能区的对象指针,这对我来说似乎有点过头了,但显然仍然不够:

  • 称为A类对象cbRibbon有一个属性.RibbonUI被分配; 在功能区的onLoad回调过程中, Set cbRibbon.RibbonUI = Rib 因此,我们有了对象本身的byRef副本。 如果功能区什么都没有,理论上我可以Set rib = cbRibbon.RibbonUI ,除非cbRibbon对象也不在范围内,否则此方法有效。
  • cbRibbon对象具有分配的属性.PointercbRibbon.Pointer = ObjPtr(Rib)
  • 一个名为“ RibbonPointer”的CustomDocumentProperty也用于存储对对象指针的引用。 注意:这甚至在状态丢失后仍然存在

因此,您可以看到我已经对此进行了一些思考,试图以一种可能将其存储在Excel中的隐藏工作表/范围中的方式复制存储该指针的方式。

附加信息

从健壮的客户端日志记录中可以看到,此错误通常(但并非总是)在以下过程中发生,该过程用于刷新/使功能区及其控件无效。

每当我需要动态刷新功能区或其部分控件时,都会调用此过程:

Call RefreshRibbon(id)

错误似乎(有时,我对此不太强调:该错误无法按需复制)发生在完全刷新期间,这称为:

Call RefreshRibbon("")

这是执行无效的过程:

Sub RefreshRibbon(id As String)

    If Rib Is Nothing Then
        If RibbonError(id) Then GoTo ErrorExit
    End If

    Select Case id
        Case vbNullString, "", "RibbonUI"
            Call Logger.LogEvent("RefreshRibbon: Rib.Invalidate", Array("RibbonUI", _
                                            "Ribbon:" & CStr(Not Rib Is Nothing), _
                                            "Pointer:" & ObjPtr(Rib)))
            Rib.Invalidate

        Case Else
            Call Logger.LogEvent("RefreshRibbon: Rib.InvalidateControl", Array(id, _
                                            "Ribbon:" & CStr(Not Rib Is Nothing), _
                                            "Pointer:" & ObjPtr(Rib)))
            Rib.InvalidateControl id
    End Select

    Exit Sub

ErrorExit:

End Sub

如您所见,我在此过程中要做的第一件事就是测试Rib对象的Nothing -ness。 如果此结果为True ,则RibbonUI对象以某种方式丢失。

然后,错误函数尝试重新实例化功能区: 首先cbRibbon.RibbonUI ,然后从cbRibbon.Pointer ,如果两个都失败,则从CustomDocumentProperties("RibbonPointer")值开始。 如果这些都不成功,则我们将显示致命错误,并提示用户关闭PowerPoint应用程序。 如果其中任何一个成功,则将以编程方式重新加载功能区,并且一切都会继续进行。

这是该过程的代码。 请注意,它调用了其他一些我没有包含代码的过程。 这些是助手功能或记录器功能。 .GetPointer方法实际上调用WinAPI CopyMemory函数以从其指针值重新加载对象。

Function RibbonError(id As String) As Boolean
'Checks for state loss of the ribbon
Dim ret As Boolean

If id = vbNullString Then id = "RibbonUI"

Call Logger.LogEvent("RibbonError", Array("Checking for Error with Ribbon" & vbCrLf & _
                                            "id: " & id, _
                                            "Pointer: " & ObjPtr(Rib), _
                                            "cbPointer: " & cbRibbon.Pointer))

If Not Rib Is Nothing Then
    GoTo EarlyExit
End If

On Error Resume Next

    'Attempt to restore from class object:
    Set Rib = cbRibbon.ribbonUI

    'Attempt to restore from Pointer reference if that fails:
    If Rib Is Nothing Then
        'Call Logger.LogEvent("Attempt to Restore from cbRibbon", Array(cbRibbon.Pointer))
        If Not CLng(cbRibbon.Pointer) = 0 Then
            Set Rib = cbRibbon.GetRibbon(cbRibbon.Pointer)
        End If
    End If

    'Attempt to restore from CDP

    If Rib Is Nothing Then
        'Call Logger.LogEvent("Attempt to Restore from CDP", Array(MyDoc.CustomDocumentProperties("RibbonPointer")))
        If HasCustomProperty("RibbonPointer") Then
            cbRibbon.Pointer = CLng(MyDoc.CustomDocumentProperties("RibbonPointer"))
            Set Rib = cbRibbon.GetRibbon(cbRibbon.Pointer)

        End If
    End If

On Error GoTo 0

If Rib Is Nothing Then
    Debug.Print "Pointer value was: " & cbRibbon.Pointer
    'Since we can't restore from an invalid pointer, erase this in the CDP
    ' a value of "0" will set Rib = Nothing, anything else will crash the appliation
    Call SetCustomProperty("RibbonPointer", "0")
Else
    'Reload the restored ribbon:
    Call RibbonOnLoad(Rib)

    Call SetCustomProperty("RibbonPointer", ObjPtr(Rib))

    cbRibbon.Pointer = ObjPtr(Rib)
End If

'Make sure the ribbon exists or was able to be restored
ret = (Rib Is Nothing)

If ret Then
    'Inform the user
    MsgBox "A fatal error has been encountered. Please save & restart the presentation", vbCritical, Application.Name
    'Log the event to file
    Call Logger.LogEvent("RibbonError", Array("FATAL ERROR"))

    Call ReleaseTrap

End If

EarlyExit:

    RibbonError = ret

End Function

所有这些在理论上都可以很好地完成,实际上,我可以直截了当地杀死运行时(通过调用End语句或其他方式),并且这些过程将按预期方式重置功能区。

在此处输入图片说明

那么,我想念什么?

好的,我忘记了这一点……虽然我仍然没有查明错误,但我有一些想法,即用户根本不会报告未处理的运行时错误,而在PowerPoint提示时,他们会打“ End”。

我有理由确定这是原因,并且我已经确认在很多情况下,这种错误是在“崩溃”之前发生的,因此,我将进行更新以尽快解决此问题。

否则,这就是我最终使用了几个月的成功方法。

创建一个将功能区的Pointer值写入用户计算机的过程。 我不想这样做,但最终不得不:

Sub LogRibbon(pointer As Long)
    'Writes the ribbon pointer to a text file
    Dim filename As String
    Dim FF As Integer

    filename = "C:\users\" & Environ("username") & "\AppData\Roaming\Microsoft\AddIns\pointer.txt"

    FF = FreeFile
    Open filename For Output As FF
    Print #FF, pointer
    Close FF

End Sub

在功能区的_OnLoad事件处理程序中,我调用LogRibbon过程:

Public Rib As IRibbonUI
Public cbRibbon As New cRibbonProperties
Sub RibbonOnLoad(ribbon As IRibbonUI)
'Callback for customUI.onLoad


    Set Rib = ribbon

    Call LogRibbon(ObjPtr(Rib))

    'Store the properties so we can easily access them later
    cbRibbon.ribbonUI = Rib


End Sub

我创建了一个类对象来存储有关功能区的一些信息,以避免重复和缓慢地调用外部API,但是为此,您可以创建一个仅存储指针值的类。 上面在cbRibbon.ribbonUI = Rib引用了这cbRibbon.ribbonUI = Rib 此类的GetRibbon方法使用WinAPI中的CopyMemory函数从其指针还原对象。

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias _
    "RtlMoveMemory" (destination As Any, source As Any, _
    ByVal length As Long)


'example ported from Excel:
'http://www.excelguru.ca/blog/2006/11/29/modifying-the-ribbon-part-6/
Private pControls As Object
Private pRibbonUI As IRibbonUI
Private pPointer As Long

Sub Class_Initialize()
    'Elsewhere I add some controls to this dictionary so taht I can invoke their event procedures programmatically:
    Set pControls = CreateObject("Scripting.Dictionary")

    Set pRibbonUI = Rib

    Call SaveRibbonPointer(Rib)

    pConnected = False
End Sub


'#############################################################
'hold a reference to the ribbon itself
    Public Property Let ribbonUI(iRib As IRibbonUI)
        'Set RibbonUI to property for later use
        Set pRibbonUI = iRib

    End Property

    Public Property Get ribbonUI() As IRibbonUI
        'Retrieve RibbonUI from property for use
        Set ribbonUI = pRibbonUI
    End Property

'http://www.mrexcel.com/forum/excel-questions/518629-how-preserve-regain-id-my-custom-ribbon-ui.html
Public Sub SaveRibbonPointer(ribbon As IRibbonUI)
    Dim lngRibPtr As Long
    ' Store the custom ribbon UI Id in a static variable.
    ' This is done once during load of UI.

    lngRibPtr = ObjPtr(ribbon)

    cbRibbon.pointer = lngRibPtr

End Sub
Function GetRibbon(lngRibPtr As Long) As Object
    'Uses CopyMemory function to re-load a ribbon that
    ' has been inadvertently lost due to run-time error/etc.
    Dim filename As String
    Dim ret As Long
    Dim objRibbon As Object

    filename = "C:\users\" & Environ("username") & "\AppData\Roaming\Microsoft\AddIns\pointer.txt"

    On Error Resume Next
    With CreateObject("Scripting.FileSystemObject").GetFile(filename)
        ret = .OpenAsTextStream.ReadLine
    End With
    On Error GoTo 0

    If lngRibPtr = 0 Then
        lngRibPtr = ret
    End If

    CopyMemory objRibbon, lngRibPtr, 4
    Set GetRibbon = objRibbon
    ' clean up invalid object
    CopyMemory objRibbon, 0&, 4
    Set objRibbon = Nothing

End Function


'##############################################################
' Store the pointer reference to the RibbonUI
    Public Property Let pointer(p As Long)
        pPointer = p
    End Property
    Public Property Get pointer() As Long
        pointer = pPointer
    End Property

'#############################################################
'Dictionary of control properties for Dropdowns/ComboBox
    Public Property Let properties(p As Object)
        Set pProperties = p
    End Property
    Public Property Get properties() As Object
        Set properties = pProperties
    End Property

然后,我有一个检查色带丢失的函数,并从指针值中恢复。 这实际上调用了OnLoad过程,因为我们有一个表示Ribbon对象的对象变量(或类对象属性),所以我们可以这样做。

Function RibbonError(id As String) As Boolean
'Checks for state loss of the ribbon
Dim ret As Boolean
Dim ptr As Long
Dim src As String

On Error Resume Next

If Not Rib Is Nothing Then
    GoTo EarlyExit
End If

If Rib is Nothing then
    ptr = GetPointerFile
    cbRibbon.pointer = ptr
    Set Rib = cbRibbon.GetRibbon(ptr)
End If
On Error GoTo 0

'make sure the ribbon has been restored or exists:
ret = (Rib is Nothing)

If Not ret then
    'Reload the restored ribbon by invoking the OnLoad procedure
    ' we can only do this because we have a handle on the Ribbon object now
    Call RibbonOnLoad(Rib)
    cbRibbon.pointer = ObjPtr(Rib) 'store the new pointer
Else
    MsgBox "A fatal error has been encountered.", vbCritical
End If

EarlyExit:
RibbonError = ret
End Function

每当您要通过InvalidateInvalidateControl方法刷新功能区时,请调用RibbonError函数。

上面的代码可能不会100%编译-我不得不对其进行修改并删掉一些东西,所以如果您在尝试实现它时遇到任何问题,请告诉我!

找到了真正的解决方案: 信誉

    Public Declare Sub CopyMemory Lib "kernel32" Alias _
    "RtlMoveMemory" (destination As Any, source As Any, _
    ByVal length As Long)

Public Sub ribbon L o a ded(ribbon As IRibbonUI)
   ' Store pointer to IRibbonUI
   Dim lngRibPtr As Long
' Store the custom ribbon UI Id in a static variable.
' This is done once during load of UI. I.e. during workbook open.
    Set guiRibbon = ribbon
    lngRibPtr = ObjPtr(ribbon)
    ' Write pointer to worksheet for safe keeping
    Tabelle2.Range("A1").Value = lngRibPtr
End Sub
Function GetRibbon(lngRibPtr as Long) As Object
   Dim objRibbon As Object
   CopyMemory objRibbon, lngRibPtr, 4
   Set GetRibbon = objRibbon
   ' clean up invalid object
   CopyMemory objRibbon, 0&, 4
   Set objRibbon = Nothing
End Function

然后

    Public Sub DoButton(ByVal control As IRibbonControl)
' The onAction callback for btn1 and btn2

    ' Toggle state
    Toggle12 = Not Toggle12

    ' Invalidate the ribbon UI so that the enabled-states get reloaded
    If Not (guiRibbon Is Nothing) Then
        ' Invalidate will force the UI to reload and thereby ask for their enabled-states
        guiRibbon.Invalidate 'Control ("tabCustom") InvalidateControl does not work reliably
    Else
      Set guiRibbon = GetRibbon(CLng(Tabelle2.Range("A1").Value))
      guiRibbon.Invalidate
        ' The static guiRibbon-variable was meanwhile lost
'        MsgBox "Due to a design flaw in the architecture of the MS ribbon UI you have to close " & _
'            "and reopen this workbook." & vbNewLine & vbNewLine & _
'            "Very sorry about that.", vbExclamation + vbOKOnly
      MsgBox "Hopefully this is sorted now?"
        ' Note: In the help we can find
        ' guiRibbon.Refresh
        ' but unfortunately this is not implemented.
        ' It is exactly what we should have instead of that brute force reload mechanism.
    End If

End Sub

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM