Access 2010 VBA on KeyPress Pause for seconds before initiating - ms-access

I'm creating an auto-filter feature in Access 2010 Forms using VBA.
My goal is to do the following:
If the user presses an Enter key - do a search
If user presses any letter character key or number key then do the following
Check to make sure the length of the search string is greater than 3 characters.
If the first condition is fulfilled is true then WAIT 0.5 seconds
After 0.5 seconds if the length of the new search text is equal to the old then call a search (the program will assume the user stopped typing and is waiting for a result)
I'm thinking my problem is that I'm trying to execute all of this in the _KeyPress event.
Private Sub search_txt_KeyPress(KeyAscii As Integer)
Dim txt As String, stxt As String
txt = Me.search_txt.Text
'If the enter key is pressed, then search
If (KeyAscii = vbKeyReturn) Then
stxt = "*" & txt & "*"
Call Module2.searchJobs(stxt, 0)
Else
End If
'If the key pressed is a letter character or number
If (KeyAscii >= 48) Then
'If the search value is greater than 3 characters
If (Len(txt) > 3) Then
'Pause for 3 seconds
Call Pause(0.5)
'If the string lengths are still equal to each other then search
If (Len(txt) = Len(Me.search_txt.Text)) Then
stxt = "*" & txt & "*"
Call Module2.searchJobs(stxt, 0)
Else
End If
Else
End If
Else
End If
End Sub
Here's my Pause Function
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Err_Pause
Dim PauseTime As Variant, start As Variant
PauseTime = NumberOfSeconds
start = Timer
Do While Timer < start + PauseTime
DoEvents
Loop
Exit_Pause:
Exit Function
Err_Pause:
MsgBox err.Number & " - " & err.DESCRIPTION, vbCritical, "Pause()"
Resume Exit_Pause
End Function

VBA is not "multi-threading". So, when you call your Pause() function, it doesn't do anything and any action the user do (including key press) are void, so the approach is not correct.
Instead of using a Pause() function, use the timer of your form:
You should enable the timer instead of calling a Pause() function, and move your check-length logic in the timer event. When you're entering the timer event, you disable the timer.
Something like this :
' Add Load event to the form
Private Sub Form_Load()
Me.TimerInterval = 0 ' disable timer
End Sub
' Add Ontimer event to the form
Private Sub Form_Timer()
Dim txt As String, stxt As String
Me.TimerInterval = 0 ' disable timer
txt = Me.search_txt.Text
'If the string lengths are still equal to each other then search
If (Len(txt) = Len(Me.search_txt.Text)) Then
stxt = "*" & txt & "*"
Call Module2.searchJobs(stxt, 0)
Else
End If
End Sub
Private Sub search_txt_KeyDown(KeyAscii As Integer, Shift As Integer)
Dim txt As String, stxt As String
txt = Me.search_txt.Text
'If the enter key is pressed, then search
If (KeyAscii = vbKeyReturn) Then
stxt = "*" & txt & "*"
Call Module2.searchJobs(stxt, 0)
Else
End If
'If the key pressed is a letter character or number
If (KeyAscii >= 48) Then
'If the search value is greater than 3 characters
If (Len(txt) > 3) Then
' enable timer to 0.5 second
Form.TimerInterval = 500 'milliseconds
Else
End If
Else
End If
End Sub
Additionally, take care to this:
When you enter the KeyDown event, your search_txt.Text will not contain yet the last letter pressed by the user.

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

Run VBA Function every 30 seconds (from startup)(Access)

So i have my code that should be executed when i start my access DB
(it imports files from a folder)
Function import_files
'all the code (works without errors)
end function
My idea was a Macro that runs on startup then opens the function and uses
Application.OnTime Now + TimeValue("00:00:30"), "import_files"
that of corse did not work because its not compatible with access... so you can use it like this:
Excel.Application.OnTime Now + TimeValue("00:00:30"), "import_files"
just that u cant run anything from then on...
is there any simple short solution to this
someting basic like this
Function Import_files
do every 30 seconds
run code
loop
end function
Thanks for any help!
(Im not that good in VBA :) )
You could consider using the Form's TimerInterval property. You could open a form (hidden) and have the timer trigger the code.
Example
The following example shows how to create a flashing button on a form by displaying and hiding an icon on the button. The form's Load event procedure sets the form's TimerInterval property to 1000 so the icon display is toggled once every second.
Sub Form_Load()
Me.TimerInterval = 1000
End Sub
Sub Form_Timer()
Static intShowPicture As Integer
If intShowPicture Then
' Show icon.
Me!btnPicture.Picture = "C:\Icons\Flash.ico"
Else
' Don't show icon.
Me!btnPicture.Picture = ""
End If
intShowPicture = Not intShowPicture
End Sub
To use this sample code:
Note: Your screen will look different based on version you are using.
You can Google: access me.timerinterval examples.
Enable macros by clicking options | enable this content | OK.
Create new blank form and view in design view.
Add command button | cancel wizard | delete caption | name it btnPicture.
Add form event procedures.
A. Deselect button and select form properties.
B. Click event loader for form in the property sheet.
C. Double click Code Builder.
D. Select all and then paste code from here.
FYI:
For the code mmehta did; you would put it in a module.
He showed you how to extend on your thought process.
You suggested:
Function Import_files
do every 30 seconds
run code
if needed reset any variables
myVariableInteger = 0
myVariableString = ""
myVariableString= vbNullString
myVariableInteger = Null
Set myVariableObject = Nothing
loop
end function
He elaborated:
Function Import_files
Do
Pause(30)
run code (You're code here or better yet put your code in a sub routine and call the routine.)
Loop
End Function
Put this code in a module
Option Compare Database
Option Explicit
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Error_GoTo
Dim PauseTime As Variant
Dim Start As Variant
Dim Elapsed As Variant
PauseTime = NumberOfSeconds
Start = Timer
Elapsed = 0
Do While Timer < Start + PauseTime
Elapsed = Elapsed + 1
If Timer = 0 Then
' Crossing midnight
PauseTime = PauseTime - Elapsed
Start = 0
Elapsed = 0
End If
DoEvents
Loop
Exit_GoTo:
On Error GoTo 0
Exit Function
Error_GoTo:
Debug.Print Err.Number, Err.Description, Erl
GoTo Exit_GoTo
End Function
Sub msgUser()
Dim x As Integer
x = MsgBox("Click 'OK' to continue.", vbOKOnly, "Ready?")
End Sub
Place your cursor in the routine Pause and press play.
Watch the msgBox pop up every 30 seconds.
Try the below function for timer.
Function Import_files
Do
Pause(30)
run code
Loop
end function
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Error_GoTo
Dim PauseTime As Variant
Dim Start As Variant
Dim Elapsed As Variant
PauseTime = NumberOfSeconds
Start = Timer
Elapsed = 0
Do While Timer < Start + PauseTime
Elapsed = Elapsed + 1
If Timer = 0 Then
' Crossing midnight
PauseTime = PauseTime - Elapsed
Start = 0
Elapsed = 0
End If
DoEvents
Loop
Exit_GoTo:
On Error GoTo 0
Exit Function
Error_GoTo:
Debug.Print Err.Number, Err.Description, Erl
GoTo Exit_GoTo
End Function

Find next record based on certain criteria

I am trying to use the .FindNext (and .FindPrevious) function on an update form "next button" to find the record that meets certain criteria.
Private Sub NextRecord_Click()
Dim foundmatch As Boolean
For x = 0 To 3 Step 1
With Me.RecordsetClone
.FindNext "[Sensitivity] = " & [TempVars]![AccessLevel] + x
If .NoMatch Then
foundmatch = False
Else
Me.Bookmark = .Bookmark
foundmatch = True
Exit For
End If
End With
Next
If foundmatch = False Then
MsgBox "No More Records"
End If
End Sub
Upon a user entering the database the users accesslevel is assigned to a temp variable (1 to 4), and each project has a sensitivity rating of 1 to 4. The code below was used and worked for both next and previous only in finding records when the sensitivity and accesslevel were equal but not for sensitivities below the users access level which they are qualified to see.
Private Sub PrevRecord_Click()
Dim Stringy As String
Stringy = "[Sensitivity] = " & [txtaccess]
With Me.RecordsetClone
.FindPrevious Stringy
If .NoMatch Then
MsgBox "No More Records"
Else
Me.Bookmark = .Bookmark
End If
End With
End Sub
Note: The form is pulled from a query with Sensitivity one of the fields, and [txtaccess] is a text box on the field with the default value set at [TempVars]![AccessLevel]. I've also tried changing it to:
Stringy = "[Sensitivity] >= " & [txtaccess]
but that doesn't work either
I was able to fix the problem by setting applying a filter for sensitivity on the actual forms On_Load event rather than the command button. It now works using a next record command button added with the default code/settings!

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.

Why is my .setfocus ignored?

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