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

时间:2011-04-28 16:44:21

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

问题

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

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

Here's a screenshot of what happens

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

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


有关我的环境的一些信息

  • Windows 7 64位。完全默认配置(即没有DPI缩放,没有奇怪的主题等)
  • Visual Studio Express 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-Beam光标的更多信息。光标的ICONINFO结构表示光标热点的x坐标位于x = 8。但是,当我转储光标的位图(hbmMask结构的ICONINFO成员,因为它是单色光标)时,垂直条距离图像左边10像素,而不是8像素。正如马克指出这可能是视觉差异的原因,但为什么会发生这种情况,我该如何解决呢?

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

1 个答案:

答案 0 :(得分:0)

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

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

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

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

可以真正解决问题吗?还是会让您失眠,想知道-知道-您把它掩盖了...

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

Not my I-beam

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

Regedit

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

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

它们位于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 ++程序来做到这一点!当然,作为我们自己是完全好的工程师,我们必须找到一种方法来做到这一点的安全和适当……

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

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

最近我要找出难以捉摸的魔术数字是来自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问题已解决。