-
How to loop through all INDIVIDUAL controls on a form especially with control arrays
Hi
I am trying to write a generic procedure to be called before and after any and all processes to do one of the following two tasks based on an input parameter:
1. (When called in the pre-process mode): Save the Enabled properties of all controls on the form in two arrays (one for the name of the control, and another for the value of the Enabled property), and then Set the Enabled properties of all these controls to False (and therefore disabling all of them)
2. (When called in the post-process mode): Restore the Enabled properties of all the controls on the form to their initial values (as saved above).
This procedure is in a bas module, so that all vbp projects can attach that bas module to themselves and use this procedure.
The way my generic procedure identifies all the controls on the form is to use the following structure:
Code:
For Each Ctrl In TheForm.Controls
...
Next
My generic procedure works perfectly when all the controls on the form are singular not arrays of controls (control arrays).
My procedure does not properly distinguish each item within a control array separately.
How can I fix my procedure to loop through not just each control and each control array (as it currently does), but to also loop through each element or each item of the control array (each individual control that is part of a control array)?
Here is my procedure as I have currently coded it:
Code:
Public Sub ToggleControlsEnabled( _
ByRef TheForm As Form, _
ByRef ToggleCtrlsEnabledDisabled As ToggleCtrlsEnabledDisabledType, _
ByRef BfrStateCtrlName() As String, _
ByRef BfrStateCtrlEnabledSetting() As Boolean _
)
Dim Ctrl As Control
Dim CtrlsEnabledSetting As Boolean
Dim i As Long
Dim L As Long
'Init
If ToggleCtrlsEnabledDisabled = tcedToggleDisablePreProcess Then
CtrlsEnabledSetting = False ' Preparations for pre-process toggling
Erase BfrStateCtrlName()
Erase BfrStateCtrlEnabledSetting()
Else
CtrlsEnabledSetting = True ' Preparations for post-process toggling
End If
'Main: Pre-process toggling: Toggling the controls disabled
If ToggleCtrlsEnabledDisabled = tcedToggleDisablePreProcess Then
For Each Ctrl In TheForm.Controls
If (TypeOf Ctrl Is CommandButton) Or (TypeOf Ctrl Is TextBox) Or (TypeOf Ctrl Is Label) Or (TypeOf Ctrl Is CheckBox) Or (TypeOf Ctrl Is OptionButton) Or (TypeOf Ctrl Is SSTab) Or (TypeOf Ctrl Is Frame) Then
L = LengthOfArray(BfrStateCtrlName())
ReDim Preserve BfrStateCtrlName(L) As String
ReDim Preserve BfrStateCtrlEnabledSetting(L) As Boolean
BfrStateCtrlName(L) = Ctrl.Name
BfrStateCtrlEnabledSetting(L) = Ctrl.Enabled
End If
Next
For Each Ctrl In TheForm.Controls
If (TypeOf Ctrl Is CommandButton) Or (TypeOf Ctrl Is TextBox) Or (TypeOf Ctrl Is Label) Or (TypeOf Ctrl Is CheckBox) Or (TypeOf Ctrl Is OptionButton) Or (TypeOf Ctrl Is SSTab) Or (TypeOf Ctrl Is Frame) Then
Ctrl.Enabled = CtrlsEnabledSetting
End If
Next
End If
'Main: Post-process toggling: Toggling the controls ensabled
If ToggleCtrlsEnabledDisabled = tcedToggleEnablePostProcess Then
L = LengthOfArray(BfrStateCtrlName())
For Each Ctrl In TheForm.Controls
If (TypeOf Ctrl Is CommandButton) Or (TypeOf Ctrl Is TextBox) Or (TypeOf Ctrl Is Label) Or (TypeOf Ctrl Is CheckBox) Or (TypeOf Ctrl Is OptionButton) Or (TypeOf Ctrl Is SSTab) Or (TypeOf Ctrl Is Frame) Then
For i = 0 To L - 1
If UCase(Ctrl.Name) = UCase(BfrStateCtrlName(i)) Then
Ctrl.Enabled = BfrStateCtrlEnabledSetting(i)
Exit For
End If
Next i
End If
Next
End If
'Final
DoEvents
End Sub
Please advise.
Thanks
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Code:
Sub PreProces(TheForm As Form)
For Each ctrl In TheForm
ctrl.Tag = ctrl.Enabled
ctrl.Enabled = False
Next
End Sub
Sub PostProces(TheForm As Form)
For Each ctrl In TheForm
ctrl.Enabled = ctrl.Tag
Next
End Sub
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
It has been a while, but I think you can just set the form enabled to true or false as needed. While the controls do not visibly reflect disabled states they are not selectable. If all you are doing is preventing the user from interrupting a process that takes time this might be the best soution.
If that doesn't work then you can write a single procedure to do the job.
Realize though that not all controls have an enabled property so you have to allow for that.
Code:
Public Sub Enable_All(byval State as boolean)
On error resume next
For each C as control in Me.Controls
C.enabled =State
Next C
On error goto 0
End Sub
You can shorten that by toggling the enable state by using NOT
Code:
Public Sub Toggle_Enabled()
On error resume next
For each C as control in Me.Controls
C.enabled = Not(C.enabled)
Next C
On error goto 0
End Sub
Regarding detecting Control arrays. This was discussed in depth on another forum over 16 years ago.
Bottom line was that there was no way to automatically determine if a control is a control array.
You could create a sub class that has a custom property or use the tag property to identify them
or even simpler use a control naming convention that identifies them as control arrays.
e.g. txtBarCodeCA, btnTotalsCA, etc...
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Here are a pair of subroutines that disables all controls (both arrays & non-arrays) in the specified Form and then reverts them to their original Enabled state afterwards:
Code:
Option Explicit 'In a standard (.BAS) module
Private m_Ctrls As Collection
Public Sub DisableControls(ByRef Form As VB.Form)
Dim Ctrl As Control
If m_Ctrls Is Nothing Then Set m_Ctrls = New Collection
m_Ctrls.Add Form.ActiveControl, CStr(ObjPtr(Form))
On Error Resume Next
For Each Ctrl In Form
With Ctrl.Container
If Not .Enabled Then .Enabled = True
End With
m_Ctrls.Add Ctrl.Enabled, CStr(ObjPtr(Ctrl))
Next
For Each Ctrl In Form
Ctrl.Enabled = False
Next
On Error GoTo 0
End Sub
Public Sub RevertControls(ByRef Form As VB.Form)
Dim Value As Boolean, sKey As String, Ctrl As Control
If Not m_Ctrls Is Nothing Then On Error Resume Next Else Exit Sub
For Each Ctrl In Form
Value = Ctrl.Enabled
If Err = 0& Then
sKey = CStr(ObjPtr(Ctrl))
Ctrl.Enabled = m_Ctrls(sKey)
m_Ctrls.Remove sKey
Else
Err.Clear
End If
Next
sKey = CStr(ObjPtr(Form))
m_Ctrls(sKey).SetFocus
m_Ctrls.Remove sKey
On Error GoTo 0
If m_Ctrls.Count = 0& Then Set m_Ctrls = Nothing
End Sub
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
a tip of the hat to Bonnie, superb solution
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
I'm confused...
Quote:
Originally Posted by
Gruff
Regarding detecting Control arrays. This was discussed in depth on another forum over 16 years ago.
Bottom line was that there was no way to automatically determine if a control is a control array.
Gruff, if you could find that thread, it would be much appreciated. To me, this seems fairly trivial. Just paste following into default form with some controls thrown on it, some arrays, some not.
Code:
Option Explicit
Private Sub Form_Load()
Dim c As Control
For Each c In Me.Controls
Debug.Print c.Name, IsControlArray(c)
Next c
End Sub
Private Function IsControlArray(c As Control) As Boolean
Dim i As Long
On Error Resume Next
i = c.Index
IsControlArray = Err = 0
End Function
I even loaded some additional controls to a control array at runtime, and that worked correctly too. Are we talking about API created control? I just don't see the problem.
Regards,
Elroy
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Also, just an FYI, if you use the Object type, you can actually pass entire control arrays as a single argument. Here are a couple of examples where I do that.
Code:
Public Function SelectedOptIndex(opt As Object) As Variant
' Returns the index number of a selected option button
' in an option button array. Returns NULL if none selected.
Dim o As OptionButton
'
For Each o In opt
If o.Value Then
SelectedOptIndex = o.index
Exit Function
End If
Next o
SelectedOptIndex = Null
End Function
Public Function SelectedOptCaption(opt As Object) As Variant
' Returns caption of selected option button, or NULL if none selected.
Dim v As Variant
'
v = SelectedOptIndex(opt)
If IsNull(v) Then
SelectedOptCaption = Null
Else
SelectedOptCaption = opt(v).Caption
End If
End Function
Now, in those examples, it MUST be an option button control array or things will go haywire, but they serve my purposes.
And yes, I'll second the cudos to Bonnie's nice code. Be a bit careful with it though, as it'll only work on one form at a time. Furthermore, you are responsible to make sure you call DisableControls and then RevertControls, not calling either twice before the other is called. One small improvement might be another module level flag named mbControlsAreDisabled, and then not running DisableControls if it's already TRUE, and not running RevertControls if it's FALSE. Also, if someone actually had a lot of need for this, you might make the m_Ctrls variable a dynamic array, and then have a collection-per-form that was using it. It'd take a bit of work to get all that going, but not much.
Regards,
Elroy
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Hey Gruff,
I keep trying to think about what you may have meant.
Quote:
Originally Posted by
Gruff
Regarding detecting Control arrays. This was discussed in depth on another forum over 16 years ago.
Bottom line was that there was no way to automatically determine if a control is a control array.
Maybe you meant JUST using the control names. Maybe some of the names would reference control ARRAYS and others would reference the controls directly (not arrayed). Detecting the arrays from the non-arrays still seems pretty straightforward to me. Below is one way. Just create a Form with two pictureboxes (arrayed, named Picture1), and also a Textbox (not arrayed, named Text1).
Then paste in the following code and execute:
Code:
Option Explicit
Private Sub Form_Load()
Debug.Print ControlNameIsArray(Picture1)
Debug.Print ControlNameIsArray(Text1)
End Sub
Public Function ControlNameIsArray(c As Object) As Boolean
ControlNameIsArray = TypeName(c) = "Object"
End Function
VoilĂ ,
Elroy
EDIT: I could certainly make that ControlNameIsArray function more bullet-proof, but this illustrates a proof-of-concept.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
A few slight modifications to Elroy's function in Post #6 seems to be all that's needed to make it even more bulletproof:
Code:
Option Explicit 'Add a CommandButton & OptionButton to a blank Form. Set the OptionButton's Index to 0.
Private Function IsControlAnArray(ByVal Ctrl As Object) As Boolean
Dim i As Integer
On Error Resume Next
i = Ctrl.Index
IsControlAnArray = Err = 0& Or Err = 438& 'Object doesn't support this property or method
On Error GoTo 0
End Function
Private Sub Command1_Click()
Debug.Print IsControlAnArray(Command1) 'Prints False
Debug.Print IsControlAnArray(Option1) 'Prints True
Debug.Print IsControlAnArray(Option1(0)) 'Prints True
End Sub
Quote:
Originally Posted by
Elroy
Be a bit careful with it though, as it'll only work on one form at a time. Furthermore, you are responsible to make sure you call DisableControls and then RevertControls, not calling either twice before the other is called. One small improvement might be another module level flag named mbControlsAreDisabled, and then not running DisableControls if it's already TRUE, and not running RevertControls if it's FALSE. Also, if someone actually had a lot of need for this, you might make the m_Ctrls variable a dynamic array, and then have a collection-per-form that was using it. It'd take a bit of work to get all that going, but not much.
Actually, those pair of subroutines can handle multiple Forms at the same time, so there's really no need to make m_Ctrls an array. Both of them can also be called as many times in a row with no problems, so again, introducing another module-level flag is unnecessary.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Ahhhh, Bonnie, I didn't test, and after staring at it a bit more, I do see that you do perform an "Is Nothing" test. Sorry to wrongly critique your code.
Ahhh, and you took my two separate detection functions and combined them into one function. Very nice.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Quote:
Originally Posted by
Elroy
I keep trying to think about what you may have meant.
Well spank me rosy. I stand corrected. :)
I did find one of the posts, however the topic was not specifically about control arrays.
The on error wrapping the index property was mentioned.
As I said there were other posts, but memory is a funny thing,
2000 to 2004 was sometime ago, and I've slept since then. :D
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Hi.
Thanks a lot to EVERYONE in here for all your great help and advice.
Also, thanks to Bonnie West for his great code (the two procedures "DisableControls" and "RevertControls") in Post #4.
I tried those two procedures and they work ALMOST perfectly, but not 100% perfectly.
There is one problem that I am demonstrating in here via these print screens:
Before process: http://i.imgur.com/2XsLAtp.jpg
During process: http://i.imgur.com/cbUW5MQ.jpg
After process: http://i.imgur.com/N2EI8EH.jpg
Why is there that problem?
Any idea as to how I can fix it?
Thanks
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Didn't test this, but maybe that happens because of controls tab order? Anyway - to prevent, ie. to explicitly set the order (if tab order setting wouldn't do), you need to expand those two functions to explicitly enable/disable control groups.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Quote:
Originally Posted by
IliaPreston
I can tell you exactly what it is, and I can give you pointers on how to fix it.
When you disable a frame that contains controls, all of its contained children will report as disabled via the .Enabled property, even if they're not explicitly disabled. If you don't believe me, start a project, put a frame on it, and then put two checkboxes on the frame. Also put a button on the form. Then, throw this code into your form, and run it, clicking the button repeatedly, watching your debug window.
Code:
Option Explicit
Private Sub Form_Load()
Check1.Enabled = True
Check2.Enabled = False
End Sub
Private Sub Command1_Click()
Frame1.Enabled = Not Frame1.Enabled
Debug.Print Check1.Enabled, Check2.Enabled
End Sub
I'll let you sort out what you're seeing, but what I say is true.
Now, here's the real "got-cha". Even though that Check2 appears to be toggling its enabled status, if you EXPLICITLY set it to false, it'll then STAY false, even when the frame is re-enabled. Weird stuff. Apparently, when checking the enabled status of a control in a container, it reports something like (control.Enabled AND container.Enabled). However, when you set it, it's explicitly setting the control.Enabled status. And that's why IliaPreston is seeing what he's seeing.
The only (somewhat easy) way I'd know to fix it is to smarten up Bonnie's code a bit. Possibly go through, disabling all the control, but skipping the frames, pictureboxes, and sstab controls on the first pass. Then, make a second pass, disabling these as well. I don't think any smartening up would be required of the RevertControls. It should work fine as it is. If Bonnie is still following this thread, maybe she'll do it for you.
Just for grins, here's a piece of code I often use that's related to this problem.
Code:
Public Property Let FrameAndChildrenEnabled(fra As Frame, bEnableStatus As Boolean)
' This enables/disables all child controls of a specific frame.
Dim i As Integer
Dim frm As Form
'
Set frm = fra.Parent
For i = 0 To frm.Controls.Count - 1
If bControlHasContainer(frm.Controls(i)) Then
If frm.Controls(i).Container Is fra Then
'
' Recursion to handle frames within frames.
If TypeOf frm.Controls(i) Is Frame Then
FrameAndChildrenEnabled(frm.Controls(i)) = bEnableStatus
Else
On Error Resume Next ' not all controls have an enabled property
frm.Controls(i).Enabled = bEnableStatus
On Error GoTo 0
End If
End If
End If
Next i
fra.Enabled = bEnableStatus
End Property
Everyone Take Care,
Elroy
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Quote:
Originally Posted by
IliaPreston
Why is there that problem?
As already pointed out by Elroy, when a control container such as a Frame control is disabled, all of its child controls are disabled as well (their Enabled property becomes False). VB6, however, "tricks" the child controls so that they continue painting themselves in the state that they were just before their container was disabled. (See Allowing Your Control to be Enabled and Disabled to learn more about this trickery.)
My original code did not capture the true "internal" Enabled state of nested controls and was instead storing what the controls' Extender object was reporting (which was False when a control's container was disabled). So, when the nested controls were being restored to their previous Enabled state, controls whose containers were disabled remained disabled because that's what the controls' Extender object previously reported.
Quote:
Originally Posted by
IliaPreston
Any idea as to how I can fix it?
Another way of rectifying this issue is by enabling the control's container first before retrieving its Enabled property. This ensures that the control's internal Enabled state is the one being accessed rather than the Extender's. The code in Post #4 has now been amended with this fix. It also now has the ability to set the focus back to the last active control in the specified Form.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Bonnie, that could be rather tricky if you have nested containers. As you can see, in that little FrameAndChildrenEnabled property I posted, it took a bit of recursion to get it done.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Quote:
Originally Posted by
Elroy
Bonnie, that could be rather tricky if you have nested containers. As you can see, in that little FrameAndChildrenEnabled property I posted, it took a bit of recursion to get it done.
In the tests I've just conducted, it seems to work well even with deeply nested assorted containers with mixed Enabled properties. Because that code is not restricted to any specific container, looping through all the controls thus becomes less complicated and no recursion is required.
-
1 Attachment(s)
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Ok, my turn to tweak Bonnie's code. :p
It took a bit of recursion, but this should work to disable-and-store-state of all controls on a form. And then, it'll re-enable all the controls that weren't originally disabled. It'll also handle frames, pictureboxes, and sstab controls just fine, and it won't matter how deeply they're nested.
As a caution, the SSTab lines are commented out because I didn't know if it would be referenced.
I've included a little ZIPped sample project, but the BAS module code is as follows:
Code:
Option Explicit 'In a standard (.BAS) module
'
Private m_Ctrls As Collection
'
Public Sub DisableControls(ByRef Form As VB.Form)
Dim Ctrl As Control
'
If m_Ctrls Is Nothing Then Set m_Ctrls = New Collection
'
m_Ctrls.Add Form.ActiveControl, CStr(ObjPtr(Form)) ' For resetting focus.
'
For Each Ctrl In Form
If Not bControlHasContainer(Ctrl) Then ' These are handled through recursion.
If TypeOf Ctrl Is VB.Frame Then
DisableContainerControls Form, Ctrl
ElseIf TypeOf Ctrl Is VB.PictureBox Then
DisableContainerControls Form, Ctrl
'ElseIf TypeOf Ctrl Is tabdlg.SSTab Then ' The SSTab may not be referenced in the project.
' DisableContainerControls Form, Ctrl
Else
If bControlHasEnabled(Ctrl) Then
m_Ctrls.Add Ctrl.Enabled, CStr(ObjPtr(Ctrl))
Ctrl.Enabled = False
End If
End If
End If
Next Ctrl
End Sub
Private Sub DisableContainerControls(ByRef Form As VB.Form, ByRef Contain As VB.Control)
Dim Ctrl As Control
'
For Each Ctrl In Form
If bControlHasContainer(Ctrl) Then
If Ctrl.Container Is Contain Then
'
' Recursion to handle containers within containers.
If TypeOf Ctrl Is VB.Frame Then
DisableContainerControls Form, Ctrl
ElseIf TypeOf Ctrl Is VB.PictureBox Then
DisableContainerControls Form, Ctrl
'ElseIf TypeOf Ctrl Is tabdlg.SSTab Then ' The SSTab may not be referenced in the project.
' DisableContainerControls Form, Ctrl
Else
If bControlHasEnabled(Ctrl) Then
m_Ctrls.Add Ctrl.Enabled, CStr(ObjPtr(Ctrl))
Ctrl.Enabled = False
End If
End If
End If
End If
Next Ctrl
'
m_Ctrls.Add Contain.Enabled, CStr(ObjPtr(Contain))
Contain.Enabled = False
End Sub
Private Function bControlHasEnabled(Ctrl As Control) As Boolean
Dim b As Boolean
'
On Error Resume Next
b = Ctrl.Enabled
bControlHasEnabled = Err = 0
End Function
Private Function bControlHasContainer(Ctrl As Control) As Boolean
Dim TheContainer As Control
'
On Error Resume Next
Set TheContainer = Ctrl.Container
bControlHasContainer = Err = 0
End Function
Public Sub RevertControls(ByRef Form As VB.Form)
Dim sKey As String
Dim Ctrl As Control
'
If m_Ctrls Is Nothing Then Exit Sub
'
For Each Ctrl In Form
sKey = CStr(ObjPtr(Ctrl))
Ctrl.Enabled = m_Ctrls(sKey)
m_Ctrls.Remove sKey
Next Ctrl
'
sKey = CStr(ObjPtr(Form))
m_Ctrls(sKey).SetFocus
m_Ctrls.Remove sKey
'
If m_Ctrls.Count = 0& Then Set m_Ctrls = Nothing ' Now put focus back.
End Sub
EDIT: I cleaned it up just a touch, but this touch-of-clean-up isn't done in the ZIP file.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Ahhh, I was posting while you were. I admit that I didn't thoroughly test your code though. Maybe the recursion isn't needed.
EDIT: Actually, I plugged in your code into my test project (attached as ZIP above), and you're right. It does seem to work just fine. So, I guess the recursion isn't needed.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
The following non-recursive routines are based on the code in Post #4. Rather than disabling all controls in the specified Form, the code below only disables child controls of the specified container control.
Code:
Option Explicit 'In a standard (.BAS) module
Private m_Ctrls As Collection
Public Sub DisableContainer(ByRef Container As Control)
Dim Ctrl As Control, Ctrls As Collection
If m_Ctrls Is Nothing Then Set m_Ctrls = New Collection
m_Ctrls.Add Container.Enabled, CStr(ObjPtr(Container))
Set Ctrls = New Collection
Ctrls.Add Container
On Error Resume Next
For Each Ctrl In Container.Parent
If IsDescendantOf(Container, Ctrl) Then
With Ctrl.Container
If Not .Enabled Then .Enabled = True
End With
Err.Clear
m_Ctrls.Add Ctrl.Enabled, CStr(ObjPtr(Ctrl))
If Err = 0& Then Ctrls.Add Ctrl
End If
Next
For Each Ctrl In Ctrls
Ctrl.Enabled = False
Next
On Error GoTo 0
End Sub
Public Sub RevertContainer(ByRef Container As Control)
Dim Value As Boolean, sKey As String, Ctrl As Control
If Not m_Ctrls Is Nothing Then On Error Resume Next Else Exit Sub
sKey = CStr(ObjPtr(Container))
Container.Enabled = m_Ctrls(sKey)
m_Ctrls.Remove sKey
For Each Ctrl In Container.Parent
If IsDescendantOf(Container, Ctrl) Then
Value = Ctrl.Enabled
If Err = 0& Then
sKey = CStr(ObjPtr(Ctrl))
Ctrl.Enabled = m_Ctrls(sKey)
m_Ctrls.Remove sKey
Else
Err.Clear
End If
End If
Next
On Error GoTo 0
If m_Ctrls.Count = 0& Then Set m_Ctrls = Nothing
End Sub
Private Function IsDescendantOf(ByRef Parent As Control, ByRef Child As Control) As Boolean
Dim Container As Object
Set Container = Child
Do: Set Container = Container.Container
If Container Is Parent Then IsDescendantOf = True: Exit Function
Loop Until Container Is Parent.Parent
End Function
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Thanks a lot for all the help and advice.
1. First of all thanks a lot to Bonnie West and Elroy for all the great code and advice that they provided. Also thanks a lot to everybody else for their contribution.
2. I implemented the code that Elroy provided:
Code:
Option Explicit
Private Sub Form_Load()
Check1.Enabled = True
Check2.Enabled = False
End Sub
Private Sub Command1_Click()
Frame1.Enabled = Not Frame1.Enabled
Debug.Print Check1.Enabled, Check2.Enabled
End Sub
And now it is clear that VB6 actually reports
Code:
Ctrl.Enabled And Container.Enabled
when the program tries to read Ctrl.Enabled
However, it is actually more precise to say that VB6 reports not just that, but this:
Code:
Ctrl.Enabled And Container.Enabled And Container.Container.Enabled And ... And Container.Container ... .Enabled (all the way to the topmost parent container)
I have tested this with a modification to that Elroy's above test case with multiple frames nested within one another (instead of just one frame). Here is a print screen:
http://i.imgur.com/h9BBsAC.jpg (Please ignore Check3 control array)
3. Having said that I viewed Bonnie West's code (the two procedures "DisableControls" and "RevertControls") with a great sense of mystery (as to why the recursion was not needed), until I built a test case to find out the order by which the "For Each Ctrl In Form" goes through all the controls.
In the procedures "DisableControls", I added a few lines of code to store the names of some of the controls and their Enabled properties before and after this:
Code:
With Ctrl.Container
If Not .Enabled Then .Enabled = True
End With
Here is the result of the process:
http://i.imgur.com/uzM0QdF.jpg
It is clear that there is no mystery anymore, because the "For Each Ctrl In Form" goes through the controls from the topmost parent down to the lower parents and eventually to the controls nested inside them.
That order or sequence by which the "For Each Ctrl In Form" goes through the controls is the reason why Bonnie West's code in post #4 works without recursion.
But, here is the big question:
Can we rest assured that "For Each Ctrl In Form" ALWAYS goes through the controls in that magical sequence?
Correct me if I am wrong, but I don't think there is a guarantee that that sequence is always followed. I guess that the sequence of "For Each Ctrl In Form" is probably supposed to be considered random.
Is it random, or is it guaranteed to follow that magical sequence that eliminates the need for recursion?
Any comments and advice on this will be greatly appreciated.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
I've actually got no idea of the order that the Controls collection presents the controls to you. Bonnie has certainly presented a unique solution and I have tremendous respect for her abilities.
However, I'm leery about not using recursion for a process that seems inherently recursive to me, such as nested control on containers that may be nested on containers that may be nested on a form (or etc).
But I'd say if it works with adequate testing, go for it.
Regards,
Elroy
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Bonnie's code doesn't appear to depend on order, so I don't understand why the order even matters.
Each Controls collection, contains all children controls, including nested ones. Hence not needing recursion.
Controls collections are already flattened.
Am i missing something, or are Ilia and Elroy missing something. *scratches head*
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Dex, I didn't examine Bonnie's code closely enough to see if it really needed the order. Hopefully, I didn't imply anywhere that it did. A question just came up regarding the order that the Controls collection passed the controls back, and I stated that I've got no idea.
Also, I didn't test Bonnie's code. However, knowing her from the forums, I suspect it works perfectly. And also, what I said wasn't meant to be a criticism of her code. More than anything, it's just a philosophical difference. IMHO, if something that has a recursive (or nested) nature it, it makes more sense to me to use code that mimics this recursion/nesting. For me, it'd be easier to come back to the code in six months and figure out what it's doing.
But again, there are many ways to skin a cat, and sometimes it just comes down to personal preference.
Regards,
Elroy
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
gotcha, the nesting of controls and containers does imply recursion.
luckily the designers of the VB6 runtime decided that each Controls collection would contain all children controls, including children of containers, precisely for the simplicity of not needing to use recursion.
The recursion itself is built into the Enumeration of the Controls collection.
VB.NET however decided to go back on that decision...
Quote:
Originally Posted by MSDN
Contained Controls
The Visual Basic 6.0 Controls collection includes controls that are children of a container control (for example, controls sited on a Frame control); the Visual Basic 2008 Control..::.ControlCollection class does not. To iterate through all the controls on a form, you must recursively iterate through the Controls class of each container control.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Okay, I've done a few more tests and I found out that a Form's Controls collection always enumerates the controls in the order that they are listed in the Form Description portion of the .FRM file (dynamically loaded/added control arrays/non-arrays are always enumerated after the design-time controls). What this means is that both of my codes in Post #4 & Post #20 can actually fail if nested controls are not added to the Form's Controls collection in a strict order starting from the outermost container down to the innermost control(s). In scenarios where nested control(s) are dynamically added/loaded and the order in which they are added/loaded cannot be guaranteed and/or in situations where a control's Container is dynamically changed, recursive code like Elroy's code in Post #18 would be the way to go.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Thanks a lot for all the help and advice.
There is one last issue in here before I can consider this matter resolved.
I am actually elaborating a bit on the RevertControls procedure:
First, I am passing the m_Ctrls as a parameter to it (but this is not the issue that I am hereby raising).
Second, I am passing a third parameter to it to decide what control should be set focus to, or none at all.
Code:
Public Sub RevertControls(ByRef Form As VB.Form, ByRef m_Ctrls As Collection, Optional ByRef SetFocusCtrl As Control = Nothing)
I have replaced this line:
Code:
m_Ctrls(sKey).SetFocus
with this
Code:
If IsNull(SetFocusCtrl) Then
ElseIf SetFocusCtrl Is Nothing Then
m_Ctrls(sKey).SetFocus
Else
SetFocusCtrl.SetFocus
End If
When I pass a specific control (as SetFocusCtrl) to this sub, it works perfectly.
When I skip this parameter, the default value (which is Nothing) comes into play, and again, it works perfectly.
But, when I pass Null (as SetFocusCtrl) to this sub, it issues a runtime error: Err# 424: Object required
Why is it that it doesn't accept Null, and how can I fix this situation?
Please advise.
Thanks.
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
I had posted the following in the wrong thread. Posting it here now. I'm now looking into your latest problem.
Pfff, well, since my code is referenced, I'll post the latest version of it. I've even included a "With" statement to reduce late-binding, and terminated some function with "On Error Goto 0" so that the Err object is cleaned up upon return (all as per Bonnie's excellent recommendations).
The bControlHasContainer function that was subsequently developed is also included, which makes all the following routines independent of whatever ActiveX references are made in the project.
Enjoy,
Elroy
FYI, this first block of code would best be in a standard (BAS) module.
Code:
Option Explicit
'
Dim mColCtrls As Collection
'
Public Sub DisableControls(frm As Form)
Dim ctl As Control
'
If mColCtrls Is Nothing Then Set mColCtrls = New Collection
'
mColCtrls.Add frm.ActiveControl, CStr(ObjPtr(frm)) ' For resetting focus.
'
For Each ctl In frm
If Not bControlHasContainer(ctl) Then ' These are handled through recursion.
If bControlIsContainer(ctl) Then
DisableContainerControls frm, ctl
Else
If bControlHasEnabledProp(ctl) Then
mColCtrls.Add ctl.Enabled, CStr(ObjPtr(ctl))
ctl.Enabled = False
End If
End If
End If
Next ctl
End Sub
Public Sub RevertControls(frm As Form)
Dim sKey As String
Dim ctl As Control
'
If mColCtrls Is Nothing Then Exit Sub
'
On Error Resume Next ' Controls without Enable property won't be in collection, causing an error.
For Each ctl In frm
sKey = CStr(ObjPtr(ctl))
ctl.Enabled = mColCtrls(sKey)
mColCtrls.Remove sKey
Next ctl
On Error GoTo 0
'
sKey = CStr(ObjPtr(frm))
mColCtrls(sKey).SetFocus
mColCtrls.Remove sKey
'
If mColCtrls.Count = 0& Then Set mColCtrls = Nothing ' Now put focus back.
End Sub
Private Sub DisableContainerControls(frm As Form, ctlContainer As Control)
Dim ctl As Control
'
For Each ctl In frm
If bControlHasContainer(ctl) Then
If ctl.Container Is ctlContainer Then
If bControlIsContainer(ctl) Then
DisableContainerControls frm, ctl ' Recursion to handle containers within containers.
Else
If bControlHasEnabledProp(ctl) Then
mColCtrls.Add ctl.Enabled, CStr(ObjPtr(ctl))
ctl.Enabled = False
End If
End If
End If
End If
Next ctl
'
If bControlHasEnabledProp(ctlContainer) Then
mColCtrls.Add ctlContainer.Enabled, CStr(ObjPtr(ctlContainer))
ctlContainer.Enabled = False
End If
End Sub
Private Function bControlHasEnabledProp(ctl As Control) As Boolean
On Error Resume Next
bControlHasEnabledProp = ctl.Enabled Or True
On Error GoTo 0
End Function
Private Function bControlHasContainer(ctl As Control) As Boolean
Dim TheContainer As Control
'
On Error Resume Next
Set TheContainer = ctl.Container
bControlHasContainer = Err = 0
On Error GoTo 0
End Function
Public Function bControlIsContainer(ByRef ctrl As Control) As Boolean
On Error GoTo GetOut
With ctrl.Parent.Controls
bControlIsContainer = IsObject(.Add("VB.Label", "somelabelxxx", ctrl))
.Remove "somelabelxxx"
End With
GetOut:
End Function
And to do final testing, I put the following code into the default form, and then threw some nested controls on this form. You can put whatever controls you like on the form (nested as deep as you like) for your own testing.
Code:
Option Explicit
Private Sub Form_Click()
Static b As Boolean
If b Then
RevertControls Me
Else
DisableControls Me
End If
b = Not b
End Sub
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
Ahhh, ok. Two possible solutions. First, Null and Nothing aren't the same thing. To immediately fix it, you need to do a comparison something like the following:
Code:
If SetFocusCtrl Is Nothing Then
And, the other solution is to just set the focus to the control you want immediately after you call RevertControls. I'd tend to go with this one because you're reverting to a condition differently from the one it was in just before you called DisableControls, and changing the focus a couple of times isn't going to cost you anything in terms of time.
Regards,
Elroy
EDIT: Just for further info, Null is specifically a value that a Variant type can take on. Whereas Nothing is more related to object type variables (which a variant can hold, but Null is still different).
-
Re: How to loop through all INDIVIDUAL controls on a form especially with control arr
This Sample To loop the individual controls
Code:
For a = 1 To Me.Controls.Count
ObjName = Me.Controls(a - 1).Name
ActiveName = ActiveControl.Name
ObjIndex = ""
ActiveIndex = ""
On Error Resume Next
ObjIndex = Me.Controls(a - 1).Index
ActiveIndex = ActiveControl.Index
On Error GoTo 0
If ObjIndex <> "" Then
MsgBox Me.Controls(ObjName)(ObjIndex).Name
Me.Controls(ObjName)(ObjIndex).Enabled = False
Else
MsgBox Me.Controls(ObjName).Name
If TypeName(Me.Controls(objname)) = "CommandButton" Then MsgBox "This Is Command Button"
End If
'MsgBox Me.Controls(a - 1).Name
Next a