How to validate several userform textboxes? - excel

I have a workbook with userforms to write to several numeric and date fields. I need to validate the textbox control for proper numbers and dates.
Rather than replicate the validation for each textbox, I thought I would call a common subprocedure within the BeforeUpdae event of each textbox.
I have two problems.
If I execute the form and test using text in tbAmount box, it seems the ContolValidate procedure is not called.
If I run it in break mode with a breakpoint on Call ContolValidate(What, CurrentControl), it will step through that procedure.
Even though it steps through the procedure, the Cancel = True does not seem to work.
If I paste the ContolValidate code directly in the BeforeUpdate, the Cancel = True does work.
This code is all on the userform.
Private Sub tbAmount1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
Dim What As String
Dim CurrentControl As Control
What = "NumericField"
Set CurrentControl = Me.ActiveControl
Call ContolValidate(What, CurrentControl)
End Sub
Private Sub ContolValidate(What, CurrentControl)
If Not IsNumeric(CurrentControl.Value) Then
ErrorLabel.Caption = "Please correct this entry to be numeric."
Cancel = True
CurrentControl.BackColor = rgbPink
CurrentControl.SelStart = 0
CurrentControl.SelLength = Len(CurrentControl.Value)
Else
If CurrentControl.Value < 0 Then
ErrorLabel.Caption = "This number cannot be negative."
Cancel = True
CurrentControl.BackColor = rgbPink
CurrentControl.SelStart = 0
CurrentControl.SelLength = Len(CurrentControl.Value)
End If
End If
End Sub
Private Sub tbAmount1_AfterUpdate()
ErrorLabel.Visible = False
tbAmount1.BackColor = Me.BackColor
End Sub

(1) When your control is named tbAmount1 and the code is in the code-behind module of the form, the trigger should fire.
(2) As #shahkalpesh mentioned in his comment, Cancel is not known in your validate-routine. Putting Option Explicit at the top of you code would show you that.
I would suggest to convert the routine to a function. In the code below, I return True if the content is okay and False if not (so you need to put a Not to the result to set the Cancel-parameter)
Private Sub tbAmount1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
Cancel = Not ControlValidate("NumericField", Me.ActiveControl)
End Sub
Private Function ControlValidate(What, CurrentControl) As Boolean
ControlValidate = False
If Not IsNumeric(CurrentControl.Value) Then
errorlabel.Caption = "Please correct this entry to be numeric."
ElseIf CurrentControl.Value < 0 Then
errorlabel.Caption = "This number cannot be negative."
Else
ControlValidate = True ' Input is okay.
End If
If ControlValidate Then
CurrentControl.BackColor = vbWhite
Else
CurrentControl.BackColor = rgbPink
CurrentControl.SelStart = 0
CurrentControl.SelLength = Len(CurrentControl.Value)
End If
End Function
P.S.: I changed the name to ControlValidate - "contol" seems wrong to me...

Related

Assigning a With statement to one of two classes depending on a toggle, issues assigning New Object to be the same as another

I'm currently a bit stuck with the "Object Variable or With block variable not set error".
Still fairly new to using With statements to simplify my code, I have two instances on my Class "ContractSelection" Both existing instances (previousContract & currentContract) are both by this time in the code called as public variables, and set with values. In this Sub I am attempting to submit one piece of information depending whether they are looking at the current selection or the previous (a toggle in the userform).
Frankly I'm not sure if contractToUpdate = currentContract is even a valid statement, but i'm finding it difficult to simply google.
(in a Public Variable module)
Public currentContract As ContractSelection
Public previousContract As ContractSelection
(in Userform module)
Private Sub UserForm_Initialize()
Set currentContract = New ContractSelection
Set previousContract = New ContractSelection
End Sub
Values are set in general Subs like this
Sub setThePreviousContractAsTheCurrent()
currentContract.DistrictNumber = previousContract.DistrictNumber
currentContract.ContractName = previousContract.ContractName
currentContract.RegionName = previousContract.RegionName
'...
End Sub
(In a Main Sub module) This Sub is where the issue is.
Sub submitNewCode()
Dim contractToUpdate As ContractSelection, response As Integer
Set contractToUpdate = New ContractSelection
If CDBENC_Form.chkbx_PreviousSearch.value = False Then
'vba stating the issue is here
contractToUpdate = currentContract
Else
contractToUpdate = previousContract
End If
With contractToUpdate
If .CodeOfContract <> "" Then
If isSimilarByOne(.CodeOfContract, CDBENC_Form.txt_Code.value) = False Then
dataSheet.Cells(.TheRowIWasFoundIn, dataMappedColumns.CodeColumnNum).value = CDBENC_Form.txt_Code.value
Else
response = MsgBox("The new code is close to the original, is " & CDBENC_Form.txt_Code.value & " the intended new code?", vbYesNo + vbQuestion, "Confirm Action")
If response = vbYes Then
dataSheet.Cells(.TheRowIWasFoundIn, dataMappedColumns.CodeColumnNum).value = CDBENC_Form.txt_Code.value
Else
Exit Sub
End If
End If
End If
End With
End Sub
I've tried checking for to see if for some reason currentContract is showing as nothing
this returns the else
If currentContract Is Nothing Then
MsgBox "Current Contract is nothing"
Exit Function
Else
MsgBox "Current Contract is not nothing"
End If
I've tried both
Dim contractToUpdate As ContractSelection
Dim contractToUpdate As New ContractSelection
also putting in the public variables as well
Public contractToUpdate As New ContractSelection
Any suggestions help, I feel as though I'm close to the idea but far from the solution.
VBA requires the use of Set when assigning a value to an object-typed variable.
So:
If CDBENC_Form.chkbx_PreviousSearch.value = False Then
Set contractToUpdate = currentContract
Else
Set contractToUpdate = previousContract
End If
If you're interested in why that's the case: https://stackoverflow.com/a/9924325/478884

BeforeUpdate event validation control

Dears,
I want to make a simple userform to record some serial numbers into excel, it contains a textbox_serialNo., a command button “enter” and another command button “cancel”.
I made a validation control in that serialNo textbox so that only number can be entered. However, when I run the program and input some numbers into the textbox, both command buttons (the "enter" button named as label_enter,the "cancel" button named as label_cancel) have no reactions (e.g. the "cancel" button doesn't unload the form when press) , how should I correct the program? Below are the relevant codes, Thanks.
Private Sub TextBox_SerialNo_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
If Not IsNumeric(TextBox_SerialNo.Value) Then
TextBox_SerialNo.BackColor = rgbYellow
End If
Cancel = True
End Sub
Private Sub TextBox_SerialNo_AfterUpdate()
If TextBox_SerialNo.Value <> "" Then
TextBox_SerialNo.BackColor = rgbWhite
End If
End Sub
Private sub label_enter_click()
sheet1.Select
Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Select
ActiveCell.Value = ActiveCell.Offset(-1, 0).Value + 1
ActiveCell.Offset(0, 1) = TextBox_SerialNo.Value
TextBox_SerialNo.Value = ""
End Sub
Private Sub Label_Cancel_Click()
Unload Me
End Sub
Sorry to be posting as an answer, not enough rep.
Shouldn't Cancel=True be inside the if statement? You are locking it up regardless of entry being numeric or not as is.
Edit:
Actually upon further testing still not working proper. However, change event works better and you can get instant feedback for any non numerics.
Updated code would look like this, control names differ. I am used to working with .Text, same thing as .Value. Also, since I am not sure what you would do with an empty string, assumed it to be yellow background as well.
One concern would be, can you allow comma or period in there? Depending on locale settings, a decimal would also be considered a numeric.
Private Sub cmdCancel_Click()
Unload Me
End Sub
Private Sub cmdEnter_Click()
If TextBox1.BackColor = rgbYellow Then Exit Sub
test4.Range("A1").Value = TextBox1.Text
End Sub
Private Sub TextBox1_Change()
If Not IsNumeric(TextBox1.Text) Or TextBox1.Text = "" Then
TextBox1.BackColor = rgbYellow
Else
If TextBox1.Text <> "" Then
TextBox1.BackColor = rgbWhite
End If
End If
End Sub
Edit 2: I use this piece of code to check for only numbers (assuming number Ascii codes are standard). Maybe it can help.
Public Function isnumber(ByVal strValue As Variant) As Boolean
On Error Resume Next
Dim i As Long
isnumber = True
If Not strValue = "" Then
For i = 1 To Len(CStr(strValue))
If Asc(Mid(strValue, i, 1)) > 57 Or Asc(Mid(strValue, i, 1)) < 48 Then
isnumber = False
Exit For
End If
Next i
Else
isnumber = False
End If
On Error GoTo 0
Err.Clear
End Function
Edit 3: I have revised the TextBox1_Change event code so all invalid characters are stripped right away. However, in this state if you copy paste a serial no with a non-allowed char, it will strip them leaving only the numbers. Not sure if it is acceptable.
Private Sub TextBox1_Change()
If Not isnumber(TextBox1.Text) Or TextBox1.Text = "" Then
TextBox1.BackColor = rgbYellow
Dim i As Long
Dim strValue As String
strValue = ""
If Not TextBox1.Text = "" Then
For i = 1 To Len(CStr(TextBox1.Text))
If Not (Asc(Mid(TextBox1.Text, i, 1)) > 57 Or Asc(Mid(TextBox1.Text, i, 1)) < 48) Then
strValue = strValue & Mid(TextBox1.Text, i, 1)
End If
Next i
End If
TextBox1.Text = strValue
Else
If TextBox1.Text <> "" Then
TextBox1.BackColor = rgbWhite
End If
End If
End Sub

Handle many ComboBox_Change Event

Well I'm also new in VBA programming. I'm creating a form which helps me to do quotations, and then there is a part of my form that shows items I've already registered, like this:
My Form with ComboBoxes
So the purpose of those ComboBoxes is to change or delete the correponding item according with the option I choose, and I would have a lot of them in my UserForm, making it hard to create many ComboBox event programs (like ComboBox1_Change, ComboBox2_Change, ... ComboBox50_Change). And then, the main question is: how could I do it in VBA without loosing a lot of time making the same code for different objects? I would like to create just one code for all ComboBoxes.
I understand that I can do in this way below, but I'm sure that it has a better way to do.
Sub ComboBox1_Change()
Call myCode
End Sub
Sub ComboBox2_Change()
Call myCode
End Sub
Sub ComboBox50_Change()
Call MyCode
End Sub
Sub myCode()
For i=1 to 50
If Controls("ComboBox" & i).Value = "Change" Then
Call MySecondCode
End If
Next i
End Sub
I spent about 30 minutes searching about this question, but I didn't find anything good for me. I hope you guys understood my question. Thanks in advance.
Update:
Axel Richter, as I said in comments, I'm having problem in this:
Private Function isNOKTest()
If prod1.Value = "" Or _
prod2.Value = "" Or _
tecido.Value = "" Or _
tamanhos.Value = "" Or _
unitario.Value = "" Or _
quantidade.Value = "" Then
isNOKTest = True
End If
End Function
Private myCBsWithEvents As Collection
Private Sub UserForm_Initialize()
Set myCBsWithEvents = New Collection
For Each c In Me.Controls
If Left(c.Name, 8) = "ComboBox" Then
c.AddItem "CHANGE"
c.AddItem "DELETE"
Set myCBWithEvents = New clsCBWithEvents
Set myCBWithEvents.myCB = c
myCBsWithEvents.Add myCBWithEvents
End If
Next
End Sub
'
'
'
'datatext.Value = Format(Now, "dd/mm/yyyy")
'bordadoqty.Value = 1
'estampaqty.Value = 1
'Itemlab.Caption = 1
'
When any code is added to the project, the event in class module doesn't work, apparently isn't linked with "Events", but I don't know what happened.
This can be achieved using a class module which handles the events.
Insert a class module in your project. Name it clsCBWithEvents. In this class module have the following code:
Public WithEvents myCB As ComboBox
Private Sub myCB_Change()
If Me.myCB.Value = "Change" Then
MsgBox Me.myCB.Name & " has changed to ""Change"""
ElseIf Me.myCB.Value = "Delete" Then
MsgBox Me.myCB.Name & " has changed to ""Delete"""
End If
End Sub
In your user form have the following code:
Private myCBsWithEvents As Collection
Private Sub UserForm_Initialize()
Set myCBsWithEvents = New Collection
For Each c In Me.Controls
If TypeName(c) = "ComboBox" Then
c.AddItem "Change"
c.AddItem "Delete"
Set myCBWithEvents = New clsCBWithEvents
Set myCBWithEvents.myCB = c
myCBsWithEvents.Add myCBWithEvents
End If
Next
End Sub
Now every ComboBox in this user form will use this event handling.

UserForm Button Still Functioning When Disabled

I am disabling a button onclick, but it is still allowing the click.
Code is like below:
UsrForm.Field1.Value = ""
UsrForm.Field2.Value = ""
UsrForm.btn.Enabled = False
UsrForm.Repaint
/*Processing Occurs*/
UsrForm.Field1.Value = val1
UsrForm.Field2.Value = val2
UsrForm.btn.Enabled = True
However, if I double click or click a few times where the disabled button is, it still runs the method several times, despite being disabled.
I think we have a proper bug here. The solution posted by S Meaden does not work (at least, not in my testing). Here's what I trew together for testing:
Private Sub CommandButton1_Click()
Dim w As Date
Me.CommandButton1.Enabled = False
w = Now + TimeSerial(0, 0, 2)
Debug.Print "point 1: " & Now
Application.Wait w
Debug.Print "point 2: " & Now
Me.CommandButton1.Enabled = True
End Sub
Clicking it makes it gray out (as it should when disabling) and run the routine. Clicking twice however runs the routine twice. Because it prints the times, it is clear that the routines run in sequence, so it seams that excel (in my case excel, haven't tested with other applications) remembers the clicks, and when the routine finishes (and the button is enabled again) the routine is called. It runs 3 or 4 times in a row as well.
Because of this, implementing S Meaden's answer, like so:
Dim clicked as Boolean
Private Sub CommandButton1_Click()
Dim w As Date
If Not clicked Then
clicked = True
Me.CommandButton1.Enabled = False
w = Now + TimeSerial(0, 0, 2)
Debug.Print Now
Application.Wait w
Debug.Print "punt 2 (" & Now & ")"
Me.CommandButton1.Enabled = True
clicked = False
End If
End Sub
does not work either.
It seems that if the button is enabled after the routine is finished, the clicks that were placed during routine execution are discarded. So as a workaround, you could use:
Private Sub CommandButton1_Click()
Dim w As Date
Me.CommandButton1.Enabled = False
w = Now + TimeSerial(0, 0, 2)
Debug.Print "point 1: " & Now
Application.Wait w
Debug.Print "point 2: " & Now
Me.Button1_clicked = False
Application.OnTime (Now + 0.000001), "enable_commandbutton"
End Sub
with "enable_commandbutton" being:
Public Sub enable_commandbutton()
Dim uf As Object
Debug.Print "check"
For Each uf In VBA.UserForms
If uf.Name = "UserForm1" Then
uf.CommandButton1.Enabled = True
End If
Next uf
End Sub
in a normal codemodule.
It is not pretty, but it works.
That's interesting. I agree your code should work and I am puzzled by that. However, I'm the sort of guy who would code around and so here is some that uses a module level variable to keep note of whether the procedure is already running.
Option Explicit
Private mbAlreadyProcessing As Boolean
Private Sub btn_Click()
On Error GoTo ErrHandler
If Not mbAlreadyProcessing Then
mbAlreadyProcessing = True
'do some work
mbAlreadyProcessing = False
End If
Exit Sub
ErrHandler:
'here we remember to "re-enable"
mbAlreadyProcessing = False
'do some error handling
End Sub

Why can't I send a OptionButton a parameter in VBA

I have the following function in VBA:
Private Function Option1Checked(option1 As OptionButton) As Integer
option1.ForeColor = vbGreen
If (option1.Value = True) Then
Option1Checked = 1
End If
Option1Checked = 0
End Function
Whenever I try to call the function like this
counter = counter + Option1Checked(OptionButton1)
I get a type mismatch error at runtime. But OptionButton1 is OptionButton, so what am I doing wrong?
You're running into one of the 'features' of VBA here. If you refer to some objects, like the option button, without a property specified, VBA assumes you want the default property, not the object itself. In the case of the option button, the default property is .Value, so in your code, OptionButton1 is not the option button object, but rather TRUE or FALSE depending on whether or not the OptionButton1 is checked.
Your best bet will be to change your function to this:
Private Function Option1Checked(option1 As Boolean) As Integer
//option1.ForeColor = vbGreen
If (option1 = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
The downside here is that you cannot change the foreground color of the option button to green without referring to it by name.
An alternative that would get you the functionality that you want would be to pass the name of the option button to your Function.
Private Function Option1Checked(ByVal option1 As String) As Integer
UserForm1.Controls(option1).ForeColor = vbGreen
If (UserForm1.Controls(option1) = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
Sub MyCountingRoutine()
Dim str As String
str = OptionButton1.Name
counter = counter + Option1Checked(str)
End Sub
Make sure you include the Else in the If..Then statement in your function, otherwise you will always get 0 back.

Resources