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
Related
I have code on a userform that contains several checkboxes and several DTPickers.
The code looks like so:
Private Sub CheckBox11_Click()
If CheckBox11.Value = True Then
DTPicker22.Enabled = True
Else
DTPicker22.Enabled = False
End If
End Sub
Private Sub CheckBox12_Click()
If CheckBox12.Value = True Then
DTPicker24.Enabled = True
Else
DTPicker24.Enabled = False
End If
End Sub
The Userform contains a lot of checkboxes that have clauses next to them. Upon their completion the DTPicker will enable entering the date of completion.
Whilst this does what I want, it only enables one DTPicker when the checkbox is ticked per private sub. There has to be some way to make this so I wouldn't need to create different private subs for every checkbox click event.
Could you also tell me where to put it, as in, what event?
A "control array" is the typical approach for something like this.
See:
http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/
eg:
Class module clsEvents
Option Explicit
'Handle events for a checkbox and a date control, associated with a worksheet cell
Private WithEvents m_CB As MSForms.CheckBox
Private WithEvents m_DP As DTPicker
Private m_dateCell As Range
'set up the controls and the cell
Public Sub Init(cb As MSForms.CheckBox, dp As DTPicker, rng As Range)
Set m_CB = cb
Set m_DP = dp
Set m_dateCell = rng
If rng.Value > 0 Then
cb.Value = True
m_DP.Value = rng.Value
Else
cb.Value = False
End If
m_DP.CustomFormat = "dd/MM/yyyy"
End Sub
Private Sub m_CB_Change()
m_DP.Enabled = (m_CB.Value = True)
End Sub
Private Sub m_DP_Change()
m_dateCell.Value = m_DP.Value 'update the cell
End Sub
Userform:
Option Explicit
Dim colObj As Collection 'needs to be a Global to stay in scope
Private Sub UserForm_Activate()
Dim obj As clsEvents, i As Long, ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set colObj = New Collection
'loop over controls and create a class object for each set
' 3 pairs of controls on my test form...
For i = 1 To 3
Set obj = New clsEvents
obj.Init Me.Controls("CheckBox" & i), _
Me.Controls("DTPicker" & i), _
ws.Cells(i, "B")
colObj.Add obj
Next i
End Sub
The first thing I'd recommend is following a proper naming convention. "CheckBox11" and "DTPciker1" are really vague and once you get further into your code, you'll forget which control is which. I would recommend naming them something that relates the two control together, like "firstDate" and "firstDateDTP". My alternate answer below uses this approach.
You could make a public function that enables the DTPicker based upon the checkbox's value.
Public Function EnableDTPicker(myPicker as String, enableBool as Boolean)
UserFormName.Controls(myPicker).Enabled = enableBool
End Function
Then, you can call the function in your CheckBox123_Click() subs like this:
Private Sub CheckBox123_Click()
EnableDTPicker("thePickerName", CheckBox123.Value)
End Sub
Alternatively, you could make a timer event that runs x number of seconds that just loops through the controls and performs the checks as needed. See this page on how to set up the timer. Using the code in the link shown, You could do something along the lines of:
'Put this in Workbook events
Private Sub Workbook_Open()
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
UserForm1.Show
End Sub
'Put this in a Module
Public Sub EventMacro()
With UserForm1
For each ctrl in .Controls
If TypeName(ctrl) = "CheckBox" Then
'The code below assumes the naming convention outlined above is followed
.Controls(ctrl.Name & "DTP").Enabled = ctrl.Value
End If
Next ctrl
End With
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
End Sub
I have an ActiveX Combobox in one of my main sheet which control/update a series of charts.
Private Sub cmBoxSelect_GotFocus()
Application.ScreenUpdating = False
With Me.cmBoxSelect
.List = Array("Grand Total", "Prod1", "Prod2", "Prod3", "Prod4", "Prod5")
.ListRows = 6
.DropDown
End With
Application.ScreenUpdating = True
End Sub
Private Sub cmBoxSelect_Change()
'series of codes which manipulates the charts, based on selection...
End Sub
I noticed that when I click the ComboBox and select one of its content, it leaves a blue highlight on the selection. So to prevent that, I added:
Private Sub cmBoxSelect_DropButtonClick()
Application.ScreenUpdating = False
ActiveCell.Activate
Application.ScreenUpdating = True
End Sub
It successfully removed the highlight.
However, it has a weird drawback. cmbSelect doesn't close automatically once user didn't select anything (once the combobox is active and the user click any cell in the sheet, it doesn't close out). It was working before I added the DropButtonClick event.
Did I missed anything or any wrong steps above? Thanks for your inputs!
EDIT#1
Seems I already found a solution by trial and error. I only added a blank Label and select it to remove the focus out of the ComboBox whenever there is a change. I also changed the DropButtonClick to LostFocus.
Private Sub cmBoxSelect_GotFocus()
Application.ScreenUpdating = False
With Me.cmBoxSelect
.List = Array("Grand Total", "Prod1", "Prod2", "Prod3", "Prod4", "Prod5")
.ListRows = 6
.DropDown
End With
Application.ScreenUpdating = True
End Sub
Private Sub cmBoxSelect_LostFocus()
ActiveCell.Select
End Sub
Private Sub cmBoxSelect_Change()
'series of codes which manipulates the charts, based on selection...
Me.Label1.Select
End Sub
You need to put the SelLength to 0 in multiple events to avoid highlighting:
so:
Me.cmBoxSelect.SelLength = 0
in:
Private Sub cmBoxSelect_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Private Sub cmBoxSelect_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Private Sub cmBoxSelect_LostFocus()
Private Sub cmBoxSelect_DropButtonClick()
Private Sub cmBoxSelect_Change()
Private Sub cmBoxSelect_GotFocus()
(you could add also Me.cmBoxSelect.SelStart = 0 )
Lets try this:
Not Event-triggered by a change, but by the dropbuttonclick
Private Sub changingComboBox(String s)
'series of codes which manipulates the charts, based on selection...
End Sub
Private Sub cmBoxSelect_DropButtonClick()
Dim s As String
s = cmBoxSelect.SelText
If (cmBoxSelect.SelText = cmBoxSelect.Value) Then
cmBoxSelect.Value = ""
cmBoxSelect.Value = s
Else
call changingComboBox(cmBoxSelect.Value)
End If
End Sub
How about that ?
I have to set ActiveX control tab order in MS Word using VBA. So here is the basic code:
Private Sub radioFull_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _
ByVal Shift As Integer)
If KeyCode = 9 Then
radioIntern.Activate
End If
End Sub
Problem is I have an active Restrict Editing Protection on the document set by password. Thus after starting protection, while pressing a tab on any control, it deny to functioning saying that I have a protection on the document.
So, during execution of the above function, I first have to un-protect the document, moving tab to next field and then re-protect by the following function:
Private Sub ToggleProtect()
If ActiveDocument.ProtectionType <> wdNoProtection Then
ActiveDocument.Unprotect Password:="password"
Else
ActiveDocument.Protect Password:="password", NoReset:=True, _
Type:=wdAllowOnlyFormFields, _
UseIRM:=False, EnforceStyleLock:=False
End If
End Sub
Private Sub radioFull_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _
ByVal Shift As Integer)
If KeyCode = 9 Then
ToggleProtect
radioIntern.Activate
ToggleProtect
End If
End Sub
It works well. So I intend to shorten the main code a little bit more by something like this:
Private Sub radioFull_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _
ByVal Shift As Integer)
tabOrder(KeyCode, controlName)
End Sub
and the tabOrder function in this case like the follwoing:
Public Sub tabOrder(K as integer,t as string)
If KeyCode = K Then
ToggleProtect
t.Activate
ToggleProtect
End If
End Sub
But I am not familiar on VBA function argument. So please tell me how to pass the argument or write the function correctly so that I can maintain tab order in MS Word form?
Even though the MS Forms controls are derived from MSForms.Control VBA is apparently unable to "cast" them to this data type. It can work with the general type, however. The trick is to declare the procedure argument as data type Variant.
While I was at it, I made another small optimization to the code by declaring an object variable of type Word.Document for passing the document to ToggleProtect. While it's unlikely, it is theoretically possible that the user will change documents during code execution, making the ActiveDocument a different one than that which triggered the code. So if you get the target document immediately then the code will always execute on the correct document, no matter which one currently has the focus.
Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _
ByVal Shift As Integer)
Dim doc As Word.Document
Set doc = Me
tabOrder KeyCode, doc, Me.TextBox1
End Sub
Public Sub tabOrder(ByVal KeyCode As MSForms.ReturnInteger, _
ByRef doc As Word.Document, ByRef t As Variant)
If KeyCode = 9 Then
ToggleProtect doc
t.Activate
ToggleProtect doc
End If
End Sub
Private Sub ToggleProtect(doc As Word.Document)
If doc.ProtectionType <> wdNoProtection Then
doc.Unprotect Password:="password"
Else
doc.Protect Password:="password", NoReset:=True, _
Type:=wdAllowOnlyFormFields, _
UseIRM:=False, EnforceStyleLock:=False
End If
End Sub
In your KeyDown event, it looks like you want to pass the KeyCode and the Control. Therefore, the arguments you pass must match the signature of the tabOrder sub. Look how KeyCode is defined and copy/paste to your tabOrder sub. The second argument will be defined as Control allowing for any control to be passed. Here is an example of what I am talking about:
Private Sub radioFull_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
tabOrder KeyCode, radioFull
End Sub
Public Sub tabOrder(ByVal KeyCode As MSForms.ReturnInteger, ByRef t As MSForms.Control)
If KeyCode = 9 Then
ToggleProtect
t.Activate
ToggleProtect
End If
End Sub
I have a User-form
For most of Check-Boxes/ Buttons I assigned a Key. Can be execute by pressing:
Alt + Assigned-key
I had googled the following code.
Private Sub UserForm_Initialize()
Me.PASTE.Accelerator = "V"
Me.CEEMEA.Accelerator = "C"
End Sub
Problem is I have to Press Alt key to perform any given task.
Q. Is there any short way of doing this without pressing AltKey?
My progress After Robin's Original-Answer
Firstly I set focus on Macros Button.
Private Sub UserForm_Initialize()
Me.Macros.SetFocus
End Sub
Then on Macro_Keydown Event I put the following code.
Private Sub Macros_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = vbKeyB Then
Bulgaria.Value = Not Bulgaria.Value
ElseIf KeyCode = vbKeyE Then
Estonia.Value = Not Estonia.Value
ElseIf KeyCode = vbKeyH Then
Hungary.Value = Not Hungary.Value
ElseIf KeyCode = vbKeyA Then
Latvia.Value = Not Latvia.Value
ElseIf KeyCode = vbKeyL Then
Lithuania.Value = Not Lithuania.Value
ElseIf KeyCode = vbKeyM Then
Macedonia.Value = Not Macedonia.Value
ElseIf KeyCode = vbKeyP Then
Poland.Value = Not Poland.Value
ElseIf KeyCode = vbKeyR Then
Romania.Value = Not Romania.Value
ElseIf KeyCode = vbKeyU Then
Ukraine.Value = Not Ukraine.Value
End If
End Sub
Updated answer
The original answer didn't really meet the brief because whilst handling the UserForm events for e.g. KeyDown works for a form with no other controls, it doesn't work for a form with controls. This is because the event only works when the form has the focus. When the form has other controls, it never receives the focus. Also, it is not possible to set the focus onto the UserForm. Almost all forms will have some other controls, so the original answer is practically useless. So let's shamelessly adapt an idea from Andy Pope on MSDN to meet the OP's requirements.
First, insert a VBA Class into the project with this code:
Public WithEvents m_objGroupCheckBox As MSForms.CheckBox
Private Sub m_objGroupCheckBox_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
MsgBox "Keypress was: " & Chr(KeyCode) & " on " & m_objGroupCheckBox.Caption
Select Case Chr(KeyCode)
Case 1:
UserForm1.CheckBox1.Value = Not UserForm1.CheckBox1.Value
Case 2:
UserForm1.CheckBox2.Value = Not UserForm1.CheckBox2.Value
Case "3"
UserForm1.CheckBox3.Value = Not UserForm1.CheckBox3.Value
End Select
End Sub
The Class defines a generic event handler for a CheckBox on the UserForm. For the purposes of this example, we will make key presses of 1, 2 and 3 toggle the checkbox state for the 3 CheckBoxs on the form.
Second, put the code in the Userform's initialize event. It creates a collection of this custom class that references back to the original checkboxes created on the UserForm.
Private m_colCheckBoxes As Collection
Private Sub UserForm_Initialize()
Dim lngIndex As Long
Dim objGroupCheckBox As clsGroupCheckBox
Set m_colCheckBoxes = New Collection
For lngIndex = 1 To 3
Set objGroupCheckBox = New clsGroupCheckBox
Set objGroupCheckBox.m_objGroupCheckBox = Me.Controls("CheckBox" & lngIndex)
m_colCheckBoxes.Add objGroupCheckBox, CStr(m_colCheckBoxes.Count + 1)
Next
End Sub
So now, if we have a UserForm in the designer like this, with each CheckBox named CheckBox1, CheckBox2 and CheckBox3:
Then, our generic event handler will allow us to define a single place to handle the KeyDown event and set CheckBox status in one spot.
Original answer - not as useful as it looks :(
You can directly handle the KeyDown event of the UserForm and enter your specific logic in there. Maybe you should check out KeyUp and KeyPress as well depending on how you think the form will work.
MSDN notes that '..."A" and "a" are returned as the same key. They have the identical keycode value. But note that "1" on the typewriter keys and "1" on the numeric keypad are returned as different keys, even though they generate the same character.' - MSDN Link
You can handle SHIFT, CTRL and ALT as well.
Private Sub UserForm_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode >= vbKeyA And KeyCode <= vbKeyZ Then
MsgBox "You pressed " & Chr(KeyCode)
ElseIf KeyCode >= vbKeyF1 And KeyCode <= vbKeyF12 Then
MsgBox "Function time!"
End If
End Sub
'VBA Shortcut Keys not work in UserForm [Partially Solved]
Public Sub CallSub() 'code must be in Module
'-do this code-
Private Sub Workbook_Activate() 'code must be in (ThisWorkbook)
Application.OnKey "^{f5}", "callSub"
'^ this code only work with Excel Worksheet not in Userform
Private Sub XxX_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer) 'code must be in Userform with SHOWMODAL = False
If KeyCode = 17 Then AppActivate "microsoft excel"
'XxX means all CommandButton and Textbox and Listbox and Combobox
'Keycode 17 is Ctrl Key if you are using Ctrl+F5 - when you press Ctrl it will activate Excel Worksheet
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