Cerrando programa WinForms tras 15 minutos de inactividad

Iniciado por Solid Water, Octubre 11, 2018, 03:53:38 PM

Tema anterior - Siguiente tema

0 Miembros y 2 Visitantes están viendo este tema.

Octubre 11, 2018, 03:53:38 PM Ultima modificación: Octubre 11, 2018, 05:01:58 PM por solid water
Cerrar programa winforms tras 15 minutos de inactividad por parte del usuario.
No parece tan difícil pero veamos como después se complica un poco.
Supongamos que tenemos un formulario padre con 2 botones que llama a los formularios hijos:



Código: csharp

   private void button1_Click(object sender, EventArgs e)
        {
            Form form2 = new Form2();
            form2.Show();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form form3 = new Form3();
            form3.Show();
        }




Bueno para controlar los 15 minutos utilizaremos un timmer que agregaremos al formulario 1 y setearemos que cada 1 minuto suba 1 punto el contador al llegar a 15 minutos sin actividad cerraríamos el programa.

En el form1 load declaramos que el timmer llamara a la función TimerEventProcessor cada 1 minuto:
Código: csharp

            timer1.Interval = 60000;
            timer1.Tick += new EventHandler(TimerEventProcessor);
            timer1.Start();


Y en la función nuestro contador aumentará de 1 en 1 y si llega a 15 cerrará el programa:

Código: csharp

private void TimerEventProcessor(object sender, EventArgs e)
        {
            _counter++;
           
            if (_counter == 15)
            {
               
                Environment.Exit(1);
                this.Close();
            }
        }


Cabe destacar que la variable counter es un static int declarado como dato miembro de form1.

Código: csharp

static int _counter;


Bueno todo muy lindo, pero ahora faltaría que cada vez que se presione una tecla o se haga click en algun control de cualquiera de los formularios la variable counter vuelva a 0 así vuelven a contarse los 15 minutos desde la última vez que hubo actividad del usuario.

Para eso debemos delegar a los controles la función que vuelve el contador a 0.

Código: csharp

public static void mdiPrincipal_Click(object sender, EventArgs e)
{
            _counter = 0;
            MessageBox.Show("Click or keypressed");
}


Como estamos testeando le puse a la función un messagebox que me avisa que hubo un click o key pressed.
Pero todavía nos falta delegarle dicha función, a los eventos click y keypress de todos los controles de los formularios.
Sin lugar a dudas hacerlo 1 por 1 sería una solución horrible.
Para delegar la función a los eventos keypressed y click podemos usar un código como el siguiente:

Código: csharp

foreach (Control control in Controls)
{

control.Click += new System.EventHandler(mdiPrincipal_Click);
        control.KeyDown += new KeyEventHandler(mdiPrincipal_Click);
}


Pero el código tiene un problema, cuando el control con el que se encuentra el foreach es un groupBox, este tiene
anidado los controles dentro de el, por lo que se saltiaría todos los controles dentro de groupboxs.
Para solucionarlo podemos decir que si es un group box, recorra los elementos que hay dentro de este último 1 por 1 como lo hace con los de los formularios. El siguiente código funcionaría en caso de que haya como máximo 2 group box (anidados) o cualquier cantidad sin anidar en el formulario:

Código: csharp

foreach (Control control in Controls)
            {
                if (control is GroupBox)
                {
                    foreach (Control controlinside in control.Controls)
                    {
                        if (controlinside is GroupBox)
                        {
                            foreach (Control controlinside2 in controlinside.Controls)
                            {
                                controlinside2.Click += new System.EventHandler(mdiPrincipal_Click);
                                controlinside2.KeyDown += new KeyEventHandler(mdiPrincipal_Click);
                            }
                        }
                        else
                        {

                            controlinside.Click += new System.EventHandler(mdiPrincipal.mdiPrincipal_Click);
                            controlinside.KeyDown += new KeyEventHandler(mdiPrincipal.mdiPrincipal_Click);
                        }
                    }
                }
                else
                {

                    control.Click += new System.EventHandler(mdiPrincipal.mdiPrincipal_Click);
                    control.KeyDown += new KeyEventHandler(mdiPrincipal.mdiPrincipal_Click);
                }
            }


Pero que pasaría si hubiera más de 2 group box no podemos hardcodear todo así. Debemos hacerlo de un modo más prolijo.
Para eso creé una función que utiliza recursividad con los group box volviendo a llamarse una y otra vez siempre y cuando encuentre otro group box anidado.
En conclusión creé 2 funciones la principal (AddEvents) que "si es un group box llama a la recursiva" y si no agrega los eventos a cada control, y la recursiva que se encarga de los group box y group box anidados en otros group box.

Código: csharp

public static void AddEvents(Form form){
             
            foreach (Control control in form.Controls)
            {
               
                if(recursiva(control)){

                }
                else
                {

                    control.Click += new System.EventHandler(mdiPrincipal_Click);
                    control.KeyDown += new KeyEventHandler(mdiPrincipal_Click);
                }
            }
       }


        public static bool recursiva(Control control){

             if (control is GroupBox)
             { 
                   foreach (Control controlinside in control.Controls)
                   {
                       if (recursiva(controlinside))
                        {


                         }else{

                           controlinside.Click += new System.EventHandler(mdiPrincipal_Click);
                           controlinside.KeyDown += new KeyEventHandler(mdiPrincipal_Click);
                        }
                   }

                  return true;

              }else{

                return false;

          }

        }


Por último, nos queda en todos los form loads, llamar a:
Código: csharp

AddEvents(this);


Como podrán ver para testearlo en un formulario anidé muchos groupbox con controles dentro:



Más tarde les dejo el proyecto para descargar que ahora no estoy en casa.

Autor: Solid Water (Matías).

PD: Seguramente podría hacerse de algún modo con hooking o capturar algún evento más, pero bueno todo depende de para que queramos usarlo, podría ser mejor o peor.

Saludos,

HOLA!!!

Admiro tu creatividad, pero con un 10% de ese codigo lo haces XD

Tenes que usar un invoke sobre "getlastuserinput" y listo te dara la ultima interaccion del usuario.

Te dejo documentacion con ejemplos y todo:
No tienes permitido ver enlaces. Registrate o Entra a tu cuenta

GRACIAS POR LEER!!!
"Algunos creen que soy un bot, puede que tengan razon"
"Como no se puede igualar a Dios, ya he decidido que hacer, ¡SUPERARLO!"
"La peor de las ignorancias es no saber corregirlas"

*Shadow Scouts Team*                                                No tienes permitido ver enlaces. Registrate o Entra a tu cuenta

Noviembre 16, 2018, 09:20:21 AM #2 Ultima modificación: Noviembre 16, 2018, 11:21:35 AM por solid water
Gracias por la información, cuando busqué una solución a eso no la encontré por eso hice todo eso.

Sin embargo al parecer esa función que mencionas devuelve la última acción sobre el sistema y no sobre la aplicación, y hay que agregar más código para comparar con la aplicación incluso un timmer:

No tienes permitido ver enlaces. Registrate o Entra a tu cuenta

Saludos,

Noviembre 16, 2018, 11:20:34 AM #3 Ultima modificación: Noviembre 16, 2018, 04:47:04 PM por solid water
Mira Lo hice utilizando esa función:

User32Interop.cs
Código: csharp

using System;
using System.Text;
//For use DLL import:
using System.Runtime.InteropServices;

namespace WindowsFormsApplication2
{
/*This class only tells me when the system has been idle, not the application.
If the user clicks into Word and works there for an hour,
I still want a timeout. To handle this case, I simply will remember when my application
loses focus by overriding the OnDeactivated and OnActivated methods
on the application object.
  */
    public static class User32Interop
    {
        public static TimeSpan GetLastInput()
        {
            var plii = new LASTINPUTINFO();
            plii.cbSize = (uint)Marshal.SizeOf(plii);
            if (GetLastInputInfo(ref plii))
                return TimeSpan.FromMilliseconds(Environment.TickCount - plii.dwTime);
            else
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
        struct LASTINPUTINFO
        {
            public uint cbSize;
            public uint dwTime;
        }
    }
}


Form1.cs
Código: csharp

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        //Variables miembro que utilizaremos:
        public static DateTime? _lostFocusTime;
        int _counter = 0;

        public Form1()
        {
            InitializeComponent();
        }


        //Si respuesta llega a 15 (minutos) cerraremos el programa
        public void Timmer_Function(object sender, EventArgs e)
        {
            bool respuesta = IsIdle;
            //MessageBox.Show(IsIdle.ToString());
            if (respuesta)
            {
                _counter++;

            }
            else
            {
                _counter = 0;
            }

            if (_counter == 15)
            {
                Environment.Exit(1);
                this.Close();
            }

        }


        //LLamaremos al timmer cada 1 minuto:
        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Interval = 60000;
            timer1.Tick += new EventHandler(Timmer_Function);
            timer1.Start();
        }

     

        //En todos los forms debemos sobreescribir estas 2 funciones:
        //Para saber cuando nuestra aplicación pierde el foco.
        override protected void OnDeactivate(EventArgs e)
        {
            _lostFocusTime = DateTime.Now;
            base.OnDeactivate(e);
        }

        protected override void OnActivated(EventArgs e)
        {
            _lostFocusTime = null;
            base.OnActivated(e);
        }


        /*The IsIdle routine was added to the application object.
         * It handles the global case where the app has focus but nothing happened
         * (IsMachineIdle) and the specific case where the application lost focus while
         * the user is doing other stuff (isAppIdle ):*/
        public bool IsIdle
        {
            get
            {
                TimeSpan activityThreshold = TimeSpan.FromMinutes(1);
                TimeSpan machineIdle = User32Interop.GetLastInput();
             
                TimeSpan? appIdle = _lostFocusTime == null ? null :
                                   (TimeSpan?)DateTime.Now.Subtract
                                             (_lostFocusTime.Value);

                bool isMachineIdle = machineIdle > activityThreshold;
                bool isAppIdle = appIdle != null && appIdle > activityThreshold;
                return isMachineIdle || isAppIdle;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 form2 = new Form2();
            form2.Show();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form3 form3 = new Form3();
            form3.Show();
        }
    }
}


Y en todos los otros forms sobreescribir las funciones así:

Código: Csharp
override protected void OnDeactivate(EventArgs e)
        {
            Form1._lostFocusTime = DateTime.Now;
            base.OnDeactivate(e);
        }

        protected override void OnActivated(EventArgs e)
        {
            Form1._lostFocusTime = null;
            base.OnActivated(e);
        }


No sé si sean menos líneas, de hecho tampoco que sea menor trabajo a nivel procesador.
Pero me agrada tener otra forma de hacerlo.
Luego lo escribo de una forma más detallada.

Saludos,