Why is my .setfocus ignored? - ms-access

I have an Access form with a textbox that is meant to allow for repeatedly typing a number, hitting enter, and letting a script do stuff. For speed, the field should keep the focus after DoStuff() is done.
However, while I'm sure that DoStuff() is run, the focus always goes to the next field in the tab order. It's like Me.MyFld.SetFocus is being ignored.
How do I keep the focus on this field after DoStuff() is done?
Private Sub MyFld_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyReturn Then
DoStuff
Me.MyFld.SetFocus
End If
End Sub

If you look at the order of events for a keypress that would change focus, you can see that it always follows this pattern:
KeyDown → BeforeUpdate → AfterUpdate → Exit → LostFocus
You can re-set the focus anywhere in there and it will still keep following the pattern. So we need to tell it to stop following the pattern. Replace your Me.MyFld.SetFocus with DoCmd.CancelEvent and it should fix your problem. Basically, this just kicks you out of the above pattern, so the Exit and LostFocus events never fire...

A workaround is moving the focus to another control and then back to the first control. Like this:
Private Sub MyFld_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyReturn Then
DoStuff
Me.anotherControl.SetFocus
Me.MyFld.SetFocus
End If
End Sub

click on access options
select Advanced
select Don't move from Move after enter
click ok
It will work 100%

Try removing the whole line for variable_name.SetFocus and simply add:
Cancel = True
Private Sub MyFld_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyReturn Then
DoStuff
Cancel = True
End If
End Sub

Another solution to the problem that I use in Excel.
Let there exist UserForm1 with the TextBox1 and CommandButton1 controls.
Code in the form module:
Option Explicit
Private Sub CommandButton1_Click()
Unload Me
End Sub
Private Sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = vbKeyReturn Then
'Call DoStuff
Application.OnTime Now, "'Control_SetFocus """ & Me.Name & """, """ & Me.ActiveControl.Name & """ '"
' The concatenation returns a string: 'Control_SetFocus "UserForm1", "TextBox1"'
End If
End Sub
And code in the standard module:
Option Explicit
Sub Control_SetFocus(FormName As String, ControlName As String)
Dim oUserForm As Object
Set oUserForm = GetFormByName(FormName)
If Not oUserForm Is Nothing Then
oUserForm.Controls(ControlName).SetFocus
End If
End Sub
Function GetFormByName(FormName As String) As Object
Dim oUserForm As Object
On Error GoTo ErrHandle
For Each oUserForm In VBA.UserForms
If StrComp(oUserForm.Name, FormName, vbTextCompare) = 0 Then
Exit For
End If
Next oUserForm
If oUserForm Is Nothing Then
Set oUserForm = UserForms.Add(FormName)
End If
Set GetFormByName = oUserForm
Exit Function
ErrHandle:
Select Case Err.Number
Case 424:
MsgBox "Userform " & FormName & " not exists.", vbExclamation, "Get userform by name"
Case Else:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Get userform by name"
End Select
End Function
Artik

An easy solution that works in Excel is to set the KeyCode to 0. If DoStuff steals the focus then you should also set the focus back:
Private Sub MyFld_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyReturn Then
DoStuff
KeyCode = 0
Me.MyFld.SetFocus
End If
End Sub

Related

Timer Routine to Auto close database doesn't always kick off

I have a routine that works perfectly as long as the timer routine kicks off. If it doesn't start, nothing happens.
A Hidden form called frm_Invisible gets loaded when my main form opens. My main form is a typical main form with buttons that open other forms. It's not a switchboard. I used to call frm_Invisible in the On Load event and not sure if it has made any difference but right now I put the call in the On Open event.
The frmMain calls it like this:
Private Sub Form_Open(Cancel As Integer)
DoCmd.OpenForm "frm_Invisible", acNormal, , , , acHidden
End Sub
frm_Invisible has an On Timer event:
Private Sub Form_Timer()
Static OldControlName As String
Static OldFormName As String
Static ExpiredTime
Dim ActiveControlName As String
Dim ActiveFormName As String
Dim ExpiredMinutes
Dim CountDown
On Error Resume Next
ActiveControlName = Screen.ActiveControl.Name
ActiveFormName = Screen.ActiveForm.Name
Me.txtActiveForm = ActiveFormName
If (OldControlName = "") Or (OldFormName = "") _
Or (ActiveFormName <> OldFormName) _
Or (ActiveControlName <> OldControlName) Then
OldControlName = ActiveControlName
OldFormName = ActiveFormName
ExpiredTime = 0
Else
ExpiredTime = ExpiredTime + Me.TimerInterval
End If
'Timer interval is set to 1000 which is equal to 1 second
'for testing, you can remove the /60 and make ExpiredMinutes happen a lot faster
'otherwise, keep the /60 and it will truly be whatever you set at the end
ExpiredMinutes = (ExpiredTime / 1000) / 60
Me.txtIdleTime = ExpiredMinutes
Form_frmMain.txtExpiredMinutes = ExpiredMinutes
If ExpiredMinutes >= 3 Then ' Can change this to 3 if you remove the '/60
'Opening this form will trigger the final count down
DoCmd.OpenForm "frmAutoClose"
End If
End Sub
If the time runs out I open a 3rd form that counts down from 20, giving the user a chance to keep the database open.
It merely counts down from 20 and runs
DoCmd.quit
unless the user clicks a button before the count down finishes. That button just closes the 3rd form, preventing the database from closing.
To test the routine, I put a textbox on frmMain, so that I could monitor if the timer gets kicked off.
Form_frmMain.txtExpiredMinutes = ExpiredMinutes
Most of the time, it does and I can see the time counting. However, there are instances that I cannot account for why the timer doesn't start. So I haven't published this latest update for my users.
I can just give you some general advice for now:
You should kick out On Error Resume Next to see if there maybe occures any error.
Later on you should add 'correct' error handling.
Add a type to this variables: ExpiredTime and ExpiredMinutes. Maybe Long?
Variable CountDown isn't used at all.
To prevent overflows you could directly store seconds instead of milliseconds in your variable ExpiredTime.
Then see what happens.
Update:
Since it can happen in your scenario that no form and therefore no control can be active, I would create two procedures to retrieve that information.
Both just return an empty string in case error 2474/2475 occurs.
Public Function GetActiveFormName() As String
On Error GoTo Catch
GetActiveFormName = Screen.ActiveForm.Name
Done:
Exit Function
Catch:
If Err.Number <> 2475 Then
MsgBox Err.Number & ": " & Err.Description, vbExclamation, "GetActiveFormName()"
End If
Resume Done
End Function
Public Function GetActiveControlName() As String
On Error GoTo Catch
GetActiveControlName = Screen.ActiveControl.Name
Done:
Exit Function
Catch:
If Err.Number <> 2474 Then
MsgBox Err.Number & ": " & Err.Description, vbExclamation, "GetActiveFormName()"
End If
Resume Done
End Function

Pass Variables From Access Form To Access Form

I have a parent form that I click a button which launches a second form for further user input, once those values are input I then need to return the values to the parent form. How do I return values from the second form to the first form?
This is my current code:
'Form 1 - Main Form called frmFirstSet
Private Sub cmdDoStep1_Click()
'Declare Variables
Dim OrderNumber As String
'Get the OrderNumber
OrderNumber = Me.[frmDLZC].Form!OrderNumber
'Open the second form for data Capture
DoCmd.OpenForm "frmInputValues", acNormal
'Return variables from frmInputValues
Debug.Print green
Debug.Print red
Debug.Print orange
End Sub
'Form 2 - Secondary Form launched for data capture
Private Sub cmdReturnToStep1_Click()
Dim green As String, red As String, orange As String
'Ensure all values have been input
If IsNull(Me!txtgreen) Then
MsgBox "Please Input the Value for green", vbOKOnly
Me.txtgreen.SetFocus
Exit Sub
Else
green = Me.txtgreen
End If
If IsNull(Me!txtred) Then
MsgBox "Please Input the Value for red", vbOKOnly
Me.txtred.SetFocus
Exit Sub
Else
red = Me.txtred
End If
If IsNull(Me!txtorange) Then
MsgBox "Please Input the Value for orange", vbOKOnly
Me.txtorange.SetFocus
Exit Sub
Else
orange = Me.txtorange
End If
'How to return these variables to the original form
End Sub
There are a lot of ways to pass values from one form to another. I prefer to read value directly from the form. I create a public function, which returns required information. Something like this:
Public Function DialogInputBox(strHeader As String, Optional strValueLabel As String) As String
On Error Resume Next
' make sure that hidden window closed
DoCmd.Close acForm, "frm_InputDialog"
On Error GoTo ErrorHandler
' open the form in dialog mode
DoCmd.OpenForm "frm_InputDialog", WindowMode:=acDialog, OpenArgs:="Header=" & strHeader & "|ValueLabel=" & strValueLabel
' when control returns here, the form is still open, but not visible
DialogInputBox = Nz(Forms("frm_InputDialog").txtValue, "")
' close the form
DoCmd.Close acForm, "frm_InputDialog"
ExitHere:
Exit Function
ErrorHandler:
MsgBox "Error " & Err.Number & " (" & Err.Description & "), vbExclamation + vbMsgBoxHelpButton"
Resume ExitHere
End Function
The dialog form accepts arguments thru OpenArgs parameter and when user clicks Ok or Cancel buttons, we hide the dialog form instead of closing:
Private Sub cmdConfirm_Click()
If Len(Nz(Me.txtValue, "")) = 0 Then
MsgBox "Please enter value", vbExclamation, GetDBName()
Me.txtValue.SetFocus
Exit Sub
End If
' return execution control to the public called function
Me.Visible = False
End Sub
I we need to return few values, use function parameters by reference.

Make custom Wait Popup wait Until activity is done MS Access 2010 - 2016

I have a custom Popup that I call whenever an activity takes more than a second. Example:
PopupMsg("Getting records")
It makes a nice box that just shows the user something is happening, then it quietly disappears when the activity is done.
It works great for anything that only takes about 3 seconds, but beyond that, it disappears and then the user is left with the impression that the activity is finished. I'd like to make it stay up exactly as long as whatever activity is happening, but I've never been successful in determining this. I'd like to make sure all screen calculations are done before the popup disappears.
Here's how I implement my PopupMsg routine
Public Function PopUpMsg(strMsg As String, Optional strTitle As String)
Dim frmWait As New Form_Wait
If strTitle <> "" Then
frmWait.OpenForm strMsg & "...", strTitle
Else
frmWait.OpenForm strMsg & "..."
End If
End Function
Wait (A form called 'Wait' contains the following code)
Option Compare Database
Option Explicit
Public Property Let Message(ByVal MessageText As String)
Me.MessageLabel.Caption = MessageText
Me.Repaint
End Property
Public Property Get Message() As String
Message = Me.MessageLabel.Caption
End Property
Public Property Let Title(ByVal TitleText As String)
Me.Caption = TitleText
End Property
Public Property Get Title() As String
Title = Me.Caption
End Property
Public Function OpenForm(Optional MessageText As Variant, _
Optional TitleText As Variant) As Boolean
If Not IsMissing(MessageText) Then Me.MessageLabel.Caption = MessageText
If Not IsMissing(TitleText) Then Me.Caption = TitleText
Me.Visible = True
Me.Repaint
OpenForm = True
End Function
As you're opening a form through a class instantiation, it doesn't actually persist, and gets removed as soon as Access decides to do garbage collection and sees there's no reference to the form. If you want a form that persists until code execution is done, the best way is to pass back that form:
Public Function PopUpMsg(strMsg As String, Optional strTitle As String) As Object
Set PopUpMsg = New Form_Wait
If strTitle <> "" Then
PopUpMsg.OpenForm strMsg & "...", strTitle
Else
PopUpMsg.OpenForm strMsg & "..."
End If
End Sub
The rest of your code is still valid
You can call it like this:
Dim WaitForm As Object
Set WaitForm = PopupMsg("Getting records")
That way, you're still depending on garbage collection to remove the form, but it will close as soon as the function calling it is done.
You could also just open the form through DoCmd.OpenForm "Wait", reference it through the Forms collection, and close it using DoCmd.Close acForm, "Wait" at the end of your function, but then you'll have to close it actively. Full code for that approach:
Public Function PopUpMsg(strMsg As String, Optional strTitle As String)
DoCmd.OpenForm "Wait"
Dim frmWait As Form
Set frmWait = Forms!Wait
If strTitle <> "" Then
frmWait.OpenForm strMsg & "...", strTitle
Else
frmWait.OpenForm strMsg & "..."
End If
End Sub
Call it: PopupMsg("Getting records")
Close it at the end of execution: DoCmd.Close acForm, "Wait"
If you're not calling DoEvents in your code, there's another alternative:
Open the form using DoCmd.OpenForm "Wait", set it's TimerInterval to 1, and add DoCmd.Close acForm, Me.Name to it's Form_Close event

Access-VBA check if the cursor in a Textbox is in the first or last line

The Goal: I've got an Access database with a continuous form and I want to add the functionality that you can go to the next or previous record by pressing the up- or down-arrow.
The Problem: I've got a multiline TextBox named txtProjekt and I want the database to check if the TextBox is filled with multi-lined text and only jump to the next record if the cursor is in the last line of the TextBox. Likewise I want it to only jump to the previous record if the cursor is in the first line of the TextBox.
I can only check the current cursor position with SelStart, but I can't find out in which line the cursor is.
Do you have any ideas?
Current code:
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
On Error GoTo err_Form_KeyDown
If Me.ActiveControl.Name = "txtProjekt" Then
If Not (Me.txtProjekt.SelStart = 0 And Me.txtProjekt.SelLength = Len(Me.txtProjekt.Text)) Then
GoTo exit_Form_KeyDown
End If
End If
If KeyCode = vbKeyUp Then
DoCmd.GoToRecord acActiveDataObject, Record:=acPrevious
KeyCode = 0
ElseIf KeyCode = vbKeyDown Then
DoCmd.GoToRecord acActiveDataObject, Record:=acNext
KeyCode = 0
End If
exit_Form_KeyDown:
Exit Sub
err_Form_KeyDown:
MsgBox Err.description
Resume exit_Form_KeyDown
End Sub
edit:
The Result (thanks to #Newd):
(be sure to active KeyPreview in your Form, otherwise it won't do anything)
Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
On Error GoTo err_Form_KeyUp
If Shift = False Then
keyAction KeyCode, True
End If
exit_Form_KeyUp:
Exit Sub
err_Form_KeyUp:
MsgBox Err.description
Resume exit_Form_KeyUp
End Sub
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
On Error GoTo err_Form_KeyDown
Dim curPos As Integer
If Shift = False Then
keyAction KeyCode, False
End If
exit_Form_KeyDown:
Exit Sub
err_Form_KeyDown:
MsgBox Err.description
Resume exit_Form_KeyDown
End Sub
Private Sub keyAction(KeyCode As Integer, KeyUp As Boolean)
On Error GoTo err_keyAction
Static curPos As Long
If KeyUp = False Then
If Me.ActiveControl.Name = "txtProjekt" Then
If Not (Me.txtProjekt.SelStart = 0 And Me.txtProjekt.SelLength = Len(Me.txtProjekt.Text)) Then
curPos = Me.txtProjekt.SelStart
GoTo exit_keyAction
End If
End If
Else
If Me.ActiveControl.Name = "txtProjekt" Then
If curPos >= 0 Then
If Me.txtProjekt.SelStart <> curPos Then
GoTo exit_keyAction
End If
curPos = -1
Else
GoTo exit_keyAction
End If
End If
End If
If KeyCode = vbKeyUp Then
DoCmd.GoToRecord acActiveDataObject, Record:=acPrevious
KeyCode = 0
ElseIf KeyCode = vbKeyDown Then
DoCmd.GoToRecord acActiveDataObject, Record:=acNext
KeyCode = 0
End If
exit_keyAction:
Exit Sub
err_keyAction:
MsgBox Err.description
Resume exit_keyAction
End Sub
(I know, all those GoTo Exit_keyAction is bad style, so don't copy too much from me)
I don't currently have the time to write this code out in full to incorporate your code as well. However I think if you were able to get to the point you are at right now you should be able to utilize it.
Basically it is just a way to tell if the user has hit the end or beginning of the Multi-line textbox.
Public intOnDown As Integer
Public intOnUp As Integer
'When the user presses key down
Private Sub txtProjekt_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyUp Or KeyCode = vbKeyDown Then
'Save the cursor position
intOnDown = txtProjekt.SelStart
End If
End Sub
'When the user lets go of the key
Private Sub txtProjekt_KeyUp(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyUp Or KeyCode = vbKeyDown Then
If intOnDown - txtProjekt.SelStart = 0 Then 'If the SelStart is the same
Debug.Print "Pointer hasn't moved so must be at the end or beginning"
End If
End If
End Sub
With the above code you listen for when the user has pressed the up or down key on Keydown then you listen again on KeyUp (When they let go of the key). Then you check to see if the SelStart has changed. If it hasn't then it must mean they are at the beginning or the end of the field and you can perform the record switching.
Note: Adjust accordingly if you have memo fields that are going to be over the max integer size by changing to a long and you probably want to have error handling for it regardless.

Microsoft Access: Attempting to detect an insert triggered by a subform within the parent form

Is it at all possible to detect an insert operation performed by a subform while still in the parent form?
To clarify: I have a series of forms for data entry, they each have a button for adding an entry to the appropriate table (using the data provided in the form). I am attempting to set each of them in turn to a subform in a 'wizard' parent form that will cycle through all the data entry forms.
My problem arises when it comes to switching between forms, as it became clear that the AfterInsert event in this parent form was not detecting the insert triggered by the form contained in the subform. I know I could move the trigger for the insert to a button in the parent form; however, to my knowledge, this would require setting the code for the click event for each of the buttons in the data entry forms as public so that they may be called from the parent form's code. I am leery to do this and was thus hoping for other options.
Create a public procedure in the parent form.
Public Sub Listener(ByVal pMsg As String)
MsgBox pMsg
End Sub
Then, in each of your subforms, call that procedure from After Insert.
Private Sub Form_AfterInsert()
Dim strMsg As String
strMsg = Me.Name & " inserted row."
Call Me.Parent.Listener(strMsg)
End Sub
If the subform may also be used stand-alone (without a parent), Me.Parent will throw error #2452, "The expression you entered has an invalid reference to the Parent property." You can create a separate function to check whether the current form has a parent, and base your code on the function's return value.
Private Sub Form_Open(Cancel As Integer)
Dim strPrompt As String
If HaveParentForm(Me) = True Then
strPrompt = "I am a subform to '" & _
Me.Parent.Name & "'."
Else
strPrompt = "I am a top level form."
End If
MsgBox strPrompt
End Sub
The function ...
Public Function HaveParentForm(ByRef frm As Form) As Boolean
Dim blnReturn As Boolean
Dim strMsg As String
On Error GoTo ErrorHandler
blnReturn = (Len(frm.Parent.Name) > 0)
ExitHere:
HaveParentForm = blnReturn
On Error GoTo 0
Exit Function
ErrorHandler:
Select Case Err.Number
Case 2452 ' The expression you entered has an invalid '
' reference to the Parent property. '
Case Else
strMsg = "Error " & Err.Number & " (" & Err.Description _
& ") in procedure HaveParentForm"
MsgBox strMsg
End Select
blnReturn = False
GoTo ExitHere
End Function