Передача данных пользователя с помощью SetTimer
Я вызываю SetTimer в функции класса.
SetTimer(NULL, 0, 10000, (TIMERPROC) TimerCallBack);
Где TimerCallBack:
static VOID CALLBACK TimerCallBack(HWND, UINT, UINT, DWORD)
Теперь мне нужно вызвать один из методов класса, который инициировал таймер, поскольку TimerCallBack статичен, он больше не имеет доступа к объекту класса.
Я не могу найти способ передать указатель объекта вместе с SetTimer, чтобы я мог вернуть его обратно на функцию обратного вызова.
Есть ли другой способ достичь этого, если он не поддерживается с помощью SetTimer, и каким другим способом я могу это реализовать.
Ответы
Ответ 1
Очевидно, что если вы направляете сообщения таймера в окне, вы можете просто сохранить пользовательские данные в окне.
Единственный способ сделать это с помощью TimerProc - создать класс, который управляет статично-ориентированной картой идентификатора таймера для пользовательских объектов данных.
Что-то вроде этого (так как это вопрос на С++, я просто реализую быструю и грязную работу типа функтора, чтобы TimerMgr мог организовывать обратные вызовы непосредственно членам классов, что обычно является причиной того, что вы пытаетесь сохранить пользовательские данные:
// Timer.h
#include <map>
class CTimer {
public:
class Callback {
public:
virtual void operator()(DWORD dwTime)=0;
};
template<class T>
class ClassCallback : public Callback {
T* _classPtr;
typedef void(T::*fnTimer)(DWORD dwTime);
fnTimer _timerProc;
public:
ClassCallback(T* classPtr,fnTimer timerProc):_classPtr(classPtr),_timerProc(timerProc){}
virtual void operator()(DWORD dwTime){
(_classPtr->*_timerProc)(dwTime);
}
};
static void AddTimer(Callback* timerObj, DWORD interval){
UINT_PTR id = SetTimer(NULL,0,interval,TimerProc);
// add the timer to the map using the id as the key
_timers[id] = timerObj;
}
static void CALLBACK TimerProc(HWND hwnd,UINT msg,UINT_PTR timerId,DWORD dwTime){
_timers[timerId]->operator()(dwTime);
}
private:
static std::map<UINT_PTR, Callback*> _timers;
};
// In Timer.cpp
#include <windows.h>
#include <Timer.h>
std::map<UINT_PTR,CTimer::Callback*> CTimer::_timers;
// In SomeOtherClass.cpp
class CSomeClass {
void OnTimer1(DWORD dwTime){
}
public:
void DoTimerStuff(){
CTimer::AddTimer( new CTimer::ClassCallback<CSomeClass>(this,&CSomeClass::OnTimer1), 100);
}
};
Удаление таймеров с карты остается в качестве упражнения для читателя:)
- ClassCallback, необходимый для наследования Callback.
- должны быть определены статические переменные
- Теперь код теперь корректно строит и работает на MSVC 9.0
Ответ 2
Вам не нужна карта. Используйте параметр idEvent. Microsoft дала ему тип UINT_PTR, чтобы он соответствовал указателю, даже в 64 бит:
SetTimer(hwnd, (UINT_PTR)bar, 1000, MyTimerProc);
void CALLBACK MyTimerProc(HWND, UINT, UINT_PTR idEvent, DWORD)
{
Bar* bar = (Bar*)idEvent;
}
Обратите внимание, что вам нужно использовать фактический HWND. Если HWND имеет значение null, Windows будет генерировать idEvent внутри.
Ответ 3
Учитывая, что вы не используете MFC (CWnd::OnTimer
означает, что у вас будет доступ к классу), и у вас нет HWND
(вы могли бы установить свойство HWND на создание таймера и вернуть его в свой proc), есть еще один вариант.
SetTimer
возвращает UINT_PTR
, который является идентификатором таймера. Это то, что вы получите в своем TimerProc
, а также перейдете к KillTimer
, когда закончите с ним. Используя это, мы можем создать карту, которая отображает идентификатор таймера на определенный пользователем объект вашего творения:
class MyAppData
{
}; // eo class MyAppData
typedef std::map<UINT_PTR, MyAppData*> MyDataMap;
MyDataMap dataMap;
Затем мы создаем ваш таймер:
MyAppData* _data = new MyAppData(); // application-specific data
dataMap[SetTimer(NULL, 0, 10000, (TIMERPROC)TimerCallBack)] = _data;
И в вашей процедуре:
static void CALLBACK TimerCallBack(HWND _hWnd, UINT _msg. UINT_PTR _idTimer, DWORD _dwTime)
{
MyAppData* data = dataMap[_idTimer];
} // eo TimerCallBack
Ответ 4
Существует еще одно решение, но для использования этого параметра - HWND
в SetTimer
должно быть non-NULL
.
Можно хранить дополнительные данные в самом окне, используя функцию API SetWindowLongPtr
с параметром GWLP_USERDATA
.
Итак, вы можете добавить в свой класс следующую функцию:
void SetLocalTimer(UINT_PTR nIDEvent, UINT nElapse)
{
SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
SetTimer(nIDEvent, nElapse, _TimerRouter);
}
и функция таймера-маршрутизатора, которая определяет, что делать с заданным таймером.
static void CALLBACK _TimerRouter(HWND hwnd, UINT, UINT_PTR nEventID, DWORD)
{
YourClassName* inst = (YourClassName*)GetWindowLong(hwnd, GWLP_USERDATA);
if( !inst )
return;
switch (nEventID)
{
case 0:
inst->DoThis();
break;
case 1:
inst->DoThat();
break;
}
}
Это позволяет также использовать nEventID в качестве имени функции, который будет вызываться.
По-прежнему существует риск того, что данные пользователя будут использоваться из более чем одного места, но я считаю хорошей практикой держать окно UI, соответствующее ему, моделирует этот указатель.