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
):
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