Pérdida de complemento de PowerPoint de RibbonUI
He estado luchando por identificar la causa de un error en un complemento PPT que se distribuye entre aproximadamente 40 usuarios finales.
Problema: pérdida del estado de la cinta / pérdida del objeto ribbonUI.
Para algunos usuarios, eventualmenteRib
objeto se convierteNothing
.
Los usuarios me aseguran que no reciben errores de tiempo de ejecución ni errores de script (del objeto COM que también invocamos a través de este complemento). Un error no manejado, si el usuario golpeaEnd
se espera que cause la pérdida del estado.
Ninguno de los usuarios ha podido reproducir de manera confiable el escenario que causa la falla observada. Esto es lo que hace que sea muy difícil solucionar problemas. Espero contra toda esperanza que haya algo obvio que me estoy perdiendo o que no anticipé.
Cómo manejo actualmente la pérdida o RibbonUI
En un intento por combatir esto, guardo el puntero del objeto en la cinta enTRES lugares, esto me parece excesivo, pero aparentemente todavía no es suficiente:
Un objeto de clase llamadocbRibbon
tiene una propiedad.RibbonUI
cual es asignadoSet cbRibbon.RibbonUI = Rib
durante la cintaonLoad
procedimiento de devolución de llamada. Entonces tenemos unbyRef
copia del objeto en sí. Si la cinta no es nada, teóricamente puedoSet rib = cbRibbon.RibbonUI
y esto funciona a menos quecbRibbon
El objeto también está fuera de alcance.loscbRibbon
objeto tiene propiedad.Pointer
que se le asigna:cbRibbon.Pointer = ObjPtr(Rib)
.A CustomDocumentProperty
llamado "RibbonPointer" también se utiliza para almacenar una referencia al puntero del objeto. (Nota: Esto persiste incluso más allá de la pérdida de estado.)Como puede ver, he pensado un poco en esto en un intento de replicar la forma de almacenar este puntero de la forma en que uno podría almacenarlo en una hoja de cálculo / rango oculto en Excel.
Información Adicional
Puedo ver en el registro robusto del lado del cliente que este error parece suceder generalmente, pero no siempre, durante el siguiente procedimiento, que se utiliza para actualizar / invalidar la cinta y sus controles.
Este procedimiento se llama cada vez que necesito actualizar dinámicamente la cinta o parte de sus controles:
Call RefreshRibbon(id)
El error parece (a veces, no puedo enfatizar esto lo suficiente: el errorno puedo replicarse a pedido) suceden durante una actualización completa, que se llama así:
Call RefreshRibbon("")
Este es el procedimiento que hace la invalidación:
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
Como puede ver, lo primero que hago en este procedimiento es probar elRib
objeto paraNothing
-ness. Si esto se evalúa comoTrue
, entonces el objeto RibbonUI de alguna manera se ha perdido.
La función de error luego intenta reiniciar la cinta:primero decbRibbon.RibbonUI
, luego de lacbRibbon.Pointer
y si ambos fallan, entonces delCustomDocumentProperties("RibbonPointer")
valor. Si ninguno de estos tiene éxito, mostramos un error fatal y se le solicita al usuario que cierre la aplicación de PowerPoint. Si alguno de estos tiene éxito, la cinta se vuelve a cargar mediante programación y todo continúa funcionando.
Aquí está el código para ese procedimiento. Tenga en cuenta que llama a varios otros procedimientos para los que no he incluido el código. Estas son funciones auxiliares o funciones de registro. los.GetPointer
El método en realidad invoca el WinAPICopyMemory
función para recargar el objeto desde su valor de puntero.
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
Todo esto funciona perfectamente bien en teoría y, de hecho, puedomatar tiempo de ejecución (invocando elEnd
declaración o no) y estos procedimientos restablecen la cinta como se esperaba.
Entonces, ¿qué me estoy perdiendo?