在調用Windows API函數SetCursor設置光標時,可能會碰到閃爍的問題:移動鼠標,光標在Class Cursor(即注冊窗口類時指定的Cursor)與預設Cursor之間閃爍。
在MSDN上有關SetCursor函數的備注中強調,如果Class Cursor非空,那麼每當鼠標移動,系統都會把光標恢復為Class Cursor。為了避免光標閃爍這一問題,必須處理WM_SETCURSOR消息。(MSDN說明)
下面是一個例子:程序在主窗口視圖的中間位置繪制RGB條帶,當鼠標移動在條帶范圍就將光標設置成為Cross,此外根據光標的位置,在RGB條帶上方30px處顯示所處條帶的顏色。程序運行起來像這樣:

如果在WM_MOUSEMOVE的消息處理中判斷光標的位置並設置光標的話,就會碰到所說的光標閃爍問題。WM_MOUSEMOVE的消息處理如下代碼所示:
01.LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
02.{
03. POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
04. RECT rect, rectText;
05. get_rects(&rect, &rectText);
06. InvalidateRect(&rectText);
07. UpdateWindow();
08. if (::PtInRect(&rect, ptCursor)) {
09. ::SetCursor(m_cursor);
10. int dx = (rect.right - rect.left) / 3;
11. LPTSTR ppsz[] = { _T("Red"), _T("Green"), _T("Blue") };
12. int index;
13. if (ptCursor.x - rect.left < dx)
14. index = 0;
15. else if (ptCursor.x - rect.left < 2 * dx)
16. index = 1;
17. else index = 2;
18. WTL::CString str;
19. str.Format(_T("Cursor on %s part"), ppsz[index]);
20. CClientDC dc(m_hWnd);
21. dc.DrawText(str, -1, &rectText, DT_CENTER | DT_VCENTER);
22. }
23. else ::SetCursor(CCursor().LoadSysCursor(IDC_ARROW));
24. return 0;
25.}
閃爍產生的原因在於每次進入OnMouseMove之前,系統都會先將光標恢復成Arrow,進入OnMouseMove之後,如果光標處在RGB條帶范圍內則立即被設置成Cross。
解決辦法就是將上面的判斷邏輯放在WM_SETCURSOR的消息處理中,當然獲得光標客戶坐標的方式不同,代碼如下所示:
[cpp] view plaincopyprint?
1.LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
2.{
3. POINT point;
4. ::GetCursorPos(&point);
5. ScreenToClient(&point);
6. set_cursor(point);
7. return 0;
8.}
LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT point;
::GetCursorPos(&point);
ScreenToClient(&point);
set_cursor(point);
return 0;
}
而代碼中的set_cursor私有方法其實就是上面的判斷邏輯,即:
[cpp] view plaincopyprint?
1.// ptCursor: in client coordinate
2.void set_cursor(POINT& ptCursor) throw()
3.{
4. RECT rect, rectText;
5. get_rects(&rect, &rectText);
6. InvalidateRect(&rectText);
7. UpdateWindow();
8. if (::PtInRect(&rect, ptCursor)) {
9. ::SetCursor(m_cursor);
10. int dx = (rect.right - rect.left) / 3;
11. LPTSTR ppsz[] = { _T("Red"), _T("Green"), _T("Blue") };
12. int index;
13. if (ptCursor.x - rect.left < dx)
14. index = 0;
15. else if (ptCursor.x - rect.left < 2 * dx)
16. index = 1;
17. else index = 2;
18. WTL::CString str;
19. str.Format(_T("Cursor on %s part"), ppsz[index]);
20. CClientDC dc(m_hWnd);
21. dc.DrawText(str, -1, &rectText, DT_CENTER | DT_VCENTER);
22. }
23. else ::SetCursor(CCursor().LoadSysCursor(IDC_ARROW));
24.}
// ptCursor: in client coordinate
void set_cursor(POINT& ptCursor) throw()
{
RECT rect, rectText;
get_rects(&rect, &rectText);
InvalidateRect(&rectText);
UpdateWindow();
if (::PtInRect(&rect, ptCursor)) {
::SetCursor(m_cursor);
int dx = (rect.right - rect.left) / 3;
LPTSTR ppsz[] = { _T("Red"), _T("Green"), _T("Blue") };
int index;
if (ptCursor.x - rect.left < dx)
index = 0;
else if (ptCursor.x - rect.left < 2 * dx)
index = 1;
else index = 2;
WTL::CString str;
str.Format(_T("Cursor on %s part"), ppsz[index]);
CClientDC dc(m_hWnd);
dc.DrawText(str, -1, &rectText, DT_CENTER | DT_VCENTER);
}
else ::SetCursor(CCursor().LoadSysCursor(IDC_ARROW));
}
這樣就解決了光標閃爍的問題。
在調用Windows API函數SetCursor設置光標時,可能會碰到閃爍的問題:移動鼠標,光標在Class Cursor(即注冊窗口類時指定的Cursor)與預設Cursor之間閃爍。
在MSDN上有關SetCursor函數的備注中強調,如果Class Cursor非空,那麼每當鼠標移動,系統都會把光標恢復為Class Cursor。為了避免光標閃爍這一問題,必須處理WM_SETCURSOR消息。(MSDN說明)
下面是一個例子:程序在主窗口視圖的中間位置繪制RGB條帶,當鼠標移動在條帶范圍就將光標設置成為Cross,此外根據光標的位置,在RGB條帶上方30px處顯示所處條帶的顏色。程序運行起來像這樣:
如果在WM_MOUSEMOVE的消息處理中判斷光標的位置並設置光標的話,就會碰到所說的光標閃爍問題。WM_MOUSEMOVE的消息處理如下代碼所示:
[cpp] view plaincopyprint? LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
RECT rect, rectText;
get_rects(&rect, &rectText);
InvalidateRect(&rectText);
UpdateWindow();
if (::PtInRect(&rect, ptCursor)) {
::SetCursor(m_cursor);
int dx = (rect.right - rect.left) / 3;
LPTSTR ppsz[] = { _T("Red"), _T("Green"), _T("Blue") };
int index;
if (ptCursor.x - rect.left < dx)
index = 0;
else if (ptCursor.x - rect.left < 2 * dx)
index = 1;
else index = 2;
WTL::CString str;
str.Format(_T("Cursor on %s part"), ppsz[index]);
CClientDC dc(m_hWnd);
dc.DrawText(str, -1, &rectText, DT_CENTER | DT_VCENTER);
}
else ::SetCursor(CCursor().LoadSysCursor(IDC_ARROW));
return 0;
}
閃爍產生的原因在於每次進入OnMouseMove之前,系統都會先將光標恢復成Arrow,進入OnMouseMove之後,如果光標處在RGB條帶范圍內則立即被設置成Cross。
解決辦法就是將上面的判斷邏輯放在WM_SETCURSOR的消息處理中,當然獲得光標客戶坐標的方式不同,代碼如下所示:
[cpp] view plaincopyprint? LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT point;
::GetCursorPos(&point);
ScreenToClient(&point);
set_cursor(point);
return 0;
}
而代碼中的set_cursor私有方法其實就是上面的判斷邏輯,即:
[cpp] view plaincopyprint? // ptCursor: in client coordinate
void set_cursor(POINT& ptCursor) throw()
{
RECT rect, rectText;
get_rects(&rect, &rectText);
InvalidateRect(&rectText);
UpdateWindow();
if (::PtInRect(&rect, ptCursor)) {
::SetCursor(m_cursor);
int dx = (rect.right - rect.left) / 3;
LPTSTR ppsz[] = { _T("Red"), _T("Green"), _T("Blue") };
int index;
if (ptCursor.x - rect.left < dx)
index = 0;
else if (ptCursor.x - rect.left < 2 * dx)
index = 1;
else index = 2;
WTL::CString str;
str.Format(_T("Cursor on %s part"), ppsz[index]);
CClientDC dc(m_hWnd);
dc.DrawText(str, -1, &rectText, DT_CENTER | DT_VCENTER);
}
else ::SetCursor(CCursor().LoadSysCursor(IDC_ARROW));
}
這樣就解決了光標閃爍的問題。