how to tab from one active x text box to another without a macro

spireup

New Member
Joined
Jan 10, 2014
Messages
2
Hi

I want to be able to use the tab key to move from one active x text box to the next active x text box in a spreadsheet without using any macros.

The controls are not in a vba form. The document will be protected and locked in its final form.

I managed to fluke this in Word and have also found a vba solution for excel but I would really like to avoid using a macro.

Can anyone help?

kind regards

Paul
 

Excel Facts

How to fill five years of quarters?
Type 1Q-2023 in a cell. Grab the fill handle and drag down or right. After 4Q-2023, Excel will jump to 1Q-2024. Dash can be any character.
I want to be able to use the tab key to move from one active x text box to the next active x text box in a spreadsheet without using any macros.

As Jerry notes, the simplest solution is to initially put the controls in a Frame control and let it manage the Tab order. It’s a pretty clean solution. But there are two unfortunate problems with frames:

  1. If you first create a complex set of ActiveX controls on a worksheet and then, belatedly, decide that you need to manage their Tab order, you can’t just cut and paste the existing, sheet-level controls into a newly added Frame control. You must start over and replicate your application inside the Frame, as described in the link provided by Jerry.
  2. If your application requires the user to interact with both the containing worksheet’s cells and the ActiveX controls, you can’t easily define a fully-functional transparent Frame control through which the user can see cell values, let alone interact with them behind the frame.
So here’s a fairly complete solution for managing ActiveX controls’ Tab order using VBA code. It may look rather complicated at first but its use is actually very straightforward.

To experiment with this example:

  • Open a new workbook.

  • On Sheet1, add three ActiveX controls: a command-button, a text-box, and a second command-button. Retain their default names, which will be “CommandButton1”, “TextBox1”, and “CommandButton2”, respectively.

  • Open the VBE editor and copy the code below into the Sheet1 module.

  • On the worksheet, select one of the controls and then use the Tab and/or Enter keys to experiment with moving from control to control.

  • In this version, if you use the Enter key on a command-button, it will just execute the button’s Click event but will not move to the next control. If that behavior is desired, it can be easily added.

  • To change the Tab order of managed controls, all you have to do is edit the TabOrderDef list! If you need to add a new control to be managed:
    • Add its name to the list in its desire tab-order position.
    • Add a KeyDown event handler for the newly managed control.
    • Copy the code from one of the exiting KeyDown event handlers.
    • IMPORTANT: Edit the "Set ThisCntrl = Me.YourControlName" statement so that it references the name of your newly managed control!

Code:
Option Explicit

'Declare the module-global tab-order control constants and variables

    'NOTE: See the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in
    'this implementation.
    
    Private Const fmShiftMask   As Integer = 1   'SHIFT was pressed.
    Private Const fmCtrlMask    As Integer = 2   'CTRL was pressed.
    Private Const fmAltMask     As Integer = 4   'ALT was pressed.
    
    Private Const ASCII_Enter   As Integer = 13
    Private Const ASCII_Tab     As Integer = 9
    
    'To change the Tab order of managed controls, just edit the following list:
    
    Private Const TabOrderDef   As String = _
            "CommandButton1," & _
            "TextBox1," & _
            "CommandButton2"                'Module-global so that all controls on this sheet can access it
    
    'NOTE: See the MoveFocus routine's header commentary regarding (re)initialization of the following variables:
        
    Private TabOrderNames    As Variant     'Module-global so that all controls on this sheet can access it
    Private TabOrderCntrls() As OLEObject   '   "

    Private Const ChkBoxType As String = "CheckBox"
    Private Const CmdBtnType As String = "CommandButton"
    Private Const OptBtnType As String = "OptionButton"
    Private Const TxtBoxType As String = "TextBox"


'=================================================================================================================
'                                           ActiveX/OLE Control Event Handlers
'=================================================================================================================

Private Sub CommandButton1_Click()
    MsgBox "CommandButton1 was clicked"
End Sub

Private Sub CommandButton1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton1   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub CommandButton2_Click()
    MsgBox "CommandButton2 was clicked"
End Sub

Private Sub CommandButton2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton2   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub TextBox1_LostFocus()
    If TextBox1.Value <> vbNullString Then MsgBox "TextBox1 contains: '" & TextBox1.Value & "'"
End Sub

Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.TextBox1         'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub


'=================================================================================================================
'                                           Module Subroutines and Functions
'=================================================================================================================

Sub DoActionKey(KeyCode As MSForms.ReturnInteger, Cntrl As Object, ByRef TabIndex As Integer, Shift As Integer)
    '
    'Checks whether the specified key code is an action key (e.g. Enter or Tab) and, if so, initiates the
    'appropriate action, depending on the type of the specified control and, possibly, its property settings.
    '
    'In this implementation:
    '
    '   * Tab key: regardless of the control's type, the focus is moved to the previous or next control in the
    '     globally defined tab-sequence order, depending on whether the Shift key was also pressed or not.  See
    '     the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in this
    '     implementation.
    '
    '   * Enter key:
    '       + If the control is a commmand button, then that button's Click event handler is called to simulate a
    '         button click.
    '
    '       + If the control is a text box:
    '           - If it is a single-line text box or its new-line Enter key behavior is disabled, then the Enter
    '             key press is treated like an un-shifted Tab key and focus is moved forward to the next control
    '             in the globally defined tab-sequence order.
    '
    '           - If it is a multi-line text box and its new-line Enter key behavior is enabled, then the Enter
    '             key press remains unprocessed by this routine and is thereby allowed to function as a new-line
    '             operation in the multi-line text box.
    '
    'NOTE: This routine may need to be customized for a different implementation.
    '
    'PARAMETERS:
    '
    '   KeyCode     The key code to be checked for being an action-key code.
    '
    '   Cntrl       The ActiveX/OLE control object from which the key code was generated due to its having focus
    '               at the time of the user key-press.
    '
    '   TabIndex    The tab-index value of the control that generated the key code.  See the MoveFocus routine's
    '               header commentary regarding (re)initialization of thie (ByRef) parameter.
    '
    '   Shift       The state of the Shift, Ctrl, and Alt keys at the time of the user's key press action.  Is a
    '               bit array defined by the constants fmShiftMask, fmCtrlMask, and fmAltMask.
    '
    '*************************************************************************************************************
    
    Select Case KeyCode
    Case ASCII_Tab
        Dim Backward As Boolean
        Backward = (Shift And fmShiftMask)  'Bit-wise "And" converted to Boolean value
    
        Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex, Backward:=Backward)
    
    Case ASCII_Enter
        Select Case TypeName(Cntrl)
        Case TxtBoxType
            If Not (Cntrl.EnterKeyBehavior And Cntrl.MultiLine) Then
                'The Textbox either isn't multi-line or its new-line Enter key behavior is disabled, so
                'treat the enter key like a Tab key and move to the next control in the tab-order.
                
                Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex)
            End If
        Case Else 'CmdBtnType (is the only alternative in this implementation)
            'This control is a command button, so treat an Enter key press like a click event.
        
            On Error Resume Next    'In case the specified command button has no Click event handler defined
            Application.Run Me.CodeName & "." & Cntrl.Name & "_Click"
            On Error GoTo 0
        End Select
    End Select
End Sub

Sub MoveFocus(Cntrl As Object, ByRef TabIndex As Integer, Optional Backward As Boolean)
    '
    'Moves the focus to the next or previous ActiveX/OLE control listed in the (global) TabOrderCntrls list,
    'depending on whether the Backward paramater is False (default) or set to True by the calling code.  The
    'next/previous control is determined relative to the specified tab-index value, which defines the calling
    'control's position in the TabOrderCntrls list.
    '
    'PARAMETERS:
    '
    '   Cntrl       The ActiveX/OLE control object from which the focus is to be moved.
    '
    '   TabIndex    The tab-index value of the control from which the focus is to be moved.  If uninitialized
    '               (value = 0) then it is (re)initialized by searching the (global) TabOrderNames array for
    '               the specified control-name and sets this (ByRef) parameter, and thereby the calling event
    '               handler's corresponding variable argument, to be its found position in that array.
    '
    '   Backward    If set to True, the focus is moved backward.  If False (default) then the focus is moved
    '               forward.
    '
    'NOTE: If necessary, the global arrays TabOrderNames and TabOrderCntrls are (re)initialized before processing
    '      the move-focus event, using the root-definition of the tab order defined by the (global) control-name
    '      list, TabOrderDef.
    '
    '*************************************************************************************************************
    
    Dim i As Integer    'Reusable loop counter

    'Check whether all variables are initialized
    
    If IsEmpty(TabOrderNames) Then
        'The (global) tab order control-names list is uninitialized, so initialize it using the TabOrderDef list
        'of the constrols to be included in move-focus control system.
        
        TabOrderNames = Split(TabOrderDef, ",")
        ReDim Preserve TabOrderNames(1 To UBound(TabOrderNames) + 1) 'Convert from zero-based to one-based array
    
        If Not IsInitArray(TabOrderCntrls) Then
            'The (global) tab order control-objects list is also uninitialized, so initialize it.
            
            ReDim TabOrderCntrls(1 To UBound(TabOrderNames)) 'First, allocate the necessary number of entries
            
            'Check whether each ActiveX/OLE control on the specified control's parent worksheet is included in the
            'tab order control-names list and, if so, add them to the tab order control-objects list in the same
            'position as in the tab order control-names list.
            
            Dim ChkCntrl As OLEObject
            Dim WkSh As Worksheet
            Set WkSh = Cntrl.Parent
            
            For Each ChkCntrl In WkSh.OLEObjects
                'Search for this next worksheet control in the tab order control-names list
    
                For i = LBound(TabOrderNames) To UBound(TabOrderNames)
                    If TabOrderNames(i) = ChkCntrl.Name Then Exit For   'If found, exit search loop
                Next i
                If i <= UBound(TabOrderNames) Then
                    'This control is on the tab order list, so add it to the tab order control-object list in the
                    'corresponding position.
                    
                    Set TabOrderCntrls(i) = ChkCntrl
                End If
            Next ChkCntrl
        End If
    End If
    
    If TabIndex = 0 Then
        'The calling control's tab index value is uninitialized, so initialize it by finding the control name's
        'position in the tab order control-names list.
        
        Dim CntrlName As String
        CntrlName = Cntrl.Name

        For i = LBound(TabOrderNames) To UBound(TabOrderNames)
            If TabOrderNames(i) = CntrlName Then Exit For   'If found, exit search loop
        Next i
        If i > UBound(TabOrderNames) Then
            'Didn't find it, so display an explanatory error message and then abort
            
            Call MsgBox("The ActiveX control '" & CntrlName & _
                        "' was not found in the 'TabOrderDef' control list.", _
                        vbOKOnly + vbCritical, "PROGRAMMING ERROR")
            Exit Sub
        End If
        
        TabIndex = i    'Found it
    End If

    'Do the move-focus operation
    
    If Backward Then
        If TabIndex > LBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex - 1).Activate           'Move to the previous control in the list
        Else
            TabOrderCntrls(UBound(TabOrderCntrls)).Activate 'Wrap-around to the last control in the list
        End If
    Else
        If TabIndex < UBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex + 1).Activate           'Move to the next control in the list
        Else
            TabOrderCntrls(LBound(TabOrderCntrls)).Activate 'Wrap-around to the first control in the list
        End If
    End If
End Sub

Public Function IsInitArray(CheckArray) As Boolean
    'Returns True if the specified array argument is an initialized array, False otherwise.  If the specified
    'argument is not an array variable, then False is returned so that the programmer will discover the
    'misapplication of this function as a result of unexpected behavior of the algorithm in which it is used.

    Dim UB As Long

    On Error Resume Next
    UB = UBound(CheckArray)
    IsInitArray = (UB > -1 And Err = 0)   'NOTE: must check value of UB, as described below*
    On Error GoTo 0

    ' * because Split() has a "bug" that can result in an uninitialized array for which UBound returns -1
    ' without throwing an error.  e.g. Split(vbNullString, ",").  I put "bug" in quotes because that is how
    ' Ubound() should always work on an uninitialized array but, unfortunately, it doesn't, except as a
    ' result of the Split() anomaly.
End Function
 
Last edited:
Upvote 1
When I tried this, this is the error I get right away. As I am new, i do not know what or how to find an OLE. Says Active Xcontrol Compile error and highlights in red as shown below .Please help



Set ChkAXObj = ChkCntrl.Object 'Get the actual ActiveX control from its OLE-object wrapper
If TypeName(ChkAXObj) = CmdBtnType Then
'Is a command-button control, which has a TakeFocus******* property, so check whether it
'is set to allow this control to actually take the focus and, if so, add it to the tab
'order control-object list.

If ChkAXObj.TakeFocus******* Then
Set TabOrderCntrls(i) = ChkCntrl
TabOrderCount = TabOrderCount + 1
End If

Else 'No other control types have a TakeFocus******* property, so no need to check the


As Jerry notes, the simplest solution is to initially put the controls in a Frame control and let it manage the Tab order. It’s a pretty clean solution. But there are two unfortunate problems with frames:

  1. If you first create a complex set of ActiveX controls on a worksheet and then, belatedly, decide that you need to manage their Tab order, you can’t just cut and paste the existing, sheet-level controls into a newly added Frame control. You must start over and replicate your application inside the Frame, as described in the link provided by Jerry.
  2. If your application requires the user to interact with both the containing worksheet’s cells and the ActiveX controls, you can’t easily define a fully-functional transparent Frame control through which the user can see cell values, let alone interact with them behind the frame.
So here’s a fairly complete solution for managing ActiveX controls’ Tab order using VBA code. It may look rather complicated at first but its use is actually very straightforward.

To experiment with this example:

  • Open a new workbook.

  • On Sheet1, add three ActiveX controls: a command-button, a text-box, and a second command-button. Retain their default names, which will be “CommandButton1”, “TextBox1”, and “CommandButton2”, respectively.

  • Open the VBE editor and copy the code below into the Sheet1 module.

  • On the worksheet, select one of the controls and then use the Tab and/or Enter keys to experiment with moving from control to control.

  • In this version, if you use the Enter key on a command-button, it will just execute the button’s Click event but will not move to the next control. If that behavior is desired, it can be easily added.

  • To change the Tab order of managed controls, all you have to do is edit the TabOrderDef list! If you need to add a new control to be managed:
    • Add its name to the list in its desire tab-order position.
    • Add a KeyDown event handler for the newly managed control.
    • Copy the code from one of the exiting KeyDown event handlers.
    • IMPORTANT: Edit the "Set ThisCntrl = Me.YourControlName" statement so that it references the name of your newly managed control!

Code:
Option Explicit

'Declare the module-global tab-order control constants and variables

    'NOTE: See the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in
    'this implementation.
    
    Private Const fmShiftMask   As Integer = 1   'SHIFT was pressed.
    Private Const fmCtrlMask    As Integer = 2   'CTRL was pressed.
    Private Const fmAltMask     As Integer = 4   'ALT was pressed.
    
    Private Const ASCII_Enter   As Integer = 13
    Private Const ASCII_Tab     As Integer = 9
    
    'To change the Tab order of managed controls, just edit the following list:
    
    Private Const TabOrderDef   As String = _
            "CommandButton1," & _
            "TextBox1," & _
            "CommandButton2"                'Module-global so that all controls on this sheet can access it
    
    'NOTE: See the MoveFocus routine's header commentary regarding (re)initialization of the following variables:
        
    Private TabOrderNames    As Variant     'Module-global so that all controls on this sheet can access it
    Private TabOrderCntrls() As OLEObject   '   "

    Private Const ChkBoxType As String = "CheckBox"
    Private Const CmdBtnType As String = "CommandButton"
    Private Const OptBtnType As String = "OptionButton"
    Private Const TxtBoxType As String = "TextBox"


'=================================================================================================================
'                                           ActiveX/OLE Control Event Handlers
'=================================================================================================================

Private Sub CommandButton1_Click()
    MsgBox "CommandButton1 was clicked"
End Sub

Private Sub CommandButton1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton1   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub CommandButton2_Click()
    MsgBox "CommandButton2 was clicked"
End Sub

Private Sub CommandButton2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton2   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub TextBox1_LostFocus()
    If TextBox1.Value <> vbNullString Then MsgBox "TextBox1 contains: '" & TextBox1.Value & "'"
End Sub

Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.TextBox1         'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
    
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub


'=================================================================================================================
'                                           Module Subroutines and Functions
'=================================================================================================================

Sub DoActionKey(KeyCode As MSForms.ReturnInteger, Cntrl As Object, ByRef TabIndex As Integer, Shift As Integer)
    '
    'Checks whether the specified key code is an action key (e.g. Enter or Tab) and, if so, initiates the
    'appropriate action, depending on the type of the specified control and, possibly, its property settings.
    '
    'In this implementation:
    '
    '   * Tab key: regardless of the control's type, the focus is moved to the previous or next control in the
    '     globally defined tab-sequence order, depending on whether the Shift key was also pressed or not.  See
    '     the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in this
    '     implementation.
    '
    '   * Enter key:
    '       + If the control is a commmand button, then that button's Click event handler is called to simulate a
    '         button click.
    '
    '       + If the control is a text box:
    '           - If it is a single-line text box or its new-line Enter key behavior is disabled, then the Enter
    '             key press is treated like an un-shifted Tab key and focus is moved forward to the next control
    '             in the globally defined tab-sequence order.
    '
    '           - If it is a multi-line text box and its new-line Enter key behavior is enabled, then the Enter
    '             key press remains unprocessed by this routine and is thereby allowed to function as a new-line
    '             operation in the multi-line text box.
    '
    'NOTE: This routine may need to be customized for a different implementation.
    '
    'PARAMETERS:
    '
    '   KeyCode     The key code to be checked for being an action-key code.
    '
    '   Cntrl       The ActiveX/OLE control object from which the key code was generated due to its having focus
    '               at the time of the user key-press.
    '
    '   TabIndex    The tab-index value of the control that generated the key code.  See the MoveFocus routine's
    '               header commentary regarding (re)initialization of thie (ByRef) parameter.
    '
    '   Shift       The state of the Shift, Ctrl, and Alt keys at the time of the user's key press action.  Is a
    '               bit array defined by the constants fmShiftMask, fmCtrlMask, and fmAltMask.
    '
    '*************************************************************************************************************
    
    Select Case KeyCode
    Case ASCII_Tab
        Dim Backward As Boolean
        Backward = (Shift And fmShiftMask)  'Bit-wise "And" converted to Boolean value
    
        Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex, Backward:=Backward)
    
    Case ASCII_Enter
        Select Case TypeName(Cntrl)
        Case TxtBoxType
            If Not (Cntrl.EnterKeyBehavior And Cntrl.MultiLine) Then
                'The Textbox either isn't multi-line or its new-line Enter key behavior is disabled, so
                'treat the enter key like a Tab key and move to the next control in the tab-order.
                
                Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex)
            End If
        Case Else 'CmdBtnType (is the only alternative in this implementation)
            'This control is a command button, so treat an Enter key press like a click event.
        
            On Error Resume Next    'In case the specified command button has no Click event handler defined
            Application.Run Me.CodeName & "." & Cntrl.Name & "_Click"
            On Error GoTo 0
        End Select
    End Select
End Sub

Sub MoveFocus(Cntrl As Object, ByRef TabIndex As Integer, Optional Backward As Boolean)
    '
    'Moves the focus to the next or previous ActiveX/OLE control listed in the (global) TabOrderCntrls list,
    'depending on whether the Backward paramater is False (default) or set to True by the calling code.  The
    'next/previous control is determined relative to the specified tab-index value, which defines the calling
    'control's position in the TabOrderCntrls list.
    '
    'PARAMETERS:
    '
    '   Cntrl       The ActiveX/OLE control object from which the focus is to be moved.
    '
    '   TabIndex    The tab-index value of the control from which the focus is to be moved.  If uninitialized
    '               (value = 0) then it is (re)initialized by searching the (global) TabOrderNames array for
    '               the specified control-name and sets this (ByRef) parameter, and thereby the calling event
    '               handler's corresponding variable argument, to be its found position in that array.
    '
    '   Backward    If set to True, the focus is moved backward.  If False (default) then the focus is moved
    '               forward.
    '
    'NOTE: If necessary, the global arrays TabOrderNames and TabOrderCntrls are (re)initialized before processing
    '      the move-focus event, using the root-definition of the tab order defined by the (global) control-name
    '      list, TabOrderDef.
    '
    '*************************************************************************************************************
    
    Dim i As Integer    'Reusable loop counter

    'Check whether all variables are initialized
    
    If IsEmpty(TabOrderNames) Then
        'The (global) tab order control-names list is uninitialized, so initialize it using the TabOrderDef list
        'of the constrols to be included in move-focus control system.
        
        TabOrderNames = Split(TabOrderDef, ",")
        ReDim Preserve TabOrderNames(1 To UBound(TabOrderNames) + 1) 'Convert from zero-based to one-based array
    
        If Not IsInitArray(TabOrderCntrls) Then
            'The (global) tab order control-objects list is also uninitialized, so initialize it.
            
            ReDim TabOrderCntrls(1 To UBound(TabOrderNames)) 'First, allocate the necessary number of entries
            
            'Check whether each ActiveX/OLE control on the specified control's parent worksheet is included in the
            'tab order control-names list and, if so, add them to the tab order control-objects list in the same
            'position as in the tab order control-names list.
            
            Dim ChkCntrl As OLEObject
            Dim WkSh As Worksheet
            Set WkSh = Cntrl.Parent
            
            For Each ChkCntrl In WkSh.OLEObjects
                'Search for this next worksheet control in the tab order control-names list
    
                For i = LBound(TabOrderNames) To UBound(TabOrderNames)
                    If TabOrderNames(i) = ChkCntrl.Name Then Exit For   'If found, exit search loop
                Next i
                If i <= UBound(TabOrderNames) Then
                    'This control is on the tab order list, so add it to the tab order control-object list in the
                    'corresponding position.
                    
                    Set TabOrderCntrls(i) = ChkCntrl
                End If
            Next ChkCntrl
        End If
    End If
    
    If TabIndex = 0 Then
        'The calling control's tab index value is uninitialized, so initialize it by finding the control name's
        'position in the tab order control-names list.
        
        Dim CntrlName As String
        CntrlName = Cntrl.Name

        For i = LBound(TabOrderNames) To UBound(TabOrderNames)
            If TabOrderNames(i) = CntrlName Then Exit For   'If found, exit search loop
        Next i
        If i > UBound(TabOrderNames) Then
            'Didn't find it, so display an explanatory error message and then abort
            
            Call MsgBox("The ActiveX control '" & CntrlName & _
                        "' was not found in the 'TabOrderDef' control list.", _
                        vbOKOnly + vbCritical, "PROGRAMMING ERROR")
            Exit Sub
        End If
        
        TabIndex = i    'Found it
    End If

    'Do the move-focus operation
    
    If Backward Then
        If TabIndex > LBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex - 1).Activate           'Move to the previous control in the list
        Else
            TabOrderCntrls(UBound(TabOrderCntrls)).Activate 'Wrap-around to the last control in the list
        End If
    Else
        If TabIndex < UBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex + 1).Activate           'Move to the next control in the list
        Else
            TabOrderCntrls(LBound(TabOrderCntrls)).Activate 'Wrap-around to the first control in the list
        End If
    End If
End Sub

Public Function IsInitArray(CheckArray) As Boolean
    'Returns True if the specified array argument is an initialized array, False otherwise.  If the specified
    'argument is not an array variable, then False is returned so that the programmer will discover the
    'misapplication of this function as a result of unexpected behavior of the algorithm in which it is used.

    Dim UB As Long

    On Error Resume Next
    UB = UBound(CheckArray)
    IsInitArray = (UB > -1 And Err = 0)   'NOTE: must check value of UB, as described below*
    On Error GoTo 0

    ' * because Split() has a "bug" that can result in an uninitialized array for which UBound returns -1
    ' without throwing an error.  e.g. Split(vbNullString, ",").  I put "bug" in quotes because that is how
    ' Ubound() should always work on an uninitialized array but, unfortunately, it doesn't, except as a
    ' result of the Split() anomaly.
End Function
 
Upvote 0
... Active Xcontrol Compile error and highlights in red as shown...

Can you be a more specific about which compiler error you receive for the call to the ChkAXObj.TakeFocusOncIick method?

Also, I notice that Mr. Excel's reply-text formatting converts your references to that Method to the text string "ChkAXObj.TakeFocus*******" (for reason's I'm not clear on). I'm assuming that that's just a quirk of Mr. Excel's rendering engine and your actual copied code references the correct method name. If the copied code does literally refer to "TakeFocus*******", that would be your problem.

Also, I have the following References set for this code (see Tools=>References...). Make sure you have these selected (or their equivalent in your version of MS Office):

Visual Basic For Applications
Microsoft Excel 16.0 Object Library
OLE Automation
Microsoft Office 16.0 Object Library
Microsoft Forms 2.0 Object Library


Note that your XX.N version number for some of those might be different, but that should be okay.
 
Upvote 0
As Jerry notes, the simplest solution is to initially put the controls in a Frame control and let it manage the Tab order. It’s a pretty clean solution. But there are two unfortunate problems with frames:

  1. If you first create a complex set of ActiveX controls on a worksheet and then, belatedly, decide that you need to manage their Tab order, you can’t just cut and paste the existing, sheet-level controls into a newly added Frame control. You must start over and replicate your application inside the Frame, as described in the link provided by Jerry.
  2. If your application requires the user to interact with both the containing worksheet’s cells and the ActiveX controls, you can’t easily define a fully-functional transparent Frame control through which the user can see cell values, let alone interact with them behind the frame.
So here’s a fairly complete solution for managing ActiveX controls’ Tab order using VBA code. It may look rather complicated at first but its use is actually very straightforward.

To experiment with this example:

  • Open a new workbook.

  • On Sheet1, add three ActiveX controls: a command-button, a text-box, and a second command-button. Retain their default names, which will be “CommandButton1”, “TextBox1”, and “CommandButton2”, respectively.

  • Open the VBE editor and copy the code below into the Sheet1 module.

  • On the worksheet, select one of the controls and then use the Tab and/or Enter keys to experiment with moving from control to control.

  • In this version, if you use the Enter key on a command-button, it will just execute the button’s Click event but will not move to the next control. If that behavior is desired, it can be easily added.

  • To change the Tab order of managed controls, all you have to do is edit the TabOrderDef list! If you need to add a new control to be managed:
    • Add its name to the list in its desire tab-order position.
    • Add a KeyDown event handler for the newly managed control.
    • Copy the code from one of the exiting KeyDown event handlers.
    • IMPORTANT: Edit the "Set ThisCntrl = Me.YourControlName" statement so that it references the name of your newly managed control!

Code:
Option Explicit

'Declare the module-global tab-order control constants and variables

    'NOTE: See the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in
    'this implementation.
   
    Private Const fmShiftMask   As Integer = 1   'SHIFT was pressed.
    Private Const fmCtrlMask    As Integer = 2   'CTRL was pressed.
    Private Const fmAltMask     As Integer = 4   'ALT was pressed.
   
    Private Const ASCII_Enter   As Integer = 13
    Private Const ASCII_Tab     As Integer = 9
   
    'To change the Tab order of managed controls, just edit the following list:
   
    Private Const TabOrderDef   As String = _
            "CommandButton1," & _
            "TextBox1," & _
            "CommandButton2"                'Module-global so that all controls on this sheet can access it
   
    'NOTE: See the MoveFocus routine's header commentary regarding (re)initialization of the following variables:
       
    Private TabOrderNames    As Variant     'Module-global so that all controls on this sheet can access it
    Private TabOrderCntrls() As OLEObject   '   "

    Private Const ChkBoxType As String = "CheckBox"
    Private Const CmdBtnType As String = "CommandButton"
    Private Const OptBtnType As String = "OptionButton"
    Private Const TxtBoxType As String = "TextBox"


'=================================================================================================================
'                                           ActiveX/OLE Control Event Handlers
'=================================================================================================================

Private Sub CommandButton1_Click()
    MsgBox "CommandButton1 was clicked"
End Sub

Private Sub CommandButton1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton1   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
   
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub CommandButton2_Click()
    MsgBox "CommandButton2 was clicked"
End Sub

Private Sub CommandButton2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.CommandButton2   'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
   
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub

Private Sub TextBox1_LostFocus()
    If TextBox1.Value <> vbNullString Then MsgBox "TextBox1 contains: '" & TextBox1.Value & "'"
End Sub

Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    'NOTE: Use the KeyDown (or KeyUp) event because it is triggered for action keys like Enter and Tab, and also
    'because it provides the Shift/Ctrl/Alt key status.

    Dim ThisCntrl As Object
    Set ThisCntrl = Me.TextBox1         'IMPORTANT: Must be customized to be this event handler's parent control!

    Static TabIdx As Integer            'Static so its value is persistent, but is local to this event handler
   
    Call DoActionKey(KeyCode, ThisCntrl, TabIdx, Shift)
End Sub


'=================================================================================================================
'                                           Module Subroutines and Functions
'=================================================================================================================

Sub DoActionKey(KeyCode As MSForms.ReturnInteger, Cntrl As Object, ByRef TabIndex As Integer, Shift As Integer)
    '
    'Checks whether the specified key code is an action key (e.g. Enter or Tab) and, if so, initiates the
    'appropriate action, depending on the type of the specified control and, possibly, its property settings.
    '
    'In this implementation:
    '
    '   * Tab key: regardless of the control's type, the focus is moved to the previous or next control in the
    '     globally defined tab-sequence order, depending on whether the Shift key was also pressed or not.  See
    '     the MoveFocus routine's header commentary for specifics about how the tab-sequence is managed in this
    '     implementation.
    '
    '   * Enter key:
    '       + If the control is a commmand button, then that button's Click event handler is called to simulate a
    '         button click.
    '
    '       + If the control is a text box:
    '           - If it is a single-line text box or its new-line Enter key behavior is disabled, then the Enter
    '             key press is treated like an un-shifted Tab key and focus is moved forward to the next control
    '             in the globally defined tab-sequence order.
    '
    '           - If it is a multi-line text box and its new-line Enter key behavior is enabled, then the Enter
    '             key press remains unprocessed by this routine and is thereby allowed to function as a new-line
    '             operation in the multi-line text box.
    '
    'NOTE: This routine may need to be customized for a different implementation.
    '
    'PARAMETERS:
    '
    '   KeyCode     The key code to be checked for being an action-key code.
    '
    '   Cntrl       The ActiveX/OLE control object from which the key code was generated due to its having focus
    '               at the time of the user key-press.
    '
    '   TabIndex    The tab-index value of the control that generated the key code.  See the MoveFocus routine's
    '               header commentary regarding (re)initialization of thie (ByRef) parameter.
    '
    '   Shift       The state of the Shift, Ctrl, and Alt keys at the time of the user's key press action.  Is a
    '               bit array defined by the constants fmShiftMask, fmCtrlMask, and fmAltMask.
    '
    '*************************************************************************************************************
   
    Select Case KeyCode
    Case ASCII_Tab
        Dim Backward As Boolean
        Backward = (Shift And fmShiftMask)  'Bit-wise "And" converted to Boolean value
   
        Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex, Backward:=Backward)
   
    Case ASCII_Enter
        Select Case TypeName(Cntrl)
        Case TxtBoxType
            If Not (Cntrl.EnterKeyBehavior And Cntrl.MultiLine) Then
                'The Textbox either isn't multi-line or its new-line Enter key behavior is disabled, so
                'treat the enter key like a Tab key and move to the next control in the tab-order.
               
                Call MoveFocus(Cntrl:=Cntrl, TabIndex:=TabIndex)
            End If
        Case Else 'CmdBtnType (is the only alternative in this implementation)
            'This control is a command button, so treat an Enter key press like a click event.
       
            On Error Resume Next    'In case the specified command button has no Click event handler defined
            Application.Run Me.CodeName & "." & Cntrl.Name & "_Click"
            On Error GoTo 0
        End Select
    End Select
End Sub

Sub MoveFocus(Cntrl As Object, ByRef TabIndex As Integer, Optional Backward As Boolean)
    '
    'Moves the focus to the next or previous ActiveX/OLE control listed in the (global) TabOrderCntrls list,
    'depending on whether the Backward paramater is False (default) or set to True by the calling code.  The
    'next/previous control is determined relative to the specified tab-index value, which defines the calling
    'control's position in the TabOrderCntrls list.
    '
    'PARAMETERS:
    '
    '   Cntrl       The ActiveX/OLE control object from which the focus is to be moved.
    '
    '   TabIndex    The tab-index value of the control from which the focus is to be moved.  If uninitialized
    '               (value = 0) then it is (re)initialized by searching the (global) TabOrderNames array for
    '               the specified control-name and sets this (ByRef) parameter, and thereby the calling event
    '               handler's corresponding variable argument, to be its found position in that array.
    '
    '   Backward    If set to True, the focus is moved backward.  If False (default) then the focus is moved
    '               forward.
    '
    'NOTE: If necessary, the global arrays TabOrderNames and TabOrderCntrls are (re)initialized before processing
    '      the move-focus event, using the root-definition of the tab order defined by the (global) control-name
    '      list, TabOrderDef.
    '
    '*************************************************************************************************************
   
    Dim i As Integer    'Reusable loop counter

    'Check whether all variables are initialized
   
    If IsEmpty(TabOrderNames) Then
        'The (global) tab order control-names list is uninitialized, so initialize it using the TabOrderDef list
        'of the constrols to be included in move-focus control system.
       
        TabOrderNames = Split(TabOrderDef, ",")
        ReDim Preserve TabOrderNames(1 To UBound(TabOrderNames) + 1) 'Convert from zero-based to one-based array
   
        If Not IsInitArray(TabOrderCntrls) Then
            'The (global) tab order control-objects list is also uninitialized, so initialize it.
           
            ReDim TabOrderCntrls(1 To UBound(TabOrderNames)) 'First, allocate the necessary number of entries
           
            'Check whether each ActiveX/OLE control on the specified control's parent worksheet is included in the
            'tab order control-names list and, if so, add them to the tab order control-objects list in the same
            'position as in the tab order control-names list.
           
            Dim ChkCntrl As OLEObject
            Dim WkSh As Worksheet
            Set WkSh = Cntrl.Parent
           
            For Each ChkCntrl In WkSh.OLEObjects
                'Search for this next worksheet control in the tab order control-names list
   
                For i = LBound(TabOrderNames) To UBound(TabOrderNames)
                    If TabOrderNames(i) = ChkCntrl.Name Then Exit For   'If found, exit search loop
                Next i
                If i <= UBound(TabOrderNames) Then
                    'This control is on the tab order list, so add it to the tab order control-object list in the
                    'corresponding position.
                   
                    Set TabOrderCntrls(i) = ChkCntrl
                End If
            Next ChkCntrl
        End If
    End If
   
    If TabIndex = 0 Then
        'The calling control's tab index value is uninitialized, so initialize it by finding the control name's
        'position in the tab order control-names list.
       
        Dim CntrlName As String
        CntrlName = Cntrl.Name

        For i = LBound(TabOrderNames) To UBound(TabOrderNames)
            If TabOrderNames(i) = CntrlName Then Exit For   'If found, exit search loop
        Next i
        If i > UBound(TabOrderNames) Then
            'Didn't find it, so display an explanatory error message and then abort
           
            Call MsgBox("The ActiveX control '" & CntrlName & _
                        "' was not found in the 'TabOrderDef' control list.", _
                        vbOKOnly + vbCritical, "PROGRAMMING ERROR")
            Exit Sub
        End If
       
        TabIndex = i    'Found it
    End If

    'Do the move-focus operation
   
    If Backward Then
        If TabIndex > LBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex - 1).Activate           'Move to the previous control in the list
        Else
            TabOrderCntrls(UBound(TabOrderCntrls)).Activate 'Wrap-around to the last control in the list
        End If
    Else
        If TabIndex < UBound(TabOrderCntrls) Then
            TabOrderCntrls(TabIndex + 1).Activate           'Move to the next control in the list
        Else
            TabOrderCntrls(LBound(TabOrderCntrls)).Activate 'Wrap-around to the first control in the list
        End If
    End If
End Sub

Public Function IsInitArray(CheckArray) As Boolean
    'Returns True if the specified array argument is an initialized array, False otherwise.  If the specified
    'argument is not an array variable, then False is returned so that the programmer will discover the
    'misapplication of this function as a result of unexpected behavior of the algorithm in which it is used.

    Dim UB As Long

    On Error Resume Next
    UB = UBound(CheckArray)
    IsInitArray = (UB > -1 And Err = 0)   'NOTE: must check value of UB, as described below*
    On Error GoTo 0

    ' * because Split() has a "bug" that can result in an uninitialized array for which UBound returns -1
    ' without throwing an error.  e.g. Split(vbNullString, ",").  I put "bug" in quotes because that is how
    ' Ubound() should always work on an uninitialized array but, unfortunately, it doesn't, except as a
    ' result of the Split() anomaly.
End Function
I know this is a really old thread, but I just wanted to thank you for this!! I have spent hours and hours testing and searching and testing some more trying to figure out a way to tab from one control to another in a worksheet and in a matter of minutes after finding your response, I had it up and running. THANK YOU!!!
 
Upvote 0

Forum statistics

Threads
1,223,234
Messages
6,170,891
Members
452,366
Latest member
TePunaBloke

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top