I'm using SetTimer and TimerProc functions to set a timer interval in MFC application.
Could you let me know how I can reset the timer?
Printable View
I'm using SetTimer and TimerProc functions to set a timer interval in MFC application.
Could you let me know how I can reset the timer?
What do you mean by 'reset'? As soon as you start a timer, you will be periodically triggered until you stop the timer by a call to 'KillTimer()'...
I'm using KillTimer but when calling TimerProc again, it immedialtely returns and doesnt wait for the time to be elapsed!
What do you mean it immediately returns? 'TimerProc()' will be called every time a WM_TIMER message is sent to your message queue. It does not wait...so I honestly do not understand the question here... :confused:
I think your last statement shows the problem. You should not call the Timerproc! All you have to do is to set the timer. Then the Timerproc gets called after the given time from the framework. AFAIK there is no "ResetTimer" function. But it can't be difficult: As you know which timer to reset, you know which timer to kill and restart.
Good luck!
Marc
To reset a timer you can use SetTimer with the same timer ID:
quote from MSDN
Quote:
nIDEvent
[in] Specifies a nonzero timer identifier. If the hWnd parameter is NULL, this parameter is ignored. If the hWnd parameter is not NULL and the window specified by hWnd already has a timer with the value nIDEvent, then the existing timer is replaced by the new timer. When SetTimer replaces a timer, the timer is reset. Therefore, a message will be sent after the current time-out value elapses, but the previously set time-out value is ignored.
Thanks all for your immediate reply and sorry for my ambiguous question.
Actually, I'm using SetTimer in OnIdle function to log out the application when it's idle for a specified time and it works properely and logs out the user when the time is expired but when the user logs in again it logs her out immeditely :(
Here's my code:
BOOL CxxApp::OnIdle(long lCount)
{
BOOL bRet = CWinApp::OnIdle (lCount);
if(lCount == 0)
{
KillTimer(NULL, m_IDTimer);
}
else
{
m_IDTimer = SetTimer(NULL,NULL,m_lSecProjectTimeOut*1000,(TIMERPROC)TimerProc);
if (m_bTimeOutFinished)
{
m_bTimeOutFinished = FALSE;
LogOff();
bRet = FALSE;
KillTimer(NULL, m_IDTimer);
}
}
return bRet;
}
I do not understand what you are doing there. But maybe my comments are nevertheless useful.
I would expect your function to set the timer again and again, always after an action is performed. So this should happen in OnIdle. I do not see the need of a recursion in that.
But it would be the job of the timer to logoff your user. So I would expect that function in your timerproc!
In the moment it seems to me that you are using the timer like wait(xxx). But the setTimer-command returns immediately!
All put together I would expect a stucture like this:
OnIdle()
{
SetTimer(..)
}
OnTimer()
{
LogOff()
}
regards,
Marc
I'd overridden OnIdle function as below, but I noticed that it uses CPU 100%. So I desided to use Timer functions but the code that I've written logs out user even if the application is not idle. So far as I know I cannot use OnTimer in SDI application.
Do you have any suggestion?
BOOL CxxxApp::OnIdle(long lCount)
{
BOOL bRet = CWinApp::OnIdle (lCount);
static COleDateTime Time;
COleDateTime now;
if (lCount == 0)
Time = COleDateTime::GetCurrentTime();
now = COleDateTime::GetCurrentTime();
COleDateTimeSpan sp;
sp = now - Time;
UINT sec = sp.GetTotalSeconds();
if (sec >= m_lSecProjectTimeOut)
{
Logoff();
bRet = FALSE;
}
else
{
bRet = TRUE;
}
}
Thanks & Regards
You couldn't be more wrong. It's not true.Quote:
So far as I know I cannot use OnTimer in SDI application.
What I think is that you are trying to do something without knowing exaclty what it is that you want to do.
This implementation is correct, except that it uses a lot of CPU time cos OnIdle gets called again and again.Quote:
Originally posted by mfc_oracle
I'd overridden OnIdle function as below, but I noticed that it uses CPU 100%. So I desided to use Timer functions but the code that I've written logs out user even if the application is not idle. So far as I know I cannot use OnTimer in SDI application.
Do you have any suggestion?
BOOL CxxxApp::OnIdle(long lCount)
{
BOOL bRet = CWinApp::OnIdle (lCount);
static COleDateTime Time;
COleDateTime now;
if (lCount == 0)
Time = COleDateTime::GetCurrentTime();
now = COleDateTime::GetCurrentTime();
COleDateTimeSpan sp;
sp = now - Time;
UINT sec = sp.GetTotalSeconds();
if (sec >= m_lSecProjectTimeOut)
{
Logoff();
bRet = FALSE;
}
else
{
bRet = TRUE;
}
}
Thanks & Regards
But back to the previous code that uses Timer, I think we still needs more info. Where and how is m_bTimeOutFinished updated? How about post up your TimerProc ?
mfc_oracle, I think your problems arise because you step into different traps. Your opinion, that a timer is not possible might come from a basic misunderstanding:
Your application, whatever it is (SDI, MDI, Dialog..) is typically mainly based on Event-Handlers. This means, that you can initiate and react on events. As long as your program is already doing something, it is typically not able to do any additional job. Those events might be qeued outside your appl.
So your application should consist of many short parts, each one will provide a reaction of your program. You should make sure, that those event-handlers stop, once they have done their job. This will enable the qeued events to be handled.
If one event-handler is never ending, the other events will never be called.
So a timer is surely working, but when your application ist doing other things all time, it will just endless wait until it gets called.
Just try to understand that your appl. consists of many small and more or less independent parts.
regards
Marc
m_bTimeOutFinished is updated in TimerProc:
VOID CALLBACK TimerProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD dwTime // current system time
)
{
m_bTimeOutFinished = TRUE;
}
Thank you all for your patience I'm not very experienced in MFC and I need your help indeed :)
BTW, I'm still thinking and investigating about your comments, Marc.
Regards
Hi mfc_quote,Quote:
Originally posted by mfc_oracle
m_bTimeOutFinished is updated in TimerProc:
VOID CALLBACK TimerProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD dwTime // current system time
)
{
m_bTimeOutFinished = TRUE;
}
Thank you all for your patience I'm not very experienced in MFC and I need your help indeed :)
BTW, I'm still thinking and investigating about your comments, Marc.
Regards
I think I more or less understand what you want to achieve by now. It is good to know that you have tried different approaches in solving the problems.
Let's talk about the first approach, which is the CTime approach.
This approach has achieved your objective, but the problem is the great amount of CPU time it "utilized". The application is busy performing tasks when it is supposed to idle, which is a bit cruel to your CPU :D .
The other approach using timer, looks a bit strange to me. I can help you visualize how this approach does not work well in the following scenario:
At time t1, there is no messages in the queue, so OnIdle() is being called, and a timer is set, say to invoke 10s later.
After t1, there are a series of user activities resulting the message queue being non-empty at all times.
Then at some time t10, which is 10s after t1, the timer invoked and the flag m_bTimeOutFinished is set to TRUE.
And finally after some time, when the message queue is empty again, OnIdle() will be invoked again and becos m_bTimeOutFinished is being set TRUE at an earlier stage, it just log off the user.
That is why the user experience a log off even when he is not idle.
:)
I suggest maybe you can do it this way:
**************************************************
BOOL CTestOnIdleApp::OnIdle(LONG lCount)
{
if(CWinApp::OnIdle(lCount))
return TRUE;
if(m_FirstTimeIdle)
{
m_PreviousIdleTime = m_CurrentIdleTime = CTime::GetCurrentTime();
m_FirstTimeIdle = FALSE;
}
else
{
m_CurrentIdleTime = CTime::GetCurrentTime();
m_TimeSpan = m_CurrentIdleTime - m_PreviousIdleTime;
if(m_TimeSpan.GetTotalSeconds() > 20)
{
AfxMessageBox("LogOff");
}
m_PreviousIdleTime = m_CurrentIdleTime;
}
return FALSE;
}
**********************************************
The flag m_FirstTimeIdle is set to TRUE at the constructor of CTestOnIdleApp().
m_CurrentIdleTime and m_PreviousIdleTime are instances of CTime.
m_TimeSpan is an instance of CTimeSpan.
It is similar to your first approach, only that OnIdle() is not constantly being called. You can try that out and tell me the outcome. :)
Thank you so much Wombat:)
Your code was life saving (or better saying CPU saving ;) )
But there's a small problem!
When the time expires, the user isn't logged out unless mouse is moved!
That's a slight change of design concept from your original request. Instead of constantly checking the idling status and use up cpu time, I just check the time elapsed when the application came "alive" again and log off the user accordingly.
I guess this is still acceptable for your application, while retaining your original design to a certain extent. Do let me know if it poses serious problem. :)
This is the approach I've taken to this problem in the past, and it works very well.
In your app's InitInstance, hook into the Windows mouse and keyboard hooks:
The hook functions look like this:Code:m_lpfnKeyboardHook = (HOOKPROC)KeyboardHookProc;
m_hKeyboardHook = ::SetWindowsHookEx(
WH_KEYBOARD,
m_lpfnKeyboardHook,
NULL,
(DWORD)GetCurrentThreadId());
m_lpfnMouseHook = (HOOKPROC)MouseHookProc;
m_hMouseHook = ::SetWindowsHookEx(
WH_MOUSE,
m_lpfnMouseHook,
NULL,
(DWORD)GetCurrentThreadId());
They call a member function in the app, which stops and restarts the timer (in the MainFrame window). Thus, every time the user uses mouse or keyboard, the timer is reset.Code:extern "C"
{
DWORD CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return theApp.HookProc(theApp.m_hKeyboardHook, nCode, wParam, lParam);
}
}
extern "C"
{
DWORD CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return theApp.HookProc(theApp.m_hMouseHook, nCode, wParam, lParam);
}
}
You will need to free the hook procedures in ExitInstance:Code:LRESULT CMyApp::HookProc(HHOOK hHook, int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
m_nTimeOutMinsElapsed = 0;
m_nTimeOutCount = 0;
((CMainFrame*)theApp.m_pMainWnd)->StopTimeOutTimer();
((CMainFrame*)theApp.m_pMainWnd)->StartTimeOutTimer();
}
return ::CallNextHookEx(hHook, nCode, wParam, lParam);
}
Declare the app's members like this:Code:if (m_hKeyboardHook)
{
::UnhookWindowsHookEx(m_hKeyboardHook);
m_hKeyboardHook = NULL;
}
if (m_lpfnKeyboardHook)
{
m_lpfnKeyboardHook = NULL;
}
if (m_hMouseHook)
{
::UnhookWindowsHookEx(m_hMouseHook);
m_hMouseHook = NULL;
}
if (m_lpfnMouseHook)
{
m_lpfnMouseHook = NULL;
}
Code:private:
HOOKPROC m_lpfnKeyboardHook; // keyboard hook function
HOOKPROC m_lpfnMouseHook; // mouse hook function
public:
UINT m_nTimeOutMins; // time out
UINT m_nTimeOutMinsElapsed; // time out minutes elapsed
UINT m_nTimeOutCount; // time out count
HHOOK m_hKeyboardHook; // keyboard hook
HHOOK m_hMouseHook; // mouse hook
public:
LRESULT HookProc(HHOOK hHook, int nCode, WPARAM wParam, LPARAM lParam);
The MainFrame message handlers are simple:
Code:void CMainFrame::StopTimeOutTimer(void)
{
KillTimer(m_nHookTimer);
}
void CMainFrame::StartTimeOutTimer(void)
{
if ((m_nHookTimer = SetTimer(ID_EVENT_HOOK, 10000, NULL)) == NULL)
{
TRACE("Failed to set time out timer\n");
ASSERT(FALSE);
}
}
void CMainFrame::OnTimer(UINT nIDEvent)
{
static BOOL bInTimer = FALSE;
if (!bInTimer && nIDEvent == m_nHookTimer)
{
bInTimer = TRUE;
if (theApp.m_nTimeOutCount == 5)
{
// 60 seconds have passed (theApp.m_nTimeOutCount == 5)
theApp.m_nTimeOutCount = 0;
if (++theApp.m_nTimeOutMinsElapsed >= theApp.m_nTimeOutMins)
{
// not currently processing previous timer message
KillTimer(m_nHookTimer);
theApp.m_nTimeOutMinsElapsed = 0;
// forces exit from application
PostMessage(WM_QUIT);
}
}
else
{
theApp.m_nTimeOutCount++;
}
bInTimer = FALSE;
}
}
Well.......you're right Wombat ......but this is a problem for my application :(
I wonder how I can keep my application alive while CPU usage is not much. I'd prefer this code to using a thread and changing all design.
Do you have any suggession?
Besides, I'm considering your suggession as well, Astanley.
Thanks & Regards
Try something such as:Be sure to initialize m_bTimerGoing to FALSE. Then in TimerProc:Code:BOOL CMyApp::OnIdle(LONG lCount) {
if (m_bTimerGoing && lCount == 0) {
m_bTimerGoing = FALSE;
KillTimer(NULL, m_IDTimer);
}
if (CWinApp::OnIdle(lCount)) // MFC have more work?
return 1; // Give MFC a chance to finish first
if (!m_bTimerGoing) {
m_bTimerGoing = TRUE;
m_IDTimer = SetTimer(NULL, NULL, m_lSecProjectTimeOut*1000,
(TIMERPROC)TimerProc);
}
return 1;
}
I am not sure if always return non-zero from OnIdle makes much difference, but it might.Code:m_bTimerGoing = FALSE;
KillTimer(NULL, m_IDTimer);
LogOff();
Yes, it does make a difference. If OnIdle returns non-zero and there is no message in the thread message queue, the framework will continue invoke OnIdle while incrementing the lCount every loop. In this case, although the application is supposed to be in idle state, it is still doing background processing and using up cpu time. This is what mfc_oracle encountered in his first approach.Quote:
Originally posted by Sam Hobbs
I am not sure if always return non-zero from OnIdle makes much difference, but it might.
What background processing?Quote:
Originally posted by Wombat
although the application is supposed to be in idle state, it is still doing background processing
Yes, by returning a non-zero value, the processor remains busy; but I think it is just because the idle processing is continually being executed. Therefore it probably has no impact on the system. However it probably does not solve the problem either, since there are too many miscelaneous messages that will reset the idle count to zero even when there has not been any activity we care about. Probably the only way to solve the problem is to use something that determines inactivity more directly; that is, by detecting keyboard and mouse messages.
An important design criteria is whether it is application inactivity or system inactivity that determines whether to logoff the application.
Thanks all for your useful comments!
Your code was perfect, Wombat but I have to log out user at the specified time. So, I tried Astanely's code and it works as I'd like.
All the best!
Kindly Regards,
mfc_oracle
I agreed. And this is what astanley has suggested, using a hook procedure to monitor keyboard and mouse events.Quote:
Originally posted by Sam Hobbs
Probably the only way to solve the problem is to use something that determines inactivity more directly; that is, by detecting keyboard and mouse messages.
Agreed. The hook procedure can be associated with all existing threads or just the identified thread.Quote:
Originally posted by Sam Hobbs
An important design criteria is whether it is application inactivity or system inactivity that determines whether to logoff the application.
It is probably not necessary to use a hook if it is just the applicaton's idleness that determines when to logoff. Also, I am not sure whether there are any Windows functions that can help to determine if the system has been idle for a specified legth of time, but if I knew that such a thing could help then if it were me trying to solve this problem I would sure try to find anything that can do that. I think I have seen something that is new for Windows 2000 that might help. If it is just the application that will determine idle time and if a hook is used, then an application-specific hook would certainly be less complicated; no DLL is needed; the hook can exist in the exe.
Time out works fine in debug mode but it doesn't work at all in release mode!:(
Could you plzzzz help me?
There is no reason why the time out code should not work in a release build, so a bug must have been introduced some where. You need to debug the release build to find this bug.
Project->Settings->Win32 Release, C/C++ tab, General Category, Debug info - set to Program Database. Link tab, Debug Category, tick Debug info, set Both formats. Rebuild All. You will now be able to debug the release build (set breakpoints, watch variables, etc).
Because of code optimisation in the release build, you may see some funny things whilst debugging (e.g., code lines where you can't set a breakpoint, variables with strange values), but it should be good enough so you can find the bug.
Take a look at the following FAQ...Quote:
Originally posted by mfc_oracle
Time out works fine in debug mode but it doesn't work at all in release mode!:(
Actually, I've turned off optimization and teh problem still exists. I've debugged it in release mode as well; all variables have proper values!
However, by debugging the release build, you should be able to see where the problem is. What exactly is going on in the debug build which does not happen in the release build?Quote:
Originally posted by mfc_oracle
I've debugged it in release mode as well; all variables have proper values!
Time out works when debugging in release mode :)
I don't really know what to do :(
Does time out work if you run the same release exe outside Visual Studio?
No it doesn't!Quote:
Originally posted by astanley
Does time out work if you run the same release exe outside Visual Studio?
It is still unclear what doesn't work. Is OnIdle() not called? Is the timer proc not being called?
Besides that, what exactly do you mean by "release mode" and "debug mode"? These modes don't exist. There are debug builds and release builds, and you can run an app directly or in the debugger. But "debug mode" is an unclear term - does it mean a debug build, or running the app in the debugger?
Bear in mind gstercken comments.
I would add this. Run the release build exe (which should contain the debug info as per our instructions yesterday) outside of Visual Studio.
Once it is running, go to Windows Task Manager, Processes tab, right click the name of the executable, and select Debug. This will bring up the JIT debugger, which will enable you to debug the process. As you have built it with debug symbols, you should have access to source and run time info.
You can set Visual Studio to be the JIT debugger by setting the registry entry: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger to
"C:\Program Files\Microsoft Visual Studio\Common\MSDev98\Bin\msdev.exe" -p %ld -e %ld
The way you are going about this is wrong - in order to do what you are trying to do, you need to use a Keyboard and Mouse hook function to detect when the appropriate idle time has passed.
If you are still stuck, let me know, I'll e-mail you code that works already.
I have modified my code so that when the time out expires it closes any dialog then closes the whole application:
if (!bInTimer && nIDEvent == m_nHookTimer)
{
bInTimer = TRUE;
if (theApp.m_nTimeOutCount == theApp.m_lSecProjectTimeOut)
{
if (++theApp.m_nTimeOutMinsElapsed >= theApp.m_nTimeOutMins)
{
//not currently processing previous timer message
CWnd* pActiveWnd = GetActiveWindow ();
if( pActiveWnd != NULL && pActiveWnd ->m_hWnd != NULL
&& pActiveWnd != this )
{
CDialog* pDlg = (CDialog*)pActiveWnd;
pDlg ->SendMessage(WM_CLOSE);
}
}
theApp.m_lSecProjectTimeOut = 1;
}//if(GetActiveWindow () != this )
else
{
theApp.OnLogoff();
}
But the problem is that when another page like word or MSoutlook is opened and the time out expires the application is closed while the dialog (its child window) is open and if the user clicks on it, it throws an exception. That's why in this situation the active window is the word or ... that is opened not that dialog.
what would you suggest to solve this problem?
Thanks & regards,