Reputation: 105
I have a question in Windows Forms on setting timer when the user is idle or Inactive. I need the timer to set even on any Mouse Events. If the user makes any moment then I need to reset the timer. So this is the requirement. Here goes the code.
using System;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
namespace FormsTimerSetup.Globals
{
public class SetApplicationTimeOut : Form
{
#region
/// <summary>
/// Private Timer Property
/// </summary>
private static Timer _timer;
/// <summary>
/// Timer Property
/// </summary>
public static Timer Timer
{
get
{
return _timer;
}
set
{
if (_timer != null)
{
_timer.Tick -= Timer_Tick;
}
_timer = value;
if (_timer != null)
{
_timer.Tick += Timer_Tick;
}
}
}
#endregion
#region Events
public event EventHandler UserActivity;
#endregion
#region Constructor
/// <summary>
/// Default/Parameterless SetApplicationTimeOut Constructor
/// </summary>
public SetApplicationTimeOut()
{
KeyPreview = true;
FormClosed += ObservedForm_FormClosed;
MouseMove += ObservedForm_MouseMove;
KeyDown += ObservedForm_KeyDown;
}
#endregion
#region Inherited Methods
/// <summary>
///
/// </summary>
/// <param name="e"></param>
protected virtual void OnUserActivity(EventArgs e)
{
// Invoking the UserActivity delegate
UserActivity?.Invoke(this, e);
}
/// <summary>
///
/// </summary>
public void SetTimeOut()
{
// postpone auto-logout by 30 minutes
_timer = new Timer
{
Interval = (30 * 60 * 1000) // Timer set for 30 minutes
};
Application.Idle += Application_Idle;
_timer.Tick += new EventHandler(Timer_Tick);
}
#endregion
#region Private Methods
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_MouseMove(object sender, MouseEventArgs e)
{
OnUserActivity(e);
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_KeyDown(object sender, KeyEventArgs e)
{
OnUserActivity(e);
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ObservedForm_FormClosed(object sender, FormClosedEventArgs e)
{
FormClosed -= ObservedForm_FormClosed;
MouseMove -= ObservedForm_MouseMove;
KeyDown -= ObservedForm_KeyDown;
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Application_Idle(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
Application.Idle -= Application_Idle;
MessageBox.Show("Application Terminating");
Application.Exit();
}
#endregion
}
}
I have implemented the code but unsure whether it is the right way of doing it.
Any leads would be appreciated.
Upvotes: 2
Views: 1566
Reputation: 8791
Q: "I need the timer to set on any Mouse Events...if the user makes any movement then I need to reset the timer...Any leads would be appreciated."
A: I'll try to offer a few leads that I hope you find useful. You say you want MouseMove event to reset the timer but there's a problem: Any time a child Control has focus, it's the child that receives the mouse event and the Main Form doesn't. This is fixable.
The short answer: "Implement the IMessageFilter interface on the main window class so that the Timer is Reset when Mouse Movement is Detected." Adding a MessageFilter can intercept the mouse messages before they are sent to the focused control.
So, now I have to give you all the details so here's the long answer: It starts by adding IMessageFilter interface to our main Form1 like this:
public partial class Form1 : Form, IMessageFilter
The IMessageFilter requires our class to implement just one method:
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE:
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Commit f9367d7c added at OP's request
case WM_KEYDOWN:
// This makes WakeUp persist if user is typing in the textbox.
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TimeOutState = TimeOutState.WakeUp;
break;
}
return false; // Do not suppress downstream message
}
const int // WinOS Messages
WM_KEYDOWN = 0x0100,
WM_MOUSEMOVE = 0x0200;
You can see that now any mouse movement sets our app's TimeOutState back to 'WakeUp'.
enum TimeOutState{ WakeUp, Sleeping, Warning, Exit }
We only need one Timer and every tick interval (here set to 5 seconds) of the timer reduces the state by one. If the mouse doesn't move, it decrements all the way down and finally exits.
Here's a 60-second video of running the app for 60 seconds. You can see changes occur either every 5 seconds or when the mouse moves. If you'd like to run the sample you can clone the latest commit from our GitHub repo.
Here are the rest of the details:
The MessageFilter needs to be connected. Since we need our Form to have its window handle, so we do this here and start the timer:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// When our main window is ready for messages, add the MessageFilter
Application.AddMessageFilter(this);
// ...and start the timer for the first time.
TimeOutState = TimeOutState.WakeUp;
}
We need to instantiate the Timer, but only once in the CTor:
public Form1()
{
InitializeComponent();
_wdt = new Timer();
_wdt.Interval = 5000; // Use a very short time-out for this demo
_wdt.Tick += _wdt_Tick;
}
Timer _wdt; // Watch-Dog Timer
The Timer.Tick needs to be handled:
private void _wdt_Tick(object sender, System.EventArgs e)
{
// A tick reduces the TimeOutState by 1
TimeOutState = (TimeOutState)(TimeOutState - 1);
}
Finally, handle the state changes of TimeOutState and show our messages.
TimeOutState TimeOutState
{
get => _timeOutState;
set
{
switch (value)
{
case TimeOutState.WakeUp:
_wdt.Stop();
_wdt.Start();
break;
case TimeOutState.Exit:
_wdt.Stop();
Application.Exit();
return;
}
if (value != _timeOutState) // If state changes, write message
{
Debug.WriteLine(value.ToString(), _timeOutState.ToString());
// In a timer callback that changes the UI, it's
// best to post the action in the message queue.
BeginInvoke((MethodInvoker)delegate
{
textBox1.AppendText(_timeOutState.ToString());
if (TimeOutState == TimeOutState.Warning)
{
textBox1.AppendText(
": Closing in " + (_wdt.Interval / 1000).ToString() + " seconds.");
}
textBox1.AppendText(Environment.NewLine);
textBox1.Select(textBox1.TextLength, 0);
});
}
_timeOutState = value;
}
}
TimeOutState _timeOutState = (TimeOutState)(-1); // Initialize to invalid state
I have used IMessageFilter very reliably in my own apps and I'm confident suggesting it to you as one alternative for answering your post.
Upvotes: 1
Reputation: 937
I won't go much deeper into your code but I would like to directly approach the issue. I think a 'roundabout' would work in this case.
For e.g. You can check whenever the mouse moves and compare it with the initial position.
Add this above Initialize Component();
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
Then add this:
void gmh_TheMouseMoved()
{
if(XY==false)
{
MouseX = Convert.ToInt32(Cursor.Position.X);
MouseY = Convert.ToInt32(Cursor.Position.Y);
}
else
{
MouseX1 = Convert.ToInt32(Cursor.Position.X);
MouseY1 = Convert.ToInt32(Cursor.Position.Y);
XY = true;
if(MouseX1==MouseX && MouseY1==MouseY)
{
if(yourTimerNameHere.Enabled==false)
{
yourTimerNameHere.Start();
}
}
else
{
yourTimerNameHere.Stop();
yourTimerNameHere.Start();
}
}
}
Add this outside the class of your form:
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
public event MouseMovedEvent TheMouseMoved;
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
if (TheMouseMoved != null)
{
TheMouseMoved();
}
}
return false;
}
}
Next create 4 ints named MouseX = 0, MouseY = 0, MouseX1 = 0 and MouseY1 = 0 and a bool XY = false;
So actually, whenever the cursor moves, the position gets recorded and gets compared with the next. So you can check if the mouse is idle or no!
Pls note that I haven't tested this code so feel free to revert back for any errors.
Upvotes: 0