Hello, in an UserControl that is a control container, I want to send the focus next control outside the UserControl (or previous one).
I found that in .Net there is a function SelectNextControl that I think does that (just for reference).
How can I do that in VB6?
I can get what controls have their windows with WS_TABSTOP style, but I don't know how to get their TabIndex with API.
I made some code that navigates the child windows and it seemed to work, but then I realized that it gets the next (or previous) window in the Z-Order and not in the TabIndex.
Here is that code (just for reference):
Code:
Private Sub SetFocusToNextControl(nForward As Boolean)
Dim iHwnd As Long
Dim iFb As Boolean
Dim iGW_N As Long
Dim iGW_F As Long
If nForward Then
iGW_N = GW_HWNDPREV
iGW_F = GW_HWNDLAST
Else
iGW_N = GW_HWNDNEXT
iGW_F = GW_HWNDFIRST
End If
iHwnd = mUserControlHwnd
iHwnd = GetWindow(iHwnd, iGW_N)
If iHwnd = 0 Then
iHwnd = GetWindow(mUserControlHwnd, iGW_F)
iFb = True
End If
If iHwnd <> 0 Then
Do Until ((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)
iHwnd = GetWindow(iHwnd, iGW_N)
If iHwnd = 0 Then
If iFb Then Exit Do
iHwnd = GetWindow(mUserControlHwnd, iGW_F)
iFb = True
End If
Loop
End If
If iHwnd <> 0 Then
SetFocusAPI iHwnd
End If
End Sub
I'm traying to do it with API, but if the TabIndex is not a property of the windows and just something that VB6 stores internally, I'll have to do it with the Parent.Controls collection.
But before going that route, I wanted to ask if someone knows how to do it.
Thanks.
You can apply it as many times as the number of controls you have in you container minus the position of the current one has the focus!
Thanks for your answer.
It is not so easy. The contained controls can have any TabIndex indeed (out of order in regard to the controls ouside the UserControl) and also can have the TabStop set to False, or be windowless.
Besides, I wouldn't like to use a keyboard simulation approach, but thank you anyway.
I finally made it using the Parent.Controls collection, but will keep this thread "open" to see if someone have some knowledge about the issue (getting next/previous TabIndex hWnd).
Here is my current code:
Code:
Private Sub SetFocusToNextControl(nForward As Boolean)
Dim iHwnd As Long
Dim iControls As Object
Dim iTi As Long
Dim iCtl As Control
Dim iCtlFound As Control
Dim iTs As Boolean
Dim iA0 As Boolean
Dim iTi2 As Long
On Error Resume Next
Set iControls = UserControl.Parent.Controls
On Error GoTo 0
If iControls Is Nothing Then Exit Sub
iTi = -1
On Error Resume Next
iTi = UserControl.Extender.TabIndex
On Error GoTo 0
If iTi = -1 Then Exit Sub
On Error Resume Next
If nForward Then
iHwnd = 0
iTi = iTi + 1
Do Until iHwnd <> 0
Set iCtlFound = Nothing
For Each iCtl In iControls
iTi2 = -1
iTi2 = iCtl.TabIndex
If iTi2 = iTi Then
Set iCtlFound = iCtl
Exit For
End If
Next
If iCtlFound Is Nothing Then
iTi = 0
If iA0 Then Exit Sub
iA0 = True
Else
iHwnd = 0
iHwnd = iCtlFound.hWnd
If iHwnd <> 0 Then
iTs = False
iTs = iCtlFound.TabStop
If iTs Then
If Not (((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)) Then
iHwnd = 0
End If
Else
iHwnd = 0
End If
End If
iTi = iTi + 1
End If
Loop
Else ' move focus to the previous control
iHwnd = 0
iTi = iTi - 1
Do Until iHwnd <> 0
Set iCtlFound = Nothing
For Each iCtl In iControls
iTi2 = -1
iTi2 = iCtl.TabIndex
If iTi2 = iTi Then
Set iCtlFound = iCtl
Exit For
End If
Next
If iCtlFound Is Nothing Then
iTi = MaxTabIndex(iControls)
If iA0 Then Exit Sub
iA0 = True
Else
iHwnd = 0
iHwnd = iCtlFound.hWnd
If iHwnd <> 0 Then
iTs = False
iTs = iCtlFound.TabStop
If iTs Then
If Not (((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)) Then
iHwnd = 0
End If
Else
iHwnd = 0
End If
End If
iTi = iTi - 1
End If
Loop
End If
If iHwnd <> 0 Then
SetFocusAPI iHwnd
End If
End Sub
Private Function MaxTabIndex(nControls As Object) As Long
Dim iCtl As Control
Dim iTs As Boolean
Dim iTi As Long
MaxTabIndex = 0
On Error Resume Next
For Each iCtl In nControls
iTs = False
iTs = iCtl.TabStop
If iTs Then
iTi = -1
iTi = iCtl.TabIndex
If iTi > MaxTabIndex Then
MaxTabIndex = iTi
End If
End If
Next
End Function
There is at least one flaw with your logic. You are not checking whether or not the control you are trying to set focus to is enabled and visible.
Another simple option would be to toggle the enabled property of the active control. When you disable the active control VB moves to the next control in the tab order, forward navigation only...
Code:
Dim oCtrl As Control
Set oCtrl = ActiveControl
oCtrl.Enabled = False
oCtrl.Enabled = True
Edited: The above would not work for a picturebox that has TabStop=True and also contains controls. Toggling its Enabled property would skip over any controls it contained. The solution is simple. Don't set the TabStop to True for containers. Typically a container that contains other controls, having its TabStop property set to True is pointless. In these cases, the container events are not monitored, are they?
And yet another option would be to create a collection of objects in sorted tab order. Find the current control in the collection (key = ObjPtr(added control)) and the next/previous item in the collection with TabStop/Enabled/Visible=True would be the one you want to set focus to. This does require a one-time setup at form load, unless you are dynamically adding controls or changing TabStop/TabIndex.
Last edited by LaVolpe; Nov 18th, 2017 at 12:52 PM.
Insomnia is just a byproduct of, "It can't be done"
There is at least one flaw with your logic. You are not checking whether or not the control you are trying to set focus to is enabled and visible.
It does at the line:
Code:
If Not (((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)) Then
Originally Posted by LaVolpe
Another simple option would be to toggle the enabled property of the active control. When you disable the active control VB moves to the next control in the tab order, forward navigation only...
I need forward and backward.
Originally Posted by LaVolpe
Code:
Dim oCtrl As Control
Set oCtrl = ActiveControl
oCtrl.Enabled = False
oCtrl.Enabled = True
Edited: The above would not work for a picturebox that has TabStop=True and also contains controls. Toggling its Enabled property would skip over any controls it contained.
That's what I need anyway (to send the focus to the next control outside the container).
Originally Posted by LaVolpe
The solution is simple. Don't set the TabStop to True for containers.
In this case I need it because the container can have focus (and do something).
Originally Posted by LaVolpe
Typically a container that contains other controls, having its TabStop property set to True is pointless.
Yes, I agree, typically.
Originally Posted by LaVolpe
In these cases, the container events are not monitored, are they?
And yet another option would be to create a collection of objects in sorted tab order. Find the current control in the collection (key = ObjPtr(added control)) and the next/previous item in the collection with TabStop/Enabled/Visible=True would be the one you want to set focus to. This does require a one-time setup at form load, unless you are dynamically adding controls or changing TabStop/TabIndex.
Yes, but the TabOrder and TabStop can be changed at run time (althought very unusual) so the right way would be to build the list every time. Not too different from what I did. In fact that was my first idea but I would have needed a way to sort the list (bubble sort or something), so i decided to go for the code I wrote.
Option Explicit
Private Declare Function SetFocusAPI Lib "user32" _
Alias "SetFocus" ( _
ByVal hwnd As Long) As Long
Private Sub Form_Load()
SetFocusAPI Combo1.hwnd
End Sub
Private Sub Text1_LostFocus()
SetFocusAPI Command1.hwnd
End Sub
regards
Chris
to hunt a species to extinction is not logical !
since 2010 the number of Tigers are rising again in 2016 - 3900 were counted. with Baby Callas it's 3901, my wife and I had 2-3 months the privilege of raising a Baby Tiger.
I added a function to skip the controls that are inside:
Code:
Private Sub SetFocusToNextControl(nForward As Boolean)
Dim iHwnd As Long
Dim iControls As Object
Dim iTi As Long
Dim iCtl As Control
Dim iCtlFound As Control
Dim iTs As Boolean
Dim iA0 As Boolean
Dim iTi2 As Long
On Error Resume Next
Set iControls = UserControl.Parent.Controls
On Error GoTo 0
If iControls Is Nothing Then Exit Sub
iTi = -1
On Error Resume Next
iTi = UserControl.Extender.TabIndex
On Error GoTo 0
If iTi = -1 Then Exit Sub
On Error Resume Next
If nForward Then
iHwnd = 0
iTi = iTi + 1
Do Until iHwnd <> 0
Set iCtlFound = Nothing
For Each iCtl In iControls
iTi2 = -1
iTi2 = iCtl.TabIndex
If iTi2 = iTi Then
Set iCtlFound = iCtl
Exit For
End If
Next
If iCtlFound Is Nothing Then
iTi = 0
If iA0 Then Exit Sub
iA0 = True
Else
iHwnd = 0
iHwnd = iCtlFound.hWnd
If iHwnd <> 0 Then
iTs = False
iTs = iCtlFound.TabStop
If iTs Then
If Not (((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)) Then
iHwnd = 0
ElseIf IsControlWithinUserControlContainer(iCtlFound) Then
iHwnd = 0
End If
Else
iHwnd = 0
End If
End If
iTi = iTi + 1
End If
Loop
Else ' move focus to the previous control
iHwnd = 0
iTi = iTi - 1
Do Until iHwnd <> 0
Set iCtlFound = Nothing
For Each iCtl In iControls
iTi2 = -1
iTi2 = iCtl.TabIndex
If iTi2 = iTi Then
Set iCtlFound = iCtl
Exit For
End If
Next
If iCtlFound Is Nothing Then
iTi = MaxTabIndex(iControls)
If iA0 Then Exit Sub
iA0 = True
Else
iHwnd = 0
iHwnd = iCtlFound.hWnd
If iHwnd <> 0 Then
iTs = False
iTs = iCtlFound.TabStop
If iTs Then
If Not (((GetWindowLong(iHwnd, GWL_STYLE) And WS_TABSTOP) <> 0) And (IsWindowVisible(iHwnd) <> 0) And (IsWindowEnabled(iHwnd) <> 0)) Then
iHwnd = 0
ElseIf IsControlWithinUserControlContainer(iCtlFound) Then
iHwnd = 0
End If
Else
iHwnd = 0
End If
End If
iTi = iTi - 1
End If
Loop
End If
If iHwnd <> 0 Then
SetFocusAPI iHwnd
End If
End Sub
Private Function MaxTabIndex(nControls As Object) As Long
Dim iCtl As Control
Dim iTs As Boolean
Dim iTi As Long
MaxTabIndex = 0
On Error Resume Next
For Each iCtl In nControls
iTs = False
iTs = iCtl.TabStop
If iTs Then
iTi = -1
iTi = iCtl.TabIndex
If iTi > MaxTabIndex Then
MaxTabIndex = iTi
End If
End If
Next
End Function
Private Function IsControlWithinUserControlContainer(nCtl As Control) As Boolean
Dim iContainer As Object
Dim iControls As Object
Dim iParent As Object
Dim iContainer2 As Object
On Error Resume Next
Set iParent = UserControl.Parent
Set iControls = iParent.Controls
If iParent Is Nothing Or iControls Is Nothing Then Exit Function
Set iContainer = Nothing
Set iContainer = nCtl.Container
Do Until (iContainer Is Nothing) Or (iContainer Is iParent)
If iContainer Is UserControl.Extender Then
IsControlWithinUserControlContainer = True
Exit Function
End If
Set iContainer2 = iContainer
Set iContainer = Nothing
Set iContainer = iContainer2.Container
Loop
End Function
The function not only checks if the control is contained in the UserControl, but also if the control is inside other control container that is in the UserControl.
For the moment, yes, but I asked the question with the intention to find out the "proper" way of doing it with API's.
Or... to confirm that it can't be done with API's.