Can't set Userform.KeyPreview to true - vba

I've built a form in Excel. It consists of 3 command buttons and a frame containing checkboxes. The checkboxes are dynamically populated at userform_initialize based on tables in an excel sheet (the idea being easy user customization). The reason for the frame is that there can be a lot of checkboxes and I want the user to be able to scroll through them.
My goal now is to create keyboard shortcuts for the form. Where I get stuck is that I can't brute force write KeyDown handlers for each of the checkboxes because I don't know which ones will exist. I realize that it would also just be better if I could have the event handler at the form level. Googling has found me the form's KeyPreview property. Unfortunately, the properties window in VBA IDE doesn't show it and when I try to access it programmatically by setting Me.KeyPreview = True at userform_initialize VBA throws a compile error: "Method or data member not found" - what I would expect given it isn't in the properties window, but was worth a try.
I feel like there's something I'm obviously missing so I thought I'd ask before spending time learning how to write and then rewriting the form entirely as a class as in the MSDN example code:
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.keypreview(v=vs.110).aspx.
Am I that lucky?
I confess to being at the limit of my VBA knowledge and I'm looking to go expand on it. Any general concepts or context I should red would be greatly appreciated.
UPDATE
I'm now thinking about GetAsyncKeyState and Application.Onkey.
From what I understand, GetAsyncKeyState only works within an infinite DoEvents loop. I tried initiating one hoping the form would still load but of course it didn’t – I’m stuck in the loop.
The problem with Application.Onkey is that I can't assign the event function to the key within the userform module. This puzzles me because other event handlers can go in the userform module. In fact, I’d put it in the Userform_Initialize procedure. Is it because it's not a form event but an application event?
EDIT
I seem to have something that works, but for the strange issue described here:
Event handling class will not fire unless I use a breakpoint when initializing form
Thank you #UGP

Here is an example how it could work, found here:
To put in a class named "KeyPreview":
Option Explicit
Dim WithEvents u As MSForms.UserForm
Dim WithEvents t As MSForms.TextBox
Dim WithEvents ob As MSForms.OptionButton
Dim WithEvents lb As MSForms.ListBox
Dim WithEvents dp As MSComCtl2.DTPicker
Event KeyDown(ByVal KeyCode As Integer, ByVal Shift As Integer)
'Event KeyPress(ByVal KeyAscii As Integer)
Private FireOnThisKeyCode As Integer
Friend Sub AddToPreview(Parent As UserForm, KeyCode As Integer)
Dim c As Control
Set u = Parent
FireOnThisKeyCode = KeyCode
For Each c In Parent.Controls
Select Case TypeName(c)
Case "TextBox"
Set t = c
Case "OptionButton"
Set ob = c
Case "ListBox"
Set lb = c
Case "DTPicker"
Set dp = c
End Select
Next c
End Sub
Private Sub u_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = FireOnThisKeyCode Then RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub t_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = FireOnThisKeyCode Then RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub ob_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = FireOnThisKeyCode Then RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub lb_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = FireOnThisKeyCode Then RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub dp_KeyDown(KeyCode As Integer, ByVal Shift As Integer)
If KeyCode = FireOnThisKeyCode Then RaiseEvent KeyDown(KeyCode, Shift)
End Sub
To put in the userform:
Option Explicit
Dim WithEvents kp As KeyPreview
Private Sub UserForm_Initialize()
Set kp = New KeyPreview
kp.AddToPreview Me, 114
End Sub
Private Sub kp_KeyDown(ByVal KeyCode As Integer, ByVal Shift As Integer)
MsgBox "F3 was pressed..."
End Sub
It works with TextBoxes, OptionButtons, ListBoxes and DTPickers. Other Controls that could get focus will need to be handled aswell.

Related

Focus on CommandButton in a SlideShow (PPT)

I have a presentation in PowerPoint with a Slide with two buttons: (Button_01 and Button_02).
My remote has arrow keys so I am trying to navigate between those two buttons with those up and down keys. The problem is I can't change the focus on the buttons.
I wrote the following routines:
Private Sub Button_01_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 40 Then
Application.ActivePresentation.Slides(1).Shapes("Button_02").OLEFormat.Object.SetFocus
End If
End Sub
Private Sub Button_02_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 38 Then
Application.ActivePresentation.Slides(1).Shapes("Button_01").OLEFormat.Object.SetFocus
End If
End Sub
and place them on the code for Slide1. However, SetFocus is not a supported method on PowerPoint.
I feel very frustrated because I can edit vba code very easily in Excel and I can´t do the same in PowerPoint.
Is there a way to do this?

Certain events not firing for combobox

I have a userform with programmatically created comboboxes, which I need to run events on. As per the advise here, I created a wrapper class which I put around each such combobox ("event listener").
This is the rough content of the clsEvntListnr class module
Public WithEvents cb As MSForms.ComboBox
Public frm As UserForm
Private Sub cb_Change()
CollectGarbage
End Sub
Private Sub cb_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
'stuff
End Sub
Private Sub cb_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
'stuff
End Sub
Private Sub cb_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
'stuff
End Sub
Private Sub cb_DropButtonClick()
'stuff
End Sub
Private Sub cb_Enter()
'stuff
End Sub
Private Sub cb_Exit()
'stuff
End Sub
Private Sub cb_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
'stuff
End Sub
Private Sub cb_Click()
'stuff
End Sub
Private Sub cb_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'stuff
End Sub
Private Sub cb_AfterUpdate()
'stuff
End Sub
This is how the comboboxes are created (as a part of an event for another combobox). The C_COMBOS at the end is a globally declared collection.
Private Sub cbTransaction_Change()
Dim oEvtListnr As clsEventListener
'other stuff and declarations
For i = LBound(var) To UBound(var)
Set ctrl = Me.Controls.Add("forms.combobox.1", "ctrlTb" & i, True)
Set oEvtListnr = New clsEventListener
Set oEvtListnr.cb = ctrl
Set oEvtListnr.frm = Me
C_COMBOS.Add oEvtListnr
next i
End sub
Now the behaviour is mostly as expected with the exceptions that certain event just will not fire. From the events I defined in the class module, the following do fire:
cb_KeyDown, cb_KeyPress, cb_KeyUp, cb_DropButtonClick, cb_DblClick, cb_MouseUp
while these do not:
cb_Change, cb_Click, cb_Enter, cb_Exit, cb_AfterUpdate
I have made the obvious tests by putting in breaks into these events and indeed they simply do not fire up. Any idea what may be the issue?

Passing MSForms.Control as argument in Word VBA

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

How to set tab order by a macro in MS Word?

I have a MS Word form with ActiveX control (not the form control). Suppose I have two textboxes and two option button as follows:
Name: [textBox1]
Address: [textBox2]
Gender: [opt1] Male [opt2] Female
Now if I want a tab order, I have to add following macro:
Private Sub textBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 9 Then
textBox2.Activate
End If
End Sub
Private Sub textBox2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 9 Then
opt1.Activate
End If
End Sub
Private Sub opt1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 9 Then
opt2.Activate
End If
End Sub
Now in my real form there are 20 text boxes and 12 option buttons, so it is quite boring to write down keydown event for each form field. How can I write a macro so that it will first get the name of current form field on keydown (and validating it as tab keydown) and then go to next form field?
For the sake of tab order, I will then rename all form field chronologically such as field1, field2, field3......... etc, so that with an increment the code can move the tab to next form field.
Here is the screen shot of ActiveX tools those I used in forms:
I have cross-posted this topic to VbaExpress forum also.
You aren't going to be any happier with this answer than you were with my last...
The problem is that the keypress, or KeyDown, is only triggered by the fact that the focus is in an ActiveX control, and will be specific to that control. So you have no choice than a KeyDown event for every control. You can keep the code in the event to a minimum, but...
There's no way to identify controls on a document surface directly by name as a string. ThisDocument.ControlName is possible, but there's nothing like ThisDocument.Controls("ControlName") available that would let you substitute names, nor allow you to identify the name of the current control.
There is a way to do it, but it's convoluted. Since these are in-line with the text (no text wrapping) they belong to the document's InlineShapes collection. Their programming interface can only be addressed through the InlineShape's OLEFormat.Object property. This means the code needs to loop the InlineShapes collection twice: once to identify the ActiveX control where the key was pressed, once to identify the control which should be next.
The following code illustrates the principle. What it does not do is
work for more than 9 controls - that would require code that checks, from the right of the name, how many characters are numerical
go back to the first control if focus is in the last
Note that it might be possible to get around event code for each control. It would involve using the Windows API, which means it would fire every time the user presses Tab. But I have no idea whether the key presses would be captured when focus is inside a control. And you would have to test each time if this is the case - and you'd still have to be able to identify which control the focus is in.
Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
GoToNextControl KeyCode, ThisDocument.TextBox1.Name
End Sub
Private Sub TextBox2_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
GoToNextControl KeyCode, ThisDocument.TextBox2.Name
End Sub
Sub GoToNextControl(KeyCode As MSForms.ReturnInteger, controlName As String)
Dim ils As Word.InlineShape, ils2 As Word.InlineShape
Dim c As MSForms.Control
Dim baseName As String, nextName
Dim nameCounter As Long
baseName = Mid(controlName, 1, Len(controlName) - 1)
nameCounter = Right(controlName, 1)
If KeyCode = 9 Then
For Each ils In ThisDocument.InlineShapes
If ils.Type = wdInlineShapeOLEControlObject Then
If ils.OLEFormat.Object.Name = controlName Then
nextName = baseName & nameCounter + 1
For Each ils2 In ThisDocument.InlineShapes
If ils2.Type = wdInlineShapeOLEControlObject Then
If ils2.OLEFormat.Object.Name = nextName Then
ils2.Select
Exit Sub
End If
End If
Next
End If
End If
Next
End If
End Sub
All-in-all it might make more sense to stick to the legacy form fields or to content controls, or move this to a UserForm that then writes to the document.

Handling / Performing / Managing tasks in Userform with the help of keyboard ( shortcut keys )

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