TextBoxes on userforms in Excel VBA
When you work with VBA macros in Excel, you often need user input via a userform. It can be text (e.g. a name) or numeric values, and the TextBox control is ideal for this.
You put one or more text boxes on your userform and ask the user for input. Afterwards you have to check, if the user "followed the rules". If you just assume that he typed a numeric value, your program will crash, if he wrote something else.
Below is a simple example on how to check the content of a textbox. That is laborious, if you have a lot of textboxes to check the same way, so afterwards I show how to check the smart way using a class module.
To test the code, highlight it with the mouse, copy (CTRL+C) and paste (CTRL+V) into a VBA module.
The simple control
Imagine a userform with a textbox (TextBox1) and a command button. The user is asked to type a numeric value in the textbox and then click the OK button.
To check the user input you put the following code in the command button's click procedure, "Private Sub CommandButton1_Click()".
First we check if the textbox contains a numeric value or is empty. If it isn't a numeric value, we delete the content, place the cursor in the textbox (the .SetFocus command) and keep the userform open.
If it is a numeric value, we convert it to the data type Double, insert the value in cell A1 on the active worksheet and close the userform.
We must convert to the data type Double, because the content of a textbox is always a String. If you don't convert it, things will go wrong, if you want to use the number in a calculation.
Private Sub CommandButton1_Click()
Dim dNumber As Double
With TextBox1
If IsNumeric(.Text) = False Or Len(.Text) = 0 Then
.Text = ""
MsgBox "It must be a numeric value"
.SetFocus
Exit Sub
Else
dNumber = CDbl(.Text)
End If
End With
Range("A1").Value = dNumber
Unload Me
End Sub
The smart way: The TextBox class
The code above works fine for a simple control, but if you have many textboxes on one or more userforms, and they all have to be checked the same way, it takes a lot of work to check them all.
The clever solution is to make a textbox class that will check all the textboxes on the fly, when the user types something. It may be that all letters must be upper case, or that the input must be numeric values.
The trick is to tell VBA that the textboxes on a userform are members of the textbox class, and when the user writes something in one of the textboxes, the event will automatically trigger a check by the textbox class' code.
It may sound a bit complicated, but you don't have to understand immediately - as long as it works, be happy!
Start by making a userform and put some textboxes on it. You must also add a command button that will close the userform when clicked.
Then you copy the following code and insert it at the very top of the userform's code. Note that I declare a public variable: "Public InputNbCol As Collection". Normally you would do that in a module, but to make things easier I do it here - after all it is just an example.
Option Explicit
Public InputNbCol As Collection
Private Sub UserForm_Initialize()
Dim InputNbEvt As clTextBoxClass
Dim ctl As control
On Error GoTo ErrorHandle
Set InputNbCol = New Collection
For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.TextBox Then
Set InputNbEvt = New clTextBoxClass
Set InputNbEvt.InputTextBox = ctl
InputNbCol.Add InputNbEvt
End If
Next
BeforeExit:
Set InputNbEvt = Nothing
Exit Sub
ErrorHandle:
MsgBox Err.Description
Resume BeforeExit
End Sub
Private Sub CommandButton1_Click()
Set InputNbCol = Nothing
Unload Me
End Sub
And now for the class module
Make a class module (in the VBA editor menu you select "Insert" and "Class Module"). In the class module's properties window you change the name to "clTextBoxClass". If the properties window isn't visible, select it in the menu "View" or press F4.
Having done that you copy the code below and paste into the class module. But first I had better explain what goes on in the class module.
In this example we accept nothing but numeric values, decimal separator (.) and minus (for negative values). This is controlled by the procedure:
Private Sub InputTextBox_keypress _
(ByVal KeyAscii As MSForms.ReturnInteger)
which is called every time the user presses a key. We check the character's Ascii code and accept numeric values only (and "." and "-").
Of course you can check the same way if for instance you only accept upper case. At the bottom of the page you can see how that would look.
However the Keypress procedure only captures "normal" keys being pressed (ANSI characters), so if a user pastes a copied text into the textbox with CTRL+V, he can cheat us.
Therefore the class will also prevent the user from pasting text into a textbox. The way it does this is neither sophisticated nor elegant, but it works.
The prevent-paste trick lies in the procedure:
Private Sub InputTextBox_BeforeDropOrPaste
that captures attempts to paste something. Here we set a flag, "bPaste = True", that will make the procedure:
Private Sub InputTextBox_Change()
insert the old text (if there was any).
Here is the code to copy. Highlight it with the mouse, copy (CTRL+C) and paste (CTRL+V) into the class module.
Option Explicit
Public WithEvents InputTextBox As MSForms.TextBox
Private mvarPaste As Boolean
Private mvarText As String
Private Sub InputTextBox_keypress _
(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case 45 To 57
Case Else
KeyAscii = 0
End Select
sText = InputTextBox.Text
End Sub
Private Sub InputTextBox_BeforeDropOrPaste _
(ByVal Cancel As MSForms.ReturnBoolean, _
ByVal Action As MSForms.fmAction, _
ByVal Data As MSForms.DataObject, _
ByVal X As Single, ByVal Y As Single, _
ByVal Effect As MSForms.ReturnEffect, _
ByVal Shift As Integer)
bPaste = True
End Sub
Private Sub InputTextBox_Change()
With InputTextBox
If bPaste Then
bPaste = False
.Text = sText
End If
End With
End Sub
Property Get bPaste() As Boolean
bPaste = mvarPaste
End Property
Property Let bPaste(ByVal vData As Boolean)
mvarPaste = vData
End Property
Property Get sText() As String
sText = mvarText
End Property
Property Let sText(ByVal vData As String)
mvarText = vData
End Property
Allowing upper case only
In the example above we accepted nothing but numeric values. Here follows the Keypress procedure to use instead, if you want to disallow anything but upper case in your textboxes.
It is an open question, if anybody will find it useful, but it is just another example to give you an impression of the technique.
Private Sub InputTextBox_keypress _
(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case 8 To 10, 13, 27
KeyAscii = KeyAscii
Case 65 To 90, 194, 197, 198, 212, 216, 219
KeyAscii = KeyAscii
Case 97 To 122, 226, 229, 230, 244, 248, 251
KeyAscii = Asc(UCase$(Chr$(KeyAscii)))
Case Else
KeyAscii = 0
End Select
sText = InputTextBox.Text
End Sub
Other events
With the textbox class you can capture and treat almost the same events as in the userform's textbox code. That is events like:
- BeforeDragover
- DblClick
- KeyDown
- KeyUp
- MouseDown
- and more
This is possible because we declared the following at the top of our class module:
Public WithEvents InputTextBox As MSForms.TextBox
where the important thing is the expression "WithEvents" - which can be described as "when something happens to our textbox".
For the same reason we cannot (like in the userform) capture things like "AfterUpdate" or "Exit", because in those situations we missed the train, so to say.
Related:
|