Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Conexión con bases de datos (https://www.clubdelphi.com/foros/forumdisplay.php?f=2)
-   -   Bloquear aplicacion despues de cierto tiempo (https://www.clubdelphi.com/foros/showthread.php?t=783)

Marcela 24-05-2003 01:25:02

Bloquear aplicacion despues de cierto tiempo
 
Como hago para saber cuando el aplicativo no ha sido usado por un determinado periodo de tiempo para asi poder controlar al usuario para desconectarlo de la base de datos y cerrar la aplicación automaticamente. Esto con fin de controlar que el usuario no deje la aplicacion abierta y se vaya.

Gracias

Marcela

andres1569 24-05-2003 13:59:01

Hola:

Puedes colocar un componente TApplicationEvents, interceptar el evento OnIdle y ahí almacenas en una variable de tipo Longint el instante mediante GetTickCount. Colocas un TTimer con un Interval no muy pequeño, puesto que debe controlar minutos supongo (p.e. Interval = 60000), y ahí compruebas si ha trnscurrido el máximo tiempo de inactividad, algo así:

Código:


var
  UltimoAcceso : Longint;

procedure TFormPrincipal.ApplicationEvents1OnIdle (Sender: TObject;
  var Done: Boolean);
begin
  UltimoAcceso := GetTickCount;
  Done := TRUE;
end;

procedure Timer1OnTimer (Sender: TObject)
begin
  if GetTickCount - UltimoAcceso > 3600000 then // 60 minutos
    Database1.Connected := FALSE;
end;

Si manejas un Delphi 4.0 o menor, no exite el componente TApplicationEvents, tienes que hacerlo "a mano":

Código:

procedure TFormPrincipal.AppMessage (var Msg:TMsg; var Handled:Boolean);
begin
  // interceptamos eventos de teclado y ratón
  if (Msg.message in [WM_KEYFIRST .. WM_KEYLAST]) OR
    (Msg.message in [WM_MOUSEFIRST .. WM_MOUSELAST]) then
    UltimoAcceso := GetTickCount;
end;

procedure TFormPrincipal.FormCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
  UltimoAcceso := GetTickCount;
end;

A ver si esto te sirve

Un saludo

Marcela 06-06-2003 23:04:05

No encuentro el componente TApplicationEvents y estoy trabajando en Borland Delphi 6 Entreprise, te agradeceria me pudieran decir donde se encuentra y si no lo tengo instalado como podria instalarlo.

Gracias.
:confused:

__marcsc 06-06-2003 23:12:12

En la paleta additional :cool:

Saludos

andres1569 06-06-2003 23:26:33

Hola Marcela:

Gracias por reaparecer. Hace ya 10 días que te contesté y no sabía cómo había ido la cosa. Si el tiempo de inactividad que le vas programar al usuario es similar al que has tardado en contestar, puedes sustituir el 360000 del ejemplo que te mandé por un Googol. :) :) :)

Por favor, no te molestes por esto, es una broma, si has vuelto a los foros despues de estos días, te recomiendo que te pases por la sección de humor, últimamente se ha vuelto la gente muy chistosa.

Mira esto: http://www.clubdelphi.com/foros/show...&threadid=1119

Saludos y ya nos contarás si funciona como quieres o no.

Marcela 19-06-2003 02:56:25

Muchisimas gracias, me funciono lo que andres1569 me dijo, disculpemen por no haber podido contestar antes.

Marcela :D

Lepe 03-07-2003 23:00:23

Hola, ya que está este hilo, pues aprovecho para preguntar esto mismo pero en Windows XP ya que GetTickCount tiene problemas en ese sistema :(

Windows NT: To obtain the time elapsed since the computer was started, look up the System Up Time counter in the performance data in the registry key HKEY_PERFORMANCE_DATA. The value returned is an 8 byte value.

¿alguien entiende como leer esa clave?? porque yo no entiendo si está en Current_User o Local_Machine del registro de windows :confused:

andres1569 03-07-2003 23:54:06

Hola Lepe, no sabía de esa limitación del XP, si es que parece que vamos hacia atrás, estoy seguro de que hay miles de aplicaciones por ahí que usan esa GetTickCount.

De todas formas, no creo que sea buena idea consultar una clave del registro para averiguar un espacio de tiempo, imagínate el tiempo que pasa desde que pides el valor hasta que lo encuentra :) :)

Si miras en este truco de Ian Marteens, verás una forma de acceder a un reloj de alta precisión y que parece encontrarse en cualquier hardware:

http://www.marteens.com/trick4c.htm

Por el nombre de la función, QueryPerformanceCounter, debe guardar cierta relación con la clave del registro que tu comentas, es posiblñe que Windows actualice ese valor a partir de dicha función, pero me extraña todo esto de que haya que mirarlo en el registro, muy raro me parece.

Lepe 04-07-2003 18:50:42

Gracias por contestar andres, si quieres leer eso que puse en inglés, consulta la ayuda de gettickcount en delphi, abajo del todo pone eso.

Puede que lo haya malinterpretado, no sé, pero es lo que me pareció al leer la ayuda :confused: aunque confieso que no entendí ciertas palabras en ingles :o

Por otra parte, he observado que en la aplicación se producen eventos OnIdle muy frecuentemente por lo que reinicia el valor UlimoAcceso que tu proponias. Para realizar las pruebas, en el evento OnTimer1 le dije que me pusiese en la barra de estado la diferencia Gettickcount - UltimoAcceso y como máximo obtuve una diferencia de 400 milisegundos.

El timer tenia un Intervalo de 10.000 milisegundos ¿demasiado bajo para funcionar?

Si no es mucha molestia te rogaria que compruebes si funciona el uso de OnIdle junto con lo del timer, porque me da la impresión de que no es posible hacerlo así. (Ojalá me equivoque :D)

En cuanto al maestro Ian Marteens, me pongo manos a la obra ahora mismo.


Gracias por contestar. Un saludote de tu pupilo menos aventajado :P

andres1569 04-07-2003 22:27:23

Hola:

Tienes, razón, Lepe, ni siquiera lo probé en su día, y lo que me extraña es que le haya funcionado a Marcela, salvo si lo ha hecho con Delphi 4.0 o anterior. El evento OnIdle no es el más apropiado porque la aplicación recibe constantemente mensajes de todo tipo (de reloj, de repintado y otros internos ...) y a continuación, cuando no tiene qué hacer dispara este evento. He comprobado, como tú, que se dispara con demasiada frecuencia, no como yo pensaba.

Lo correcto es interceptar el evento OnMessage de TApplicationEvents y meter ahí el código que ya puse para la versión Delphi 4.0, es decir, chequear si se ha producido un mensaje de teclado o de ratón, que son los más normales de un usuario.

Otro error que he detectado es en la forma de comprobar si el mensaje es de teclado o de ratón, no vale usar un rango de constantes aquí, así que hay que sustituir:

(Msg.message in [WM_KEYFIRST .. WM_KEYLAST])
por
(Msg.message >= WM_KEYFIRST) AND (Msg.message <= WM_KEYLAST)

Lo mismo para los mensajes del mouse.

Todo quedaría así, para quien lo quiera usar, y que se olvide de lo anterior:
Código:

var
  UltimoAcceso: Longint;  // variable global

procedure TForm1.FormCreate(Sender: TObject);
begin
  UltimoAcceso := GetTickCount;
end;

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
    // interceptamos eventos de teclado y ratón
  if ((Msg.message >= WM_KEYFIRST) AND (Msg.message <= WM_KEYLAST)) OR
    ((Msg.message >= WM_MOUSEFIRST) AND (Msg.message <= WM_MOUSELAST)) then
    UltimoAcceso := GetTickCount;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if GetTickCount - UltimoAcceso > 3600000 then // 60 minutos
    Database1.Connected := FALSE;
end;

Esto ya está probado y cumple su cometido.

Lo de la clave de registro HKEY_PERFORMANCE_DATA en WinNT, yo he leído lo mismo que tú, por lo visto en esa clave del registro se almacena el tiempo transcurrido desde el arranque de Windows, lo que no sé es con qué frecuencia se actualiza ese valor. De lo que no me cabe duda es que no es un buen sistema para estar llamándolo constantemente, no es lo mismo llamar a una función como GetTickCount que está ya cargada en memoria (en su DLL correspondiente), que acceder a un fichero de varios megas para localizar ese valor.

Dijiste: Un saludote de tu pupilo menos aventajado
Digo yo: Mejor no seas pupilo de alguien que mete la pata de esta forma :) . Todos aprendemos de todos :)

Lepe 05-07-2003 12:47:50

Muchas gracias por contestar con tanta rapidez y eficiencia andres.


Lo de la clave de registro... casi mejor nos olvidamos de elllo y dejemos que Microsoft siga usando su amado registro de Windows ;) Nosotros usaremos otras alternativas :D


Dijiste: Mejor no seas pupilo de alguien que mete la pata de esta forma :)

Digo yo: Nunca habia usado los applicationsEvents hasta ahora, así me obligaste a leerme la ayuda; tambien esa es una forma de enseñar-aprender ;)

Dijiste: Todos aprendemos de todos
Y además es una muy buena forma de ampliar los horizontes abriendo la mente a ideas que a uno solo no se le hubiese ocurrido en la vida.

Un saludo Maestro :)

sebas 07-07-2003 14:47:10

Hola a todos, soy nuevo en el foro y me gustaria me ayudaran con éste tema también.

Estuve probando la funcion ApplicationEvents1OnIdle con el "Timer" y primero que nada a que se refiere exatamente el "Idle" y segundo, esta funcion se ejecuta por cualquier movimiento del mouse o bien porque el cursor esta titilando en algún edit "esto es justamente lo que quiero evitar".

Yo tengo un sistema del cual no tengo los fuentes y este si en 5 minutos no das click en alguna parte del sistema o no apretas alguna techa dentro del sistema, envia un aviso de no uso del sistema y sale del mismo; esto mismo es lo que quiero hacer yo pero con la funcion ApplicationEvents1OnIdle incluso resetea el tiempo transcurrido cuando se mueve el mouse o titila el cursor.

Me pueden ayudar, y gracias.

andres1569 07-07-2003 15:32:55

Hola Sebas:

Tres mensajes antes que éste que estás leyendo y dos antes del que tu escribiste, he puesto la forma correcta de hacer esto que pides.

En realidad, el evento OnIdle (que significa en espera) no es el más adecuado para saber cuándo una aplicación deja de recibir órdenes, no es que se dispare cuando se procesa un mensaje, sino justo después de realizar cualquier acción por parte de la aplicación, la pega es que se dispara con demasiada frecuencia (en mi primer post lo utilicé porque creía que no), puesto que la aplicación recibe continuamente mensajes de todo tipo, sobre todo mensajes de reloj.

Mira el nuevo código, utilizando el evento OnMessage, y no debería darte problemas.

sebas 08-07-2003 17:40:05

Muchas gracias Andres, esto funciona a la perfeccion; ahora solo tengo una duda a raiz de esto, dentro de mi sistema tengo algunos programas que ejecutan procesos SQL o copias de archivos y verificacion de archivos de texto, cuando se esta ejecutando uno de estos procesos, tanto el "reloj" que tengo dentro del sistema como el "timer" que verifica el GetTickCount quedan parados hasta tanto termine de ejecutarse dichos procesos, esto hace que si el proceso dura más de cinco minutos tiempo en el cual quiero que se cierre el sistema, automaticamente termina el proceso y se cierra el sistema porque el tiempo transcurrido entre el ultimo acceso y el nuevo son mayores a cinco minutos.

Como hago si es que se puede y me supongo que si; para que todo el tiempo se ejecute tanto el "timer" de verificacion como el "timer" del reloj digamos en background..

Sea como sea, espero tus comentarios.

andres1569 08-07-2003 18:32:30

Para que tu aplicación siga chequeando la cola de mensajes, necesitas llamar de vez en cuando al método Application.ProcessMessages. Esta llamada la deberás poner dentro del bucle donde realices tus operaciones, siempre y cuando te sea posible intercalar esta orden en medio de los procesos que realices; aunque si éstos son operaciones del servidor, mucho me temo que tendrás que esperar a que el servidor termine; me consta que algunos servidores de BD permiten pasarles funciones tipo Callback para controlar la evolución de los procesos, consultas y demás, y así es posible por ejemplo mostrar una barra de progreso al usuario.
Código:

while not condicion do
begin
  ... tu proceso
  Application.ProcessMessages;
end;

Otra posible solución es que dichos procesos los ejecutes dentro de un thread aparte (aparte del thread general de la aplicación), de esta forma, aunque no llames a ProcessMessages, la aplicación siempre tendrá su momento de reloj para despachar mensajes.

sebas 08-07-2003 21:12:02

Andres, gracias por la ayuda, bueno al final para solucionar mi problema, voy a poner al final de cada proceso una llamada al processmessages asi no tendre problemas; pero me queda una duda; yo tengo un menu desplegable y cuando lo abro y recorro con el mouse la lista completa del menu, el .onmessage no es llamado por lo tanto el timer no es actualizado, sabes porque ocurre esto!!

Otra cosa como funciona este tema del "thread" podrias darme un ejemplo de como utilizarlo.

andres1569 10-07-2003 22:19:53

Hola:

Sebas, leí tu mensaje hace días, pero ya me temía que llevaría un ratillo de sacar esto. He comprobado que es cierto lo que tú dices, si desplegamos un menú y movemos el ratón sobre él, no se dispara el evento OnMessage del ApplicationEvents, por lo tanto, no se reinicia el contador. Me temo que esto se debe a que los menús son controles que Delphi no implementa directamente sino que delega en Windows, y una vez que se muestran, los eventos que suceden sobre ellos no son capturados por la aplicación, salvo cuando el usuario hace click sobre una opción.

En definitiva, me he decidido a implementarlo mediante Hooks de ratón y de teclado y ha dado buen resultado. Reconozco que nunca había usado este recurso de programación, aunque había oído hablar de ellos bastantes veces. Básicamente se trata de registrar en Windows unas funciones que se interponen entre los eventos y nuestra aplicación, como una capa intermedia. Hay varios tipos de Hooks, para lo que nos preocupa capturamos sólo los que tienen que ver con acciones directas del usuario. Éste es el código nuevo, donde ya podemos olvidarnos del componente TApplicationEvents y del evento OnMessage (por cierto ya vamos por la versión 2.0 de este código, release 5, quien tenga la versión 1.0 que se baje el Update Pack 1 :D :D :D ):
Código:

var
  Form1: TForm1;
  UltimoAcceso : Longint;  // variable global
  HookHandleTec : hHook = 0;  // Hook de teclado
  HookHandleRat : hHook = 0;  // Hook de ratón

implementation

{$R *.DFM}

// ésta es la función Callback que instalamos para controlar eventos de teclado
function CapturaMensajesTeclado (Code: Integer; wParam, lParam: Longint)
  : Longint;  stdcall;
begin
  Case Code of
    HC_ACTION,
    HC_NOREMOVE : begin
                    UltimoAcceso := GetTickCount;
                    result := 0;
                  end
    else result := CallNextHookEx (WH_KEYBOARD, Code, wParam, lParam);
    end;
end;

// ésta es la función Callback que instalamos para controlar eventos del mouse
function CapturaMensajesRaton (Code: Integer; wParam, lParam: Longint)
  : Longint;  stdcall;
begin
  Case Code of
    HC_ACTION,
    HC_NOREMOVE : begin
                    UltimoAcceso := GetTickCount;
                    result := 0;
                  end
    else result := CallNextHookEx (WH_MOUSE, Code, wParam, lParam);
    end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // inicializamos el Contador de tiempo y registramos en Windows las funciones Hook
  UltimoAcceso := GetTickCount; 
  HookHandleTec := SetWindowsHookEx (WH_KEYBOARD, CapturaMensajesTeclado, hInstance, 0);
  HookHandleRat := SetWindowsHookEx (WH_MOUSE, CapturaMensajesRaton, hInstance, 0);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // si se llega al tope previsto, se muestra un mensaje
  if GetTickCount - UltimoAcceso > 10000 then
  begin
    UltimoAcceso := GetTickCount; // reinicalizamos el contador, sólo para pruebas
    ShowMessage('Tiempo excedido');
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // Liberamos los Hooks
  UnHookWindowsHookEx (HookHandleTec);
  UnHookWindowsHookEx (HookHandleRat);
end;

Como veis, ha cambiado la forma de hacerlo, pero la filosofía es la misma. Ahora, las notificaciones de eventos de usuario no nos vienen desde la aplicación sino que las notifica el mismo Windows.

Por cierto, los Hooks que instala una aplicación, como los del ejemplo, afectan al thread de la aplicación, de modo que si el usuario se pone a jugar al Solitario sin pasar el ratón por encima de nuestro programa, nuestro contador seguirá contando como si estuviera inactivo. Si se quisiera controlar a nivel del sistema, para asegurarse de que el usuario realmente no está actuando con la máquina, habría que registrar los Hooks desde una DLL, que cargaríamos al arrancar nuestra aplicación. De esa forma, los Hooks afectarían a todas las aplicaciones (threads) que tuviéramos activas en ese moneto. Si tenéis acceso a la "Guía de Desarrollo en Delphi X", de Teixeira y Pacheco, ahí viene muy bien explicado cómo usar los Hooks. En la DDG 5 está en la pag. 665.

Respecto a lo de los threads los ejemplos que te puedo poner son sacados de libros. Si no tienes ninguno a mano, te los posteo en otro mensaje (ahora dame un respiro :) ). En concreto tengo aquí un ejemplo bastante bueno de Marco Cantú, libro "Mastering Delphi 4.0", pag. 925, donde aplica un thread para realizar una operación sobre una tabla. Para usar un thread, básicamente, hay que crear una clase derivada de TThread y redefinir el método Execute, donde irá el código que quieres que se ejecute "separadamente" del resto. Luego, en el momento que creas el TThread, Delphi ya se encarga de registrarlo en la lista de hilos de Windows y de que se ejecute el código que le has programado. Imagino que en cualquier libro de Delphi debe venir algo sobre el asunto.

Saludos

guillotmarc 10-07-2003 23:11:05

Hola.

¿ No sería más sencillo en lugar de mirar periodicamente el intervalo de tiempo desde la ultima acción, simplemente resetear el timer a cada evento ?. De forma que si el Timer llega a provocar un evento es porqué hemos superado los 5 minutos, y tenemos que bloquear la aplicación. Es más sencillo y no dependemos del funcionamiento del GetTickCount.

Versión 3.0 :D :D :
  • Al iniciar la aplicación configuramos y arrancamos el Timer para 5 minutos.
  • En el evento de AppEvents, reseteamos el Timer (poniendo el Enabled a False para volverlo a poner a True).
  • En el evento del Timer, bloqueamos la aplicación (puesto que solo podemos haber llegado tras 5 minutos de inactividad).

NOTA : También podemos aprovechar igualmente la magnífica detección de eventos propuesta por Andrés, en lugar de utilizar el AppEvents.

Saludos.

andres1569 10-07-2003 23:51:35

Hola:

Guillotmarc, la idea que propones es elegante pero tiene un inconveniente si decidimos utilizar lo de los Hooks:

Las funciones callback que pasamos al Hook no son funciones de objetos, sino funciones "sueltas", por lo que no sé cómo en el cuerpo de estas funciones podríamos acceder a los TTimers (podríamos acceder a ellos mediante la expresión Form1.Timer.Enabled := FALSE, es decir referenciando la variable Form1, pero esto puede ser algo peligroso). Aunque a decir verdad las funciones Hooks siempre irán en el Form principal o en el DataModule principal, que normalmente estará activo durante toda la vida del programa, pero se debe tener en cuenta esa cuestión.

De todas formas, el llamar a GetTickCount creo que es más eficiente que poner Timer1.Enabled := FALSE y luego TRUE, puesto que al hacer esto se mata el Timer (KillTimer) y luego se vuelve a reservar. Esto quizás sea más lento. Ten en cuenta que si se está escribiendo o moviendo el ratón continuamente se estará ejecutando ese código repetidamente.

En realidad, si queremos aplicar un tiempo de espera de más de un cuarto de hora, por ejemplo, el Timer lo podemos poner a un intervalo de 1 minuto (60000) y no repercutirá mucho en la marcha de la aplicación.

guillotmarc 11-07-2003 01:31:14

Hola.

Cita:

Posteado originalmente por andres1569
Las funciones callback que pasamos al Hook no son funciones de objetos, sino funciones "sueltas", por lo que no sé cómo en el cuerpo de estas funciones podríamos acceder a los TTimers (podríamos acceder a ellos mediante la expresión Form1.Timer.Enabled := FALSE, es decir referenciando la variable Form1, pero esto puede ser algo peligroso). Aunque a decir verdad las funciones Hooks siempre irán en el Form principal o en el DataModule principal, que normalmente estará activo durante toda la vida del programa, pero se debe tener en cuenta esa cuestión.
No creo realmente que hubiese problemas, es lo mismo que acceder a la variable UltimoAcceso que estará definido en el formulario principal, también tenemos que poner el Timer en ese formulario. Así aseguramos que el tiempo de vida del Timer sea el mismo que las llamadas a los Hooks. Si hubiera la posiblidad de que se ejecutarán en threads distintos si que habría que sincronizar (hay una función para eso) el acceso al Timer para asegurar que no se provoque un conflicto, pero no es el caso.

Cita:

Posteado originalmente por andres1569
De todas formas, el llamar a GetTickCount creo que es más eficiente que poner Timer1.Enabled := FALSE y luego TRUE
Completamente de acuerdo, aunque lo que tienes que comparar són la ejecución de tu función garfio, con el reseteo que propongo yo. Para el tema del rendimiento nos podemos olvidar de los disparos de los timers, en mi solución, simplemente porqué nunca se dispara, y en la tuya, porqué como dices, se puede definir para intervalos suficientemente largos como para despreciarlo.

Esta claro que poner en la función garfio un acceso a GetTickCount es más eficiente que resetear el Timer. No he propuesto esa solución por rendimiento, sinó como comentas, por la elegancia de solucionar el problema unicamente con dos componentes y dos lineas de código. (El rendimiento no siempre lo es todo).

Un saludo.


La franja horaria es GMT +2. Ahora son las 12:57:31.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi