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

Ger*_*502 6 windows user-interface winapi windows-7 mouse-pointer

问题

在 Windows 上,为“鼠标按钮按下”事件返回的坐标对于 I-Beam 光标似乎略有错误。基本上,x 坐标总是比它应该在的位置左两个像素。

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

这是发生的事情的屏幕截图

如您所见,红线位于其应有位置左侧两个像素处(标准箭头指针的行为是正确的),因此 I-Beam 光标的热点似乎是错误的。

我已经让其他运行 Windows 7 64 位的人确认他们遇到了同样的问题,但是 Vista 上的另一个测试人员没有遇到这个问题。


关于我的环境的一些信息

  • Windows 7 64 位。完全默认配置(即没有 DPI 缩放,没有奇怪的主题等)
  • Visual Studio Express 2010
  • 带有最新驱动程序的 NVidia 显卡 (v270.61)
  • 打开或关闭空气动力没有区别。在显示首选项中选择不同的光标没有区别

相关代码

我的测试项目基本上是 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);
}
Run Code Online (Sandbox Code Playgroud)

以下是我的主消息循环中的相关部分:

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;
Run Code Online (Sandbox Code Playgroud)

摘要

我已经做了大量的谷歌搜索来寻找答案,但找不到任何相关的东西。我处理传入光标坐标的方式有问题吗?

谢谢!


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

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

(我还注意到另一个问题的答案有一些关于处理工字梁光标的不同方式的有趣信息。我想知道这是否相关)

muk*_*nda 5

这个问题困扰了我很多年,显然还有很多其他 Windows 用户?您是否曾经只在两个字符之间单击,但文本插入符号最终距离左侧太远?您的光标显然位于另外两个之间!

\n\n

如果打开记事本,然后将光标移动到底部边缘,移出文本区域,并观察工字梁和指针之间的变化,您可以看到指针从工字梁左侧两个像素开始。OP 已经彻底观察到了这一点,甚至编写了一个程序来测试行为不当的工字梁光标点击的位置。肯定是热点设置错误。(没想到我\xe2\x80\x99d会成为那些用手机截图的人之一,但在这种情况下,这实际上是我想到的捕获鼠标光标的最简单方法。)

\n\n

工字梁与光标未对准\n工字梁与光标未对准

\n\n

好吧,那么我们如何解决这个问题呢?\n好吧,任何理智的 Windows 用户都会在控制面板中打开鼠标设置,然后非常简单地将 I 形光标更改为具有正确热点的不同版本。我可以\xe2\x80\x99ve发誓我\xe2\x80\x99以前做过这个,从某个地方下载了一个校正过的工字光标(看起来完全一样),但我可以\xe2\x80\x99t似乎找到我从 \xe2\x80\x93 获取它的链接,但是是的,这种方法肯定会为您提供正确的文本选择热点。

\n\n

但它真的能解决问题吗?或者它会让你失眠,想知道 \xe2\x80\x93 知道 \xe2\x80\x93你掩盖了它......

\n\n

无论如何,\xe2\x80\x99t 似乎很难真正修复......对吧?我们\xe2\x80\x99就去调整原来的光标文件。因此,我在控制面板中打开鼠标设置,然后单击浏览...,在列表中搜索Windows/Cursors,但是... it\xe2\x80\x99s 不在那里?我看了两遍,然后三次 \xe2\x80\x93 肯定不见了。没有\xe2\x80\x99t任何类似于我正在使用的东西。

\n\n

不是我的工字钢

\n\n

因此,我通过regedit \xe2\x80\x93查看 Windows 注册表,我想我可以在那里找到它的文件路径。通过 Google 找到钥匙的位置非常简单:HKEY_CURRENT_USER/Control Panel/Cursors。但是等等 \xe2\x80\x93 它\xe2\x80\x99s也不在那里?!我看到箭头、手形和其他光标,但在任何地方都没有 \xe2\x80\x9cIbeam\xe2\x80\x9d 或 \xe2\x80\x9cTextSelection\xe2\x80\x9d 条目!

\n\n

注册表编辑器

\n\n

你们这些聪明人可能会嘲笑我的困惑,因为我完全知道 Windows 将秘密光标保存在哪里,但唉,我的无知折磨着我。我继续毫无结果地挖掘其他键试图找到它 \xe2\x80\x93也许文本选择是特殊的并且在相关键下的其他地方有光标信息?

\n\n

很快我就得出了合理的假设,即如果密钥是 \xe2\x80\x99t set \xe2\x80\x93,Windows 将使用默认光标文件,但那会在哪里?幸运的是,我有一点 Windows 编程经验,并且知道事物可以来自嵌入式资源而不是独立的 .cur 文件。经过更深入的挖掘,一个名叫赫比的人给了我答案:

\n\n
\n

它们位于 user32.dll [%WinDir%/system32] 中。

\n
\n\n

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

\n\n

光在隧道的尽头。由于某种原因,我已经在我的计算机上安装了 Resource Hacker,可能是因为我之前做了一些疯狂的事情,但我向里面看了看user32.dll,果然找到了默认的光标资源。资源 ID 73 下有工字梁。

\n\n

资源黑客

\n\n

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

\n\n

简单的!

\n\n

\xe2\x80\x99 足够简单,但我们真的想要简单吗?我们\xe2\x80\x99已经走了这么远,所以不妨浪费我们这一天剩下的时间。让\xe2\x80\x99s写一个C++程序来做吧!当然,作为完全优秀的工程师,我们必须找到一种方法来安全、正确地做到这一点......

\n\n

就这样,我开始了程序员地狱最深处的旅程,忍受着过去描述晦涩的 WinAPI 方法的文档,比如那些与更新 DLL 资源有关的方法。这里的第一个大障碍是那个幻数,我们要修改的光标资源的ID,\xe2\x80\x9c73。\xe2\x80\x9d它是什么意思?它从哪里来的?

\n\n

好吧,对我来说,它\xe2\x80\x99s 显然是一个生成的ID,并且\xe2\x80\x99t 可以被信任为代表工字形光标的事实上的常量。因此,我们需要找到某种方法来可靠地找到那个神奇的数字。纸面上看起来很简单,不是吗?嗯,它是\xe2\x80\x99t。

\n\n

我最接近追踪难以捉摸的幻数的是字符串 \xe2\x80\x9cUSER32\xe2\x80\x9d 标识模块,来自GetIconInfoEx. 没有什么真正有用的东西。(哦,顺便说一句,对任何想要弄清楚 .cur 文件及其被屠杀的 BMP 格式的人,这是一个公平的警告。)如果您能找到一种方法来将IDC_IBEAMuser32.dll 资源中的键名变成键名,那么向您致敬,但是在这个项目的大部分时间里我把头撞到墙上后,我决定采用一种更愚蠢的方法。

\n\n

我只是复制了原始数据,直接从游标资源中导出来用作签名。然后我可以LoadLibrary通过 user32.dll 枚举游标,然后检查它们是否与签名文件完全匹配。如果找到匹配项,则 I\xe2\x80\x99 已找到我要修改的 ID。我还了解到,我在 Resource Hacker 中看到的第二个神奇数字是语言代码 \xe2\x80\x93 \xe2\x80\x9c1033\xe2\x80\x9d (美国英语)。我还不得不烦人地进行另一次枚举才能找到该数字。

\n\n

所有\xe2\x80\x99s 都很好,几个小时后,通过挖掘文档,我有了我的解决方案。Windows API中有一些函数可以更新DLL文件中的资源。我所要做的就是更改签名文件的第一个字节(这是水平热点偏移量),并更新资源。

\n\n

当然,经过许可

\n\n

大约在这个项目进行到一半时,我提醒自己修改系统文件\xe2\x80\x99是一个可怕的想法(特别是如果它让系统认为某些东西是错误的/过时的),更不用说衡量系统的内容了我会尽力阻止你这样做,但如果没有知道我完成了解决方案的甜蜜满足感,我就无法\xe2\x80\x99 生活。它确实有效 \xe2\x80\x93 我复制了 user32.dll,运行代码,果然,光标热点已得到纠正。

\n\n

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

\n\n

系统文件就是系统文件,即使具有管理员访问权限,系统也不会让你乱动它们。我\xe2\x80\x99m 不会费心去弄清楚如何规避这个问题。

\n\n

更好的方法可能是简单地(简单地说,我的意思是处理 CUR/BMP 地狱)从 user32.dll 导出光标,检查其特征以确保它仍然存在热点缺陷,修改热点坐标,然后然后更新注册表以使用该光标。

\n\n

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

\n