VC++ 解决在鼠标移动时,光标闪烁的问题。其实本质是 ON_SETCURSOR的用法

来源:转载

在调用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? 
  1. LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)  
  2. {  
  3.     POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };  
  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.     return 0;  
  25. }  
 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? 
  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)); }

这样就解决了光标闪烁的问题。



分享给朋友:
您可能感兴趣的文章:
随机阅读: