Application Idle
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.
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.
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.
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.
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.
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.cs
- Properties
- Events
- Methods
- Notes - Details property/method constraints and event raise orders.
- ActivityMessages.cs
- ActivityEventArgs.cs
- TickEventArgs.cs
- WarnSettings.cs
ApplicationIdle
The WinForms component that determines whether an application has received any defined ActivityMessages for a specified TimeSpan
.
Properties
Designer Visible Properties
IdleTime
TickInterval
WarnSetting
WarnTime
A
TimeSpan
after which the application should be considered idle if no defined ActivityMessages are received.
The
TimeSpan
at which the component 'ticks'. Idleness is checked and appropriate events are raised on each tick.
The WarnSettings value used to control warning events generation.
A
TimeSpan
at which warning events will be generated depending on the WarnSetting. Additional Editor Visible Properties (readonly)
IsPaused
IsRunning
TimeElapsed
TimeRemaining
Indicates whether the component is currently paused.
Indicates whether the component is currently running.
A
TimeSpan
representing the time since the last activity was detected.
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
Idle
[default]Paused
Started
Stopped
Tick
UnPaused
Warn
Raised when the component detects an activity that is defined in ActivityMessages.
Raised when the IdleTime is reached.
Raised when the component is paused.
Raised when the component is started.
Raised when the component is stopped.
Raised when the component 'ticks'.
Raised when the component is unpaused.
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.
ActivityAsync
IdleAsync
PausedAsync
StartedAsync
StoppedAsync
TickAsync
UnPausedAsync
WarnAsync
See Activity.
See Idle.
See Paused.
See Started.
See Stopped.
See Tick.
See UnPaused.
See Warn.
Methods
None of the methods take any parameters.
GetVersion
Restart
Start
Stop
TogglePause
Gets the assembly version of the component.
Stops, and then starts the component.
Starts the component.
Stops the component.
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.
Setting TickInterval forces recalculation of WarnTime.
Events are fired only if the associated property changes in the following order:
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.
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.
IsWarnPeriod
A
Boolean
indicating whether TimeRemaining is less than or equal to WarnTime and WarnSetting is not set to WarnSettings.Off. WarnSettings
An enum
used for the WarnSetting property.
Tick
Once
Off
Warning events are raised when the WarnTime is reached and on each subsequent Tick.
Warning events are raised only when the WarnTime is reached.
Warning events are not raised.
History
- 22 October 2008: Initial version
- 01 March 2009: Version 2