View RSS Feed

Development Team Blog

The Case of OnIdle

Rate this Entry
From time to time the question arises "how can I terminate my program when no activity took place?".

Visual DataFlex offers two features named DFTIMER and cIdleHandler but both do not really cover the topic. Let us look closer at these two options and find a real solution.

A Timer (DFTIMER)
A timer (DFTIMER) fires a message every NN milliseconds no matter there is activity or not. In the list of tables treeview in Database Explorer we use an object of this class to make a kind of idle handler after NN seconds. Database Explorer can do this because you navigate through the treeview and on each change in the navigation by keyboard we reset the timer (stop and start the timer again). It works but it is quite a one control solution and not a more general one.

An Idle Handler (cIdleHandler)
The cIdleHandler fires ONE message as soon as the program becomes idle. It is specifically added to the product to let tool bar buttons show enabled or disabled based on the current program status (like "is save wanted or not"). Since the message fires immediately after inactivity starts this is not the solution we are looking for.

Can You Query the Inactivity?
It is possible to do this indeed. The Windows kernel exposes a function named GetLastInputInfo. The returned value (a member of a struct) contains the tickcount since last input was done in the current session. You need to compare this value with the result of the system wide GetTickCount function to find out the idle timeout.

An Idle Time Timer
With the above information we can write the following class:
Code:
Struct tLastInputInfo
    UInteger cbSize
    DWord dwTime
End_Struct

External_Function GetLastInputInfo "GetLastInputInfo" User32.dll Pointer pLastInputInfo Returns Integer
External_Function GetTickCount     "GetTickCount"     Kernel32.dll Returns Integer

Use Dftimer.pkg

{ OverrideProperty = TimeOut Visibility = Private }
{ OverrideProperty = Timer_Message Visibility = Private }
{ OverrideProperty = Timer_Object Visibility = Private }
{ OverrideProperty = Timer_Active_State Visibility = Private }
{ OverrideProperty = psTooltip Visibility = Private }
{ OverrideProperty = pbCenterTooltip Visibility = Private }
{ OverrideProperty = phoTooltipController Visibility = Private }
{ OverrideProperty = Auto_Start_State Visibility = Private }
{ OverrideProperty = Auto_Stop_State Visibility = Private }
{ OverrideProperty = Delegation_Mode Visibility = Private }
{ OverrideProperty = peNeighborHood Visibility = Private }
Class cProgramIdleTimer is a DfTimer
    { MethodType = Event }
    Procedure OnIdle Integer iIdleTime
    End_Procedure
    
    { Visibility = Private }
    { MethodType = Event }
    Procedure OnTimer Integer iwParam Integer ilParam
        Integer iIdleTime iInActivityTimeOut
        
        Get IdleTime to iIdleTime
        Get piInActivityTimeOut to iInActivityTimeOut
        If (iIdleTime >= iInActivityTimeOut) Begin
            Send OnIdle iIdleTime
        End
    End_Procedure
    
    Procedure Construct_Object
        Forward Send Construct_Object        
        
        Set Timeout to 30000 // 30 seconds
        
        { Visibility = Private }
        Property Integer private_piInActivityTimeOut 29000
        { DesignTime = False }
        Property Boolean pbBusy False
    End_Procedure
    
    { MethodType = Property }
    { Category = "Behavior" }
    Procedure Set piInActivityTimeOut Integer iTimeOut
        Set private_piInActivityTimeOut to iTimeOut
        Set Timeout to (iTimeOut + 100)
    End_Procedure
    
    { MethodType = Property }
    Function piInActivityTimeOut Returns Integer
        Integer iTimeOut
        
        Get private_piInActivityTimeOut to iTimeOut
        
        Function_Return iTimeOut
    End_Function
    
    { MethodType = Property }
    { Category = "Behavior" }
    Procedure Set pbEnabled Boolean bEnabled
        Set Timer_Active_State to bEnabled
    End_Procedure
    
    { MethodType = Property }
    Function pbEnabled Returns Boolean
        Boolean bEnabled
        
        Get Timer_Active_State to bEnabled
        
        Function_Return bEnabled
    End_Function
    
    Function IdleTime Returns Integer
        tLastInputInfo LastInputInfo
        Integer iResult iTick iIdleTime
        
        Move (SizeOfType (tLastInputInfo)) to LastInputInfo.cbSize    
        Move (GetLastInputInfo (AddressOf (LastInputInfo))) to iResult
        If (iResult <> 0) Begin
            Move (GetTickCount ()) to iTick            
            Move (iTick - LastInputInfo.dwTime) to iIdleTime
        End
        
        Function_Return iIdleTime
    End_Function
End_Class
This can be used to make an object like:
Code:
Object oCloseProgramTimer is a cProgramIdleTimer
    Set piInActivityTimeOut to 5000        
    
    Procedure OnIdle Integer iIdleTime
        Integer iAnswer
        Boolean bBusy
        Handle hWnd
        
        Get pbBusy to bBusy
        If (not (bBusy)) Begin
            Set pbBusy to True            
            Move (YesNo_Box ("No Activity for" * String (iIdleTime) * "milliseconds, Quit?", "Confirm", MB_DEFBUTTON2)) to iAnswer
            If (iAnswer = MBR_Yes) Begin
                Send Exit_Application
            End
            Set pbBusy to False
        End
    End_Procedure
End_Object
The DFTimer's TimeOut property does need to have a higher value than the piInActivityTimeOut