Duilib 中的 Timer 是封装了 Windows 的 Timer, 将事件绑定到了具体的控件上。
相关逻辑实现代码如下:
内部 Timer 的数据结构
typedef struct tagTIMERINFO { CControlUI* pSender; UINT nLocalID; HWND hWnd; UINT uWinTimer; bool bKilled; } TIMERINFO;
pSender
-> 当前 Timer 绑定的控件,当 Timer 的间隔时间到了之后会发送UIEVENT_TIMER
事件给当前控件
nLocalID
-> 当前 Timer 的逻辑 id,并不是传入到::Setimer
中的 id,而是调用bool CPaintManagerUI::SetTimer(CControlUI* pControl, UINT nTimerID, UINT uElapse)
时的第 2 个参数
hWnd
-> 当前 Timer 对应的窗口句柄,是实际传入到::SetTimer
中的参数
uWinTimer
-> 实际的 Timer 的 Id
bKilled
-> 当前 Timer 是否已无效,调用bool KillTimer(CControlUI* pControl, UINT nTimerID)
后会将对应的 Time 杀掉SetTimer 的实现
bool CPaintManagerUI::SetTimer(CControlUI* pControl, UINT nTimerID, UINT uElapse) { ASSERT(pControl!=NULL); ASSERT(uElapse>0); for( int i = 0; i< m_aTimers.GetSize(); i++ ) { TIMERINFO* pTimer = static_cast<TIMERINFO*>(m_aTimers[i]); if( pTimer->pSender == pControl && pTimer->hWnd == m_hWndPaint && pTimer->nLocalID == nTimerID ) { if( pTimer->bKilled == true ) { if( ::SetTimer(m_hWndPaint, pTimer->uWinTimer, uElapse, NULL) ) { pTimer->bKilled = false; return true; } return false; } return false; } } m_uTimerID = (++m_uTimerID) % 0xFF; if( !::SetTimer(m_hWndPaint, m_uTimerID, uElapse, NULL) ) return FALSE; TIMERINFO* pTimer = new TIMERINFO; if( pTimer == NULL ) return FALSE; pTimer->hWnd = m_hWndPaint; pTimer->pSender = pControl; pTimer->nLocalID = nTimerID; pTimer->uWinTimer = m_uTimerID; pTimer->bKilled = false; return m_aTimers.Add(pTimer); }
CPainterManagerUI 中的
CDuiPtrArray m_aTimers
保存了所有的TIMERINFO
对象, 每调用一次SetTimer
就会从既存m_aTimers
中查找相同的TIMERINFO
, 若找到且已被杀掉,则重启开启此 Timer,否则忽略当前SetTimer
的调用。 这一点和 Windows 的默认::SetTimer
有区别, Windows 的::SetTimer
会覆盖原始 Timer 且重新计时KillTimer的实现
bool CPaintManagerUI::KillTimer(CControlUI* pControl, UINT nTimerID) { ASSERT(pControl!=NULL); for( int i = 0; i< m_aTimers.GetSize(); i++ ) { TIMERINFO* pTimer = static_cast<TIMERINFO*>(m_aTimers[i]); if( pTimer->pSender == pControl && pTimer->hWnd == m_hWndPaint && pTimer->nLocalID == nTimerID ) { if( pTimer->bKilled == false ) { if( ::IsWindow(m_hWndPaint) ) ::KillTimer(pTimer->hWnd, pTimer->uWinTimer); pTimer->bKilled = true; return true; } } } return false; }
调用了
KillTimer
之后对应的TIMERINFO
的bKilled
设置为true
, 且调用了::KillTimer
杀掉了实际的 Timer,但TIMERINFO
对象未析构,在调用SetTimer
时有可能会重新激活此对象,并将bKilled
设置为false
CPaintManagerUI 中的 WM_TIMER 处理:
case WM_TIMER: { for (int i = 0; i < m_aTimers.GetSize(); i++) { const TIMERINFO* pTimer = static_cast<TIMERINFO*>(m_aTimers[i]); if (pTimer->hWnd == m_hWndPaint && pTimer->uWinTimer == wParam && pTimer->bKilled == false) { TEventUI event = { 0 }; event.Type = UIEVENT_TIMER; event.pSender = pTimer->pSender; event.wParam = pTimer->nLocalID; event.dwTimestamp = ::GetTickCount(); pTimer->pSender->Event(event); break; } } }
根据
wParam
中的 TimerID 查找对应的TIMERINFO
对象,然后向绑定的控件发送UIEVENT_TIMER
事件进行处理。 像CGifAnimUI
CScrollBarUI
等会自定义对UIEVENT_TIMER
事件的响应,否则则会将事件默认发送给监听者