So, I created a class to avoid all the work behind the validation of input in a textbox.
The idea is to pass an existing textbox and a type of the desired content and the class will take care of block unacceptable input (for example letters in a numeric textbox), validate the text while the user writes and display a message if the content is invalid.
Exposed Methods
- Create: associates the existing textbox to the class, set the content type, and set appearance properties
- Validate: checks validity of content and display the message
Exposed properties
- TextBoxType: Let|Get custom - content type
- MaxValue: Let|Get double - only valid for numeric types
- MinValue: Let|Get double - only valid for numeric types
- FixedFormat: Let|Get boolean - only valid for numeric types, maintain the format of the number while typing
- ToCase: Let|Get custom - only valid for non-numeric types, change the case of the string while typing
- InvalidValueMessage: Let|Get string - message showed by the Validate function if the content is not vald
- IsValid: Get boolean - content validity by the type expected
- ShowValidityThrough: Let|Get custom - IsValid property can colour the textbox to indicate to the user if the content is valid or not. You can choose to colour backcolor, forecolor or bordercolor
- ValidColor: Let|Get long - the color of the ShowValidityThrough property if the content is valid
- InvalidColor: Let|Get long - the colour of the ShowValidityThrough property if the content is not valid
I would like to have some advice if you can on the design and on the possible errors you can see. Also advises on other possible types are very welcome! Thank you!
Class Name AdvTextBox
Option Explicit
Private WithEvents txt As MSForms.TextBox
' properties storage
Private pTextBoxType As TextBoxTypes
Private pMaxValue As Double
Private pMinValue As Double
Private pFixedFormat As Boolean
Private pToCase As DesiredCase
Private pInvalidValueMessage As String
Private pIsValid As Boolean
Private pShowValidityThrough As ValidityProperty
Private pValidColor As Long
Private pInvalidColor As Long
' calculated
Private pAllowedCharacters As String
Private pEvaluateMinMax As Boolean
Private pAllowEvents As Boolean
Private pOutputFormat As String
Private pEnlarged As Boolean
Private DecimalSeparator As String
' constants
Private Const numbers As String = "0123456789"
Private Const letters As String = "abcdefghijklmnopqrstuvwxyz"
Private Const accented As String = "èéàòì"
Private Const numberPunctuation As String = ",."
Private Const otherPunctuation As String = " !?=_/|-@€+"
Private Const defaultInvalidColor As Long = &H5F5BDD
Public Enum TextBoxTypes
ShortText = 0
Notes = 1
Iban = 10
ItalianVatNumber = 11
Email = 12
WholeNumber = 20
Decimal1Digit = 21
Decimal2Digit = 22
Decimal3Digit = 23
Decimal4Digit = 24
Decimal5Digit = 25
Decimal6Digit = 26
End Enum
Public Enum DesiredCase
Normal = 0
UpperCase = 1
LowerCase = 2
ProperCase = 3
End Enum
Public Enum ValidityProperty
NoOne = 0
vBorders = 1
vBackColor = 2
vForeColor = 3
End Enum
' class
Private Sub Class_Initialize()
DecimalSeparator = Application.DecimalSeparator
pAllowEvents = True
pFixedFormat = True
pShowValidityThrough = NoOne
pToCase = Normal
pValidColor = -1
pInvalidColor = -1
End Sub
' let properties
Public Property Let InvalidValueMessage(value As String)
pInvalidValueMessage = value
End Property
Public Property Let ShowValidityThrough(value As ValidityProperty)
pShowValidityThrough = value
ColorTextBox pIsValid
End Property
Public Property Let ValidColor(value As Long)
pValidColor = value
ColorTextBox pIsValid
End Property
Public Property Let InvalidColor(value As Long)
pInvalidColor = value
ColorTextBox pIsValid
End Property
Public Property Let ToCase(value As DesiredCase)
pToCase = value
End Property
Public Property Let FixedFormat(value As Boolean)
pFixedFormat = value
Select Case pTextBoxType
Case WholeNumber
pOutputFormat = "#,##0"
pAllowedCharacters = numbers
Case Decimal1Digit
pOutputFormat = "#,##0.0"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
Case Decimal2Digit
pOutputFormat = "#,##0.00"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
Case Decimal3Digit
pOutputFormat = "#,##0.000"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
Case Decimal4Digit
pOutputFormat = "#,##0.0000"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
Case Decimal5Digit
pOutputFormat = "#,##0.00000"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
Case Decimal6Digit
pOutputFormat = "#,##0.000000"
pAllowedCharacters = numbers & IIf(value, vbNullString, numberPunctuation)
End Select
End Property
Private Property Let IsValid(value As Boolean)
pIsValid = value
ColorTextBox value
End Property
Public Property Let MinValue(value As Double)
pEvaluateMinMax = True
pMinValue = value
End Property
Public Property Let MaxValue(value As Double)
pEvaluateMinMax = True
pMaxValue = value
End Property
Private Property Let TextBoxType(value As TextBoxTypes)
Dim text As String
Dim maxLength As Long
pTextBoxType = value
Select Case value
Case ShortText
maxLength = 40
pAllowedCharacters = numbers & letters & numberPunctuation & otherPunctuation
Case Notes
txt.EnterKeyBehavior = True
txt.MultiLine = True
pAllowedCharacters = numbers & letters & numberPunctuation & otherPunctuation & accented & Chr(10) & Chr(13)
Case Iban
maxLength = 31
pAllowedCharacters = numbers & letters
Case ItalianVatNumber
maxLength = 11
pAllowedCharacters = numbers
Case Email
pAllowedCharacters = numbers & letters & numberPunctuation & otherPunctuation
Case WholeNumber
text = 0
pOutputFormat = "#,##0"
pAllowedCharacters = numbers
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal1Digit
text = 0
pOutputFormat = "#,##0.0"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal2Digit
text = 0
pOutputFormat = "#,##0.00"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal3Digit
text = 0
pOutputFormat = "#,##0.000"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal4Digit
text = 0
pOutputFormat = "#,##0.0000"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal5Digit
text = 0
pOutputFormat = "#,##0.00000"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
Case Decimal6Digit
text = 0
pOutputFormat = "#,##0.000000"
pAllowedCharacters = numbers & IIf(pFixedFormat, vbNullString, numberPunctuation)
txt.ControlTipText = "Press ""-"" to change the sign"
End Select
If maxLength > 0 Then txt.maxLength = maxLength
txt.text = text
End Property
' get properties
Public Property Get InvalidValueMessage() As String
InvalidValueMessage = pInvalidValueMessage
End Property
Public Property Get ShowValidityThrough() As ValidityProperty
ShowValidityThrough = pShowValidityThrough
End Property
Public Property Get ToCase() As DesiredCase
ToCase = pToCase
End Property
Public Property Get FixedFormat() As Boolean
FixedFormat = pFixedFormat
End Property
Public Property Get MaxValue() As Double
MaxValue = pMaxValue
End Property
Public Property Get MinValue() As Double
MinValue = pMinValue
End Property
Public Property Get IsValid() As Boolean
ColorTextBox pIsValid
IsValid = pIsValid
End Property
Public Property Get ValidColor() As Long
ValidColor = pValidColor
End Property
Public Property Get InvalidColor() As Long
InvalidColor = pInvalidColor
End Property
Private Property Get TextBoxType() As TextBoxTypes
TextBoxType = pTextBoxType
End Property
' exposed methods and functions
Public Function Create(ByVal obj As MSForms.TextBox, _
ByVal txtType As TextBoxTypes) As AdvTextBox
If pValidColor = -1 Then
Select Case pShowValidityThrough
Case NoOne, vBackColor
pValidColor = obj.BackColor
Case vBorders
pValidColor = obj.BorderColor
Case vForeColor
pValidColor = obj.ForeColor
End Select
End If
If pInvalidColor = -1 Then
pInvalidColor = defaultInvalidColor
End If
Set txt = obj
TextBoxType = txtType
Set Create = Me
End Function
Public Function Validate() As Boolean
ColorTextBox pIsValid
If (Not pIsValid) And (Not pInvalidValueMessage = vbNullString) Then MsgBox pInvalidValueMessage, vbInformation, "Invalid value"
Validate = pIsValid
End Function
' textbox events
Private Sub txt_Change()
If Not pAllowEvents Then Exit Sub
pAllowEvents = False
Dim valore As Variant
valore = txt.text
Select Case pTextBoxType
Case ShortText
If Not pToCase = Normal Then valore = StrConv(valore, pToCase)
Case Notes
If Not pToCase = Normal Then valore = StrConv(valore, pToCase)
Case Iban
IsValid = isValidIBAN(valore)
valore = UCase(valore)
Case ItalianVatNumber
IsValid = IsValidItalianVatNumber(valore)
Case Email
IsValid = IsValidEmail(valore)
valore = LCase(valore)
Case Else
Dim selectText As Boolean
If pFixedFormat Then
valore = Replace(Replace(valore, ",", vbNullString), ".", vbNullString)
If valore = vbNullString Then valore = 0
valore = CDbl(valore)
Select Case pTextBoxType
Case Decimal1Digit
valore = valore / 10
Case Decimal2Digit
valore = valore / 100
Case Decimal3Digit
valore = valore / 1000
Case Decimal4Digit
valore = valore / 10000
Case Decimal5Digit
valore = valore / 100000
Case Decimal6Digit
valore = valore / 1000000
End Select
Else
valore = Replace(valore, IIf(DecimalSeparator = ",", ".", ","), IIf(DecimalSeparator = ",", ",", "."))
If Not IsNumeric(valore) Then
valore = 0
selectText = True
End If
End If
If pEvaluateMinMax Then
IsValid = (Not valore < pMinValue) And (Not valore > pMaxValue)
End If
If pFixedFormat Then valore = Format(valore, pOutputFormat)
End Select
txt.text = valore
If selectText Then
txt.SelStart = 0
txt.SelLength = Len(CStr(valore))
End If
pAllowEvents = True
End Sub
Private Sub txt_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii = 45 Then
Select Case pTextBoxType
Case WholeNumber, Decimal1Digit, Decimal2Digit, Decimal3Digit, Decimal4Digit, Decimal5Digit, Decimal6Digit
txt.text = CDbl(txt.text) * -1
End Select
End If
If Not KeyAscii = 8 Then
If InStr(1, pAllowedCharacters, Chr(KeyAscii), vbTextCompare) = 0 Then KeyAscii = 0
End If
End Sub
' validation routines
Private Sub ColorTextBox(validity As Boolean)
If (Not pShowValidityThrough = NoOne) And (Not txt Is Nothing) Then
Select Case pShowValidityThrough
Case vBackColor
txt.BackColor = IIf(validity, pValidColor, pInvalidColor)
Case vBorders
txt.BorderStyle = fmBorderStyleSingle
txt.BorderColor = IIf(validity, pValidColor, pInvalidColor)
txt.Width = txt.Width + IIf(pEnlarged, -0.1, 0.1)
pEnlarged = Not pEnlarged
Case vForeColor
txt.ForeColor = IIf(validity, pValidColor, pInvalidColor)
End Select
End If
End Sub
Private Function IsValidItalianVatNumber(ByVal str As String) As Boolean
IsValidItalianVatNumber = False
If Not IsNumeric(str) Then Exit Function
If Not Len(str) = 11 Then Exit Function
Dim X As Long
Dim Y As Long
Dim z As Long
Dim t As Long
Dim i As Long
Dim c As Long
Dim ch As Variant
Dim pari As Boolean
pari = True
For i = 1 To Len(str) - 1
pari = Not pari
ch = CLng(Mid(str, i, 1))
If pari Then
Y = Y + (ch * 2)
If ch > 4 Then z = z + 1
Else
X = X + ch
End If
Next i
t = (X + Y + z) Mod 10
c = (10 - t) Mod 10
IsValidItalianVatNumber = (c = CLng(Right(str, 1)))
End Function
Private Function isValidIBAN(ByVal Iban As String) As Boolean
' Written by Davide Tonin
' Documentation at https://davidetonin.com/code-snippets/how-to-validate-an-iban-with-vba
isValidIBAN = False
Dim LengthByCountry As Long
Dim ReorderedIBAN As String
Dim NumericIBAN As String
Dim ch As String
Dim i As Long
Const Div As Integer = 97
Const SepaCountries As String = "AT20,BE16,BG22,CY28,HR21,DK18,EE20,FI18,FR27,DE22,GI23,GR27,GL18,IE22,IS26,FO18,IT27,LV21,LI21,LT20,LU20,MT31,MC27,NO15,NL18,PL28,PT25,GB22,CZ24,SK24,RO24,SM27,SI19,ES24,SE24,CH21,HU28"
If Iban = vbNullString Then Exit Function
'Check if the first 2 characters are letters
If IsNumeric(Left(Iban, 1)) Or IsNumeric(Mid(Iban, 2, 1)) Then Exit Function
'Get the expected legth by country
LengthByCountry = InStr(1, SepaCountries, Left(Iban, 2), vbTextCompare)
If LengthByCountry > 0 Then LengthByCountry = CInt(Mid(SepaCountries, LengthByCountry + 2, 2))
If Len(Iban) <> LengthByCountry Then Exit Function
'Move first 4 characters to right
ReorderedIBAN = Right(Iban, Len(Iban) - 4) & Left(Iban, 4)
'Loop through every single character in ReorderedIBAN and, if not numeric, return 10 based number from letter using string to store the returned value in place of number
For i = 1 To Len(ReorderedIBAN)
ch = Mid(ReorderedIBAN, i, 1)
If Not IsNumeric(ch) Then
NumericIBAN = NumericIBAN & CStr(Asc(UCase(ch)) - 55)
Else
NumericIBAN = NumericIBAN & CStr(ch)
End If
Next i
ch = vbNullString
'Perform primary school style division, digit by digit. I don't need to store the result, only the remainder
For i = 1 To Len(NumericIBAN)
ch = ch & Mid(NumericIBAN, i, 1)
'If is the last character in NumericIBAN I check if remainder is 1 - Only fired once
If i = Len(NumericIBAN) Then
isValidIBAN = ((CLng(ch) Mod Div) = 1)
Exit Function
End If
ch = IIf(CLng(ch) < Div, ch, CLng(ch) Mod Div)
Next i
End Function
Private Function IsValidEmail(ByVal emailAddress As String) As Boolean
IsValidEmail = False
Const emailPattern As String = "^([a-zA-Z0-9_\-\.]+)@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$"
With CreateObject("VBScript.RegExp")
.Global = True
.IgnoreCase = True
.Pattern = emailPattern
IsValidEmail = .Test(emailAddress)
End With
End Function