How can i make a sub for a keypress event? - vba

I'm working on an excel with macros. I have a userform with textboxes, several of them use a function for only press numbers.
Private Sub quantity1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case Asc("0") To Asc("9")
Case Else
KeyAscii = 0
End Select
End Sub
How can i put the select case on a procedure or function
and then call it from any keypress event that i need it (in this case, quantity1 quantity2, price1 and price2, but no in buyer)?
I tried making a sub that uses the same parameters like the event e.g:
Sub Only_Numbers(ByVal KeyAscii As MSForms.ReturnInteger)

Here is a quick example that I created for you.
Let's say your userform looks like this
Now place this in a class module
Public WithEvents TextBoxEvents As MSForms.TextBox
Private Sub TextBoxEvents_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case Asc("0") To Asc("9")
Case Else
KeyAscii = 0
End Select
End Sub
Screenshot
And place this in the userform
Dim myTBs() As New Class1
Private Sub UserForm_Initialize()
Dim i As Integer, objControl As Control
For Each objControl In Me.Controls
If TypeOf objControl Is MSForms.TextBox Then
i = i + 1
ReDim Preserve myTBs(1 To i)
Set myTBs(i).TextBoxEvents = objControl
End If
Next objControl
Set objControl = Nothing
End Sub
Screenshot
Now try entering text/numbers in any of the textboxes :)
EDIT
#SiddharthRout Wow! Awesome Reply! it's almost like that, but i require that certain textboxes (not all) on the userform could have this restriction. I guess that if i do this Set myTBs(i).TextBoxEvents = quantity1 (Considering that quantity1 is a textbox) should work? – fjatp 6 mins ago
If TypeOf objControl Is MSForms.TextBox Then
Select Case objControl.Name
Case "TextBox1", "TextBox3", "TextBox4" '<~~ Include only these
i = i + 1
ReDim Preserve myTBs(1 To i)
Set myTBs(i).TextBoxEvents = objControl
End Select
End If

Related

VBA SetFocus on Keydown not working if checkbox true?

I have the following form in excel. It is part of a simple inventory worksheet, and is used to update the status of items. It was functioning as intended, however when I tried to add in a checkbox to make the selected status persist (allowing me to only type in the serial rather than serial and status every time when working with a batch of items) I noticed that the focus after submitting cycles forward as if I pressed tab rather than being where I set it with SetFocus.
I'm presuming this is an oversight related to either the event cycle for KeyDown or nested If/Else logic, but I've had no luck actually diagnosing it. I've only recently started using VBA, and there are a lot of quirks I'm trying to understand.
Private Sub clear()
Me.txtSerial = ""
If cbPersist.Object.Value = False Then
Me.cboxStatus = ""
Me.cboxStatus.SetFocus
Else
Me.txtSerial.SetFocus
End If
End Sub
Private Sub submit()
Dim loc As Range
Dim ws As Worksheet
Set ws = Worksheets("Full Inventory")
Set loc = ws.Columns("B").Find(what:=Me.txtSerial.Value, LookIn:=xlValues, lookat:=xlWhole)
If Not loc Is Nothing Then
ActiveWindow.ScrollRow = loc.Row
ws.Cells(loc.Row, 10).Value = Me.cboxStatus.Value
Else
MsgBox "Serial not found."
End If
clear
End Sub
Private Sub txtSerial_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = vbKeyReturn Then
submit
ElseIf KeyCode = vbKeyEscape Then
clear
End If
End Sub
Private Sub UserForm_Initialize()
cboxStatus.List = Array("Stock", "Shipped", "Research", "Sent Back", "Return")
End Sub
Suggest the following:
Code snippet of UserForm module
Private Sub txtSerial_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = vbKeyReturn Then
submit
KeyCode = vbKeyPageDown ' << modify Enter key value to prevent from tab hopping
ElseIf KeyCode = vbKeyEscape Then
clear
End If
End Sub

Check TextBoxes in Userform to make sure that they are integers

I'm trying to check my Userform when a command button is clicked to ensure that all the values entered in textboxes are integers. However, right now it's popping up with the message box even if I enter an integer value (i.e. 1 for all the textboxes).
Here's my code right now:
'Store inputs when clicking Continue
Private Sub Continue1_Click()
'Define variables
Dim Ctl As Control
For Each Ctl In CategoriesForm.Controls
If TypeName(Ctl) = "TextBox" Then
If IsNumeric(Ctl.Value) = True Then
If Int(Ctl.Value) <> Ctl.Value Then
MsgBox ("All inputs need to be integers for calculations to work. Please check the values."), vbCritical, "Error: Not All Inputs are Integers"
Exit Sub
End If
Else
MsgBox ("All inputs need to be integers for calculations to work. Please check the values."), vbCritical, "Error: Not All Inputs are Integers"
Exit Sub
End If
End If
Next
NumberClamps = Number_Clamps.Value
NumberBrackets = Number_Brackets.Value
NumberWashers = Number_Washers.Value
NumberScrews = Number_Screws.Value
NumberNuts = Number_Nuts.Value
NumberUNuts = Number_UNuts.Value
NumberRivets = Number_Rivets.Value
NumberStuds = Number_Studs.Value
Unload CategoriesForm
End Sub
Thanks
this is a sub that only allows user to enter numeric keys
Private Sub TextBox1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case vbKey0 To vbKey9
Case Else
KeyAscii = 0
Beep
End Select
End Sub
In conjunction with Doug Coats Solution
Create Class Module called "CTextboxes"
Option Explicit
Public WithEvents TextGroup As MSForms.TextBox
'Sub to allow users to only enter integer values
Private Sub TextGroup_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case vbKey0 To vbKey9
Case Else
KeyAscii = 0
Beep
MsgBox ("All inputs need to be integers for calculations to work. Please check the values."), vbCritical, "Error: All Inputs must be Integers"
End Select
End Sub
Put this code in the UserForm code module for when the USerForm initializes
Private Sub UserForm_Initialize()
'Stop user from entering non-integer values
Dim Ctl As MSForms.Control
Dim i As Long
i = 1
For Each Ctl In Me.Controls
If TypeName(Ctl) = "TextBox" Then
ReDim Preserve TextBoxes(1 To i)
Set TextBoxes(i).TextGroup = Ctl
i = i + 1
End If
Next Ctl
End Sub
Finally define this variable at the top of the UserForm Code
Option Explicit
Dim TextBoxes() As New CTextboxes

Close UserForm if All Data Captured

Say you have aUserForm with TextBox1, TextBox3, TextBox3 and an OK Button.
To only allow the UserForm to close if all three TextBox have data I would use the following script assigned to the OK Button:
Private Sub CommandButton1_Click()
If Len(TextBox1.Value) >= 1 And _
Len(TextBox2.Value) >= 1 And _
Len(TextBox3.Value) >= 1 Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
Is there another way to do this besides an If statement?
Direct User Before Errors Are Made
Preferable to informing a user after an invalid action has been made is to prevent the user from performing that invalid action in the first place[1]. One way to do this is to use the Textbox_AfterUpdate event to call a shared validation routine that controls the Enabled property of your OK button, and also controls the display of a status label. The result is a more informative interface that only allows valid actions, thereby limiting the nuisance of msgbox popups. Here's some example code and screenshots.
Private Sub TextBox1_AfterUpdate()
RunValidation
End Sub
Private Sub TextBox2_AfterUpdate()
RunValidation
End Sub
Private Sub TextBox3_AfterUpdate()
RunValidation
End Sub
Private Sub RunValidation()
If Len(TextBox1.Value) = 0 Or Len(TextBox2.Value) = 0 Or Len(TextBox3.Value) = 0 Then
CommandButton1.Enabled = False
Label1.Visible = True
Else
CommandButton1.Enabled = True
Label1.Visible = False
End If
End Sub
Private Sub CommandButton1_Click()
Me.Hide
End Sub
The If Statement
As far as the If statement is concerned, there are a ton of ways that can be done, but I think anything other than directly evaluating TextBox.Value leads to unnecessary plumbing and code complexity, so I think it's hard to argue for anything other than the If statement in the OP. That being said, this particular If statement can be slightly condensed by capitalizing on its numeric nature, which allows for
Len(TextBox1.Value) = 0 Or Len(TextBox2.Value) = 0 Or Len(TextBox3.Value) = 0
to be replaced with
Len(TextBox1.Value) * Len(TextBox2.Value) * Len(TextBox3.Value) = 0
Although that doesn't gain you much and is arguably less readable code, it does allow for a condensed one liner, especially if the textboxes are renamed...
If Len(TB1.Value) * Len(TB2.Value) * Len(TB3.Value) = 0 Then
.Value vs .Text
Lastly, in this case, I think .Value should be used instead of .Text. .Text is more suited for validating a textbox entry while its being typed, but in this case, you're looking to validate a textbox's saved data, which is what you get from .Value.
More User feedback - Colorization
I almost forgot, I wanted to include this example of how to include even more user feedback. There is a balance between providing useful feedback and overwhelming with too much. This is especially true if the overall form is complicated, or if the intended user has preferences, but color indication for key fields is usually beneficial. A lot of applications may present the form without color at first and then colorize it if the user is having trouble.
Private InvalidColor
Private ValidColor
Private Sub UserForm_Initialize()
InvalidColor = RGB(255, 180, 180)
ValidColor = RGB(180, 255, 180)
TextBox1.BackColor = InvalidColor
TextBox2.BackColor = InvalidColor
TextBox3.BackColor = InvalidColor
End Sub
Private Sub TextBox1_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub TextBox2_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub TextBox3_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub RunValidation(ByRef tb As MSForms.TextBox)
If Len(tb.Value) > 0 Then
tb.BackColor = ValidColor
Else
tb.BackColor = InvalidColor
End If
If Len(TextBox1.Value) * Len(TextBox2.Value) * Len(TextBox3.Value) = 0 Then
CommandButton1.Enabled = False
Label1.Visible = True
Else
CommandButton1.Enabled = True
Label1.Visible = False
End If
End Sub
Private Sub CommandButton1_Click()
Me.Hide
End Sub
As I said in my comment, that is an ok way to do it. But i'll post this just so you have an example of another way. This would allow you to evaluate what is going into the text boxes as they are set.
Option Explicit
Dim bBox1Value As Boolean
Dim bBox2Value As Boolean
Dim bBox3Value As Boolean
Private Sub TextBox1_Change()
If Trim(TextBox1.Text) <> "" Then
bBox1Value = True
End If
End Sub
Private Sub TextBox2_Change()
If Trim(TextBox2.Text) <> "" Then
bBox2Value = True
End If
End Sub
Private Sub TextBox3_Change()
If Trim(TextBox3.Text) <> "" Then
bBox3Value = True
End If
End Sub
Private Sub CommandButton1_Click()
If bBox1Value = True And bBox2Value = True And bBox3Value = True Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
You can use a loop:
Private Sub CommandButton1_Click()
Dim n as long
For n = 1 to 3
If Len(Trim(Me.Controls("TextBox" & n).Value)) = 0 Then
MsgBox "Please Complete All Fields!"
Exit Sub
End If
Next n
Me.Hide
End Sub
You can use the below code
Private Sub CommandButton1_Click()
If Trim(TextBox1.Value & vbNullString) = vbNullString And _
Trim(TextBox2.Value & vbNullString) = vbNullString And _
Trim(TextBox3.Value & vbNullString) = vbNullString Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
I got the answer from this question
VBA to verify if text exists in a textbox, then check if date is in the correct format

Do seaching in excel on typing

I am dealing with data input. Every time before I input one record, I need to search and locate one cell in about 5000 rows. I am thinking if it is possible to do the search while typing instead of using the search function in excel. My idea is to capture the keypress event of a spreadsheet, if it is a character (letter 'A' to 'Z' or 'a' to 'z'), append it to a string called searchVal do the search immediate with a function. When the user press ESC, it will clean up the searchVal. First of all, I got the following code from online, trying to capture the ESC keydown
Private searchVal As String
Private Sub Workbook_Activate()
Application.OnKey "ESC", "CleanSearchKey"
End Sub
Private Sub Workbook_Deactivate()
Application.OnKey "ESC"
End Sub
Sub CleanSearchKey()
searchVal = ""
MsgBox "CleanSearchKey"
End Sub
But his code doesn't work, it seems that the CleanSearchKey never triggered. As for capturing the key down for 'A' to 'Z' and 'a' to 'z', I don't want know how to capture it in a sheet so I add an inputbox on sheet1, assigned the following macro to the inputbox
Sub TextBox1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If (KeyAscii >= 65 And KeyAscii <= 90) Or (KeyAscii >= 97 And KeyAscii <= 122) Then
searchVal = searchVal & KeyAscii
Else
KeyAscii = 0
End If
End Sub
But it then said "Compile error: User-defined type not defined". So what is the right way to capture the key on a sheet? Thanks.
The code you added for a TextBox is only applicable to a TextBox which appears on a UserForm object. I don't think text boxes on worksheets respond to events like KeyPress.
That said, you could easily configure a simple userform to do this.
I modified your code a little bit. I think this should get you started. In a normal code module, do a procedure like this which will launch the UserForm. This will allow you to show the form from the Macros ribbon menu. Otherwise, you will have to invoke it manually from the Immediate Window or by pressing F5 in the VBE.
Sub ShowForm()
UserForm1.Show vbModeless
End Sub
Create a UserForm, and add a textbox. Its default name should be TextBox1, if it is not, then make sure to change it. In the userForm's code module, do this:
Option Explicit
Dim searchVal As String
Sub TextBox1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
searchVal = TextBox1.Value
If (KeyAscii >= 65 And KeyAscii <= 90) Or (KeyAscii >= 97 And KeyAscii <= 122) Then
searchVal = searchVal & Chr(KeyAscii)
TextBox1.Value = searchVal
Call FindValue
End If
If KeyAscii = 27 Then 'ESC
searchVal = vbNullString
TextBox1.Value = vbNullString
Else:
KeyAscii = 0
End If
End Sub
Sub FindValue()
Dim rngFound as Range
With ActiveSheet
set rngFound = .Cells.Find(searchVal)
End With
If rngFound Is Nothing Then
MsgBox searchVal & " not found!"
Else
MsgBox searchVal & " found at " & rngFound.Address
End If
End Sub

Excel ActiveX ComboBox onClick event

I am trying to use the ActiveX ComboBox in excel. Everything works fine to the point of being populated from the drop down button click_event. But when it set the click event I find it is triggered even from keystrokes like the Arrow keys. Is this normal behavior, and if so how can I bypass this?
I am working on Excel 2007 VBA
This is the method i used to allow navigating in the combo box using keys , i will wait to see if there is a better solution.. : lastkey is a public variable
Private Sub ComboBox1_KeyDown(ByVal KeyCode As _
MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 38 Then
If ComboBox1.ListIndex <> 0 Then
lastkey = KeyCode
ComboBox1.ListIndex = ComboBox1.ListIndex - 1
KeyCode = 0
End If
ElseIf KeyCode = 40 Then
If ComboBox1.ListIndex <> ComboBox1.ListCount - 1 Then
lastkey = KeyCode
ComboBox1.ListIndex = ComboBox1.ListIndex + 1
KeyCode = 0
End If
End If
End Sub
Private Sub ComboBox1_Click()
If lastkey = 38 Or lastkey = 40 Then
Exit Sub
Else
MsgBox "click"
End If
End Sub
Yes this is normal behavior. Using the arrow keys navigates the items in the combo and hence the click event is fired.
To bypass that insert this code. This captures all the 4 arrow keys and does nothing when it is pressed. The only drawback of this method is that you will not be able to navigate using the Arrow keys anymore.
Private Sub ComboBox1_KeyDown(ByVal KeyCode As _
MSForms.ReturnInteger, ByVal Shift As Integer)
Select Case KeyCode
Case 37 To 40: KeyCode = 0
End Select
End Sub
FOLLOWUP
Dim ArKeysPressed As Boolean
Private Sub ComboBox1_Click()
If ArKeysPressed = False Then
MsgBox "Arrow key was not pressed"
'~~> Rest of Code
Else
ArKeysPressed = False
End If
End Sub
Private Sub ComboBox1_KeyDown(ByVal KeyCode As _
MSForms.ReturnInteger, ByVal Shift As Integer)
Select Case KeyCode
Case 37 To 40: ArKeysPressed = True
End Select
End Sub