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)
{
}
(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
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.
- IdleTimeChanged
- TickIntervalChanged
- WarnTimeChanged
Setting TickInterval forces recalculation of WarnTime.
Events are fired only if the associated property changes in the following order:
- TickIntervalChanged
- 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.
- Tick
- Warn
- Idle
- 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