4
votes

I have a custom ribbon tab in Word, created with the CustomUI editor, with a host of buttons that all work fine. I am trying to find out if I can set the focus of this custom tab when the Word document is opened. [Or when a new document is created.]

Currently, it just shows the Home tab by default. i have found ways of turning the custom tab on and off, but not to 'set focus' to it.

Any ideas?

5

5 Answers

3
votes

You can achieve this via the Microsoft Active Accessibility, I've include the code from the following webpage, but if you want more information then check if out. (sample is at the bottom of the page).

http://www.wordarticles.com/Shorts/RibbonVBA/RibbonVBADemo.php

In a new module you have the code to accept the accessibilty API:

    ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
    ' Definitions and Procedures relating to Accessibility, used by the Ribbon VBA  '
    ' Demonstration UserForm. The constants have been lifted from oleacc.h, and are '
    ' just a subset of those available.                                             '
    '                                                                               '
    '                                                    Tony Jollans, August 2008. '
    ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '

    Option Explicit

    Public Const CHILDID_SELF                  As Long = &H0&
    Public Const STATE_SYSTEM_UNAVAILABLE      As Long = &H1&
    Public Const STATE_SYSTEM_INVISIBLE        As Long = &H8000&
    Public Const STATE_SYSTEM_SELECTED         As Long = &H2&

    Public Enum RoleNumber
        ROLE_SYSTEM_CLIENT = &HA&
        ROLE_SYSTEM_PANE = &H10&
        ROLE_SYSTEM_GROUPING = &H14&
        ROLE_SYSTEM_TOOLBAR = &H16&
        ROLE_SYSTEM_PAGETAB = &H25&
        ROLE_SYSTEM_PROPERTYPAGE = &H26&
        ROLE_SYSTEM_GRAPHIC = &H28&
        ROLE_SYSTEM_STATICTEXT = &H29&
        ROLE_SYSTEM_TEXT = &H2A&
        ROLE_SYSTEM_BUTTONDROPDOWNGRID = &H3A&
        ROLE_SYSTEM_PAGETABLIST = &H3C&
    End Enum

    Private Enum NavigationDirection
        NAVDIR_FIRSTCHILD = &H7&
    End Enum

    Private Declare Function AccessibleChildren Lib "oleacc.dll" _
                        (ByVal paccContainer As Object, ByVal iChildStart As Long, ByVal cChildren As Long, _
                               rgvarChildren As Variant, pcObtained As Long) _
                    As Long

    Public Function GetAccessible _
                        (Element As IAccessible, _
                         RoleWanted As RoleNumber, _
                         NameWanted As String, _
                         Optional GetClient As Boolean) _
                    As IAccessible

        ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
        ' This procedure recursively searches the accessibility hierarchy, starting '
        ' with the element given, for an object matching the given name and role.   '
        ' If requested, the Client object, assumed to be the first child, will be   '
        ' returned instead of its parent.                                           '
        '                                                                           '
        ' Called by: RibbonForm procedures to get parent objects as required        '
        '            Itself, recursively, to move down the hierarchy                '
        ' Calls: GetChildren to, well, get children.                                '
        '        Itself, recursively, to move down the hierarchy                    '
        ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '

        Dim ChildrenArray(), Child As IAccessible, ndxChild As Long, ReturnElement As IAccessible

        If Element.accRole(CHILDID_SELF) = RoleWanted And Element.accName(CHILDID_SELF) = NameWanted Then

            Set ReturnElement = Element

        Else ' not found yet
            ChildrenArray = GetChildren(Element)

            If (Not ChildrenArray) <> True Then
                For ndxChild = LBound(ChildrenArray) To UBound(ChildrenArray)
                    If TypeOf ChildrenArray(ndxChild) Is IAccessible Then

                        Set Child = ChildrenArray(ndxChild)
                        Set ReturnElement = GetAccessible(Child, RoleWanted, NameWanted)
                        If Not ReturnElement Is Nothing Then Exit For

                    End If                  ' Child is IAccessible
                Next ndxChild
            End If                          ' there are children
        End If                              ' still looking

        If GetClient Then
            Set ReturnElement = ReturnElement.accNavigate(NAVDIR_FIRSTCHILD, CHILDID_SELF)
        End If

        Set GetAccessible = ReturnElement

    End Function

    Private Function GetChildren(Element As IAccessible) As Variant()
        ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
        ' General purpose subroutine to get an array of children of an IAccessible  '
        ' object. The returned array is Variant because the elements may be either  '
        ' IAccessible objects or simple (Long) elements, and the caller must treat  '
        ' them appropriately.                                                       '
        '                                                                           '
        ' Called by: GetAccessible when searching for an Accessible element         '
        ' Calls: AccessibleChildren API                                             '
        ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
        Const FirstChild As Long = 0&
        Dim NumChildren As Long, NumReturned As Long, ChildrenArray()

        NumChildren = Element.accChildCount

        If NumChildren > 0 Then
            ReDim ChildrenArray(NumChildren - 1)
            AccessibleChildren Element, FirstChild, NumChildren, ChildrenArray(0), NumReturned
        End If

        GetChildren = ChildrenArray
    End Function

And then in your ThisTemplate or ThisDocument module:

    Option Explicit

    Private Sub Document_New()
        SwitchTab "MyTab"
    End Sub

    Private Sub Document_Open()
            SwitchTab "MyTab"
    End Sub


    Private Sub SwitchTab(TabName As String)
        Dim RibbonTab   As IAccessible

        'Get the Ribbon as an accessiblity object and the
        Set RibbonTab = GetAccessible(CommandBars("Ribbon"), ROLE_SYSTEM_PAGETAB, TabName)

        'If we've found the ribbon then we can loop through the tabs
        If Not RibbonTab Is Nothing Then
            'If the tab state is valid (not unavailable or invisible)
            If ((RibbonTab.accState(CHILDID_SELF) And (STATE_SYSTEM_UNAVAILABLE Or _
                         STATE_SYSTEM_INVISIBLE)) = 0) Then
                'Then we can change to that tab
                RibbonTab.accDoDefaultAction CHILDID_SELF
            End If
        End If

    End Sub
3
votes

The answers presented are unnecessarily complex. The simplest solution is part of what Sam answered.

You add an "onLoad"-callback attribute referring to a method name in the custom UI root node of your custom XML. E.g.:

<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Ribbon_Load">
<ribbon startFromScratch="false">
    <tabs>
        <tab id="customTab" label="Custom Tab">
        </tab>
    </tabs>
</ribbon>
</customUI>

You then add a callback method with the correct signature to a module in the document. In this method, you'll have access to an IRibbonUI object, which can be used to activate tabs - be they custom or built-in. The following example activates a custom tab with an id equal to "customTab":

Sub Ribbon_Load(ribbon As IRibbonUI)
    ribbon.ActivateTab "customTab"
End Sub
2
votes

I wanted to make an addition to CuberChase's excellent answer, which worked for me on a 32-bit instance, by simply copying all the code into a module of the document you work in.

However, when you work on a 64-bit instance, it is necessary to change a bit of code.

Instead of Private Declare Function ... As Long:

Private Declare Function AccessibleChildren Lib "oleacc.dll" _
                    (ByVal paccContainer As Object, ByVal iChildStart As Long, ByVal cChildren As Long, _
                           rgvarChildren As Variant, pcObtained As Long) _
                As Long

You require to declare Private Declare PtrSafe Function ... As LongPtr. The function thus changes to:

Private Declare PtrSafe Function AccessibleChildren Lib "oleacc.dll" _
                    (ByVal paccContainer As Object, ByVal iChildStart As Long, ByVal cChildren As Long, _
                           rgvarChildren As Variant, pcObtained As Long) _
                As LongPtr

See also this website for some more explanation.

Note, I used this code to get SolverStudio in Excel 2016, to work. I was referred to this page by http://solverstudio.org/using-studio/

1
votes

After your custom ribbon tab gets drawn to the Ribbon, you can send a key to the model like so:

Application.SendKeys "%keytosend%"

To find out what key needs to be sent, open the workbook and press Alt. You will notice key icons display over the tabs.

enter image description here

For example, to load the Page Layout tab, you would send the key like so:

Application.SendKeys "%P%"

NOTE: You may have to tell the custom ribbon code what macro to call onLoad. This can be done in the XML using the Custom UI Editor. Load the workbook that contains the custom ribbon code, then add the onLoad attribute to the <customUI></customUI> tag, like so:

enter image description here

Once added, you then have to create a VBA function that matches the onLoad function you defined (in this case, ribbonLoad). In that function is where you can then send the appropriate key:

Sub ribbonLoad(ctl As IRibbonUI)
    Application.SendKeys "%Y1%"
End Sub
0
votes

I couldn't get the Sendkeys solution to work reliably enough. The Active Accessibility solution seems to work, but needs a bit more time than I have. I'll certainly review this method later.

I did find that there is a way of displaying the custom tab if it is the first one in the tab list. I used these settings:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon startFromScratch="false"> 
<tabs>
<tab id="customTab" insertBeforeMso="TabHome" label="CustomTabName">

Thanks for the alternatives. Much appreciated.