
While using the Compact Framework on a mobile device, you can need to invalidate some data if the user is inactive for a certain period of time. In my case I have a custom authentication system that create a custom Token class that is invalidated after few minutes the user is inactive.
Windows CE operating system offers two precious global named events called:
They are manual reset events, resetted from the operating system. If the user is active, the active event is set and the inactive is reset. The opposite happens once the user is inactive.
The inactivity event is triggered as soon as the user stops working. There is no delay at all. This means that if the user clicks a button, you will see the activity event immediately followed by inactivity event. Probably in your application you'll want to use a timer before invalidate some data. In this case you will want to use this lambda as the delegate parameter for the Start method:
(bool isActive) => myTimer.Enable = !isActive
I built a class that use a worker thread to monitor these events. But let me write few words about the implementation before looking at the raw code.
And here is the code!
using System;using System.Linq;using System.Collections.Generic;using System.Text;using System.Runtime.InteropServices;using System.Threading;using System.Windows.Forms;using System.Diagnostics;using System.ComponentModel;// Raffaele Rialdi, http://www.iamraf.net, 2010// This class use the Windows CE global events to know if there is user activity or not.//// You should expect that user activity is true for a very small period of time.// In fact the user activity is true only when the user is currently typing, dragging the// stylus on the screen or using the touch screen with the finger.//// For this reason, if you click on a button, you will see activity on immediately followed// by activity off//// There is no delay (such as the one used to dim the screen) in user activity.//// Some comments about the class:// - it is a component so that the Dispose pattern for derived classes is cleaner// - it is tied to Windows Forms 'Control' since user activity makes more sense in a UI application// - it uses Action<bool> so that you can directly pass a simple lambda expression like this:// (bool b) => myTimer.Enable = !b/*// Example.// Put this code in a windows form project.// The form has a listbox and a button to quit the thread that listen to user activity UserActivity _userActivity; private void Form1_Load(object sender, EventArgs e) { _userActivity = new UserActivity(); _userActivity.Start(this, new Action<bool>(SignalActivity)); } private void SignalActivity(bool IsUserActive) { string item; if (IsUserActive) item = "Active " + DateTime.Now.ToShortTimeString(); else item = "Inactive " + DateTime.Now.ToShortTimeString(); listBox1.Items.Add(item); listBox1.SelectedIndex = listBox1.Items.Count - 1; } private void btQuit_Click(object sender, EventArgs e) { _userActivity.Quit(); } */namespace DetectUserActivity{public sealed class UserActivity : Component, IDisposable
{ #region PInvoke declarationsprivate static readonly Int32 EVENT_ALL_ACCESS = 0x1F0003;
private static readonly string Activity = "PowerManager/UserActivity_Active";
private static readonly string Inactivity = "PowerManager/UserActivity_Inactive";
[DllImport("coredll.dll", SetLastError = true)]
private static extern IntPtr OpenEvent(int desiredAccess, bool inheritHandle, string name);
[DllImport("coredll.dll", SetLastError = true)]
private static extern IntPtr CreateEvent(IntPtr securityAttributes, bool IsManual, bool initialState, string name);
[DllImport("coredll.dll", SetLastError = true)]
private static extern bool EventModify(IntPtr hEvent, UInt32 dEvent);
private enum EventFlags
{Pulse = 1,
Reset = 2,
Set = 3,
}
[DllImport("coredll.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("coredll.dll", SetLastError = true)]
private static extern int WaitForSingleObject(IntPtr handle, UInt32 dwMilliseconds);
[DllImport("coredll.dll", SetLastError = true)]
private static extern int WaitForMultipleObjects(UInt32 nCount, IntPtr[] lpHandles, bool fWaitAll, UInt32 dwMilliseconds);
// In WinCE SetEvent is a Macro that calls EventModifyprivate static bool SetEvent(IntPtr hEvent)
{ return EventModify(hEvent, (UInt32)EventFlags.Set);}
#endregion#region Dispose Pattern for Derived Classes
protected override void Dispose(bool bDisposing)
{ if (bDisposing) { // destroy Managed resources}
// destroy Unmanaged resourcesSetEvent(QuitHandle);
base.Dispose(bDisposing);}
#endregion private IntPtr QuitHandle;private Action<bool> SignalActivity;
private Control Control; /// <summary> /// Start monitoring the user activity /// </summary> /// <param name="control">Windows Form control used to invoke the delegate</param> /// <param name="signalActivity">delegate to call when the user activity changes</param>public void Start(Control control, Action<bool> signalActivity)
{Control = control;
SignalActivity = signalActivity;
QuitHandle = CreateEvent(IntPtr.Zero, true, false, null);
Thread t = new Thread(MonitorUserActivity); t.IsBackground = true;t.Start();
}
public void Quit()
{ this.Dispose();}
public bool IsUserActive { get; private set; }
private void MonitorUserActivity()
{ IntPtr activitySignal = OpenEvent(EVENT_ALL_ACCESS, false, Activity); IntPtr inactivitySignal = OpenEvent(EVENT_ALL_ACCESS, false, Inactivity); if (activitySignal == IntPtr.Zero || inactivitySignal == IntPtr.Zero) { return;}
IntPtr[] evtActivity = new IntPtr[] { QuitHandle, activitySignal }; IntPtr[] evtInactivity = new IntPtr[] { QuitHandle, inactivitySignal }; // First we have to check for user activity without waiting // IsUserActive will be set only if currently there is user activity int result = WaitForSingleObject(activitySignal, 0); IsUserActive = (result == 0); // result == 0 <==> object is signaledwhile (true)
{ if(IsUserActive)result = WaitForMultipleObjects((uint)evtActivity.Length, evtInactivity, false, uint.MaxValue);
elseresult = WaitForMultipleObjects((uint)evtActivity.Length, evtActivity, false, uint.MaxValue);
if (result == 0) {CloseHandle(activitySignal);
CloseHandle(inactivitySignal);
CloseHandle(QuitHandle);
return;}
if(result == -1) { Trace.WriteLine("WaitForMultipleObjects returned WAIT_FAILED (-1)"); return;}
Debug.Assert(result == 1, "result was already checked for the other possible values");IsUserActive = !IsUserActive;
SetUserActivity(IsUserActive);
}
}
private void SetUserActivity(bool IsActive)
{Control.Invoke(SignalActivity, IsActive);
}
}
}
In the comments there is an example about the usage. To create the complete sample you can:
using System;using System.Linq;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;namespace DetectUserActivity{public partial class Form1 : Form
{ public Form1() {InitializeComponent();
}
UserActivity _userActivity;
private void Form1_Load(object sender, EventArgs e)
{ _userActivity = new UserActivity();_userActivity.Start(this, new Action<bool>(SignalActivity));
}
private void SignalActivity(bool IsUserActive)
{ string item; if (IsUserActive) item = "Active " + DateTime.Now.ToShortTimeString(); else item = "Inactive " + DateTime.Now.ToShortTimeString();listBox1.Items.Add(item);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
private void btQuit_Click(object sender, EventArgs e)
{_userActivity.Quit();
}
private void btClear_Click(object sender, EventArgs e)
{listBox1.Items.Clear();
}
}
}
Enjoy!
Copyright (c) Raffaele Rialdi 2009, Senior Software Developer, Consultant, p.iva IT01741850992, hosted by Vevy Europe Advanced Technologies Division. Site created by Raffaele Rialdi, 2009 - 2011