2014-11-26 3 views
1

약 40 명의 최종 사용자에게 배포되는 PPT 추가 기능에서 오류의 원인을 확인하는 데 어려움을 겪고 있습니다.PowerPoint 추가 기능 RibbonUI의 손실

문제점 : 리본 상태의 손실/리본 UI 객체의 손실.

일부 사용자의 경우 결국 Rib 개체는 Nothing이됩니다.

사용자는 런타임 오류 나 스크립트 오류 (이 추가 기능을 통해 호출하는 COM 개체에서)가 발생하지 않는다고 확신합니다. 사용자가 End에 도달하면 처리되지 않은 오류로 인해 상태 손실이 예상됩니다.

관찰 된 오류를 일으키는 시나리오를 안정적으로 재현 할 수있는 사용자가 없습니다. 이것이 문제 해결을 어렵게 만드는 이유입니다. 내가 누락 된 부분이나 예상하지 못한 부분이 있다는 희망에 반대하기를 희망합니다.

나는 현재 손실 또는 RibbonUI이, 나는 장소에서 리본에 개체 포인터를 저장 대처하기 위해 시도에서

, 이것은 나에게 잔인한 것 같아를 처리하지만 분명히 아직 충분하지 않다 방법 :

  • cbRibbon이라는 클래스 개체는 할당 된 .RibbonUI 속성을가집니다. 리본 onLoad 콜백 절차 중에 Set cbRibbon.RibbonUI = Rib. 그래서 우리는 객체 자체의 byRef 사본을 가지고 있습니다. 리본이 없으면 이론적으로 Set rib = cbRibbon.RibbonUI을 사용할 수 있으며 cbRibbon 개체도 범위를 벗어나지 않으면이 작업이 가능합니다.
  • cbRibbon 개체의 속성은 .Pointer이며 cbRibbon.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 

이 절차에서 가장 먼저해야 할 일은 Nothing-ness 인 Rib 개체를 테스트하는 것입니다. 이 값이 True으로 평가되면 RibbonUI 객체가 손실 된 것입니다. 다음 cbRibbon.Pointer에서 cbRibbon.RibbonUI에서제 이들 모두 다음 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 문을 호출 또는으로) 실행 시간을 죽이고 예상대로 이러한 절차가 리본을 다시 설정합니다. 그래서

enter image description here

, 내가 뭘 놓친 거지?

+0

Access 2010에서 내 자신의 리본 처리 방법을 살펴보면 리본 개체를 설정하기 위해'CopyMemory' API 호출을 사용하고있는 것을 볼 수 있습니다. 개인 함수 GetRibbon (lngRibPtr As Long) 객체 : 희미한 objRibbon 객체 : CopyMemory objRibbon, lngRibPtr, 4 : GetRibbon = objRibbon : Set objRibbon = Nothing : End Function' 아마도 당신을 도울 수 있습니까? – Bobort

+0

@Bobort 예, 실제로이 WinAPI 호출을 내부적으로 사용하고 있습니다 (이 호출의 GetRibbon 메서드에서 : Set Rib = cbRibbon.GetRibbon (cbRibbon.Pointer)). 여기서 문제는 "포인터를 리본에서 복원하는 방법"이 아니라 A) 리본 상태가 손실되는 원인과 B) 어디에서 PowerPoint에서 포인터 값을 안정적으로 저장할 수 있는지 (디스크에 쓰거나 레지스트리를 변경하는 것이 바람직 함) 둘 다 할 수는 있지만 가능하면하지 않는 편이 더 좋습니다.) –

답변

1

내가 ...이 잊어 확인을 나는 아직도 정확히하지 않은 상태에서 오류가 나는 사용자가 단순히 수없는 몇 가지 아이디어를 가지고 처리되지 않은 런타임 오류를보고하고 대신 PowerPoint에서 메시지가 나타나면 "끝"을 치게됩니다.

나는 그 원인이 합리적이라고 확신하고 있으며, 많은 경우에 그 종류의 오류가 "충돌"에 선행한다는 것을 알고 있으므로 곧 해결할 예정입니다.

그렇지 않으면, 내가 궁극적으로 성공과 함께 몇 달 동안 사용해온 방법입니다.

리본의 포인터 값을 사용자 컴퓨터에 쓰는 절차를 만듭니다.나는이 작업을 수행 할 수 있지만, 궁극적으로했다하지 않았다 : 리본의 _OnLoad 이벤트 핸들러에서

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 

, 나는 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입니다. 이 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 프로 시저를 호출합니다.이 프로 시저는 리본 객체를 나타내는 객체 변수 (또는 클래스 객체 속성)가 있으므로 수행 할 수 있습니다. RibbonError 기능에 대한

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 

전화 언제든지 당신은 Invalidate 또는 InvalidateControl 중 방법을 통해 리본을 새로 고침 할 것입니다.

위의 코드는 100 % 컴파일되지 않을 수 있습니다.이를 수정하고 일부 내용을 다듬어야 했으므로 구현에 문제가 있는지 알려주세요!

1

은 진정한 해결책을 찾을 : 다음 Credit

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 
+0

'On Error Resume Next : Debug.Print 1/0'은 VBA의 퍼블릭 변수를 리셋하지 않습니다. 실제로 처리 된 예외이기 때문입니다. :) 궁극적으로 내가 뭘했는지 (나는 싫어하지만 결국 만족 스럽습니다. 사용자 경험)은 사용자 컴퓨터에 텍스트 파일을 만들고 리본 파일의 포인터 값을 해당 파일에 저장하는 것입니다. 'Nothing '을 테스트 할 때'CopyMemory' API 호출을 사용하여 포인터 값에서 복원 할 수 있습니다. 불행히도 오류의 근본 원인을 파악하는 데 어떤 진전도 없었지만, 적어도 나는 그것에 대한 핸들을 가지고 있습니다. –

+0

안녕하세요. 예를 하나 더 읽으시면 Excel은 '손실 상태'일 때마다 전역 변수를 지우고 시트 삭제, 활성 x 컨트롤 삽입, 처리되지 않은 예외 삽입 등을 포함합니다. 상태를 저장하는 방법과 공유 방법을 공유해주십시오. 전역 변수를 다시 설정하십시오. 감사합니다 – AndrewT

+0

실제로 이것을하는 방법에 대한 좋은 설명을 발견 [여기] (http://www.mrexcel.com/forum/excel-questions/518629-how-preserve-regain-id-my-custom-ribbon-ui .html # post2562883) 다른 사람이 찾고있는 사람을위한 링크입니다. – AndrewT