-
Aug 6th, 2016, 12:50 AM
#1
Thread Starter
Hyperactive Member
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.
-
Aug 6th, 2016, 01:06 AM
#2
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
-
Aug 6th, 2016, 01:04 PM
#3
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)
-
Aug 7th, 2016, 01:51 PM
#4
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.
-
Aug 12th, 2016, 01:01 PM
#5
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
-
Aug 12th, 2016, 05:02 PM
#6
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:
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.
-
Aug 12th, 2016, 06:53 PM
#7
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
-
Aug 12th, 2016, 07:29 PM
#8
Re: Function to 'watch' for changes
Originally Posted by Dragokas
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
Originally Posted by Dragokas
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.
-
Aug 12th, 2016, 08:04 PM
#9
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.
-
Aug 13th, 2016, 09:54 AM
#10
Re: Function to 'watch' for changes
Originally Posted by Dragokas
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:
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.
-
Aug 13th, 2016, 10:28 AM
#11
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.
-
Aug 13th, 2016, 11:27 AM
#12
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.
-
Aug 13th, 2016, 12:13 PM
#13
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.
-
Aug 13th, 2016, 12:41 PM
#14
Re: Function to 'watch' for changes
Originally Posted by Elroy
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:
Originally Posted by Bonnie West
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)
-
Aug 13th, 2016, 01:05 PM
#15
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|