'What is a persistent storage location for a PowerPoint add-in tied to the ribbon?

Lengthy backstory: I am creating a PowerPoint add-in in VBA. My add-in creates a custom tab on the ribbon, and then I am using the getEnabled callback to determine whether certain buttons should be enabled or disabled depending on the selection, much like PowerPoint does with the Bold or Underline buttons based on whether you've selected something with text or not.

To do this, you use the onLoad callback:

<customUI xmlns="..." onLoad="initRibbon">

which passes a variable pointing to the ribbon. You save it as a global variable like this:

Global myRibbon As IRibbonUI

Sub initRibbon(ribbon As IRibbonUI)
    Set myRibbon = ribbon

Then I can set up the WindowSelectionChange event in PowerPoint to invalidate the button controls to drive the getEnabled callback and set the button state.

Private Sub App_WindowSelectionChange(ByVal Sel As Selection)
'Enable or disable buttons depending on selection

...
    
    'By invalidating the control, the rbnGetEnabled sub in the Ribbon module will run
    myRibbon.InvalidateControl ("AddLinkToOneNote")

End Sub

Here's the button XML:

<button id="AddLinkToOneNote" visible="true" size="large" 
        label="Link to OneNote" 
        screentip="Add a link to OneNote to the selected node" 
        onAction="rbnAddLinkToOneNote" imageMso="LinkedNotes"
        getEnabled="rbnGetEnabled" />

Initial problem and solution: The problem is that there are a lot of situations that cause the ribbon global variable to be lost and reset to "Nothing". It could be stopping code as it executes in VBA. It could be changing the Class module where the Event code is (which happens a LOT when you are developing code!!!). I think there are more reasons, but when this happens, the ribbon callbacks no longer run and your ribbon is trapped in the state it was. Solution? Restart your Office app.

The brilliant people of the interwebs have devised a clever solution to this problem. You save the ribbon pointer somewhere safer than a global variable and reload it if it gets lost. Here's a common code pattern for that part:

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

Public Sub ribbonLoaded(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
End Function

Finally, my question!!! There are lots of cool places to store the address if you are using Excel such as a worksheet or name within the Add-in file, the ExecuteExcel4Macro hidden name space, or even a VBA module! PowerPoint add-ins don't have any of these places available.

If my code only was in a single PowerPoint .pptm file, I would be fine. I could just use Tags on the Presentation. I have that code working. But I need my code in an Add-in, which loads before any Presentations are open. I get the ribbon pointer when the ribbon loads the first time, and I need to use it for every presentation that is opened tied to that ribbon. If someone opens a separate instance of PowerPoint, it needs to have a separate ribbon pointer for that application instance. (One sample on the article previously linked above handled that nicely by saving both the Application pointer and the Ribbon pointer to make sure you had the right pointer before using it!)

You could try to store it in the registry or in a file, but it's going to be a mess if PowerPoint crashes or these entries don't get cleaned up.

Another option is using the Window handle for storage, but the method to finding the hWnd for PowerPoint sucks and I'm not sure it could handle multiple PowerPoint instances properly:

Select Case VersionNo
   Case "8"  ' For PPT97:
      hWnd = FindWindow("PP97FrameClass")
   Case "9"  ' For PPT2K:
      hWnd = FindWindow("PP9FrameClass")
   Case "10" ' For XP:
      hWnd = FindWindow("PP10FrameClass")
   Case "11" ' For 2003:
      hWnd = FindWindow("PP11FrameClass")
   Case "12" ' For 2007:
      hWnd = FindWindow("PP12FrameClass")
   Case "14" ' For 2010:
      hWnd = FindWindow("PPTFrameClass")
   Case "15" ' For 2013:
      hWnd = FindWindow("PPTFrameClass")
   Case "16" ' For 2016:
      hWnd = FindWindow("PPTFrameClass")
   Case Else
      Err.Raise number:=vbObjectError + ERR_VERSION_NOT_SUPPORTED, _
            Description:="Newer version: " & VersionNo
      Exit Property
End Select

So any ideas on where I could store this value? Alternatively, is there a better way to invalidate the controls from the Event code that doesn't require a pointer to the ribbon?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source