I see, you somewhere have
Code:
This.lUserActed = .T.
* So start counting again.
This.Reset()
In a method bound to mouse move.
I think you could profit binding to windows events instead, especially binding to 0, see below.
What I checked, and this should also be interesting to vernspace, you can't just listen to all windows events, your process can only listen to events that come to any of your forms.
If you use Declare Integer GetDesktopWindow in Win32Api to get the desktop HWND you don't get ResetIdleWaitTime calls when you move the mouse on the desktop, outside of _screen. If you use 0 as HWND, you bind to all your own application windows, so that's a generalization for which you won't need additional Bindevents for any form you start. But it's still limited to your own process HWNDs.
So I'd change my own idea to:
Code:
BindEvent([s]_screen.hWnd[/s] 0, WM_KEYDOWN, goInactivityTimer,'ResetIdleWaitTime')
BindEvent([s]_screen.hWnd[/s] 0, WM_MOUSEMOVE, goInactivityTimer,'ResetIdleWaitTime')
And while I'm at improving it, change the Timer event to:
Code:
Procedure Timer()
This.Enabled = .F.
* MessageBox(Textmerge('last keyboard or mouse activity <<This.Interval/60000>> minutes ago'))
* Instead of the messagebox, do whatever you want to do in the idle state:
Do idlestate.prg
This.Reset()
This.Enabled = .T.
EndProc
There still is nothing that reactivates this timer from idle mode to non-idle mode, except idlestate.prg returning. So again, I stress out: The code you do in idle mode (in idlestate.prg in this case) has to be able to detect a user action and then quit. How to do that is shown already, by binding to windows events. To cover both things in the same timer is possible, but that would need a change:
In idle state the timer should now not reset the timer interval when a user acts, but instead ResetIdleWaitTime in that mode must become the method that stops idlestate.prg from running. Which in theory is possible with CANCEL. As the help chapter on CANCEL in short says "Ends execution of the current Visual FoxPro program file." that would be possible. It would be idlestate.prg. At the same time, though, using CANCEL in any code means it is the current PRG file. And it also stops executing that method itself. When binding to windows events you have to return the value you get from CallWindowProc and so can't do CANCEL and RETURN. Besides that, the help on CANCEL also tells "If a distributed run-time application is running, CANCEL terminates the application and control returns to Windows." So that's a red herring.
I could add a property ApplicationIsIdle signaling the idle state in the timer, so that ResetIdleWaitTime could act accordingly, I'd still need to solve stopping idlestate.prg - assuming it still runs. The simplest way could be that idlestate.prg checks this new property of the timer and when it becomes .F. it stops doing something, for example idlestate.prg could run a loop with
Code:
Do While goInactivityTimer.ApplicationIsIdle
* Do something
EdnDo
I'd still not cover cases the "Do something" part takes a long time and will not be interrupted by user activity directly. On the other side cancelling code also has some pitfalls. When code you cancel is within a transaction, you don't let the transaction complete, neither commit nor roll back. If it even doesn't use transactions it could leave data changes in an undefined state. So whatever you do in an interruptible idlestate.prg would require well planned acting on things that can't leave whatever it manipulates in an unwanted intermediate state. From that perspective I'd suggest idlestate.prg in such a while loop should neither do critical things nor take long for each iteration of the loop waiting for the user to become active again. With that restrictions the simple signaling method would work okay, and there would be no need for an interruption.
So all in all, idelstate.prg has some restrictions of what it can do purposefully.
Also be careful with multi user vs. single user applications. In a single user application you might use the time to do PACKs on tables. But if a PACK takes long, you risk the user still sitting at the computer, just had a 20 minute phone call, you start a PACK taking maybe 10 minutes on a large table and start that at 15 minutes into the phone call. When the user gets back to using the application in the middle of the PACK, there's no way of stopping that.
It's no wonder the usual recommendation of data maintenance actions are to be done in a separate administrative tool or a scheduled task running at night.
What you could easily do instead of a pack is find one deleted row, RECALL it, copy the last undeleted record of the DBF over here and DELETE that record near the end of the DBF, so finally all deleted rows of the DBF are at its end, at least. The file then could be truncated after a fix of the reccount in the DBF header. Still even the record swapping isn't as easy as it sounds, as the primary key index will need unique values in all steps of such a record swap.
A completely different matter would be not just starting a PRG but instead call a new EXE: For example, RUN /N idlestatet.exe. There are ways to do this and be able to also stop idlestatet.exe to run, but then there also would be simpler ideas of running a second process all the time and then simply signal it to become active as your own EXE detects its user is inactive. Which also closes the circle in what I said about nefarious actions: Any code could do nefarious actions, it doesn't need to wait for the user to become inactive. So I don't think you teach a bad hacker how to do things, they already know it takes code to be done and they surely put theirs into viruses embedding themselves in your exe or make their own executable. The other side of this, teaching how to react to windows events, also loses it's power when you only can react to events your own process receives anyway. So, a key logger requires more than reactions to WM_KEYDOWN.
Chriss