Results 1 to 31 of 31

Thread: How to loop through all INDIVIDUAL controls on a form especially with control arrays

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Mar 2010
    Posts
    844

    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

  2. #2
    Frenzied Member
    Join Date
    Jun 2014
    Posts
    1,084

    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
    do not put off till tomorrow what you can put off forever

  3. #3
    Frenzied Member Gruff's Avatar
    Join Date
    Jan 2014
    Location
    Scappoose Oregon USA
    Posts
    1,293

    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...
    Last edited by Gruff; Jul 23rd, 2016 at 07:41 AM.
    Burn the land and boil the sea
    You can't take the sky from me


    ~T

  4. #4
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    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
    Last edited by Bonnie West; Jul 25th, 2016 at 02:48 PM. Reason: Added With Ctrl.Container block & setting the focus back to last ActiveControl & also changed Hex$ to CStr.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  5. #5
    Frenzied Member
    Join Date
    Jun 2014
    Posts
    1,084

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    a tip of the hat to Bonnie, superb solution
    do not put off till tomorrow what you can put off forever

  6. #6
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    I'm confused...

    Quote Originally Posted by Gruff View Post
    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
    Last edited by Elroy; Jul 23rd, 2016 at 02:08 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  7. #7
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  8. #8
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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 View Post
    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.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  9. #9
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    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 View Post
    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.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  10. #10
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  11. #11
    Frenzied Member Gruff's Avatar
    Join Date
    Jan 2014
    Location
    Scappoose Oregon USA
    Posts
    1,293

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Quote Originally Posted by Elroy View Post
    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.
    Burn the land and boil the sea
    You can't take the sky from me


    ~T

  12. #12

    Thread Starter
    Fanatic Member
    Join Date
    Mar 2010
    Posts
    844

    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

  13. #13
    Fanatic Member
    Join Date
    Apr 2015
    Location
    Finland
    Posts
    692

    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.

  14. #14
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Quote Originally Posted by IliaPreston View Post
    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
    Last edited by Elroy; Jul 25th, 2016 at 02:18 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  15. #15
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Quote Originally Posted by IliaPreston View Post
    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 View Post
    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.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  16. #16
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  17. #17
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Quote Originally Posted by Elroy View Post
    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.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  18. #18
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Ok, my turn to tweak Bonnie's code.

    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.
    Attached Files Attached Files
    Last edited by Elroy; Jul 25th, 2016 at 04:39 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  19. #19
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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.
    Last edited by Elroy; Jul 25th, 2016 at 04:43 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  20. #20
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    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
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  21. #21
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    Re: How to loop through all INDIVIDUAL controls on a form especially with control arr

    Very nice, Bonnie.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  22. #22

    Thread Starter
    Fanatic Member
    Join Date
    Mar 2010
    Posts
    844

    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.

  23. #23
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  24. #24
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,229

    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*

  25. #25
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  26. #26
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,229

    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.

  27. #27
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,060

    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.
    On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  28. #28

    Thread Starter
    Fanatic Member
    Join Date
    Mar 2010
    Posts
    844

    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.

  29. #29
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  30. #30
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,910

    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).
    Last edited by Elroy; Aug 1st, 2016 at 07:14 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  31. #31
    New Member
    Join Date
    Jul 2016
    Posts
    9

    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
    Last edited by mhd_jamil; Aug 2nd, 2016 at 11:51 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width