在调用Windows API函数SetCursor设置光标时,可能会碰到闪烁的问题:移动鼠标,光标在Class Cursor(即注册窗口类时指定的Cursor)与预设Cursor之间闪烁。
在MSDN上有关SetCursor函数的备注中强调,如果Class Cursor非空,那么每当鼠标移动,系统都会把光标恢复为Class Cursor。为了避免光标闪烁这一问题,必须处理WM_SETCURSOR消息。(MSDN说明)
下面是一个例子:程序在主窗口视图的中间位置绘制RGB条带,当鼠标移动在条带范围就将光标设置成为Cross,此外根据光标的位置,在RGB条带上方30px处显示所处条带的颜色。程序运行起来像这样:
如果在WM_MOUSEMOVE的消息处理中判断光标的位置并设置光标的话,就会碰到所说的光标闪烁问题。WM_MOUSEMOVE的消息处理如下代码所示:
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的消息处理中,当然获得光标客户坐标的方式不同,代码如下所示:
LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT point;
::GetCursorPos(&point);
ScreenToClient(&point);
set_cursor(point);
return 0;
}
而代码中的set_cursor私有方法其实就是上面的判断逻辑,即:
// 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));
}
这样就解决了光标闪烁的问题。本例的工程文件可在这里下载,它是基于WTL的SDI程序。
P.S. 另外一个问题虽然简单但仍值得一提。上面程序的截图显示的结果实际上并不准确,Cross光标的中心已经处于Green条带,但条带上方显示的却是"Cursor on Red part"。造成此问题的原因是自定义光标的热点Hot Spot默认值为(0, 0),而不是以中心+来标识的。解决办法是在VS资源编辑器中使用Hot Spot Tool,如下图所示:
点击Cross光标的中心点,热点坐标将变成你所设置的值,如下图所示: