SetFocus inside a GotFocus procedure initiated by another SetFocus - vba

Objective: Redirect focus from one command button to another using the first's GotFocus procedure.
Context: I have a form-independent procedure in a generic module that, on most forms, sets focus to the NewRecord button after saving the previous record. But on one form, I would like to redirect (based on certain conditions) focus back to the SignRecord button so the user can "sign" a second part of the same record (I may need this for other uses in the future). The target control is enabled and visible and can otherwise be focused and the original control can be focused when the redirect doesn't occur. Reference [2] below implies that this should be possible, though I'm not changing visibility of my controls.
Issue: When the conditions are met to redirect focus in the GotFocus procedure, it redirects as desired but the original (test) SetFocus call throws a "Run-time error '2110', Can't move focus to the control CommandNew".
What I've tried:
Exit Sub after my downstream SetFocus calls.
Call CommandSign.SetFocus in the hopes that it would make it happen outside the previous SetFocus process.
In a module,
Public Sub test()
Forms("TargetForm").CommandNew.SetFocus 'This gets the error '2110'
End Sub
In the 'TargetForm',
Private Sub CommandNew_GotFocus()
If IsNull(textDateTime) Then Exit Sub 'Works as expected
'I can see these two parts work. The framSign value changes
'and CommandSign gets focus
If checPPC And IsNull(textSigID_PPC) And framSign = 2 Then
framSign = 1
CommandSign.SetFocus
ElseIf checDAS And IsNull(textSigID_DAS) And framSign = 1 Then
framSign = 2
CommandSign.SetFocus
End If
End Sub
References:
[1]: SelectNextControl() a bad idea in a GotFocus event?
[2]: http://www.access-programmers.co.uk/forums/showthread.php?t=100071

I think your problem is that the call to Forms("TargetForm").CommandNew.SetFocus doesn't quite seem to, in fact, finish setting the focus to CommandNew until after Private Sub CommandNew_GotFocus() has finished executing. Because you've called another SetFocus before the first SetFocus could finish, there is a conflict that Access seems to be unable to cope with.
Whether or not that is the case, one thing is clear: the way you have your execution plan set up right now is unfortunately not going to work. You might try adding either a global variable or a public variable to each form that determines whether or not you should set your focus to CommandSign after you set the focus to CommandNew.
Ex. TargetForm:
Public boolSetCommandSignFocusInstead As Boolean
Private Sub CommandNew_GotFocus()
If IsNull(textDateTime) Then Exit Sub 'Works as expected
'I can see these two parts work. The framSign value changes
'and CommandSign gets focus
If checPPC And IsNull(textSigID_PPC) And framSign = 2 Then
framSign = 1
boolSetCommandSignFocusInstead = True
ElseIf checDAS And IsNull(textSigID_DAS) And framSign = 1 Then
framSign = 2
boolSetCommandSignFocusInstead = True
Else
boolSetCommandSignFocusInstead = False
End If
End Sub
Module:
Public Sub test()
Forms("TargetForm").CommandNew.SetFocus
If Forms("TargetForm").boolSetCommandSignFocusInstead Then
Forms("TargetForm").CommandSign.SetFocus
End If
End Sub

Related

Access VBA Stopped showing ActiveControl Is the focused control

I have a bit of an oddball problem. I use a routine to check focus; and ... it stopped working. I have no idea how or why.
The routine basically checks to see if the active control is the one you're checking, and if so, returns true (so we can handle the cases where it's not).
However...it recently started returning false all the time (we didn't change anything, we only noticed when some field auditing started returning weird values). Even when the control is focused, and if there's no other controls on the form, or only one form open, and the form clearly has focus.
Does anyone have any ideas how or why this might be? It's confounding me. As you can see, I've got a test field, where we're running an init in it...and the values clearly match, name, values, every field compared, and it still doesn't return true.
What am I doing wrong?
Edit: forgot to add the code.
The whole thing as-is:
' I call it from here:
' Inside form, any control, say `PurchaseCostBox`
Private Sub PurchaseCostBox_AfterUpdate()
' Check if the focus is had
If VerifyFocus(Me.PurchaseCostBox) Then
' Save more field info.
Debug.Print Me.PurchaseCostBox.SelStart
Debug.Print Me.PurchaseCostBox.SelLen
Debug.Print Me.PurchaseCostBox.Value
Else
' Do limited stuff
Debug.Print Me.PurchaseCostBox.Value
End if
End Sub
Public Function VerifyFocus(ByRef ctlWithFocus As Control) As Boolean
Dim FrmParent As Form
Dim ctlCurrentFocus As Control
On Error Resume Next
' Determine parent form for control
' Verify focus of parent form
Set FrmParent = Screen.ActiveForm
' Verify focus of control on form
Set ctlCurrentFocus = FrmParent.ActiveControl
If Not ctlCurrentFocus Is ctlWithFocus Then
ctlWithFocus.SetFocus
DoEvents
End If
' Even adding the below line does not return true:
ctlWithFocus.SetFocus
' Return true if the control currently has the focus
VerifyFocus = FrmParent.ActiveControl Is ctlWithFocus
' Discard any errors
Err.Clear
End Function
I've also had it try this:
Public Function VerifyFocus(ByRef ctlWithFocus As Control) As Boolean
On Error Resume Next
' Return true if the control currently has the focus
VerifyFocus = Screen.ActiveControl Is ctlWithFocus
' Discard any errors
Err.Clear
End Function
Neither work any more...and I'm floundering.
Well, this turned out to be something utterly unexpected, and totally unrelated to the focus.
Turns out, one of the ways I call this is by getting a control's parent, by using Control.Properties.Parent.Form. While this DOES return the correct form, it also makes the above VerifyFocus routine never return true, ever (even when it's not being used). I don't know why. I really, at this point, don't care. But I'm going to leave it here for others to find.
Refactoring my GetTopForm routines allowed me to get the focus.

How to check for when a checkbox is checked in a Userform?

I have a Userform that has a Checkbox. I am able to check the value of it, but it is always False whether it is checked or not.
Update
This is how the UserForm is being called (This is in another UserForm ):
Private Sub AddOutgoingbtn_Click()
With New AddIncomingForm
.TopBottom.Value = False
.Show
.Repaint
End With
End Sub
End Update
I created a sub to look for a change in the value like:
Sub TopBottom2_Change()
With AddOutgoingForm
If .TopBottom2.Value = True Then TopBottom = True
If .TopBottom2.Value = False Then TopBottom = False
End With
End Sub
But no matter what I do the .TopBottom2.Value is always False.
I've put a breakpoint on the With line so that I know it is hitting this Sub, then I step through it each time. I open the UserForm and check the box, step through and the value is False, then I uncheck the box and step through. The value is still False.
I am not setting the value in any other way with VBA. I am checking the value in the UserForms code, not in any other place.
I have an If in a Calculation Module that is looking at this value for when it is true or falses, but it is always false.
Here are all the Properties of the Checkbox:
With AddOutgoingForm
That's referring to the form's default instance, which may or may not be the instance that's currently being displayed.
You have two options:
At the call site, instead of doing this (or something similar):
With New AddOutgoingForm
.Show
'...
End With
Do this:
AddOutgoingForm.Show
'...
That way you'll be working with the default instance and the checkbox value-check should work.
...but IMO that's a very very bad idea, because then your form contains code that will only ever work when you're showing the default instance.
Leave the call site alone, and NEVER refer to the default instance of a UserForm inside that form's code-behind. In other words change With AddOutgoingForm for With Me.
The Me keyword refers to the current instance - and that is what you want. Doing this will make the form work regardless of what the call site does.
Alternatively, just drop the With block altogether: With Me wouldn't be doing anything useful here.
I will assume the checkbox is indeed on your user form so just use me or you could use
AddOutgoingForm.TopBottom2.Value
But let me ask you, where is TopBottom and what is it a Boolean variable or another Checkbox? Also on the same form? The Subs are also all in the form? You have to be calling the form from somewhere so be careful between the worksheet, workbook, module variables. Is TopBottom a Global variable to the whole project (in a module called Global_Variables with Public in front of it perhaps?) You may not have access to TopBottom from inside of your form if you are not passing anything in or out.
Private Sub TopBottom2_Change()
If (Me.TopBottom2.Value) = True Then
Me.TopBottom.Value = True
Else
Me.TopBottom.Value = False
End If
End Sub
Cheers,
-WWC

Event handling class doesn't fire unless I break userform initialization

This is a follow-up to the following question:
Can't set Userform.KeyPreview to true
To recap: the goal is to build a form with some command buttons and a frame containing check boxes. The check boxes are dynamically populated at userform_initialize in the frame so the user can scroll through them. My problem was with keyboard shortcuts. It wasn't possible to brute force write KeyDown handlers for each of the checkboxes because I don't know which ones will exist. Unfortunately, Excel doesn't support KeyPreview so I had to mock up my own version. Thank you to #UGP for giving me promising avenues that seem to work, but not quite...
First, this is my class module called clsReasonPickKP. I create a new instance for each checkbox to listen for KeyDown events:
Option Explicit
Dim WithEvents vChkBx As MSForms.CheckBox
Friend Sub initializeListener(cControl As control)
Set vChkBx = cControl
End Sub
Private Sub vChkBx_KeyDown(ByVal keyCode As MSForms.ReturnInteger, ByVal shift As Integer)
frm2.keyChooser keyCode
End Sub
The line frm2.keyChooser keyCode launches a quick sub located in the userform code module. Code below:
Public Sub keyChooser(ByVal keyCode As MSForms.ReturnInteger)
Select Case keyCode
Case vbKeyEscape: cancelBtn_Click
Case vbKeyReturn: completeDecision_Click
Case vbKeyN: customizeNote_Click
Case vbKeyS: resetDecisionNote_Click
Case vbKeyR: chkRefGrnds_Click
End Select
End Sub
I've copied the relevant part of the UserForm_Initialize sub below. The loop creates the checkboxes and an event listener for each.
Sub UserForm_Initialize()
Dim x As Long, maxWidth as Long
Dim cControl As control
Dim keyPreviewCollection As New Collection
Dim keyPreviewer As clsReasonPickKP
For x = 1 To dTbl.Rows.Count - 1
Set cControl = chkBoxFrame.Controls.Add("Forms.CheckBox.1", "chkBox" & x, True)
With cControl
.AutoSize = True
.WordWrap = False
.Left = 10
.Top = 16 * x - 12
.Caption = dTbl(x, 1).Value
If .Width > maxWidth Then maxWidth = .Width
End With
Set keyPreviewer = New clsReasonPickKP
keyPreviewer.initializeListener cControl
keyPreviewCollection.Add keyPreviewer
Next x
'Additional initialization code here
End Sub
The odd thing is that unless I break code some time after keyPreviewCollection.Add keyPreviewer, the listener doesn't seem to handle the event. For example, if I set a break point at Next x or for x > 1 and then complete initialization, then when the form is finished initializing and appears the listener calls keyChooser and all is well; if I don't break code like that, it doesn't trap the event or call the sub, etc.
To trouble-shoot, I've tried not adding keyPreviewer to the collection, and then the listener also doesn't work, no matter if or when I break. It seems adding the object to the collection, and being in code break mode after adding it to the collection, somehow makes the listener trap the event.
Also interesting, if I put a breakpoint in the vChkBx_KeyDown module, it breaks when the event is raised (assuming an appropriate break as described above). After I then run the code, however, it stops handling the KeyDown event when its raised.
In case it helps, I'm currently working in Excel 2010.
Does anybody have any idea what's going on? Any idea how to solve this, even with a different code approach?
Thank you as always for everybody's help.
It turns out that the problem was so simple and right in front of my eyes. I just had to make the keyPreviewer and keyPreviewCollection variables public in my userform code module.
That still doesn't answer why breaking code execution after adding the object to the collection made VBA treat it as public, but just happy that it all works.
DoEvents might be the ticket. See the article below:
https://www.automateexcel.com/vba/doevents/

Stopping "TextBox_Change" event from executing if called by code

I have a userform which contains a TextBox object named myTextBox.
The text inside the object can be changed either by the user or by the code. There's a "onChange" procedures attached to the textbox:
Private Sub myTextBox_Change()
'do some stuffs
End Sub
I would like the event to be processed only when is the user changing the text, but not the code. I had thought about adding an Optional parameter like the following:
Private Sub myTextBox_Change(Optional isCode As Boolean)
If isCode = False Then
'do some stuffs
End If
End Sub
but this is not really helpful because I cannot pass the isCode = True when changing the name programmatically, like this:
myForm.myTextBox = "new text"
Does anyone have an idea on how I can fix this?
One way (perhaps not the right way?) would be to declare a global variable to use in the same way as your IsCode Boolean. Set it to true during your code block so the textbox_change event knows code is processing in the background, and then set back to false when that code has finished.
Put this at the top of a normal module (won't work in a form module)
Option Compare Database
Option Explicit
Global IsCode As Boolean
and then in the code you're running just set IsCode=true as required.

How do I load something when the page is selected?

I'm trying to make a form with a lot of pages. So far it works ok and I added the controls from the code. There are hundreds of them and it makes the app "heavy".
Now I'm adding all the stuff in the comboboxes in at Initialization but what I want to do is to load the Controls when I change the page.
So when I start the form the controls should be loaded for page 1. What I want to do is when I click on Page 2 of the Multipage to load it's components (instead of adding all of them at the initialization of the UserForm).
Thanks!
If you want to initialize each page only when it becomes clicked, you can track which has been clicked using something like;
Private mbInitialised() As Boolean
Private Sub UserForm_Initialize()
ReDim mbInitialised(MultiPage1.Pages.Count - 1)
SetupPage 0
End Sub
Private Sub MultiPage1_Change()
SetupPage MultiPage1.Value
End Sub
Private Sub SetupPage(index As Integer)
If (mbInitialised(index)) Then Exit Sub
mbInitialised(index) = True
MsgBox "init page " & index + 1
'//setup here
End Sub
I think this is an easier way to do it
Load UserForm: UserForm.MultiPage1.Value = 0: UserForm.Show
where the multipage value starts to "page1" as arrays starts from zero
if you want a different page change .Value = 0 into the page value - 1
ex:
Load UserForm: UserForm.MultiPage1.Value = 4 : UserForm.Show
this will take you to "page3" of multipage upon initializing userform
*edit:
misunderstood the question a bit, my code will take you to the page but not execute something when changing pages, use Alex's code instead