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

发布于 2024-11-04 09:04:38 字数 2972 浏览 4 评论 0原文

问题

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

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

这是所发生情况的屏幕截图

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

我让其他运行 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。但是,当我转储光标的位图(ICONINFO 结构的 hbmMask 成员,因为它是单色光标)时,垂直条距左侧 10 像素图像的大小,而不是 8 个像素。正如马克指出的那样,这可能是造成视觉差异的原因,但为什么会发生这种情况,我该如何解决呢?

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

The Problem

On windows, the coordinates returned for a "mouse button down" event seem to be slightly wrong for an I-Beam cursor. Basically, the x-coordinate is always two pixels left of where it should be.

I've written a very simple win32 program to demonstrate the problem. All it does is turn the cursor into an IBeam and render a vertical red line where the last mouse down event was. I would expect the red line to match up exactly with the vertical part of the I-Beam, but this is not the case.

Here's a screenshot of what happens.

As you can see, the red line is two pixels to the left of where it should be (the behaviour is correct for the standard arrow pointer), so it appears that the hotspot for the I-Beam cursor is wrong.

I've had someone else running Windows 7 64 bit confirm that they experience the same problem, but another tester on Vista does not have the problem.


Some information about my environment

  • Windows 7 64 bit. Completely default configuration (i.e. no DPI scaling, no weird themes etc)
  • Visual Studio Express 2010
  • NVidia graphics card with latest drivers (v270.61)
  • Switching aero on or off makes no difference. Choosing different cursors in display preferences makes no difference

The Relevant Bits Of Code

My test project is basically the "Win32 Project" template in Visual C++ 2010, with the changes outlined below.

Here's the code where I register the window class and set the cursor to an 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);
}

Here are the relevant parts from my main message loop:

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;

The Summary

I've done loads of googling for answers, but can't find anything relevant. Am I doing something wrong with the way that I'm handling the incoming cursor coordinates?

Thanks!


EDIT: More Information After Insightful Questions in the Comments

As guided by @Mark Ransom in the comments, I've used the GetIconInfo function to get more information about the I-Beam cursor. The ICONINFO struct for the cursor indicates that the x coord for the cursor hotspot is at x=8. However, when I dump the bitmap for the cursor (the hbmMask member of the ICONINFO struct, as it is a monochrome cursor), the vertical bar is 10 pixels from the left of the image, not 8 pixels. As Mark points out this is likely the cause of the visual discrepency, but why has this occurred, and how might I fix it?

(I also noticed that the answer to this other question has some interesting information about the different way that I-Beam cursors are handled. I wonder if this is relevant)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

镜花水月 2024-11-11 09:04:38

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

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

工字梁与光标未对准
工字梁与光标未对齐

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

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

看起来很难真正解决,无论如何......对吗?我们只需去调整原始光标文件即可。因此,我在控制面板中打开鼠标设置,然后单击浏览...,在Windows/Cursors 中搜索列表,但是...它不在那里?我看了两遍,然后又看了三次——肯定不见了。没有任何类似于我正在使用的东西。

不是我的我-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,然后将修改后的文件导入回 user32。 dll与Resource Hacker,我的问题就可以解决(除了权限问题)。

简单!

这很简单,但是我们真的想要简单吗?我们已经走了这么远,不妨浪费一天剩下的时间。让我们编写一个 C++ 程序来完成它!当然,作为完全优秀的工程师,我们必须找到一种方法来安全且正确地执行此操作...

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

嗯,对我来说,很明显它是一个生成的 ID,不能被信任为代表 I 型光标的实际常量。因此,我们需要找到某种方法来可靠地找到那个神奇的数字。纸面上看起来很简单,不是吗?嗯,事实并非如此。

我最接近追踪到的难以捉摸的神奇数字是来自 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 问题已解决。

This was plaguing me for many years, and evidently many other Windows users? Do you ever just click between two characters, but the text caret ends up too far to the left? Your cursor was clearly between the other two!

If you open Notepad, and then move your cursor to the bottom edge, out of the text region, and observe the change between I-beam and pointer, you can see that the pointer begins two pixels to the left of the I-beam. OP has thoroughly observed this by even writing a program to test just where that misbehaved I-beam cursor is clicking. It must be that the hotspot is set incorrectly. (Never imagined I’d be one of those people who take screenshots with their phone, but in this case, it was actually the easiest method I had in mind to capture the mouse cursor.)

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

All right, so how do we fix it?
Well, any sane Windows user would open their Mouse settings in the Control Panel, and then very simply change the I-beam cursor to a different version that has a correct hotspot. I could’ve sworn that I’ve done this before, having downloaded a corrected I-beam cursor (that looked just the same) from somewhere, but I can’t seem to find the link where I got it from – but yes, this approach will most assuredly give you a correct hotspot for text selection.

Would it truly fix the problem, though? Or would it leave you sleepless, wondering – knowing – you covered it up...

Doesn’t seem so hard to really fix, anyway...right? We’ll just go and adjust the original cursor file. So, I open Mouse settings in the Control Panel, and then click Browse..., search through the list in Windows/Cursors, but... it’s not there? I looked twice, and then thrice – definitely missing. There weren’t any that resembled what I was using.

Not my I-beam

So, I peer into the Windows Registry via regedit – I figured that I could find the file path to it in there. It was simple enough to find where the keys were via Google: HKEY_CURRENT_USER/Control Panel/Cursors. But wait – it’s not there either?! I see Arrow, Hand, and other cursors, but no “Ibeam” or “TextSelection” entry anywhere!

Regedit

You smart people are probably laughing at my bewilderment, knowing fully just where Windows keeps its secret cursors, but alas, my ignorance tormented me. I continued to dig fruitlessly through the other keys trying to find it – maybe text selection was special and had cursor information elsewhere under a related key?

Soon I came to the reasonable assumption that Windows uses a default cursor file if the key isn’t set – but where would that be? Fortunately, I have a modicum of Windows programming experience and know that things can come from embedded resources rather than a standalone .cur file. With some deeper digging, someone named Herby had the answer for me:

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

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

Light at the end of the tunnel. I already had Resource Hacker installed on my computer for some reason, probably because I was doing something deranged before, but I peered inside user32.dll and, sure enough, found the default cursor resources. There was the I-beam under resource ID 73.

Resource Hacker

I exported it and had a look with a hex editor while referencing the ICO file format. Byte offset 10 has the horizontal pixel coordinate of the hotspot, which was 8. I could just change that byte from 0x08 to 0x0A and then import the modified file back into user32.dll with Resource Hacker, and my problem would be solved (permission issues aside).

Easy!

That’s simple enough, but do we really want simple? We’ve come this far, so may as well waste the rest of our day. Let’s write a C++ program to do it! Of course, being the totally good engineers we are, we must find a method to do this safe and proper...

Thus began my journey into the deepest depths of programmer hell, suffering the documents of yore which describe obscure WinAPI methods, like those pertaining to updating a DLL resource. First big obstacle here was that magic number, the ID of the cursor resource we want to modify, “73.” What did it mean? Where did it come from?

Well, to me it’s obvious that it’s a generated ID and can’t be trusted to be the de-facto constant that represents the I-beam cursor. So, we need to find some way to find that magic number reliably. Seems simple on paper, no? Well, it isn’t.

Closest I had gotten to tracking down the elusive magic number was the string “USER32” identifying the module, from GetIconInfoEx. Nothing that was actually useful. (Oh, and by the way, fair warning to anyone who wants to figure out .cur files and their butchered BMP format.) If you can find a way to turn IDC_IBEAM into a key name in the user32.dll resources, hats off to you, but after slamming my head against the wall for the better part of this project I decided to go with a dumber approach.

I just copied the original data, exporting it directly from the cursor resource to be used as a signature. I could then LoadLibrary user32.dll, enumerate through the cursors, and then check if they exactly match the signature file. If I find a match, I’ve found the ID I want to modify. I also learned that the second magic number I saw in Resource Hacker was a language code – “1033” (English US). I had to annoyingly do another enumeration to find that number, too.

All’s well and good, several hours later from digging through documentation, I have my solution. There are functions in the Windows API to update resources in DLL files. All I had to do was change the first byte of the signature file (which was the horizontal hotspot offset), and update the resource.

With permission, of course

It was about halfway through this project that I reminded myself it’s a horrible idea to modify system files (especially if it makes the system think something is wrong/out-of-date), let alone what measures the system will take to try and stop you from doing that, but I just couldn’t live without the sweet satisfaction knowing that I completed the solution. And it did work – I made a copy of user32.dll, ran the code, and sure enough, the cursor hotspot was corrected.

RESULT: https://github.com/mukunda-/IBeamFix

System files are system files though, and not even with Administrator access will the system let you mess with them. I’m not going to bother with figuring out how to circumvent that.

A better approach may have been to simply (and by simply, I mean dealing with CUR/BMP hell) export the cursor from user32.dll, checking its characteristics to make sure that it still has the hotspot flaw, modifying the hotspot coordinate, and then updating the registry to use that cursor.

Or, even better, not even bothering with any of this utter madness and just using a replacement cursor. I should have stopped at paragraph four. Look, I made one. https://mukunda.com/stuff/IBeamFixed.cur Problem solved.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文