SonicUI中有一個內部定時器的概念(InternalTimer),SonicUI中的動畫效果都是使用的這個定時器。這個定時器實現的思路是很清晰的:WM_TIMER消息加定時輪詢。
首先使用SonicUI的工程都有一個全局的CSonicUI類的實例。在這個類中有一個靜態的成員變量HWND m_hWnd,它指向的是一個 "SonicWnd"的窗口類的窗口實例。此窗口類在CSoinicUI::Init中定義如下:
1:
2: #define MY_WND _T("SonicWnd")
3: BOOL CSonicUI::Init()
4:
5: {
6: WNDCLASSEX wcex;
7: wcex.cbSize = sizeof(WNDCLASSEX);
8: wcex.style = CS_HREDRAW | CS_VREDRAW;
9: wcex.lpfnWndProc = (WNDPROC)CSonicUI::InternalWndProc;
10: wcex.cbClsExtra = 0;
11: wcex.cbWndExtra = 0;
12: wcex.hInstance = NULL;
13: wcex.hIcon = NULL;
14: wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
15: wcex.hbrBackground = NULL;
16: wcex.lpszMenuName = NULL;
17: wcex.lpszClassName = MY_WND;
18: wcex.hIconSm = NULL;
19: if(!RegisterClassEx(&wcex))
20: {
21: return FALSE;
22: }
23: HMODULE hMod = GetModuleHandle(_T("User32.dll"));
24: if(hMod == NULL)
25: {
26: return FALSE;
27: }
28: m_pOldBeginPaint = ReplaceFuncAndCopy(GetProcAddress(hMod, "BeginPaint"), MyBeginPaint);
29: m_pOldEndPaint = ReplaceFuncAndCopy(GetProcAddress(hMod, "EndPaint"), MyEndPaint);
30: if(m_pOldEndPaint == NULL || m_pOldEndPaint == NULL)
31: {
32: return FALSE;
33: }
34: return TRUE;
35: }
這個窗口始終是不可見的,但SonicUI通過他來實現內部消息的轉發,定時器消息就是在 這個窗口類的消息處理函數(InternalWndProc)中處理的。m_hWnd成員變量是在CSonicUI::GetSonicUI ()中被賦值的。代碼如下:
1: ISonicUI * GetSonicUI()
2: {
3: BOOL bRet = FALSE;
4: if(CSonicUI::m_hWnd == NULL)
5: {
6: // Initialization
7: __try
8: {
9: // 省略
10: }
11: __finally
12: {
13: if(bRet)
14: {
15: //創建了全局唯一的不可見窗口,用于轉發內部消息
16: CSonicUI::m_hWnd = CreateWindow(MY_WND, NULL, WS_POPUP, 0, 0, 1, 1, NULL, NULL, NULL, NULL);
17: g_UI.CreateTip();
18:
19: //開啟了一個 10ms定時器,相當于每隔10ms就輪詢下當前是否有定時器到期
20: SetTimer(CSonicUI::m_hWnd, TIMER_BASE_DATA, GIF_INTERVAL, NULL);
21: }
22: }
23: }
24: else
25: {
26: bRet = TRUE;
27: }
28: if(!bRet)
29: {
30: return NULL;
31: }
32: return &g_UI;
33: }
可見,當SonicUI第一次初始化后就開始了一個10ms間隔的定時器。那這個定時器如何使用呢?看一下設置、刪除定時器的代碼。在 ISonicBaseData 類中。代碼如下:
1: typedef list<ISonicBaseData *> LIST_BASE_DATA;
2: class ISonicBaseData
3: {
4: public:
5: //刪除了和定時器不相關的代碼
6:
7: typedef map<int, DWORD> INTERVAL_TO_TIMER; //定時器間隔和定時器id的map,注意一個定期是間隔可能關聯著多個定時器ID
8:
9: void OnInternalTimer();//全局的窗口的的輪詢周期(10ms)到達如果當前類中設置了定時器,此方法會被調用
10: virtual void SetInternalTimer(DWORD dwTimerId, int nInterval, BOOL bOnceTimer = FALSE);//設置定時器,注意 dwTimerId 的定義
11: virtual void KillInternalTimer(DWORD dwTimerId);
12: virtual BOOL QueryInternalTimer(DWORD dwTimerId);//查看當前是否設置了dwTimerId的定時器
13: virtual void ClearInternalTimer();//刪除所有的定時器
14: virtual void OnInternalTimer(DWORD dwTimerId); //dwTimerId 對應的定時周期到達
15:
16: DWORD m_dwTimer; // 從第一次設置定時器起到現在止一共有多少毫秒
17: DWORD m_dwTimerOnce; //保存所有一次性定時器的定時器ID
18: DWORD m_dwTimerId; // 保存當前的所有定時器ID,使用 位掩碼
19: INTERVAL_TO_TIMER m_mapIntervalToTimer;
20: HWND m_hWnd;
21: static LIST_BASE_DATA m_TimerList; //靜態成員,類似全局變量的作用,保存了當前所有設置了定時器的 ISonicBaseData 的實例
22: };
由于 ISonicBaseData 是SonicUI中所有控件的基類,這意味這SonicUI所有的控件都支持內部定時器。目前我們看到了兩個全局變量(類似于全局變量):可以每10ms產生一個wm_timer消息的 sonicui對象和記錄的所有申請了定時器的控件對象(ISonicBaseData 的子類)。SonicUI的定時器機制是這樣的:每一個輪詢周期(10ms)到達,sonicui對象 就問每一個ISonicBaseData 對象,“又過去10ms了,你有沒有定時器到期啊?”,ISonicBaseData 就看自己內部申請的定時器中有沒有到期的,如果有的話,他就執行這個定時器(調用OnInternalTimer(DWORD dwTimerId))。
接下來要了解的有兩個問題:
- ISonicBaseData怎么維護的定時器ID的?SonicUI的內部定時器的ID定義如下:
1:
2: #define TIMER_SHOWING_GENTLY 0x1
3: #define TIMER_MOVE_GENTLY 0x2
4: #define TIMER_FRAME 0x8
5: #define TIMER_TRANSFORM 0x10
6: #define TIMER_SLIDE 0x20
7: #define TIMER_TRACK_MOUSE 0x40
8: #define TIMER_SHUTTER 0x80
不難看出這是win32api中常用的“按位設置值”(這個不知道怎么說,掩碼?)的定義方法。可以使用一個DWORD來表示多個定時器的ID,對定時器ID的增刪查就可以用下面的語句完成:
1: //定義了一個定時器
2: DWORD dwTimerID = TIMER_SHOWING_GENTLY;
3: //增加一個定時器
4: dwTimerID |= TIMER_MOVE_GENTLY;
5: //刪除一個定時器
6: dwTimerID &= ~TIMER_MOVE_GENTLY;
7: //查詢一個定時器是否存在
8: BOOL bExist = dwTimerID&TIMER_MOVE_GENTLY;
這個問題的答案就是 ISonicBaseData的m_dwTimer變量。它記錄了從上次 增加定時器到現在的總時長(單位毫秒)。每當調用SetInternalTimer是就把他清零(這個會影響當前已經設置了的定時器),當全局的輪詢周期到達就把m_dwTimer加10毫秒,然后看m_dwTimer是內部哪個定時器的周期的整倍數,是就代表這個定時器到期了,否則就是沒到期,等下一個輪詢周期的到來。因此m_dwTimer的取值總是10的倍數,而內部定時器的周期也必須是10的倍數。SonicUI中幾種內部定時器的周期定義如下:
1: // internal timer defined must be times of base interval
2: #define BASE_INTERVAL 10
3: #define ANIMATION_INTERVAL 20
4: #define GIF_INTERVAL 10
5: #define FADEOUT_INTERVAL 50
可見,SonicUI的定時器精度并不高,但相比較 CreateTimerQueueTimer 避免了多線程。