【问题标题】:Reset cursor in WM_SETCURSOR handler properly正确重置 WM_SETCURSOR 处理程序中的光标
【发布时间】:2013-10-15 22:31:57
【问题描述】:

简介及相关资料:

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

我的初始应用程序处于全屏模式,但最近条款发生了变化,它必须有一个可调整大小的窗口。

这意味着我的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 中绘制的图片,当光标悬停在静态控件上方、位于客户区域以及用户调整窗口大小时,它会显示所需的光标外观。

如果需要额外的代码 sn-ps,请询问,我将编辑我的帖子,但现在,为了保持帖子简短而省略它们。

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

我解决问题的努力:

下面是我尝试过的代码sn-ps,但都失败了:

第一个sn-p:

   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

第三个sn-p:

   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 语句来检查命中测试代码,并相应地设置光标的外观。

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

谢谢。

问候。

【问题讨论】:

  • 静态控件是否覆盖了窗口的整个客户区?
  • @Stuart,Mr.Stuart 静态控件没有覆盖整个窗口区域,只覆盖了一部分。问候。
  • 我从您的 cmets 中看到您已经解决了您的问题,但我对某些事情感到好奇。我编写了一个小测试程序,在其中创建了一个带有静态控件的框架窗口作为子窗口,但在我的情况下,当 WM_SETCURSOR 发送到框架窗口时,WPARAM 始终是框架窗口的句柄,而不是静态控件。所以当然在这种情况下,您使用的代码将永远无法工作。 (HWND)wparam 永远不会等于 GetDlgItem(hwnd, 4000)。所以我很好奇,你是从“STATIC”窗口类创建静态类的吗?
  • @Stuart, Mr.Stuart 这里是创建静态控件的代码:HWND SomeStatic = CreateWindowEx( 0, L"Static", L"", WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_OWNERDRAW, 20, 50, 150, 100, hwnd, (HMENU)4000, hInst, 0);。请注意,我必须拥有绘制控件并将通知发送到我的主窗口。也许添加样式SS_NOTIFY 可以解决您的问题。祝你好运,如果你需要什么,再问一次。问候。
  • @Stuart,Strange... 我做了一个演示项目,但我从这里粘贴的相同代码遇到了问题...

标签: c++ winapi


【解决方案1】:

一般来说,如果您处理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);

【讨论】:

  • Mr.Potter 感谢您的快速回复。我的窗口不是对话框,它是通过CreateWindowEx 制作的。我将尝试您的解决方案并报告结果。谢谢。问候。
  • @AlwaysLearningNewStuff:我编辑了“不是对话框”的代码,因为我在粘贴时无意中留下了 SetWindowLongPtr() 调用 - 确保你不要使用它,因为它会有非对话的未定义行为
  • 波特先生非常感谢您!现在一切正常。 +1 来自我。问候。
  • Mr.Potter,看来我的第一个 sn-p 是正确的。问题似乎在于为窗口类定义光标。我使用尚未初始化的光标句柄,因为光标已加载到WM_CREATE。以标准方式(wc.hCursor = LoadCursor( NULL, IDC_ARROW ); 而不是wc.hCursor = hCursorArrow;)加载光标后,一切正常。作为更好、更有经验的开发人员,您能验证我的结论吗?谢谢你。问候。
  • 是的,您是对的,您在注册窗口类时不能使用在WM_CREATE 中加载的光标,因为此时尚未创建窗口!实际上,如果只是像 IDC_ARROWIDC_HAND 这样的普通游标,那么缓存它们的值并没有真正的好处——只要你需要使用它,只需调用 LoadCursor(NULL, IDC_ARROW); 等。
【解决方案2】:

这是我的第一个例子,如果光标转到菜单栏,光标变为光标手:

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;

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

【讨论】:

    【解决方案3】:

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

    为了检查一下,我编写了一个简单的程序,其中包含一个顶级窗口和一个静态控件作为子窗口。我的第一次尝试如下:

    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;
    }
    

    【讨论】:

    • 在您的回答中,您说我的代码在添加 SS_NOTIFY 后有效,但我自己无法在我通过 Visual Studio 向导创建的新项目中使用 Jonathan 的回答产生正确的结果。有没有机会给我发电子邮件/在 PasteBin 上发帖/我的代码可以工作的任何简单项目?在我当前的项目中,一切正常,但它是空的。但是,如果我将新项目创建为空/通过向导,并尝试一下,它就不能正常工作。您的代码可能会帮助我了解我做错了什么。谢谢你。问候。
    • 完整代码太大,无法在此处发布。你想让我把它发到哪里。
    • 您能在我的个人资料中看到我的电子邮件吗?我只是对 Jonathans 解决方案感兴趣,因为它更短。你的答案对我来说很清楚,我相信它不会出错。谢谢。
    • 如果静态控件没有设置SS_NOTIFY 样式,则WM_NCHITTEST 返回HTTRANSPARENT,我认为这意味着包括WM_SETCURSOR 在内的鼠标消息将落入父窗口-它们似乎不会来自孩子。但如果设置了SS_NOTIFY,则WM_NCHITTEST 返回HTCLIENT,这意味着控件获取鼠标消息,因此当它为WM_SETCURSOR 调用DefWindowProc() 时,这将传递给父窗口。
    猜你喜欢
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多