Future-proofing Office 2003 Custom Menu Development

More and more companies are slowly rolling out Windows Vista as they bring on new machines. However, if you’ve tried upgrading an Office 2003 document that uses custom menus, you will have noticed that those custom menus don’t appear in Office 2007’s new Ribbon. Well, that’s not entirely true; they do appear in the Add-Ins tab. However, leaving users to figure that out for themselves is not a little unkind.

You might think your only option is to create two files–one for Office 2003 and previous and another for Office 2007. You would be wrong. Instead, you can use an Excel 2007 Add-In to extend the ribbon when the file is opened in Office 2007 and open the file as normal in Office 2003. Ron de Bruin explains how to do this in his post “Dealing with Ribbons and Menus – Avoiding Two Versions.”

To make this work, you’ll need the CustomUIEditor from OpenXMLDeveloper.org. Also, I’ve always been a big fan of class modules, so I have attempted to roll all this up into one class module, following Ron’s second example.

Here’s the code for the CustomMenu.cls class:

  MultiUse = -1  ‘True
Attribute VB_Name = “CustomMenu”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ Class:        CustomMenu

‘ Created by:   Ryan Riley (Catapult Systems)
‘ Created on:   5/22/2008

‘ Description:  This class allows menus and menu items to be added
‘               to assist the user with additional reporting options.

‘ Dependencies: None

Option Explicit

‘ Private Members
Private mstrName As String            ‘ Use an ‘&’ to assign a shortcut key.
Private mstrRibbonxPath As String     ‘ Store the Excel 2007 Add-in path.
Private mstrRibbonxName As String     ‘ Store the Excel 2007 Add-in filename.
Private mcolMenuItems As Collection
Private mcbc As CommandBarControl

‘ Class C’tors and D’tors
Private Sub Class_Initialize()
    ‘ Initialize the Name property.
    mstrName = “My Menu”
    mstrRibbonxPath = ThisWorkbook.Path
    mstrRibbonxName = “RibbonxAddin.xlam”
    Set mcolMenuItems = New Collection
End Sub

Private Sub Class_Terminate()
    ‘ Destroy the menu item collection object.
    Set mcolMenuItems = Nothing

    ‘ Destroy the CommandBarControl object.
    Set mcbc = Nothing
End Sub

‘ Public Properties
Public Property Get Name() As String
    Name = mstrName
End Property

Public Property Let Name(ByVal value As String)
    mstrName = value
End Property

Public Property Get RibbonxPath() As String
    RibbonxPath = mstrRibbonxPath
End Property

Public Property Let RibbonxPath(ByVal value As String)
    mstrRibbonxPath = value
End Property

Public Property Get RibbonxName() As String
    RibbonxName = mstrRibbonxName
End Property

Public Property Let RibbonxName(ByVal value As String)
    mstrRibbonxName = value
End Property

‘ Public Methods
‘ Add a menu to the document.
Public Function Attach( _
  Optional Before As Integer = 0) As Boolean
    ‘ Initialize the function return value.
    Attach = False
    If Val(Application.Version) > 11 Then
        If (Dir(mstrRibbonxPath & “\” & mstrRibbonxName) <> “”) Then
            Workbooks.Open mstrRibbonxPath & “\” & mstrRibbonxName
            MsgBox “The RibbonX add-in (” & mstrRibbonxName & “) could not be found.”
        End If
        AddMenu Before
    End If

    ‘ If all has gone well, return true.
    Attach = True
End Function

Public Function Detach()
    Detach = False

    If Val(Application.Version) > 11 Then
        On Error Resume Next
        Workbooks(mstrRibbonxName).Close False
    End If

    Detach = True
End Function

‘ Add menu items to the menu. Call this for each menu
‘ item you wish to add to the menu.
Public Function AddItem( _
  ByVal Name As String, _
  ByVal Macro As String) As Boolean
    AddItem = False
    If (Val(Application.Version) > 11) Then
        ‘ Add the menu item to the private collection.
        mcolMenuItems.Add Macro, Name
        ‘ Add a menu item to our new menu, give it a caption,
        ‘ and tell it which macro to run (OnAction).
        With mcbc.Controls.Add(Type:=msoControlButton)
            .Caption = Name
            .OnAction = Macro
        End With
    End If

    AddItem = True
End Function

‘ Define the function for all Ribbon controls to call when executed.
‘ This method execute the button that matches the format:
‘ “buttonName” where Name is the Menu Item name.
Public Function OnActionCall(MenuItem As Variant) As Boolean
    Dim RibbonItem As IRibbonControl

    ‘ Initialize the return value.
    OnActionCall = False

    ‘ Set the MenuItem to the RibbonItem as an implementation
    ‘ of the IRibbonControl.
    Set RibbonItem = MenuItem

    ‘ Run the macro that matches that stored in the collection.
    Application.Run mcolMenuItems(Mid(RibbonItem.ID, 7))

    ‘ If all goes well, return true.
    OnActionCall = True
End Function

‘ Private Methods
Private Function AddMenu( _
  Optional Before As Integer = 0) As Boolean
    Dim cbMainMenu As CommandBar
    Dim intBeforeIndex As Integer

    ‘ Initialize the function return value.
    AddMenu = False

    ‘ Delete any existing menus with the same name.
    ‘ We must use On Error Resume next in case it does not exist.
    On Error Resume Next
    Application.CommandBars(“Worksheet Menu Bar”).Controls(mstrName).Delete
    On Error GoTo 0
    ‘ Set a CommandBar variable to use for the Worksheet menu bar
    Set cbMainMenu = Application.CommandBars(“Worksheet Menu Bar”)
    ‘ If the Before Index is > 0, set the Before Index to the passed
    ‘ in index; otherwise set it to the index for the last menu.
    If (Before > 0 And Before <= cbMainMenu.Controls.Count) Then
        intBeforeIndex = Before
        intBeforeIndex = cbMainMenu.Controls.Count
    End If

    ‘ Add “My Menu” to the Main Menu CommandBar, just before the last menu.
    ‘ Set a CommandBarControl variable to it
    Set mcbc = cbMainMenu.Controls.Add( _
        Type:=msoControlPopup, Before:=intBeforeIndex)
    ‘ Set the menu control’s caption to the provided name.
    mcbc.Caption = mstrName

    ‘ If all has gone well, return True
    AddMenu = True
End Function

Private Function DeleteMenu() As Boolean
    On Error Resume Next
    ‘ Initialize the function return value.
    DeleteMenu = False
    ‘ Remove the menu added earlier.
    Application.CommandBars(“Worksheet Menu Bar”).Controls(mstrName).Delete

    DeleteMenu = True
End Function[/vb]

As for the implementation, you could do something like this to get the same affect as Ron’s Example 2 (above):

[vb]Option Explicit

Const RibbonxAddin As String = “HasRibbonX.xlam”
Private menu As CustomMenu

Private Sub Workbook_Open()

    Set menu = New CustomMenu

    menu.Name = “Test Tab”
    menu.RibbonxPath = ThisWorkbook.Path    ‘ Default
    menu.RibbonxName = RibbonxAddin
    menu.AddItem “A”, “BtnA”
    menu.AddItem “B”, “BtnB”
    menu.AddItem “C”, “BtnC”
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Set menu = Nothing
End Sub

Sub OnActionCall(MenuItem As Variant)
    menu.OnActionCall MenuItem
End Sub[/vb]

Using this approach, you’ll be able to add in your custom menu/ribbon for all users of your custom Office development.