VBA Excel - Working with multiple textboxes with the same code - excel

So I'm new to this area. I just want to ask if how can I minimize the use of the code below since I do have 13 textboxes with the same code. Is there a short way to do this?
Here's the UserForm that I'm using ->
Here's the code
Private Sub tb_mtb_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If Not IsNumeric(tb_mtb.Value) Then
MsgBox "Only numbers allowed!", vbOKOnly + vbCritical, "Title"
tb_mtb.Value = ""
End If
End Sub
Private Sub tb_fil_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If Not IsNumeric(tb_fil.Value) Then
MsgBox "Only numbers allowed!", vbOKOnly + vbCritical, "Title"
tb_fil.Value = ""
End If
End Sub
I tried this solution but I can't make it work.

The "normal" way to avoid writing the same event handler code over and over (or to avoid having to write even a "stub" handler for each like control) is to use a "control array".
Here's a basic example.
First a small custom class clsTxt which can be used to capture events from a text box:
Private WithEvents tb As MSForms.TextBox 'note the "WithEvents"
Sub Init(tbox As Object)
Set tb = tbox 'assigns the textbox to the "tb" global
End Sub
'Event handler works as in a form (you should get choices for "tb" in the
' drop-downs at the top of the class module)
Private Sub tb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii >= 48 And KeyAscii <= 57 Then
Debug.Print tb.Name, "number"
Else
Debug.Print tb.Name, "other"
KeyAscii = 0
End If
End Sub
Then in your userform you can (for example) grab all textboxes inside the frame frmTest and create an instance of clsTxt for each one, storing it in a Collection (which is Global and so does not go out of scope when the Activate event completes.
Private colTB As Collection 'holds your class instances
' and keeps them in scope
'This performs the setup
Private Sub UserForm_Activate()
Dim c As Object
Set colTB = New Collection
'loop all controls in the frame
For Each c In Me.frmTest.Controls
'look for text boxes
If TypeName(c) = "TextBox" Then
Debug.Print "setting up " & c.Name
colTB.Add TbHandler(c) ' create and store an instance of your class
End If
Next c
End Sub
' "factory" method
Private Function TbHandler(tb As Object) As clsTxt
Dim o As New clsTxt
o.Init tb
Set TbHandler = o
End Function
Once the setup is complete then events for each "wired up" textbox are handled by the class instances (you can add more events to the class if you need to manage different things like Change etc) and any new textbox added to the frame will automatically get handled without the need to write a handler for it.

Make it a subroutine and pass the control as an argument
(Make it Public if you want to put it into a module to make it re-usable for any form):
Public Sub CheckNumeric(ctl as Control)
If Not IsNumeric(ctl.Value) Then
MsgBox "Only numbers allowed!", vbOKOnly Or vbCritical, "Title"
ctl.Value = ""
End If
End Sub
And then for each control on the form:
Private Sub tb_fil_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
CheckNumeric tb_fil
End Sub
Although, a better way might be to check the KeyAscii value in the KeyPress event, and not allow non-numeric characters at all.
Making VBA Form TextBox accept Numbers only

Related

VBA Excel: Limit the textboxes to a value between 60 to 100

I'm trying to validate textboxes in a UserForm. I'm using this solution on my worksheet.
How can I add validation to my textboxes to only accept numbers between 60 to 100 using the given solution? Should I add something after the If KeyAscii >= 48 And KeyAscii <= 57 Then or it should be elsewhere?
UserForm:
Class code:
Private WithEvents tb As MSForms.TextBox 'note the "WithEvents"
Sub Init(tbox As Object)
Set tb = tbox 'assigns the textbox to the "tb" global
End Sub
'Event handler works as in a form (you should get choices for "tb" in the
' drop-downs at the top of the class module)
Private Sub tb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii >= 48 And KeyAscii <= 57 Then
Debug.Print tb.Name, "number"
Else
MsgBox "The value should be in number only!", vbOKOnly + vbCritical, "Error"
Debug.Print tb.Name, "other"
KeyAscii = 0
End If
End Sub
Code on UserForm:
Private colTB As Collection 'holds your class instances
' and keeps them in scope
'This performs the setup
Private Sub UserForm_Activate()
Dim c As Object
Set colTB = New Collection
'loop all controls in the frame
For Each c In Me.Frame3.Controls
'look for text boxes
If TypeName(c) = "TextBox" Then
Debug.Print "setting up " & c.Name
colTB.Add TbHandler(c) ' create and store an instance of your class
End If
Next c
End Sub
' "factory" method
Private Function TbHandler(tb As Object) As clsTxt
Dim o As New clsTxt
o.Init tb
Set TbHandler = o
End Function
I don't think you can achieve that easily using a Class. The solution you are using will help you monitor each keystroke, but it looks as though you are wanting to validate input after the user has finished with the field and then you need to verify that what he has input is between 60 and 100. For that you would probably be looking at the AfterUpdate event but sadly that is not available within a class.
I think you will either need to create a stub for each textbox_AfterUpdate to do the validation.
Try this:
Private Sub tb_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If Not IsNumeric(tb.Value) Then
'MsgBox "only numbers between 60 and 100 are allowed"
tb.Value = ""
ElseIf tb.Value >= 60 And tb.Value <= 100 Then
'MsgBox "OK!"
Else
'MsgBox "only numbers between 60 and 100 are allowed"
tb.Value = ""
End If
End Sub

Is it possible to hook CommandBarComboBox.Change event such that when a change has been made from Excel VBA code it fires event?

I have a ComboBox control on a custom CommandBar. I want to detect when a change has been made to the ComboBox and run some code.
This works fine when the user changes the value in the ComboxBox, but if some other VBA code changes it, the Event is not fired.
I have taken the code from here: https://learn.microsoft.com/en-us/office/vba/api/office.commandbarcombobox.change
and modified it slightly to delete a CommandBar with the same name so I can run it a number of times. I have then added another function to change the value of the ComboBox programmatically. When I change the value programmatically it can be seen to change on the Excel GUI, but the event code does not fire.
I have also tried the easier OnAction method, rather than hooking events, both seem to give the same result.
In a ClassModule called ComboBoxHandler I have the following code:
Private WithEvents ComboBoxEvent As Office.CommandBarComboBox
Public Sub SyncBox(box As Office.CommandBarComboBox)
Set ComboBoxEvent = box
If Not box Is Nothing Then
MsgBox "Synced " & box.Caption & " ComboBox events."
End If
End Sub
Private Sub Class_Terminate()
Set ComboBoxEvent = Nothing
End Sub
Private Sub ComboBoxEvent_Change(ByVal Ctrl As Office.CommandBarComboBox)
Dim stComboText As String
stComboText = Ctrl.Text
Select Case stComboText
Case "First Class"
FirstClass
Case "Business Class"
BusinessClass
Case "Coach Class"
CoachClass
Case "Standby"
Standby
End Select
End Sub
Private Sub FirstClass()
MsgBox "You selected First Class reservations"
End Sub
Private Sub BusinessClass()
MsgBox "You selected Business Class reservations"
End Sub
Private Sub CoachClass()
MsgBox "You selected Coach Class reservations"
End Sub
Private Sub Standby()
MsgBox "You chose to fly standby"
End Sub
In a module I have the following:
Private ctlComboBoxHandler As New ComboBoxHandler
Sub AddComboBox()
Set HostApp = Application
On Error Resume Next
CommandBars("Test CommandBar").Delete
Dim newBar As Office.CommandBar
Set newBar = HostApp.CommandBars.Add(Name:="Test CommandBar", Temporary:=True)
Dim newCombo As Office.CommandBarComboBox
Set newCombo = newBar.Controls.Add(msoControlComboBox)
With newCombo
.AddItem "First Class", 1
.AddItem "Business Class", 2
.AddItem "Coach Class", 3
.AddItem "Standby", 4
.DropDownLines = 5
.DropDownWidth = 75
.ListHeaderCount = 0
End With
ctlComboBoxHandler.SyncBox newCombo
newBar.Visible = True
End Sub
Sub test()
Dim newBar As Office.CommandBar
Set newBar = Application.CommandBars("Test CommandBar")
Dim cmbox As Office.CommandBarComboBox
Set cmbox = newBar.Controls(1)
cmbox.Text = "Business Class" ''<< I was hoping this would fire an event, but it does not! Same is true if I
End Sub
First I run AddComboBox and the events work fine if I manually change the ComboBox.
Then I run test() and the value displayed inside the ComboBox changes, but the event does not fire.

How to textbox checks itself

I am trying to do textboxes which would be validating input (for numbers only).
I am still new to classes and am little bit confused about some things, but trying my best to learn.
I have multiple textboxes in userform and want to every one of them to be numeric input only.
For the begining I started to check just one textbox (vzdalenost1).
First code just to create connection between textbox and class
Dim chk As New Class1
Private Sub UserForm_Initialize()
Set chk.ChkEvents = Controls("Vzdalenost1")
End Sub
Second code is actual class module
Option Explicit
Public WithEvents ChkEvents As MSForms.TextBox
Private Sub ChkEvents_change()
If IsNumeric(Me.Value) Or Me.Value = "" Then
Else
MsgBox "blablabla"
Me.Value = ""
End If
End Sub
When I try to write something into textbox "vzdalenost1" excel shows error message "Method or data member not found"..
I have even something like replacing "me.value" for "me.control.value" which didnt work either..
The keyword Me in this context refers to the class object itself, not the textbox. Use ChkEvents to refer to the textbox - ChkEvents.Value.
Here's how you could re-write your code to deal with multiple textboxes. First, let's give your class a meaningful name, let's call it clsTextbox. Then, in keeping with the principle of encapsulation, let's declare the object in your class as Private, instead of Public. This will prevent the public from having direct access to it. Instead, we'll use a member function to assign a textbox to a class object. So the code for our class module would look like something this...
Option Explicit
Private WithEvents tb As MSForms.TextBox
Private Sub tb_Change()
MsgBox tb.Name
End Sub
Public Function SetTextbox(ByRef obj As MSForms.TextBox)
Set tb = obj
End Function
Then, for our userform, first we declare a collection at the module level to hold our class objects. Then, since we will be creating multiple class objects, we declare a class object without the keyword New. Instead, we'll use that keyword each time we create a new object, and then we'll add that new object to our collection. So the code for our userform would look something like this...
Option Explicit
Dim textboxCollection As Collection
Private Sub UserForm_Initialize()
Set textboxCollection = New Collection
Dim cTextbox As clsTextbox
Dim ctrl As MSForms.Control
For Each ctrl In Me.Controls
If TypeName(ctrl) = "TextBox" Then
Set cTextbox = New clsTextbox
cTextbox.SetTextbox ctrl
textboxCollection.Add cTextbox
End If
Next ctrl
End Sub
If I get your question correctly you want a textbox to accept only numbers, correct?
In that case try this:
Private Sub TextboxName_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
char = Chr(KeyAscii)
KeyAscii = Asc(UCase(char))
Debug.Print KeyAscii
If KeyAscii >= 48 And KeyAscii <= 57 Then
Debug.Print "number"
Else
Debug.Print "other"
KeyAscii = 0
End If
End Sub
Just replace TextboxName in the Sub name with whatever name you have and it should work.

VBA Userforms Show the Same Userform again and again

currently i am programming a excel macro. The macro shows a Userform.
In the Userform the User can Select something. After the User has selected something i call Userform.Hide to Hide the Userform and to read the Selection from the Form. After the selection was read i call Unload Userform. Now the Code interacts with the selection. I want to do this in a loop but when the Code trys to show the Userform the second time. I get a exception that the Form is already displayed. I cant understand it, because i called Unload Userform. When i do it in debug mode everthing works as it should.
Userform Code
Private Sub Image1_Click()
SelectCard 1
End Sub
Private Sub Image2_Click()
SelectCard 2
End Sub
Private Sub SelectCard(number As Integer)
SelectedNumber = number
Me.Hide
End Sub
Public Sub CardSelector_Activate(Cards As Cards)
Dim c As card
For Each Key In Cards.CardDictionary.Keys
Set c = Cards.CardDictionary.Items(Key - 1)
If c.value = 1 And c.played Then
Image1.Enabled = False
End If
If c.value = 2 And c.played Then
Image2.Enabled = False
End If
Next Key
number = SelectedNumber
CardSelector.Show
End Sub
Code in the ClassModule i call this in a loop
Sub Costum(Spalte As Integer, Zeile As Integer, SpalteBeginn As Integer, Cards As Cards, CardsOpponent As Cards)
CardSelector.CardSelector_Activate Cards
Dim c As card
Dim number As Integer
number = CardSelector.SelectedNumber
Set c = Cards.CardDictionary.Items(CardSelector.SelectedNumber - 1)
SetCardAsPlaced c, Zeile, Spalte, SpalteBeginn
Unload CardSelector
End Sub
Can someone help me here ?
I am not sure if I fully understand your issue, but this is how I invoke a form using VBA. This is assuming you have a Cancel and OK button:
In the form:
Option Explicit
Private m_ResultCode As VbMsgBoxResult
Private Sub btnCancel_Click()
Call CloseWithResult(vbCancel)
End Sub
Private Sub btnOK_Click()
' Store form control values to member variables here. Then ...
Call CloseWithResult(vbOK)
End Sub
Private Sub CloseWithResult(Value As VbMsgBoxResult)
m_ResultCode = Value
Me.Hide
End Sub
Public Function ShowMe(Optional bNewLayerOptions As Boolean = True) As VbMsgBoxResult
' Set Default to Cancel
m_ResultCode = vbCancel
' Execution will pause here until the form is Closed or Unloaded
Call Me.Show(vbModal)
' Return Result
ShowMe = m_ResultCode
End Function
Then, to call it (please note that frmLayers is my own VBA form object - you would use yours):
Dim dlgLayers As New frmLayers
If (dlgLayers.ShowMe(False) = vbOK) Then
' Proceeed
End If
Does this help you with your issue? I am sorry if I have misunderstood, and I will remove my answer if needed.
Things like xxxxx_Activate etc. are event handlers called by the framework. So, for example, there is an event for activate and an event for initialize. You don't normally have to directly call these yourself if you set your code up correctly. See https://support.microsoft.com/en-us/kb/138819.

How to select the contents of a textbox once it is activated?

I have this simple Userform, where I only have TextBox1 and TextBox2. I enter some text in both of them. Assume the focus is on (the cursor is in) the TextBox2. When I click on TextBox1, I want the whole text in this control to be highlighted (selected). Thus I use this code:
Private Sub TextBox1_Enter()
With TextBox1
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
MsgBox "enter event was fired"
End Sub
There is a MsgBox at the end which is loaded, that means the event works. However, the text is not highlighted. How to fix this?
I use the Enter event and don't want to use the MouseDown event, because I need the code to also work when the TextBox1 is activated programatically, so I feel the Enter event to be the best choice, as it's fired in both cases! Another drawback of the MouseDown event is: when I click for the second time on the TextBox1, I would not expect the whole text to be highlighted anymore, because the focus was set on the first click and it was not changed after I clicked on the same control for the second time; so in this case I would like the cursor to act normally (not to keep the text marked).
Update
When I click once on the TextBox1, I expect to have this result:
If clicked again, the highlight would be removed and the cursor would be placed in the place where it was clicked.
Can't be more simple than this I guess...
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
With TextBox1
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
Whether you click on the textbox or you tab into it, it will do what you want. To deselect the text, use the arrow keys.
Explanation
If you debug the code you will see that even though you have said .SetFocus, the focus is not on the Textbox. .SetFocus doesn't work in TextBox1_Enter() and you need to have focus for the rest of the code to work. And hence my alternative...
Alternative
You may also like this version :) This overcomes the limitation of using the mouse in the TextBox
Dim boolEnter As Boolean
Private Sub TextBox1_Enter()
boolEnter = True
End Sub
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
If boolEnter = True Then
With TextBox1
.SelStart = 0
.SelLength = Len(.Text)
End With
boolEnter = False
End If
End Sub
Pff, took me a while. Actually, your code works, but it highlights the text BEFORE the click event happens. So you clicking in the box instantly overrides the selection created by the code.
I have used a delayed selection, and it works, though it is a bit disgusting...
The code for the textboxes:
Private Sub TextBox1_Enter()
Application.OnTime Now + TimeValue("00:00:01"), "module1.SelectText1"
End Sub
Private Sub TextBox2_Enter()
Application.OnTime Now, "module1.SelectText2"
End Sub
Note that it works even withouth the {+ TimeValue("00:00:01")} part, but it might theoretically stop it from working at times. Hmm, on a second thought, just leave it out. I doubt it would ever cause a problem.
Now the code in module1:
Sub SelectText1()
UserForm1.TextBox1.SelStart = 0
UserForm1.TextBox1.SelLength = Len(UserForm1.TextBox1.Text)
End Sub
Sub SelectText2()
UserForm1.TextBox2.SelStart = 0
UserForm1.TextBox2.SelLength = Len(UserForm1.TextBox2.Text)
End Sub
Hope this works for you too. Ineresting problem. :) Cheers!
I couldn't manage to select/highlight text in the Enter event as the the mousedown and mouseup events coming after are somewhat resetting the selection.
I think the most proper way of achieving what you want is this :
' if you want to allow highlight more then once, reset the variable LastEntered prior to call SelectTboxText:
' LastEntered = ""
' SelectTboxText TextBox2
Dim LastEntered As String
' Button to select Textbox1
Private Sub CommandButton1_Click()
SelectTboxText TextBox1
End Sub
' Button to select Textbox2
Private Sub CommandButton2_Click()
SelectTboxText TextBox2
End Sub
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
SelectTboxText TextBox1
End Sub
Private Sub TextBox2_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
SelectTboxText TextBox2
End Sub
Public Sub SelectTboxText(ByRef tBox As MSForms.TextBox)
If LastEntered <> tBox.Name Then
LastEntered = tBox.Name
With tBox
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
End If
End Sub
So each time you want to activate one of the textbox programmatically, you should call the sub SelectTboxText, which is not really annoying IMO. I made 2 buttons for this as an example.
This is somewhat an enhancement of what #vacip posted. The benefit you get is that you don't need to add a separate method in the Module for each new textbox.
The following code in your User Form:
'===== User Form Code ========
Option Explicit
Private Sub TextBox1_Enter()
OnTextBoxEnter
End Sub
Private Sub TextBox2_Enter()
OnTextBoxEnter
End Sub
Private Sub TextBox3_Enter()
OnTextBoxEnter
End Sub
The following code goes in a Module:
'===== Module Code ========
Sub SelectAllText()
SendKeys "{HOME}+{END}", True
End Sub
Sub OnTextBoxEnter()
Application.OnTime Now + 0.00001, "SelectAllText", Now + 0.00002
End Sub
Private Sub UserForm_Initialize()
TextBox1.SetFocus
TextBox1.SelStart = 0
TextBox1.SelLength = Len(TextBox1.Text)
End Sub
Add this to the form's code
I know this is well out of date but I'm leaving this here in case it helps someone in my position.
What I want is:
If I click on the box for the first time: select all the text
If I click on it a subsequent time: put the cursor where the mouse is and allow me to use the mouse to select a substring
Firstly it is important to know that "Select all the text" is the default behaviour when tabbing into a TextBox and that "Put the cursor here" is the default behaviour when clicking on a TextBox so we only need to worry about what the mouse is doing.
To do this, we can keep track of the Active Control, but only while the mouse is moving over our TextBox (ie. before the Click)
Code:
Private m_ActiveControlName As String
Private Sub Text1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
m_ActiveControlName = Me.ActiveControl.Name
End Sub
Private Sub Text1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
If m_ActiveControlName <> Me.Text1.Name Then
Call Text1_Enter 'we don't have to use Text1_Enter for this, any method will do
Exit Sub 'quit here so that VBA doesn't finish doing its default Click behaviour
End If
End Sub
Private Sub Text1_Enter()
With Text1
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
There's another solution than the one given by Siddharth.
EDIT: but there's this bug of SendKeys, so the solution I propose below is a lot worse than Siddharth one. I keep it in case one day the bug is corrected...
It relies on the property EnterFieldBehavior of the TextBox field. This property works only when the Tab key is pressed to enter that field, and if this property is fmEnterFieldBehaviorSelectAll (0) the whole field text is automatically selected.
So a dummy caret movement between fields when the form is shown, will activate the feature automatically. For instance this movement can be achieved by pressing Tab to move to the next field, and pressing Shift+Tab to move to the previous field (so back to the original field):
Private Sub UserForm_Activate()
SendKeys "{TAB}+{TAB}"
End Sub
The (very little) advantage of this solution is that you can tune your user form by editing manually the properties EnterFieldBehavior, TabIndex, TabKeyBehavior and TabStop without changing the VBA code anymore to set "select all" on the field with the initial focus.
In short, the VBA code above tells to consider the property EnterFieldBehavior of the field which has the initial focus when the user form is displayed (provided that it's a TextBox or ComboBox field of course).
use this
Private Sub TextBox1_Enter()
With TextBox2
.ForeColor = vbBlack
.Font.Bold = False
End With
With TextBox1
.ForeColor = vbRed
.Font.Bold = True
End With
End Sub
Private Sub TextBox2_Enter()
With TextBox1
.ForeColor = vbBlack
.Font.Bold = False
End With
With TextBox2
.ForeColor = vbRed
.Font.Bold = True
End With
End Sub
The behavior you're trying to implement is already built in to the TextBox. When you move the mouse over the left side of the text box, the mouse pointer will point to the right. If you click, it will select all the text in the field. Clicking anywhere else will deselect the text.
I will try a few other strategies to see if I can get this to work in one Sub.
Try the same code with TextBox1_MouseDown. It should work.
Private Sub TextBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
With TextBox1
.SetFocus
.SelStart = 0
.SelLength = Len(.Text)
End With
MsgBox "Text in TextBox1 is selected"
End Sub

Resources