Detecting User activity and waring about idle time (1 Viewer)

Rx_

Nothing In Moderation
Local time
Today, 15:48
Joined
Oct 22, 2009
Messages
2,803
Found this for Access 2000. We are using 2007 (soon 2010) on a Citrix network. Does this look like the best solution?
Would really appreciate any comments or sugesstions on a better way.

If users have not done anything on the Access application for 2 hours, put up a system model message box instrucing them to close the Access application.
The Access appication starts with a Logo Schreen that keeps tables connected. The Logo screen has one button that launches the Main Switchboard.

This code is designed for a splash screen that stays active but is hidden. It should work for the Logo screen.

http://support.microsoft.com/kb/210297

Is this the best solution to determine no user activity and then warn users to close Access? Thanks
 

Rx_

Nothing In Moderation
Local time
Today, 15:48
Joined
Oct 22, 2009
Messages
2,803
I did not get any comments. This post is a solution that my users have been testing for several days and it seems to work. It is being posted in case others are searching for an example. The Access application is running on Citrix. Sometimes people close the wireless PC and the Access session is still running on Citrix. Due to business rules, some forms will not allow an exit unless all requirements are completed. For that reason, this code does not hard force the Access application to quit.
After 2 hours of user idle, it notifies the user to close. From there, it reminds the user every 1 min.
The code is on a Splash Screen (first screen to open) that persist the linked tables during the session. There is one hidden textActive on the splash screen. The Timer Interval is set to 12000
Code:
Private Sub Form_Timer()
        ' Rx_  10/8/2011  -- After 120 minutes of idle - warn user then warn every 60 seconds
          Const c_intIDLEMINUTES = 120 '120 ' lower number for testing
          Const c_intIDLEMINUTES2 = 1#  '30 ' after evaluation - asked to change to 1 min
          Static strPrevCtlName As String
          Static strPrevFrmName As String
          Static lngExpiredTime As Long
          Dim strActiveFrmName    As String
          Dim strActiveCtlName    As String
          Dim lngExpiredMinutes   As Long
          Dim txtmsgboxTitle      As String
          Dim txtmsgboxMessage    As String
            txtmsgboxTitle = "Regulatory Database Inactivity Warning"
            txtmsgboxMessage = "Complete any data entry and close the current database session. " & vbNewLine & "A new session can be initiated immediately after closure."
10    On Error Resume Next
 
20       If Not IdleTimeExceeded Then
30            strActiveFrmName = Screen.ActiveForm.Name
40            If Err Then
50                strActiveFrmName = "No Active Form"
60                Err = 0
70            End If
 
80            strActiveCtlName = Screen.ActiveControl.Name
90            If Err Then
100               strActiveCtlName = "No Active Control"
110               Err = 0
120           End If
130           txtActive.Text = strActiveCtlName & ";" & strActiveFrmName
 
140           If (strPrevCtlName = "") Or (strPrevFrmName = "") Or (strActiveFrmName <> strPrevFrmName) Or (strActiveCtlName <> strPrevCtlName) Then
150               strPrevCtlName = strActiveCtlName
160               strPrevFrmName = strActiveFrmName
170               lngExpiredTime = 0
180           Else
190               lngExpiredTime = lngExpiredTime + Me.TimerInterval
200           End If
 
210           lngExpiredMinutes = (lngExpiredTime / 1000) / 60
220           If lngExpiredMinutes >= c_intIDLEMINUTES Then
230               lngExpiredTime = 0
240               MsgBox txtmsgboxMessage, vbOKOnly + vbCritical + vbSystemModal, txtmsgboxTitle
250               IdleTimeExceeded = True     ' once exceeded - change to new schedule
260           End If
270       Else
280           lngExpiredTime = lngExpiredTime + Me.TimerInterval
290                   lngExpiredMinutes = (lngExpiredTime / 1000) / 60
300           If lngExpiredMinutes >= c_intIDLEMINUTES2 Then
310               lngExpiredTime = 0
320               MsgBox txtmsgboxMessage, vbOKOnly + vbCritical + vbSystemModal, txtmsgboxTitle
                  'Application.Quit acQuitSaveAll       ' <--- action here may not work (or loose data) if some forms have required fields before form_close
330           End If
340       End If
End Sub
If this is helpful, a thanks would be appreciated
 

DeonO

Novice Programmer
Local time
Tomorrow, 00:48
Joined
Sep 15, 2011
Messages
31
Hi Rx,
Thanks a Mil!! This is what I have been looking for for a very long time.

Regards
Deon
 

mdlueck

Sr. Application Developer
Local time
Today, 17:48
Joined
Jun 23, 2011
Messages
2,631
The code is on a Splash Screen (first screen to open) that persist the linked tables during the session

hhhmmm... As I am currently developing an Client/Server app in Access 2007 with SQL Server 2008 R2, that sentence catches my eye.

I believe I have been careful NOT to use linked tables within the app, though my development copy of the FE DB has linked table objects so I can quick change things in the BE DB without always needing to write FE code to make adjustments to the BE DB.

I have noticed at times my VBA class objects reset themselves. I am still waiting to receive a test machine where I specifically will never go into the VBA code and see how well the objects persist with hours of "waiting" involved.

To handle people leaving records in "edit mode" for lengths of time, I have coded all Stored Procedure UPDATE events to update based on the records unique ID AND the last save timestamp. If the UPDATE does not modify one record, then I display a custom message that "perhaps someone else edited the record / rollback / requery / and attempt the edit again" So in that way I have covered edit screens getting left for long periods of time.
 

Rx_

Nothing In Moderation
Local time
Today, 15:48
Joined
Oct 22, 2009
Messages
2,803
A very good point.. In your situation, the warning does not apply. One of my clients has SQL with transactions and SP as well. What a difference it makes.

The warning is for those lesser designed applications or in my case an inherited support situation. Four people before me didn't understand some general concepts. It is not an uncommon situation.

Forcing an application to close is not a universal guarantee depending on the individual application and design. Other code such as hiding a ribbon would work in any situation. Thanks for bringing up the difference.
 

kyoch

Registered User.
Local time
Today, 16:48
Joined
Mar 5, 2014
Messages
58
Hello,

First of all, thank you for posting this code. This is exactly what I was looking for!

There are two "challenges" I'm having trouble trying to overcome with this code.

First, if the users desktop is set to go to screen saver after 5 minutes and the detectidletime is set to 10 minutes, the database does not successfully close. How can I change this to where the database will still close after 10 minutes of inactivity even if the user is in screensaver mode?

Second, within my database I have a button which opens up another form as a pop up. When that form pops up, the database will close after one minute even if I am typing or clicking during that period. I have to go back to the original form, click something, then return to my pop form to keep it from auto closing.

Please help! I don't think I explained this in the best way but I'd appreciate any help.
 

Nero1916

New member
Local time
Today, 14:48
Joined
Jan 29, 2020
Messages
4
I have something similar going on. I have a simple vba running in a hidden form, found it in a tutorial. Works great on some databases we've got running and I get an error for others:
"On timer you enter as an event property setting produced the fiollowing error: Error accessing file. Network connection may have been lost."
All our network connections are working, nothing is unplugged, I can get to the form the error points to with no problem. What could be triggering that? Could it be a sub form property? Or can someone share a tip on how to recognize forms regardless of settings?
 

CJ_London

Super Moderator
Staff member
Local time
Today, 22:48
Joined
Feb 19, 2013
Messages
16,616
@Nero1916 - you really should start your own thread rather than hijacking a 6 year old one
 

apr pillai

AWF VIP
Local time
Tomorrow, 03:18
Joined
Jan 20, 2005
Messages
735
I have tried something to detect idle time and to invoke a slide-show on the Control Screen. The method used is to check the active control on the Form and runs a timer and if the same control remains active for a certain period it assumes that the Application is idle for that much time and the slide-show begins on the Form. When the User comes back and clicks somewhere else on the form the slide-show stops.

Find more details here: Run Slide-Show When Form is idle
 

Adelina_RO

Member
Local time
Tomorrow, 00:48
Joined
Apr 9, 2021
Messages
42
Just a thought: this kind of function is more usable in a small app, where setting the "Break on unhandled errors" option is an option :)P pun intended). I try to evoid that as much as possible (alway almost) since it opens a whole can of worms.
I found some code here https://www.tek-tips.com/viewthread.cfm?qid=1012106, but i had a flaw: it detected user input anywhere in the system, not only on Access window, as I need it, so i tweaked it a little. Here is what i came up with.

Updated it a little :D

Code:
Option Compare Database
Option Explicit

'===== DETECT USER INPUT =====
'CODE FROM: https://www.tek-tips.com/viewthread.cfm?qid=1012106

'TWEAKED BY: Adelina 13/08/2021
'- Added option to detect if the main window has focus or not
'-->Property OnlyAccessApp: boolean (YES-detect input in access, NO-detect input overall)
'-->Property WindowId: Required if OnlyAccessApp=True. Passed at class initialisation from the detecting form, with the api GetForegroundWindow. Needs to be declared at form level also.
'- Added 64b declarations
'=============================


'@---------------------- API Functions -------------------------@
#If Win64 Then
    Private Declare PtrSafe Function GetCursorPos Lib "User32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Long) As Integer
    Private Declare PtrSafe Function GetForegroundWindow Lib "User32" () As LongPtr
    Private Declare PtrSafe Function IsIconic Lib "User32" (ByVal hWnd As LongPtr) As Long
#Else
    Private Declare Function GetCursorPos Lib "User32" (lpPoint As POINTAPI) As Long
    Private Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Long) As Integer
    Private Declare Function GetForegroundWindow Lib "User32" () As Long
    Private Declare Function IsIconic Lib "User32" (ByVal hWnd As Long) As Integer
#End If
'@------------------------- Events -----------------------------@

Public Event InputDetected(ByVal dtTimeOccurred As Date)
Public Event MaxIdleTimeDetected(ByVal dtTimeOccurred As Date)

'@------------------------ Variables ---------------------------@
Private mlngTimeOutMinutes As Long
Private mlngWindowId As Long
Private mblnOnlyAccessApp As Boolean
Private mblnPaused As Boolean
Private mblnShowSplash As Boolean
Private mdtStartTime As Date
Private mPosOld As POINTAPI
Private mPosNew As POINTAPI

'@-------------------------- Types -----------------------------@

Private Type POINTAPI
    X As Long
    Y As Long
End Type

'@----------------------- Constructor --------------------------@

Private Sub Class_Initialize()
    TimeOutMinutes = 15   'default timeout
End Sub

'@----------------------- Properties ---------------------------@

Public Property Get TimeOutMinutes() As Long
    TimeOutMinutes = mlngTimeOutMinutes
End Property

Public Property Let TimeOutMinutes(ByVal lngTimeOutMinutes As Long)
    mlngTimeOutMinutes = lngTimeOutMinutes
End Property

Public Property Get StartTime() As Date
    StartTime = mdtStartTime
End Property

Private Property Let StartTime(ByVal dtStartTime As Date)
    mdtStartTime = dtStartTime
End Property

Public Property Get ElapsedSeconds() As Long
    ElapsedSeconds = DateDiff("s", StartTime, Now)
End Property

Public Property Get ElapsedMinutes() As Long
    ElapsedMinutes = DateDiff("s", StartTime, Now) \ 60
End Property

Public Property Get Paused() As Boolean
    Paused = mblnPaused
End Property

Public Property Let Paused(ByVal blnPaused As Boolean)
    mblnPaused = blnPaused
End Property

Public Property Get OnlyAccApp() As Boolean
    OnlyAccApp = mblnOnlyAccessApp
End Property

Public Property Let OnlyAccApp(ByVal blnAccessApp As Boolean)
    mblnOnlyAccessApp = blnAccessApp
End Property

Public Property Get WindowID() As Long
    WindowID = mlngWindowId
End Property

Public Property Let WindowID(ByVal lngWindowID As Long)
    mlngWindowId = lngWindowID
End Property

Public Property Get ShowSplash() As Boolean
    ShowSplash = mblnShowSplash
End Property

Public Property Let ShowSplash(ByVal blnShowSplash As Boolean)
    mblnShowSplash = blnShowSplash
End Property

'@---------------------- Public Methods ------------------------@

Public Sub CheckInput()

If Paused Then Exit Sub

If OnlyAccApp Then
    If WindowID <> CLng(GetForegroundWindow) Then
        'for popup forms the function will trigger since the ForegroundWindow is different from the main window
        'so i show in the statusbar how much time it passed since opening that form
        SysCmd acSysCmdSetStatus, ElapsedSeconds & " sec."
        
        'if main window is minimised i show the splash screen if ShowSplash=True
        If IsIconic(hWndAccessApp) = 1 Then
            If ShowSplash Then If Not IsFormOpen("frmIDLE") Then DoCmd.OpenForm "frmIDLE", acNormal
        End If
        
        Call UpdateElapsedTime
            
        Exit Sub
    End If
End If

If ShowSplash Then If IsFormOpen("frmIDLE") Then DoCmd.Close acForm, "frmIDLE", acSaveNo

If Not CheckMouseMoved() Then
    If Not CheckKeyboardUsed() Then
        Call UpdateElapsedTime
        Exit Sub
    End If
End If

Call Reset

RaiseEvent InputDetected(Now)
End Sub

Public Sub StartCheckingIdleTime()
Call Reset
Paused = False
End Sub

Public Sub StopCheckingIdleTime()
    Paused = True
End Sub

Public Sub Reset()
If GetCursorPos(mPosNew) <> 0 Then
    mPosOld.X = mPosNew.X
    mPosOld.Y = mPosNew.Y
End If
StartTime = Now
End Sub

'@--------------------- Private Methods ------------------------@

Private Sub UpdateElapsedTime()
If ElapsedMinutes >= TimeOutMinutes Then
    RaiseEvent MaxIdleTimeDetected(Now)
End If
    
If ShowSplash Then
    If IsFormOpen("frmIDLE") Then Forms!frmIDle!Sec = ElapsedSeconds & " sec."
End If

End Sub

Private Function CheckMouseMoved() As Boolean
If GetCursorPos(mPosNew) = 0 Then Exit Function

If ((mPosNew.X <> mPosOld.X) Or (mPosNew.Y <> mPosOld.Y)) Then
    mPosOld.X = mPosNew.X
    mPosOld.Y = mPosNew.Y
    CheckMouseMoved = True
End If
End Function

Private Function CheckKeyboardUsed() As Boolean
Dim lngKey As Long

For lngKey = 0 To 255
    If (GetAsyncKeyState(lngKey) And &H8001) <> 0 Then
        CheckKeyboardUsed = True
    End If
Next lngKey
End Function

Private Function IsFormOpen(frmName As String) As Boolean

IsFormOpen = False

If SysCmd(acSysCmdGetObjectState, acForm, frmName) <> 0 Then
    If Forms(frmName).CurrentView <> 0 Then
        IsFormOpen = True
    End If
End If

End Function
'@----------------------- End of Class -------------------------@
 
Last edited:

theDBguy

I’m here to help
Staff member
Local time
Today, 14:48
Joined
Oct 29, 2018
Messages
21,474
Just a thought: this kind of function is more usable in a small app, where setting the "Break on unhandled errors" option is an option :)P pun intended). I try to evoid that as much as possible (alway almost) since it opens a whole can of worms.
I found some code here https://www.tek-tips.com/viewthread.cfm?qid=1012106, but i had a flaw: it detected user input anywhere in the system, not only on Access window, as I need it, so i tweaked it a little. Here is what i came up with.
Code:
Option Compare Database
Option Explicit

'===== DETECT USER INPUT =====
'CODE FROM: https://www.tek-tips.com/viewthread.cfm?qid=1012106

'TWEAKED BY: Adelina 13/08/2021
'- Added option to detect if the main window has focus or not
'-->Property OnlyAccessApp: boolean (YES-detect input in access, NO-detect input overall)
'-->Property WindowId: Required if OnlyAccessApp=True. Passed at class initialisation from the detecting form, with the api GetForegroundWindow. Needs to be declared at form level also.
'- Added 64b declarations
'=============================


'@---------------------- API Functions -------------------------@
#If Win64 Then
    Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare PtrSafe Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Long) As Integer
    Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr
#Else
    Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Private Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Long) As Integer
    Private Declare Function GetForegroundWindow Lib "user32" () As Long
#End If
'@------------------------- Events -----------------------------@

Public Event InputDetected(ByVal dtTimeOccurred As Date)
Public Event MaxIdleTimeDetected(ByVal dtTimeOccurred As Date)

'@------------------------ Variables ---------------------------@
Private mlngTimeOutMinutes As Long
Private mlngWindowId As Long
Private mblnOnlyAccessApp As Boolean
Private mdtStartTime As Date
Private mblnPaused As Boolean
Private mPosOld As POINTAPI
Private mPosNew As POINTAPI

'@-------------------------- Types -----------------------------@

Private Type POINTAPI
    X As Long
    Y As Long
End Type

'@----------------------- Constructor --------------------------@

Private Sub Class_Initialize()
    TimeOutMinutes = 15   'default timeout
End Sub

'@----------------------- Properties ---------------------------@

Public Property Get TimeOutMinutes() As Long
    TimeOutMinutes = mlngTimeOutMinutes
End Property

Public Property Let TimeOutMinutes(ByVal lngTimeOutMinutes As Long)
    mlngTimeOutMinutes = lngTimeOutMinutes
End Property

Public Property Get StartTime() As Date
    StartTime = mdtStartTime
End Property

Private Property Let StartTime(ByVal dtStartTime As Date)
    mdtStartTime = dtStartTime
End Property

Public Property Get ElapsedSeconds() As Long
    ElapsedSeconds = DATEDIFF("s", StartTime, Now)
End Property

Public Property Get ElapsedMinutes() As Long
    ElapsedMinutes = DATEDIFF("s", StartTime, Now) \ 60
End Property

Public Property Get Paused() As Boolean
    Paused = mblnPaused
End Property

Public Property Let Paused(ByVal blnPaused As Boolean)
    mblnPaused = blnPaused
End Property

Public Property Get OnlyAccApp() As Boolean
    OnlyAccApp = mblnOnlyAccessApp
End Property

Public Property Let OnlyAccApp(ByVal blnAccessApp As Boolean)
    mblnOnlyAccessApp = blnAccessApp
End Property

Public Property Get WindowID() As Long
    WindowID = mlngWindowId
End Property

Public Property Let WindowID(ByVal lngWindowID As Long)
    mlngWindowId = lngWindowID
End Property
'@---------------------- Public Methods ------------------------@

Public Sub CheckInput()
If Paused Then Exit Sub

If OnlyAccApp Then
    If WindowID <> CLng(GetForegroundWindow) Then
        Call UpdateElapsedTime
        Exit Sub
    End If
End If

If Not CheckMouseMoved() Then
    If Not CheckKeyboardUsed() Then
        Call UpdateElapsedTime
        Exit Sub
    End If
End If

Call Reset

RaiseEvent InputDetected(Now)
End Sub

Public Sub StartCheckingIdleTime()
Call Reset
Paused = False
End Sub

Public Sub StopCheckingIdleTime()
    Paused = True
End Sub

Public Sub Reset()
If GetCursorPos(mPosNew) <> 0 Then
    mPosOld.X = mPosNew.X
    mPosOld.Y = mPosNew.Y
End If
StartTime = Now
End Sub

'@--------------------- Private Methods ------------------------@

Private Sub UpdateElapsedTime()
If ElapsedMinutes >= TimeOutMinutes Then
    RaiseEvent MaxIdleTimeDetected(Now)
End If
End Sub

Private Function CheckMouseMoved() As Boolean
If GetCursorPos(mPosNew) = 0 Then Exit Function

If ((mPosNew.X <> mPosOld.X) Or (mPosNew.Y <> mPosOld.Y)) Then
    mPosOld.X = mPosNew.X
    mPosOld.Y = mPosNew.Y
    CheckMouseMoved = True
End If
End Function

Private Function CheckKeyboardUsed() As Boolean
Dim lngKey As Long

For lngKey = 0 To 255
    If (GetAsyncKeyState(lngKey) And &H8001) <> 0 Then
        CheckKeyboardUsed = True
    End If
Next lngKey
End Function

'@----------------------- End of Class -------------------------@
Hi. Thanks for sharing. :)
 

Adelina_RO

Member
Local time
Tomorrow, 00:48
Joined
Apr 9, 2021
Messages
42
Hi. Can I ask who you are addressing with the above statement? Thanks.
The guy who wrote the initial function. Moreso, anyone using on error resume next :p. But i took that down since i don't want to offend anyone. To each his own. I just don't like that construct. It got me in a lot of trouble back when i started using vba
 

The_Doc_Man

Immoderate Moderator
Staff member
Local time
Today, 16:48
Joined
Feb 28, 2001
Messages
27,188
@Adelina_RO - just like everything else that VBA does, the "On Error Resume Next" is a useful (if rare) tool. It allows you to do something that you very strongly suspect will fail and you don't want to go through the difficulties of programming your already complex error handler code to allow for this case. So you drop away from the error handler. BUT you do NOT want to set On Error GoTo 0 because that means the next unhandled error could give you the dreaded message box that includes "DEBUG" and "RESET" as options. So you tell Access to ignore the error trap - but crucial to the exercise is that after the potentially offensive action, you test the Err object to see if it has a non-zero Err.Number for you. Then you can reinstate your original handler.

Admittedly it is rare to do this, but it is another tool in the tool box for those rare times when it is right.
 

theDBguy

I’m here to help
Staff member
Local time
Today, 14:48
Joined
Oct 29, 2018
Messages
21,474
The guy who wrote the initial function. Moreso, anyone using on error resume next :p. But i took that down since i don't want to offend anyone. To each his own. I just don't like that construct. It got me in a lot of trouble back when i started using vba
Hi. Thanks for the clarification. For future reference, you might consider addressing the person directly by name or include a quote from their post that you're responding to.

Also, the original author you're referring to doesn't seem to be a member of this forum, so I am not sure how they will see your comment and respond to it.
 

Adelina_RO

Member
Local time
Tomorrow, 00:48
Joined
Apr 9, 2021
Messages
42
theDBguy + The_Doc_Man: thank you both for your kind messages. I'm new to this forum (actually to any forum - i'm more of a lone rider myself) so i'm still getting used to the rules and how to write a comment. And about the resume next issue, @The_Doc_Man , i know what it's used for. I was just saying how i hate using it as a way for the function to advance.
 

Users who are viewing this thread

Top Bottom