【问题标题】:Incorrect hotspot for I-Beam cursor in Windows 7?Windows 7 中 I-Beam 光标的热点不正确?
【发布时间】:2011-08-14 21:24:27
【问题描述】:

问题

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

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

Here's a screenshot of what happens

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

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


关于我的环境的一些信息

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

相关代码

我的测试项目基本上是 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;

总结

我已经在谷歌上搜索了很多答案,但找不到任何相关信息。我处理传入光标坐标的方式有问题吗?

谢谢!


编辑:在评论中提出有见地的问题后的更多信息

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

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

【问题讨论】:

  • 您可以使用GetIconInfo获取热点坐标,这样可以解决问题。
  • 嗨@Mark-Ransom。 GetIconInfo 告诉我 IDC_IBEAM 指针的热点是 xHotspot==8 yHotspot==9。 (作为参考,该函数告诉我标准 IDC_ARROW 指针的热点是xHotspot==0 yHotspot==0)。我可以从这些信息中获得更多信息吗?谢谢。
  • 点击点应该在相对于光标图像左上角的热点坐标上。如果您可以转储光标位图并对其进行检查,则垂直条也应距左侧 8 个像素。如果不是,那就是你的问题。
  • 好的,我已经转储了光标位图(好吧,实际上我已经从ICONINFO 结构中转储了hbmMask 位图GetIconInfohbmColor 位图不是' t 设置,因为 I-Beam 是单色光标)。结果是垂直条距左侧 10 个像素,而不是 xHotspot 成员指示的 8 个像素。正如@Mark 指出的那样,这显然是错误的,但我完全不知道结构中如何存在这种相互冲突的信息以及如何修复它。
  • 我知道为什么会这样,但这更像是一种解决方法。当然,我要么 (a) 做错了什么,要么 (b) 内置 I-Beam 光标的热点对于 Windows 7 的所有用户都不准确?换句话说,我更愿意在采取变通办法之前尝试找出问题的根本原因。我希望这看起来是合理的;)

标签: windows user-interface winapi windows-7 mouse-cursor


【解决方案1】:

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

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

好的,那我们该如何解决呢? 好吧,任何理智的 Windows 用户都会在控制面板中打开他们的鼠标设置,然后非常简单地将 I 型光标更改为具有正确热点的不同版本。我可以发誓我以前做过这个,从 somewhere 下载了一个更正的 I 型光标(看起来一样),但我似乎找不到我所在的链接从那里得到它——但是,是的,这种方法肯定会为您提供正确的文本选择热点。

它会真正解决问题吗?或者它会让你失眠,想知道 - 知道 - 你把它掩盖了......

似乎并不那么很难真正修复,无论如何...对吗?我们将去调整原始光标文件。所以,我在控制面板中打开鼠标设置,然后点击浏览...,搜索Windows/Cursors中的列表,但是...它不存在?我看了两遍,然后看了三次——肯定不见了。没有任何与我使用的相似。

所以,我通过 regedit 查看了 Windows 注册表——我想我可以在其中找到它的文件路径。通过 Google 找到密钥的位置非常简单:HKEY_CURRENT_USER/Control Panel/Cursors。但是等等——它也不在那里?!我看到 Arrow、Hand 和其他光标,但在任何地方都没有“Ibeam”或“TextSelection”条目!

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

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

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

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

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

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

这很简单,但我们真的想要简单吗?我们已经走到了这一步,所以不妨浪费我们剩下的时间。让我们编写一个 C++ 程序来完成它!当然,作为完全优秀的工程师,我们必须找到一种安全和适当的方法......

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

嗯,对我来说,很明显它是一个生成的 ID,不能相信它是代表 I 型光标的事实上的常量。所以,我们需要找到某种方法来可靠地找到这个幻数。在纸上看起来很简单,不是吗?好吧,事实并非如此。

我最接近于追踪难以捉摸的幻数是标识模块的字符串“USER32”,来自GetIconInfoEx。没有什么真正有用的东西。 (哦,顺便说一句,对任何想弄清楚 .cur 文件及其被破坏的 BMP 格式的人发出公平的警告。)如果您能找到将IDC_IBEAM 转换为 user32.dll 资源中的键名的方法,请顶一下交给你了,但是在这个项目的大部分时间里我把头撞到墙上后,我决定采用一种更愚蠢的方法。

我只是复制了原始数据,直接从游标资源中导出,用作签名。然后我可以LoadLibraryuser32.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 问题解决了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    • 1970-01-01
    相关资源
    最近更新 更多