Excel Userform Textbox Behavior - excel

I have a textbox on a userform. It is the only textbox on the form. There are three labels and two buttons in addition to this textbox. Basically, I want the focus to remain on this textbox under all scenarios, other than the moment that one of the buttons would be clicked, but then I want the focus to come right back to the text box. Both buttons have "TakeFocusOnClick" and "TabStop" set to False. I was having problems with getting the focus set to the textbox, which is why I changed these two settings.
Once I changed these settings, the Enter key in the textbox stopped having any effect. I have events written for _AfterUpdate and _KeyPress for the textbox, but they don't fire. As you can see in the code, I have commented out the lines to set the focus to this textbox. Since it is now the only object that can take focus, these lines are not needed (theoretically). When I allowed the other objects to take focus, these lines weren't having any effect (focus was switching to the buttons despite these SetFocus lines).
Here is the code. It is very simple, except that the Enter key isn't triggering the event. Can anyone see why? Thanks.
Private Sub btnDone_Click()
Application.Calculation = xlCalculationAutomatic
formMath.Hide
'Clear statistics
Range("attempts").Value = 0
Range("correct").Value = 0
Sheet5.Range("A2:W500").ClearContents
End Sub
Private Sub btnSubmit_Click()
recordAnswer
'formMath.txtAnswer.SetFocus
End Sub
Private Sub txtAnswer_AfterUpdate()
recordAnswer
'formMath.txtAnswer.SetFocus
End Sub
Private Sub txtAnswer_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii = 13 Then
recordAnswer
End If
End Sub
Private Sub UserForm_Initialize()
'Initialize manual calculation
Application.Calculation = xlCalculationManual
Application.Calculate
'Initialize statistics
Range("attempts").Value = 0
Range("correct").Value = 0
Sheet5.Range("A2:W500").ClearContents
'Initialize first problem
newProblem
End Sub
Sub recordAnswer()
'Update statistics
Dim attempts, correct As Integer
attempts = Range("attempts").Value
correct = Range("correct").Value
Range("results").Offset(attempts, 0).Value = attempts + 1
Range("results").Offset(attempts, 1).Value = lblTopNum.Caption
Range("results").Offset(attempts, 2).Value = lblBotNum.Caption
Range("results").Offset(attempts, 3).Value = lblBop.Caption
Range("results").Offset(attempts, 4).Value = Range("Answer").Value
Range("results").Offset(attempts, 5).Value = txtAnswer.Text
If (Range("Answer").Value = txtAnswer.Text) Then
Range("results").Offset(attempts, 6).Value = 1
Else
Range("results").Offset(attempts, 6).Value = 0
End If
'Update attempts and success
Range("attempts").Value = attempts + 1
Range("correct").Value = correct + 1
newProblem
End Sub
Sub newProblem()
Application.Calculate
formMath.lblTopNum.Caption = Range("TopNum").Value
formMath.lblBotNum.Caption = Range("BotNum").Value
formMath.lblBop.Caption = Range("ProbType").Value
formMath.txtAnswer.Value = ""
'formMath.txtAnswer.SetFocus
End Sub

To start off
You can either in the design mode, set the TabIndex property of the Textbox to 0 or you can set the focus on the textbox in the UserForm_Initialize()
Private Sub UserForm_Initialize()
TextBox1.SetFocus
End Sub
Similarly after any operation that you perform, simply call the TextBox1.SetFocus to revert to the textbox.
Option Explicit
Private Sub UserForm_Initialize()
TextBox1.SetFocus
End Sub
Private Sub CommandButton1_Click()
MsgBox "Hello from Button 1"
TextBox1.SetFocus
End Sub
Private Sub CommandButton2_Click()
MsgBox "Hello from Button 2"
TextBox1.SetFocus
End Sub
Let me know if this is not what you want?

I found a way to accomplish this. In the code above I took out the _KeyPress and _AfterUpdate events and replaced them with:
Private Sub txtAnswer_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Select Case KeyCode
Case 13: recordAnswer
End Select
End Sub
Not sure why the other methods didn't work, but this does.
Also not sure why simply setting the focus directly didn't work. I suspect that the focus was being set, but then something else was happening subsequently that was changing the focus off of the textbox. Just a guess.
Thanks for the help. I appreciate it.

Related

Userform - Textbox only accept number

I've been searching for a solution to change apperance of my textbox into red when the entered value is not a number.
Actually, I succeeded with a textbox only but I have 14 textboxes at total. I've tried with the codes below:
Private Sub Justi_num()
Dim i As Integer
For i = 2 To 14
If IsNumeric(Controls("TextBox" & i).Value) Then
Controls("TextBox" & i).BackColor = RGB(255, 255, 255) 'Blanc
Else 'Sinon
Controls("TextBox" & i).BackColor = RGB(247, 205, 201) 'Rouge clair
End If
Next i
End Sub
But it didnt work. Any help? Thanks in advance.
You first need to ensure that your code is called whenever the user enters something. This is done with Event-Routines. Controls (like buttons, checkboxes, textboxes and so on) have different events like change (something was changed), dblClick (there was a double click on the control) and so on.
If you want some code to be executed, you need to put code into that event routine. The name of the event routine is combined by the name of the control and the event name. If the name is not following that rule, the code is not called automatically.
In design mode, go to the form editor and double click onto one of your textboxes: The editor will open the text editor window and create a subroutine with the most common event of that control (for a textbox, that's the Change-event, for a button, it would be the Click-event):
Private Sub TextBox1_Change()
End Sub
You could call your routine Justi_num from this routine:
Private Sub TextBox1_Change()
Justi_num
End Sub
However, you will need to create such an event routine for every of your text boxes, there is no way around that (ok, yes, there is, but it is much too complicated for a beginner).
Private Sub TextBox2_Change()
Justi_num
End Sub
Private Sub TextBox3_Change()
Justi_num
End Sub
and so on...
One thing: Your routine Justi_num handles all textboxes, no matter which textbox was changed. You could change the code so that the event routine passes the information which control was changed and the color changing routine handles only that control:
Private Sub TextBox1_Change()
setTextBoxColor Me.TextBox1
End Sub
Private Sub TextBox2_Change()
setTextBoxColor Me.TextBox2
End Sub
(and so on...)
Private Sub setTextBoxColor(tbox As Control)
If IsNumeric(tbox.Value) Or tbox.Value = "" Then
tbox.BackColor = vbWhite
Else
tbox.BackColor = RGB(247, 205, 201)
End If
End Sub
I'll try to help:
One thing that may fail is that "IsNumeric" gives a boolean result. Try using If IsNumeric(Controls("TextBox" & i).Value) = True Then
It would be something like:
Private Sub Justi_num()
Dim i As Integer
Dim NA As Boolean
NA = False
For i = 2 To 14
NA = IsNumeric(Controls("TextBox" & i).Value)
If NA = True Then
Controls("TextBox" & i).BackColor = RGB(255, 255, 255) 'Blanc
Else 'Sinon
Controls("TextBox" & i).BackColor = RGB(247, 205, 201) 'Rouge clair
End If
Next i
End Sub
Hope it helps!

Auto scroll sheet to active row while using userform

Entering data via a userform. After a few entries I can no longer see the active cell and I can't scroll the sheet while the userform is active. I would like to have the sheet auto-scroll to keep the active cell/row visible while I enter data on the userform.
I've tried adding some code I found online but they've all failed to get the result I'm looking for.
Private Sub Label1_Click()
End Sub
Private Sub Label2_Click()
End Sub
Private Sub TextBox1_Change()
End Sub
Private Sub TextBox2_AfterUpdate()
Dim emptyRow As Long
If TextBox1.Value <> "" And TextBox2.Value <> "" Then
With ThisWorkbook.Sheets("RO-BOX DETAILS")
emptyRow = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
.Cells(emptyRow, 1).Value = TextBox1.Value
.Cells(emptyRow, 2).Value = TextBox2.Value
TextBox1.Value = ""
TextBox2.Value = ""
TextBox1.SetFocus
End With
End If
End Sub
Private Sub UserForm_Click()
End Sub
I'm not experienced with VBA. I honestly thought there would be a simple code snippet I could add in, but so far, no joy.
I scan into textbox 1 and it moves the focus to textbox 2
I scan into textbox 2 and it adds the data to the next empty row - no
clicking 'OK' required.
The focus returns to textbox 1.
Repeat
Now I just want it to keep the next empty row visible so I can see the entries go in on the fly. Basically to ensure they scanned correctly. Right now, I have to close the userform every so often and scroll down and look manually. PITA. :)
It won't quite achieve the autoscrolling you requested, but you could try calling the userform with it's ShowModal property set to "modeless". That would at least allow the user to manually scroll the sheet.
https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/showmodal-property
...there's no example to follow on that page, but it would go something like...
sub CallNormsUserForm()
NormsUserForm.show vbModeless
end sub

How to exit from the loop when user clicks X button on userform?

I have vba code below to edit every row one by one on userform interface:
Private Sub CommandButton1_Click()
Dim MyRange As Range
Dim cell As Range
Set MyRange = Range("A2", Range("A2").End(xlDown))
For Each cell In MyRange
cell.Select
'Build form
MyForm.Title.Caption = cell.Offset(0, 2)
If cell.Offset(0, 2) = 1 Then
MyForm.Checked.Value = True
Else
MyForm.Checked.Value = False
End If
'Show form
MyForm.Show
Next cell
End Sub
I have thousand of rows and I need to give user a some break!
I want this process to pause if user clicks X button, but I couldn't find a way to exit from the loop.
My userform code is like below:
Private Sub Checked_Click()
If MyForm.Checked.Value = True Then
ActiveCell.Offset(0, 1).Value = 1
Else
ActiveCell.Offset(0, 1).Value = ""
End If
End Sub
Private Sub DoneButton_Click()
ActiveCell.Offset(0, 2).Value = 1
Unload MyForm
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
MsgBox "paused"
End If
End Sub
What I'm trying to accomplish?
I need to stop for each loop at first block of code when users click X button to close the userform. But I don't know how to listen if UserForm_QueryClose called to exit loop from first block of code.
Sadly I don't think this is possible, given vba doesn't support multi-threading.
If you really insist on asking the user whether to pause, it would be better to simply ask an If conditional during the run-time.
eg.
For i = 1 to myRange.Cells.Count
' your code here...
If i = myRange.Cells.Count / 2 Then
If MsgBox("Continue to the for loop?", vbYesNo) = vbNo Then
UnLoad MyForm
Exit For
End If
End If
Next i
You need to add DoEvents which will check for a user interaction. However, simply closing the form will not stop the code from running so you will need to add another controller.
For example
Option Explicit
Private stopCode As Boolean
Private Sub UserForm_Click()
Dim i As Long
stopCode = False
For i = 1 To 1000000
Debug.Print i
DoEvents
If stopCode = True Then Exit For
Next
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If MsgBox("Do you want to stop", vbQuestion + vbYesNo) <> vbYes Then Cancel = True
stopCode = True
End Sub
When the user closes the form they will be asked if they want to stop the code. If they say yes it closes the form but the stopCode 'controller' also makes sure the running code is stopped.

Excel form listbox issue when using locked property

I'm having an issue with the way a listbox behaves on an Excel form. Steps to reproduce the issue:
Create a user form with one listbox control
Use following code with this user form:
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.ListBox1.Locked = True
Me.ListBox1.Locked = False
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
When the form is first shown, I am able to navigate the list box normally, using arrow keys and page keys. However, after the double-click event is triggered, all keyboard navigation has no effect, including tabbing to other controls (if they're on the form). Clicking on the listbox does seem to work, and the focus outline is shown correctly, but there's something wrong with the way the focus is handled after the listbox is locked and then unlocked. What is going on?
Using Office 2013 32-bit edition.
I managed to replicate this issue, and setting the focus somewhere else before locking and unlocking the listbox worked for me:
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.TextBox1.SetFocus 'or some other control.
Me.ListBox1.Locked = True
Me.ListBox1.Locked = False
Me.ListBox1.SetFocus
End Sub
2 solutions I tested that allows you to use Arrow Keys to navigate.
Given that there isn't a single click event handler, try call the Single Click Event after the DblClick (works all the time):
Private Sub ListBox1_Click()
Debug.Print "ListBox1_Click()"
End Sub
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Debug.Print "ListBox1_DblClick()"
With Me.ListBox1
.Locked = True
.Locked = False
End With
ListBox1_Click
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
Setting the Cancel = False at the end of DblClick. (Sometimes does not work!)
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Debug.Print "ListBox1_DblClick()"
With Me.ListBox1
.Locked = True
.Locked = False
End With
Cancel = False
End Sub
Private Sub UserForm_Initialize()
Dim i As Integer
For i = 1 To 10
Me.ListBox1.AddItem i
Next i
End Sub
Changing the zoom in the sheet that has the listbox worked for me.
Private Sub Worksheet_Activate()
Dim temp As Double
Application.ScreenUpdating = False
'Change worksheet zoom setting for the active window
temp = ActiveWindow.Zoom
ActiveWindow.Zoom = temp + 10
ActiveWindow.Zoom = temp
Application.ScreenUpdating = True
End Sub

Loop repeated code to set userform checkbox values

I've a dashboard that uses a userform box field with checkboxes to choose what data to display on a chart.
My code is very copy and pasted.
Private Sub CommandButton21_Click()
UserForm1.Show
If Worksheets("Data Directorate").Range("X4").Value = True Then UserForm1.CheckBox1 = True
If Worksheets("Data Directorate").Range("X5").Value = True Then UserForm1.CheckBox2 = True
If Worksheets("Data Directorate").Range("X6").Value = True Then UserForm1.CheckBox3 = True
Is there a way to use a loop to do this?
I've more repeated code later on:
Private Sub CheckBox1_Click()
Select Case CheckBox1.Value
Case True
Worksheets("Data Directorate").Range("X4").Value = True
Case False
Worksheets("Data Directorate").Range("X4").Value = False
End Select
End Sub
This repeats for 24 check boxes. Would it be possible to loop this?
All great advice posted in this thread, so I'd like to add something that can maybe help to simplify your loops. Controls have a Tag property which, as best I can tell, does nothing other than to store additional information about the control.
Using this to our advantage, we can include the linked cell in the Tag property for each checkbox. (For example, enter X4 into the Tag for linkage to cell X4). This permits for less of the information to be hardcoded, which makes it more adaptable.
Finally, the code would look like this.
Private Sub UserForm_Click()
For Each octrl In Me.Controls
If TypeName(octrl) = "CheckBox" Then
Sheet1.Range(octrl.Tag).Value = octrl.Value
End If
Next octrl
End Sub
My approach in this scenario would be:
Set ControlSource property of the checkboxes to appropriate cells, leave only:
UserForm1.Show
One thing to simplify: instead of using If statements, just make the two sides equal. Like this:
Private Sub CommandButton21_Click()
UserForm1.Show
UserForm1.CheckBox1 = Worksheets("Data Directorate").Range("X4").Value
And the other one:
Private Sub CheckBox1_Click()
Worksheets("Data Directorate").Range("X4").Value = CheckBox1.Value
End Sub
I recommend using the change event instead of the click event. Some user might use Tab and Space, then the click event won't trigger. Like this:
Private Sub CheckBox1_Change()
...
About looping through the checkboxes in the CommandButton21_Click event, that depends on the order of your checkboxes and your table. This might work, but you will have to try it on your table (the order of the checkboxes compared to the order of your cells can ruin the game...)
Dim contr As control
dim i as integer
i=4
For Each contr In UserForm1.Controls
If TypeName(contr) = "CheckBox" Then
contr.Value = cells(i,24).Value 'column 24 is column X
i=i+1
End If
Next
A possible variation to work when CheckBoxes are not in the expected order or get added/removed:
Dim contr As control
Dim J as Integer
Dim Offset as Integer
Offset = 3 'set this to the difference between 1 and the first row containing data
For Each contr In UserForm1.Controls
If TypeName(contr) = "CheckBox" Then
'set j to the checkboxes "number"
J = cInt(right(contr.name, len(contr.name) - len("CheckBox")))
'use the checkbox number + offset to find the row we want
contr.Value = cells(J + Offset,24).Value 'column 24 is column X
End If
Next
Hope this helps.
For the second portion of your question:
Private Sub CheckBox1_Click()
Worksheets("Data Directorate").Range("X4").Value = CheckBox1.Value
End Sub
You can group the checkboxes inside a given frame and try the the following
Sub Test()
Dim i As Long
i = 5
For Each cb In UserForm1.Frame1.Controls
If Worksheets("Data Directorate").Range("X" & i).Value = True Then cb.Value = True
i = i + 1
Next cb
End Sub

Resources