Click here to Skip to main content
15,879,095 members
Articles / Desktop Programming / Win32

Application Idle

Rate me:
Please Sign up or sign in to vote.
4.89/5 (67 votes)
2 Mar 2009CPOL7 min read 247.3K   6K   168   89
A WinForms component to notify when your app has been idle for a specified timespan.

Purpose

I recently needed to have an automatic, timed user-log-out facility in an application. I couldn't find any built-in components, so I 'Googled'. I found various suggestions but the only decent ones worked on activity system wide, not just within the application in use, so I set about writing my own. What I wanted was any key strokes or mouse movements within my application would keep the user logged in, but if none were detected for a pre-defined period, then the user would be automatically logged out.

Rejected Ideas

.NET has an event that initially looked promising, System.Windows.Forms.Application.Idle. Combined with a timer, I thought it would be the solution, but it was not suitable as there is no control over what interrupts it. For example, the timer itself causes the idle state to be exited, which makes perfect sense, of course, but excludes it for this scenario. The next obvious thing was to process key and mouse events on the main form. I rejected this as well as it would have required these to be handled individually for all controls etc... a maintenance nightmare.

Solution

In the end, I opted to use IMessageFilter's PreFilterMessage method, not to actually filter any messages, but to get notification of the ones I was interested in. This, combined with a timer, gave the functionality I needed. For convenience, I've wrapped these up into a component that you can drop onto your app's main form.

The Component

The ApplicationIdle component is very simple to use. Documentation is below but I'll cover the basics here.
This second version adds more functionality to the original but can be used in the same way. Drag it onto your form and it will appear in the component bar below the form (or just create an instance of it in your code). Set the IdleTime as required, subscribe to the Idle event and call the Start method. When the application has had no mouse or keyboard activity for the time you specified, your Idle event handling method will be raised.

The Code

The code is built around a TimeSpan _TimeRemaining that is decremented on each tick of a System.Windows.Forms.Timer timer.

The Start method sets the remaining time to the IdleTime's value, registers the component to receive application Windows Messages and starts the internal timer.

C#
public void Start()
{
    if (!_IsRunning)
    {
        _TimeRemaining = _IdleTime;
        _TimeElapsed = ZeroTime;
        Application.AddMessageFilter(this);
        timer.Start();
        _IsRunning = true;
        OnStartedAsync(EventArgs.Empty);
        OnStarted(EventArgs.Empty);
    }
}

You will notice two OnEvent methods are called at the end of this method. All the behavior events have two versions - a standard synchronous and an asynchronous one. This is in response to comments that were made on the first version.

Normally we raise events synchronously. In other words, the event doesn't return until all subscribers have done their thing in their handlers. If you decided to do something time intensive in one of your event handlers, it would block the component which isn't ideal for something that is time based like this. The answer is to raise the event asynchronously on a separate thread. The downside is, the event handling method must handle the threading properly if for example it is going to update the UI.

This is an example of how the StartedAsync event may be handled.

C#
void applicationIdle_StartedAsync(object sender, EventArgs e)
{
    BeginInvoke(new MethodInvoker(
        delegate() { applicationIdle_Started(sender, e); })
        );
}
void applicationIdle_Started(object sender, EventArgs e)
{
    // do stuff here
}

(For more information see my Events Made Simple article.)

When a message is received, we only need to respond to key and mouse messages. The ones we are interested in are defined in an enum ActivityMessages. All we need to do is check if the message is defined and react accordingly. Regardless of the message, we return false to indicate that we aren't actually processing the message.

C#
bool IMessageFilter.PreFilterMessage(ref Message m)
{
    if (Enum.IsDefined(typeof(ActivityMessages), m.Msg))
    {
        _TimeRemaining = _IdleTime;
        _TimeElapsed = ZeroTime;
        ActivityEventArgs e = new ActivityEventArgs((ActivityMessages)m.Msg);
        OnActivityAsync(e);
        OnActivity(e);
    }
    return false;
}

Every time the internal timer ticks, we decrease the time remaining and check if it's zero. If it is, we raise the idle events and stop the component.

C#
private void timer_Tick(object sender, EventArgs e)
{
    _TimeElapsed = _TimeElapsed.Add(_TickInterval);
    _TimeRemaining = _TimeRemaining.Subtract(_TickInterval);
    // ...
    if (_TimeRemaining == ZeroTime)
    {
        OnIdleAsync(EventArgs.Empty);
        OnIdle(EventArgs.Empty);
        Stop();
    }
}

When we stop the component, we need to stop the timer, reset some properties, and unregister from the application messages.

C#
public void Stop()
{
    if (_IsRunning)
    {
        timer.Stop();
        _TimeRemaining = ZeroTime;
        _TimeElapsed = ZeroTime;
        _IsRunning = false;
        _IsPaused = false;
        Application.RemoveMessageFilter(this);
        OnStoppedAsync(EventArgs.Empty);
        OnStopped(EventArgs.Empty);
    }
}

The Demo

I have included a simple demonstration application to show the component in use. Use the menu or F12 to 'Log In'. Moving/clicking the mouse or pressing a key within the application will reset the counters. Leave it idle until Remaining reads 00:00:00 and you will be automatically logged out.

Conclusion

That's an overview of the basics and is probably all that you will need. The next section gives an outline of all properties, events and methods of all the classes involved.

Application Idle Project Documentation

The project is a class library of the following five files. This is part of a larger component library. It builds down to Winforms.Components.dll and the component resides in the Winforms.Components namespace. Other related classes reside in the Winforms.Components.ApplicationIdleData namespace.

ApplicationIdle

The WinForms component that determines whether an application has received any defined ActivityMessages for a specified TimeSpan.

Properties

Designer Visible Properties

  • IdleTime

  • A TimeSpan after which the application should be considered idle if no defined ActivityMessages are received.
  • TickInterval

  • The TimeSpan at which the component 'ticks'. Idleness is checked and appropriate events are raised on each tick.
  • WarnSetting

  • The WarnSettings value used to control warning events generation.
  • WarnTime

  • A TimeSpan at which warning events will be generated depending on the WarnSetting.

Additional Editor Visible Properties (readonly)

  • IsPaused

  • Indicates whether the component is currently paused.
  • IsRunning

  • Indicates whether the component is currently running.
  • TimeElapsed

  • A TimeSpan representing the time since the last activity was detected.
  • TimeRemaining

  • A TimeSpan representing the time until Idle assuming no activity is detected.

Events

Property Changed Events

All the property changed events are raised when the corresponding property changes.

  • IdleTimeChanged
  • TickIntervalChanged
  • WarnSettingChanged
  • WarnTimeChanged

Synchronous Behavior Events

All the synchronous events listed below also have an asynchronous counterpart.

  • Activity

  • Raised when the component detects an activity that is defined in ActivityMessages.
  • Idle [default]

  • Raised when the IdleTime is reached.
  • Paused

  • Raised when the component is paused.
  • Started

  • Raised when the component is started.
  • Stopped

  • Raised when the component is stopped.
  • Tick

  • Raised when the component 'ticks'.
  • UnPaused

  • Raised when the component is unpaused.
  • Warn

  • May be raised when the WarnTime is reached and on each subsequent Tick depending on the WarnSetting.

Asynchronous Behavior Events

For descriptions of these events see the Synchronous Behavior Events section above.

Methods

None of the methods take any parameters.

  • GetVersion

  • Gets the assembly version of the component.
  • Restart

  • Stops, and then starts the component.
  • Start

  • Starts the component.
  • Stop

  • Stops the component.
  • TogglePause

  • Toggles the pause state of the component.

Notes

Setting IdleTime forces recalculation of TickInterval and WarnTime.
Events are fired only if the associated property changes in the following order.

  1. IdleTimeChanged
  2. TickIntervalChanged
  3. WarnTimeChanged

Setting TickInterval forces recalculation of WarnTime.
Events are fired only if the associated property changes in the following order:

  1. TickIntervalChanged
  2. WarnTimeChanged

TickInterval is forced to be a factor of the IdleTime.
WarnTime is forced to be a multiple of TickInterval and a factor of IdleTime.

IdleTime and TickInterval can only be set when the component isn't running.
Stop, Restart and TogglePause have no effect if the component isn't running.

When the component 'ticks' several events are possible. They are fired in the following order as appropriate.

  1. Tick
  2. Warn
  3. Idle
  4. Stop

All asynchronous events are fired immediately before their synchronous counterparts.

ActivityMessages

This is a simple enum of Windows messages (sourced from the winuser.h header file) that we're interested in.

ActivityEventArgs

This class derives from System.EventArgs and has the following property. It is used in the Activity and ActivityAsync events.

  • ActivityMessage

  • An ActivityMessages member indicating the activity that was detected.

TickEventArgs

This class derives from System.EventArgs and has the following property. It is used in the Tick and TickAsync events.

WarnSettings

An enum used for the WarnSetting property.

  • Tick

  • Warning events are raised when the WarnTime is reached and on each subsequent Tick.
  • Once

  • Warning events are raised only when the WarnTime is reached.
  • Off

  • Warning events are not raised.

History

  • 22 October 2008: Initial version
  • 01 March 2009: Version 2

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Dave Meadowcroft
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: Application Idle Pin
DaveyM6919-Jun-12 8:54
professionalDaveyM6919-Jun-12 8:54 
GeneralRe: Application Idle Pin
habay4lordcom19-Jun-12 22:59
habay4lordcom19-Jun-12 22:59 
GeneralMy vote of 5 Pin
magic.on9-Jun-12 9:12
magic.on9-Jun-12 9:12 
GeneralRe: My vote of 5 Pin
DaveyM6919-Jun-12 8:56
professionalDaveyM6919-Jun-12 8:56 
QuestionEvents rised during PAUSED period... Pin
Hurty22-Nov-11 22:30
Hurty22-Nov-11 22:30 
AnswerRe: Events rised during PAUSED period... Pin
DaveyM6926-Nov-11 0:49
professionalDaveyM6926-Nov-11 0:49 
GeneralRe: Events rised during PAUSED period... Pin
Hurty12-Dec-11 4:58
Hurty12-Dec-11 4:58 
GeneralMy vote of 5 Pin
jeffrey007022-Nov-11 3:31
jeffrey007022-Nov-11 3:31 
QuestionBug in component Pin
khoirom23-Jun-11 15:49
khoirom23-Jun-11 15:49 
GeneralThank You for very useful component Pin
Pradeep Babu Yadagani7-Nov-10 5:17
Pradeep Babu Yadagani7-Nov-10 5:17 
GeneralSetting IdelTime programmatically Pin
Lamazhab14-Jun-10 6:35
Lamazhab14-Jun-10 6:35 
GeneralRe: Setting IdelTime programmatically Pin
DaveyM6914-Jun-10 11:45
professionalDaveyM6914-Jun-10 11:45 
GeneralRe: Setting IdelTime programmatically Pin
Lamazhab16-Jun-10 6:18
Lamazhab16-Jun-10 6:18 
GeneralWon't run Pin
Lamazhab9-Jun-10 10:31
Lamazhab9-Jun-10 10:31 
GeneralRe: Won't run Pin
Lamazhab9-Jun-10 15:34
Lamazhab9-Jun-10 15:34 
GeneralRe: Won't run Pin
DaveyM6910-Jun-10 1:37
professionalDaveyM6910-Jun-10 1:37 
GeneralGreat job, but I need help Pin
Member 385422512-Mar-10 4:31
Member 385422512-Mar-10 4:31 
GeneralRe: Great job, but I need help Pin
DaveyM6912-Mar-10 8:05
professionalDaveyM6912-Mar-10 8:05 
GeneralRe: Great job, but I need help Pin
Member 385422512-Mar-10 8:45
Member 385422512-Mar-10 8:45 
GeneralRe: Great job, but I need help Pin
DaveyM6912-Mar-10 23:22
professionalDaveyM6912-Mar-10 23:22 
GeneralRe: Great job, but I need help Pin
Member 385422515-Mar-10 3:49
Member 385422515-Mar-10 3:49 
GeneralRe: Great job, but I need help Pin
DaveyM6917-Mar-10 10:59
professionalDaveyM6917-Mar-10 10:59 
GeneralRe: Great job, but I need help Pin
Member 385422517-Mar-10 11:17
Member 385422517-Mar-10 11:17 
GeneralRe: Great job, but I need help Pin
DaveyM6917-Mar-10 23:52
professionalDaveyM6917-Mar-10 23:52 
QuestionMultiple Forms... Pin
Torbs3-Oct-09 15:02
Torbs3-Oct-09 15:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.