windows - Windows 7中I型光标的热点不正确?

标签 windows winapi user-interface windows-7 mouse-pointer

问题

在Windows上,对于I型光标,似乎为“鼠标按下按钮”事件返回的坐标略有错误。基本上,x坐标始终在应保留的位置两个像素处。

我编写了一个非常简单的win32程序来演示该问题。它所做的只是将光标变成一个IBeam,并在最后一次鼠标按下事件所在的位置绘制一条垂直的红线。我希望红线与工字梁的垂直部分完全匹配,但事实并非如此。

Here's a screenshot of what happens

如您所见,红线位于应有的位置的左侧两个像素(此行为对于标准箭头指针而言是正确的),因此看来I光束光标的热点不正确。

我曾让其他人运行Windows 7 64位版确认他们遇到相同的问题,但是Vista上的另一台测试仪没有此问题。

有关我的环境的一些信息

  • Windows 7 64位。完全默认配置(即无DPI缩放,无怪异主题等)
  • Visual Studio速成版2010
  • 具有最新驱动程序的NVidia图形卡(v270.61)
  • 打开或关闭aero没什么区别。在显示首选项中选择不同的光标没有区别


  • 相关的代码位

    我的测试项目基本上是Visual C++ 2010中的“Win32项目”模板,下面列出了所做的更改。

    这是我注册窗口类并将光标设置为I Beam的代码
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASSEX wcex;
    
        wcex.cbSize = sizeof(WNDCLASSEX);
    
        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = 0;
        wcex.hInstance      = hInstance;
        wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CURSOR_TEST));
        wcex.hCursor        = LoadCursor(NULL, IDC_IBEAM); // this is the only line I changed in this function
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_CURSOR_TEST);
        wcex.lpszClassName  = szWindowClass;
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
        return RegisterClassEx(&wcex);
    }
    

    这是我的主消息循环中的相关部分:
    case WM_LBUTTONDOWN:
        // record position of mouse down. 
        // xPos and yPos are just declared as
        // global ints for the purpose of this test
        xPos = GET_X_LPARAM(lParam); 
        yPos = GET_Y_LPARAM(lParam);
        // cause redraw
        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
        break;      
    
    case WM_PAINT:
        // paint vertical red line at position of last click
        hdc = BeginPaint(hWnd, &ps);
        RECT rcClient;
        GetClientRect(hWnd, &rcClient);
        hPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
        SelectObject(hdc, hPen);
        MoveToEx(hdc, xPos, 0, NULL);
        LineTo(hdc, xPos, rcClient.bottom);
        DeleteObject(hPen);
        EndPaint(hWnd, &ps);
        break;
    

    摘要

    我已经完成了大量搜寻工作,但找不到任何相关的内容。我处理传入光标坐标的方式有问题吗?

    谢谢!

    编辑:评论中有洞察力的问题后的更多信息

    按照@Mark Ransom在评论中的指导,我使用了GetIconInfo函数来获取有关I型光束光标的更多信息。光标的ICONINFO结构指示光标热点的x坐标为x = 8。但是,当我转储光标的位图时(hbmMask结构的ICONINFO成员,因为它是单色光标),垂直条距图像左侧10个像素,而不是8个像素。正如Mark所指出的,这可能是视觉差异的原因,但是为什么会发生这种差异,我该如何解决呢?

    (我还注意到this other question的答案提供了一些有关处理I-Beam游标的不同方式的有趣信息。我想知道这是否相关)

    最佳答案

    这困扰了我很多年,显然还有许多其他Windows用户?您是否曾经单击过两个字符之间,但是文本插入符号的结尾距离左侧太远了?您的光标显然在其他两个光标之间!

    如果打开记事本,然后将光标移到底部边缘,移出文本区域,并观察I型光束和指针之间的变化,您会看到指针从I型光束的左边开始两个像素。 OP甚至通过编写程序来测试行为异常的I光束光标正在单击的位置,从而彻底观察到了这一点。一定是热点设置不正确。 (从来没有想过我会成为用手机拍摄屏幕截图的人之一,但是在这种情况下,这实际上是我想到的捕获鼠标光标的最简单方法。)

    I-beam misaligned with cursor
    I-beam misaligned with cursor

    好吧,那么我们该如何解决呢?
    好吧,任何精明的Windows用户都可以在“控制面板”中打开其“鼠标”设置,然后只需将I型光标更改为具有正确热点的其他版本即可。我已经发誓自己以前已经做到这一点,已经从某个地方下载了校正后的I形光标(看起来完全一样),但是我似乎找不到从中获得链接的链接–但是,是的,这种方法肯定会为您提供正确的文本选择热点。

    不过,这真的可以解决问题吗?还是会让你失眠,想知道-知道-你把它掩盖了...

    看起来似乎很难真正修复,对吧?我们将去调整原始光标文件。因此,我在“控制面板”中打开“鼠标设置”,然后单击“浏览...”,在Windows/Cursors中搜索列表,但是...不存在吗?我看了两次,然后三次–肯定不见了。没有与我使用的类似的东西。

    Not my I-beam

    因此,我通过regedit进入Windows注册表–我发现可以在其中找到指向它的文件路径。通过Google查找 key 在哪里很简单:HKEY_CURRENT_USER/Control Panel/Cursors。但是等等-它也不在那里吗?我看到箭头,手形和其他光标,但任何地方都看不到“Ibeam”或“TextSelection”项!

    Regedit

    聪明的人可能在 mock 我的困惑,他们完全知道Windows保留其 secret 光标的位置,但是可惜,我的无知使我痛苦。我继续不遗余力地寻找其他按键来寻找它-也许文本选择很特殊,并且光标信息在相关按键下的其他位置?

    很快我就得出一个合理的假设,即如果未设置键,Windows将使用默认的光标文件–但这在哪里呢?幸运的是,我有少量Windows编程经验,并且知道事情可能来自嵌入式资源,而不是独立的.cur文件。经过更深入的挖掘,一个叫Herby的人为我找到了答案:

    They are located in user32.dll [%WinDir%/system32].



    (通过https://www.neowin.net/forum/topic/374461-default-xp-cursor-location/)

    光在隧道的尽头。出于某种原因,我已经在计算机上安装了Resource Hacker,这可能是因为我之前做过一些杂乱的事情,但是我浏览了user32.dll并确定找到了默认的光标资源。资源ID 73下有一个工字梁。

    Resource Hacker

    我导出了该文件,并在引用ICO文件格式的同时使用了十六进制编辑器。字节偏移量10具有热点的水平像素坐标,即8。我可以将该字节从0x08更改为0x0A,然后使用Resource Hacker将修改后的文件导入回user32.dll,这样我的问题将得到解决(权限问题在旁边)。

    Easy!

    这很简单,但是我们真的想要简单吗?我们已经走了这么远,所以也可能会浪费我们余下的时间。让我们编写一个C++程序来做到这一点!当然,作为我们这样的优秀工程师,我们必须找到一种方法来安全,正确地进行此操作。

    因此,我开始了进入程序员 hell 最深处的旅程,遭受了曾经描述过模糊的WinAPI方法(例如与更新DLL资源有关的方法)的文档。这里的第一个大障碍是魔术数字,即我们要修改的光标资源的ID,为“73”。什么意思它从哪里来的?

    好吧,对我来说,很明显,这是一个生成的ID,不能相信它是代表工字梁光标的实际常数。因此,我们需要找到某种方法来可靠地找到该魔数(Magic Number)。在纸上看起来很简单,不是吗?好吧,不是。

    我最近要查找的魔术数字是从GetIconInfoEx识别模块的字符串“USER32”。实际上没有任何用处。 (哦,顺便说一句,对任何想弄清楚.cur文件及其被破坏的BMP格式的人都应该给予警告。)如果您可以找到一种方法来将IDC_IBEAM转换为user32.dll资源中的键名,则可以避免这种情况。您,但是在我的脑袋猛烈地摔了一下这个项目的大部分时间后,我决定采用愚蠢的方法。

    我只是复制了原始数据,直接从游标资源中将其导出以用作签名。然后,我可以LoadLibrary user32.dll,通过游标枚举,然后检查它们是否与签名文件完全匹配。如果找到匹配项,我已经找到了要修改的ID。我还了解到,在Resource Hacker中看到的第二个魔术数字是语言代码–“1033”(美国英语)。我也不得不烦恼地进行另一个枚举才能找到该数字。

    一切顺利,从翻阅文档几小时后,我有了解决方案。 Windows API中有一些功能可以更新DLL文件中的资源。我要做的就是更改签名文件的第一个字节(这是水平热点偏移量),然后更新资源。

    当然要得到许可

    在这个项目进行到一半的时候,我提醒自己,修改系统文件是一个可怕的主意(尤其是如果它使系统认为有问题/过时的话),更不用说系统将采取什么措施来尝试并停止了。做到了这一点,但我就无法离开,即使我知道我已完成解决方案,也无法忍受如此甜蜜的满足。它确实起作用了–我制作了user32.dll的副本,运行了代码,并且确定光标热点已得到纠正。

    结果:https://github.com/mukunda-/IBeamFix

    系统文件不过是系统文件,即使没有管理员访问权限,系统也不会让您弄乱它们。我不会为弄清楚该如何做而烦恼。

    更好的方法可能是简单地(并且简单地说,我的意思是与CUR/BMP打交道)从user32.dll中导出游标,检查其特征以确保它仍然具有热点缺陷,修改热点坐标,以及然后更新注册表以使用该游标。

    或者,甚至更好的是,甚至不必为任何这种疯狂而烦恼,而只是使用替换光标。我应该在第四段停下来。看,我做了一个。 https://mukunda.com/stuff/IBeamFixed.cur问题已解决。

    关于windows - Windows 7中I型光标的热点不正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5822090/

    相关文章:

    c++ - 我的 CListCtrl 中的选定项目显示省略号,尽管有足够的空间!

    java - 区分已签名的 Java 小程序

    java - 这个线程/绘制异常是关于什么的?

    windows - 在 Windows 7 上构建 Hadoop,无法复制文件

    python - msvcrt.getch() 每次都会检测到空格

    c# - ContactManager.RequestStoreAsync() 抛出 System.UnauthorizedAccessException

    c++ - 在旧 MFC/Win32 应用程序上使用 C++ 互操作时遇到错误

    c++ - 由 JOB 中的进程启动的子进程能否将 JOB 属性设置为脱离作业?

    windows - 在 Windows 服务模式下运行 GUI 应用程序

    iphone - 连接手机