正确地在WM_SETCURSOR处理程序中重置游标

时间:2013-10-08 20:03:24

标签: c++ winapi

引言及相关资料:

我已经创建了一个应用程序,当鼠标悬停在静态控件上方时,需要将光标的外观更改为手,否则会将其重置为普通光标。

我的初始应用程序处于全屏模式,但最近条款已更改,并且必须具有可调整大小的窗口。

这意味着我必须重写WM_SETCURSOR的处理程序以反映新引入的更改。

游标加载在WM_CREATE中,我已经定义了类游标,如下所示:

   // cursors 
   case WM_CREATE:
      hCursorHand = LoadCursor( NULL, IDC_HAND );
      hCursorArrow = LoadCursor( NULL, IDC_ARROW );
      // other stuff

在我班上:

   WNDCLASSEX wc;
   // ...
   wc.hCursor = hCursorArrow;
   //...

这是我的旧WM_CURSOR处理程序(为简化起见,代码已经过简化):

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
             SetCursor(hCursorHand);
        else
             SetCursor(hCursorArrow);
        return TRUE;

如果光标悬停在静态控件之上,那么我的处理程序将其更改为手动,否则将其设置为默认光标(箭头)。

Bellow是我在Paint中绘制的图片,它显示光标悬停在静态控件上方时所需的外观,它位于客户端区域,当用户调整窗口大小时。

enter image description here

如果需要额外的代码段,请询问,我将编辑我的帖子,但是现在,它们被省略以使帖子简洁明了。

我在Windows XP上工作,使用MS Visual Studio C ++和纯Win32 API。

我解决问题的努力:

Bellow是我尝试过的代码片段,但它们都失败了:

第一个片段:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        else
             return DefWindowProc( hWnd, msg, lParam, wParam );

第二个片段:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        break; // based on MSDN example

第三个片段:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        else
             return FALSE;

无论它在哪里,都可以设置光标。

如果我将WM_SETCURSOR处理程序保持不变,我唯一的问题是,当我将鼠标悬停在边框上时,我会获得常规箭头(如光标的外观),而不是调整箭头大小,但窗口可以调整大小。

如果我注释掉我的WM_SETCURSOR处理程序,则调整箭头和光标箭头会正确显示,但是当鼠标悬停在静态控件之上时,光标不会变为手(这是合乎逻辑的,因为没有WM_SETCURSOR处理程序来改变它。)

我浏览了SO档案,查看了MSDN,CodeProject,DaniWeb,Cprogramming和CodeGuru,但没有成功。

通过这些,我找到了一个例子,人们将lParam的低字与命中测试代码进行比较。

通过MSDN查找我找到了命中测试值的链接(http://msdn.microsoft.com/en-us/library/windows/desktop/ms645618%28v=vs.85%29.aspx),我找到了游标类型的链接(http://msdn.microsoft.com/en-us/library/windows/desktop/ms648391%28v=vs.85%29.aspx)。

目前我正在阅读它们,因为我认为我必须加载额外的游标资源,对命中测试值进行几次比较,然后使用这些资源来相应地设置游标外观。

问题:

我真的希望我的WM_SETCURSOR处理程序看起来像这样:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE;
        }
        else
             // reset cursor's look to default

所以我请社区指导我如何做到这一点。

如果这不可行,那么我将考虑使用多个if语句来检查命中测试代码,并相应地设置光标外观。

当然,如果我的问题有更好的解决方案,请提出建议,我也会考虑。

谢谢。

问候。

3 个答案:

答案 0 :(得分:6)

通常,如果您处理WM_SETCURSOR消息,则必须

  • 调用SetCursor()设置光标,然后返回TRUE
  • 如果消息来自子窗口,请返回FALSE进行默认处理,或
  • 如果邮件来自您自己的窗口,请将邮件传递到DefWindowProc()

我认为MSDN文档中没有明确说明最后两点。

鼠标指针下的窗口获取第一条WM_SETCURSOR消息。如果它处理它并在那时返回,则不会发生任何其他事情。但是,如果它调用DefWindowProc(),则DWP会将消息转发给窗口的父级以进行处理。如果父选择不处理它,它可以返回FALSE并且DefWindowProc处理将继续。

但这仅适用于此消息来自之前对DWP的调用。如果消息来自窗口本身而不是子节点,则返回TRUEFALSE而不设置光标意味着光标将根本不设置。

另一件事:虽然您的问题未指定,但我假设您使用GetDlgItem()表示您的顶级窗口是对话框。如果这是真的,那么您不能只为消息返回TRUEFALSE - 您需要使用SetWindowLongPtr()返回值并将返回值存储在DWLP_MSGRESULT中。从对话框过程返回FALSE表示您根本没有处理该消息 - 这相当于将消息传递给DefWindowProc()

因此,我认为在您的顶级窗口中正确处理您的情况:

case WM_SETCURSOR:
    if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
    {
        SetCursor(hCursorHand); 
        SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE);
        return TRUE;
    }
    return FALSE;

如果您的顶级窗口实际上不是对话框,您可以这样做:

case WM_SETCURSOR:
    if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
    {
        SetCursor(hCursorHand); 
        return TRUE;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);

答案 1 :(得分:1)

这是我的第一个示例,如果光标转到菜单栏,则光标变为光标指针:

HCURSOR cursorHand = LoadCursor(NULL, IDC_HAND);
case WM_SETCURSOR:
    if(LOWORD(lParam) == HTMENU)
    {
          SetCursor(cursorHand);
    }
 break;

这是我的第二个示例,如果光标移至按钮,则光标更改为cursorHand:

HCURSOR cursorHand =  LoadCursor(NULL, IDC_HAND);

case WM_SETCURSOR:
      if(LOWORD(lParam) == buttonId)//declare you
      {
            SetCursor(cursorHand);
       }
   break;

警告:菜单和按钮未创建!创建您,请检查我的答案示例。

答案 2 :(得分:0)

我理解的问题是,给定一个父窗口还有一个以上的子窗口(其中一个是静态控件),当光标在静态控件上时,如何将光标设置到手上,以及当光标位于父窗口的客户区域上时箭头,当光标位于父窗口的非客户区域时,进行默认处理。

为了检查一下,我写了一个简单的程序,顶层窗口带有一个静态控件作为子窗口。我的第一次尝试如下:

1)将顶级窗口的类光标设置为LoadCursor(NULL,IDC_ARROW)。这允许默认处理在适当时将光标设置为箭头。

2)通过调用我的HandleWMMouseMove函数处理WM_MOUSEMOVE消息来跟踪鼠标光标的位置,如下所示:


case WM_MOUSEMOVE:
    HandleWMMouseMove(lParam);
    break;

//ptCurrMousePos is a global variable of type POINT
static void     HandleWMMouseMove(LPARAM mousepos)
{
    ptCurrMousePos.x = (int)(short)(LOWORD(mousepos));
    ptCurrMousePos.y = (int)(short)(HIWORD(mousepos));
}

3)然后我所要做的就是通过调用我的HandleWMSetCursor函数来处理WM_SETCURSOR消息,如下所示:


    case WM_SETCURSOR:
        if (HandleWMSetCursor())
            return TRUE;
        break;

//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL     HandleWMSetCursor(void)
{
    if (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic) {
        SetCursor(hCursorHand);
        return TRUE;
    }

    return FALSE;
}

这很好但我无法理解问题中发布的代码是如何工作的甚至是部分的。所以我问提问者子窗口是否真的是一个静态控件。答案是肯定的,但它是使用SS_NOTIFY样式创建的。所以我用这种风格创建了我的子窗口,我的代码停止工作,但随问题发布的代码开始起作用。在Visual Studio分发的Spy ++程序的帮助下,我学到了以下内容。

静态控件通常是透明的(不是在视觉上,但意味着即使鼠标悬停在透明窗口上,Windows也会将鼠标视为透明窗口下方的窗口)。

使用SS_NOTIFY创建静态控件时,控件不再透明(即它开始接收进程鼠标消息(如WM_MOUSEMOVE)),因此我的代码停止工作,因为当光标位于静态时,它从未收到WM_MOUSE消息另一方面,当没有SS_NOTIFY创建静态控件时,问题中的代码不起作用,因为没有这种样式,静态控件是透明的,这意味着WM_SETCURSOR消息中的WARAM永远不等于静态控件(在换句话说,Windows从不认为鼠标是静态控件,因为它是透明的。

可以通过将WM_SETCURSOR处理函数更改为以下内容来组合这两种方法:


//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL     HandleWMSetCursor(WPARAM wParam)
{
    if (((HWND)wParam == hwndStatic) || (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic)) {
        SetCursor(hCursorHand);
        return TRUE;
    }

    return FALSE;
}