Results 1 to 15 of 15

Thread: Function to 'watch' for changes

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    May 2001
    Location
    MN
    Posts
    362

    Function to 'watch' for changes

    Lets say I have 20 TextBoxes on a form. If the user enters text into any 1 of them, I want b = 1. Instead of doing the following for each TextBox;
    Code:
    Private Sub Text1.Change()
      Dim b As Integer
      b = 1
    End Sub
    I'd like some sort of Function (or Module) to always 'watch' for any changes in a textbox. So, somehow I'd have to Call that function when the form is loaded, and not have to Call it in all Text().Change events.

  2. #2
    PowerPoster
    Join Date
    Feb 2012
    Location
    West Virginia
    Posts
    14,205

    Re: Function to 'watch' for changes

    Well the simplest way is to use a control array for the text boxes that way they all fire the same event and pass their index to it.

    btw if you do it the way in your example b will have no value once it exits the sub. You would have to have it defined at the form level

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

    Re: Function to 'watch' for changes

    If for some reason you don't want to convert your TextBoxes to a control array, you might want to consider subclassing them instead. Watch for the EN_CHANGE notification code and then call your desired event procedure.
    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)

  4. #4
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,852

    Re: Function to 'watch' for changes

    Gosh, yes, I vote for the Control Array option. I'm often in a similar situation, where I've splashed editable data all over a form and want to know if the user has changed something.

    When initially loading up the data, I load it (often in the Form_Load or Form_Activate event), and then do something like:

    Code:
        SomethingChanged = False ' Where that's a module level boolean.
    And then, a small section of my code would have:
    Code:
    Private Sub txoLeftOther_Change(index As Integer)
        SomethingChanged = True
        CtrlBackColorWhite txoLeftOther(index)
    End Sub
    
    Private Sub txoLeftScale_Change(index As Integer)
        SomethingChanged = True
        CtrlBackColorWhite txoLeftScale(index)
    End Sub
    
    Private Sub txoOther_Change(index As Integer)
        SomethingChanged = True
        CtrlBackColorWhite txoOther(index)
    End Sub
    
    Private Sub txoRightOther_Change(index As Integer)
        SomethingChanged = True
        CtrlBackColorWhite txoRightOther(index)
    End Sub
    You can see that they're all Control Arrays, and the SomethingChanged boolean is set to true anytime something is changed.

    If you're thinking about code being "self documenting" possibly use the Tab property to further describe your controls. However, my actual database fields often do this for me as well. Here's a small section where the data is loaded into my controls:

    Code:
            txtLeftStrength(0) = FloatVal(PhysicalExamsMore![LeftStrengthHipAbductionT1], 1, True)
            txtLeftStrength(1) = FloatVal(PhysicalExamsMore![LeftStrengthHipExtensionT1], 1, True)
            txtLeftStrength(2) = FloatVal(PhysicalExamsMore![LeftStrengthKneeFlexionT1], 1, True)
            txtLeftStrength(3) = FloatVal(PhysicalExamsMore![LeftStrengthHipFlexionT1], 1, True)
            txtLeftStrength(4) = FloatVal(PhysicalExamsMore![LeftStrengthKneeExtensionT1], 1, True)
            txtLeftStrength(5) = FloatVal(PhysicalExamsMore![LeftStrengthAnkleDorsiflexionT1], 1, True)
    
            ...
    
                SomethingChanged = False
    You can see that the database field names document things quite well.

    EDIT: But Bonnie's suggestion would also work. I just tend to stay away from subclassing whenever there's a workable non-subclassing option. It certainly prevents heartache when you use the STOP box in the IDE.

    Best Of Luck,
    Elroy
    Last edited by Elroy; Aug 7th, 2016 at 01:54 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.

  5. #5
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: Function to 'watch' for changes

    If you do not want to create control array you can create class to handle events of any control.
    Example:

    frmMain.frm:
    Code:
    Option Explicit
    
    Private ControlsEvent() As New clsEvents
    
    Private Sub Form_Load()
    
        Dim Ctl     As Control
        Dim TxtB    As TextBox
        Dim i       As Long
        
        For Each Ctl In Me.Controls
            ReDim Preserve ControlsEvent(i)
            Select Case TypeName(Ctl)
                Case "TextBox"
                    Set ControlsEvent(i).txtInArr = Ctl
                'Case "CheckBox"
                '    Set ChkB = Ctl
                '    Set ControlsEvent(i).chkBoxInArr = Ctl
                ' e.t.c.
            End Select
            i = i + 1
        Next Ctl
    End Sub
    clsEvents.cls:
    Code:
    Option Explicit
    
    Public WithEvents txtInArr      As TextBox
    
    Private Sub txtInArr_Change()
        Debug.Print "Source: " & txtInArr.Name
        Debug.Print "Data  : " & txtInArr.Text
    End Sub
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

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

    Re: Function to 'watch' for changes

    Dragokas, that's an interesting idea. It makes me a bit nervous instantiating possibly a great many extra clsEvents objects, but I suppose there's nothing inherently wrong with it. I do sometimes get a great many textboxes on a form though:

    Name:  OtExam.jpg
Views: 288
Size:  100.9 KB

    Notice that you're looking at one of four tabs. Your approach will create a clsEvents object for each control, regardless of whether or not they're a control array. It will certainly work though.

    It's a shame that you can't use WithEvents in array declarations, sort of like defining our own control arrays at runtime. That would totally solve the problem.

    Also, just another FYI, I'd sure tend to loop through twice, with the first pass making a count of how many controls we'll be dealing with, and then do your "ReDim ControlEvents" only once, forgetting the Preserve keyword. Then, your second time through wouldn't have to mess with that.

    Also, you'd sure want to make sure the form's COM object was unloaded, so as to unload that ControlsEvent array. For instance, a "Set Form1 = Nothing" in Form_Unload would get it done.

    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.

  7. #7
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: Function to 'watch' for changes

    Dragokas, that's an interesting idea.

    In fact, it is originally an idea of 'The Trick'.
    It makes me a bit nervous instantiating possibly a great many extra clsEvents objects, but I suppose there's nothing inherently wrong with it.
    You mean array of clsEvents or clsEvents on each form?
    For several forms you don't need to define it again. One public array in Standard module will be enough.
    Notice that you're looking at one of four tabs. Your approach will create a clsEvents object for each control, regardless of whether or not they're a control array. It will certainly work though.

    It's a shame that you can't use WithEvents in array declarations, sort of like defining our own control arrays at runtime. That would totally solve the problem.
    Yea, forgot to tell this.
    It's pity, but for some reason you unable to assing element of control array to event:
    Code:
    Set ControlsEvent(i).txtInArr = me.SomeCommandButtonArray(0)
    That's why we have to exclude all control arrays by its names during enumerating all controls. It is even more complicated if there are many of them (several control arrays). Of course, we can do it automatically, but do you know how to determine whether the control is a control array ? Something like isArray(Ctl).
    Anyway, it remains an open question how to combine events across multiple control arrays.

    Also, just another FYI, I'd sure tend to loop through twice, with the first pass making a count of how many controls we'll be dealing with, and then do your "ReDim ControlEvents" only once, forgetting the Preserve keyword. Then, your second time through wouldn't have to mess with that.
    For what advantage ?
    I do not think there will be a significant gain in speed.

    Also, you'd sure want to make sure the form's COM object was unloaded, so as to unload that ControlsEvent array. For instance, a "Set Form1 = Nothing" in Form_Unload would get it done.

    Not clearly understand, what if you don't do that (set Form1 = Nothing) ? Form_Unload, not Form_Terminate ?
    Last edited by Dragokas; Aug 12th, 2016 at 07:09 PM. Reason: Add info about several clsEvents objects
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

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

    Re: Function to 'watch' for changes

    Quote Originally Posted by Dragokas View Post
    That's why we have to exclude all control arrays by its names during enumerating all controls. It is even more complicated if there are many of them (several control arrays). Of course, we can do it automatically, but do you know how to determine whether the control is a control array ? Something like isArray(Ctl).
    Anyway, it remains an open question how to combine events across multiple control arrays.
    You really don't have to exclude the control arrays. Your "For Each" loop is going to catch all the controls, whether they're in a control array or not. The only downside is that you're going to instantiate a copy of your clsEvents, regardless of whether it's out of a control array or not.

    But here are the functions you asked for.

    Code:
    Public Function bObjectIsControlArray(obj As Object) As Boolean
        Dim o As Object
        If TypeName(obj) = "Object" Then
            On Error GoTo NotControlArray
            For Each o In obj
                If TypeOf o Is Control Then bObjectIsControlArray = True
                Exit Function ' We only need to test the first one.
            Next o
        End If
    NotControlArray: ' Just in case we try to loop through something that won't loop.
    End Function
    
    Private Function bControlIsInArray(TheControl As Control) As Boolean
        Dim i As Long
        On Error GoTo NotInArray
        i = TheControl.index
        bControlIsInArray = True
    NotInArray:
    End Function
    Quote Originally Posted by Dragokas View Post
    Not clearly understand, what if you don't do that (set Form1 = Nothing) ? Form_Unload, not Form_Terminate ?
    EDIT2: I re-read this again, and I think I understand your meaning better. And YES, without the "Set Form1 = Nothing", you do not get the Form_Terminate until the whole program ends.

    If you use the implicit, self-instantiating form names like I do, the COM/code portion of the form is NOT destroyed even when the form is unloaded (unless the entire program is terminating).

    For instance, let's say I have Form1 and Form2. Form1 is my start-up form. And on this Form1, I have a Command1 with the following code:

    Code:
    Private Sub Command1_Click()
        Form2.Show
    End Sub
    Then, I click this button, loading Form2. But next, I use the X (close) button and close Form2. Is Form2's COM/code object unloaded? NO! In fact, if I re-load Form2, it'll continue using the same COM/code object that it was using the first time it was loaded. The only way to get this COM/code object unloaded (short of terminating the program) is to set it to "Nothing" in the form's unload event. In other words, the following code is necessary in Form2's unload event:

    Code:
    Private Sub Form_Unload(Cancel As Integer)
        Set Form2 = Nothing
    End Sub
    If using these implicit self-instantiating (Form1, Form2, etc) form/object names, this is the only way to get the associated COM/code objects unloaded.

    Also, this can NOT be placed in the Form_Terminate event because this event does not fire until the COM/code object is being unloaded. In other words, it never fires if nothing unloads this COM/code object.

    This is particularly important when using module-level variables in these objects (such as your "Private ControlsEvent() As New clsEvents" array).

    Best Regards,
    Elroy

    EDIT: Just FYI, if you don't believe me, try putting some...
    Code:
    Debug.Print "terminating"
    ... lines in Form2's terminate event, and watch when it executes. This is the last event to execute before a form's COM/code object is purged from memory.
    Last edited by Elroy; Aug 12th, 2016 at 07:42 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.

  9. #9
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: Function to 'watch' for changes

    Your "For Each" loop is going to catch all the controls, whether they're in a control array or not.
    Ohh, I miss it.

    But here are the functions you asked for.

    So, I asked wrong question. I need a function to define
    whether a control is an element of control array.

    You really don't have to exclude the control arrays.

    Yes and no. We have to exclude all controls which represent elements of control arrays, because you'll get error if try to assing element of control array to Event.

    The only downside is that you're going to instantiate a copy of your clsEvents, regardless of whether it's out of a control array or not.

    Don't understand how do you plan to do that with control array. It will produce error "Object or class does not support the set of events".

    Thank you very much for the detailed explanation about freeing the memory. Very clear and easy to understand.
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

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

    Re: Function to 'watch' for changes

    Quote Originally Posted by Dragokas View Post
    I need a function to define whether a control is an element of control array.
    Hi Dragokas,

    There were actually two functions posted in the first code block of post #8:

    bObjectIsControlArray

    and

    bControlIsInArray

    I believe the second one is the one you probably requested.


    Also, I now understand what you were saying about the control arrays. They give the following error in the loop:
    Name:  Err2.gif
Views: 174
Size:  6.9 KB

    I didn't realize that would happen. I've learned something. But there's got to be a way to fix this. Now I've got a challenge.

    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.

  11. #11
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: Function to 'watch' for changes

    I didn't realize that would happen. I've learned something. But there's got to be a way to fix this. Now I've got a challenge.

    I see. Sorry,
    I thought you meant exactly that in your first post. Good luck to us

    I believe the second one is the one you probably requested.

    Ohh, now
    I saw Anyway, I don't like tricks with 'On Error'. So, another challenge: to refactor it without error handling.
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

  12. #12
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,852

    Re: Function to 'watch' for changes

    I'm also not one to have all kinds of error trapping in my code. Personally, I think my code should just be error free, or I should be fixing it. However, for quick things like that bControlIsInArray function, I've never had a problem with it. Also, for things like networking, database, and other possible errors that aren't my doing, I do use "On Error..." in my code.

    Regarding the WithEvents and arrays, I think that's worth a new thread. This whole idea of having a class to detect changes is very appealing to me, and we could get it working if this one problem could be solved.

    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.

  13. #13
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: Function to 'watch' for changes

    I'm using error handling at almost all functions. But, it is not the case. I mean I dislike to use 'On Error' for other tasks which are not directly related to error handling, e.g. 1 \ 0 for checking if in IDE instead of Debug.Assert e.t.c.

    I will follow your advice and will open a new topic.

    Kind regards,
    Alex.
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

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

    Re: Function to 'watch' for changes

    Quote Originally Posted by Elroy View Post
    Regarding the WithEvents and arrays, I think that's worth a new thread.
    The Classic VB FAQ Why can't I use WithEvents on arrays of objects? already dealt with this issue.



    BTW, here's another variation of the control array detecting function:

    Quote Originally Posted by Bonnie West View Post
    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
    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)

  15. #15
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,852

    Re: Function to 'watch' for changes

    Hey Bonnie,

    Yeah, I already read penagate's thread. And I've been composing the start of a new thread for the last couple of hours. I'm probably tilting at windmills again, but I just thought it was worth another look.

    It just seem a shame that we can't easily (outside of the form) monitor events on Control Array controls.

    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.

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