Sandeep
Sandeep

Reputation: 105

How to set a timer in Windows application when user is idle or not active

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

Answers (2)

IV.
IV.

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:

  1. 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;
    }
    
  2. 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
    
  3. 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);
    }
    
  4. 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

D J
D J

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

Related Questions