如何消除 RICHEDIT 控件的 MessageBeep?

How to eliminate the MessageBeep from the RICHEDIT control?

RichEdit control 有这个非常烦人的功能。每次用户试图将光标移过其“终点”时,它都会发出哔哔声。例如,您可以使用同样实现了 RICHEDIT 的 WordPad 来测试它。打开它,输入一些文本,然后点击 Home 键。如果光标不在行首:

Home 键会将其移动到那里,但再次按 Home 键会发出此蜂鸣声。

乍一看,似乎覆盖了 WM_KEYDOWNWM_KEYUP 消息并阻止了 RICHEDIT 可以发出哔哔声的情况是一个解决方案……直到我真正开始实施它。不幸的是,它并不像听起来那么简单,因为在很多情况下该控件都会发出哔哔声!因此,我的击键阻止代码确实膨胀到 300 多行,而且我仍然看到有一些我没有考虑到的按键,或者更糟的是,我可能已经覆盖了一些有用的行为。 (阅读下文了解更多详情。)


然后我决定深入了解 RICHEDIT 控件本身的实现。果然,例如,如果我们查看 Home 按键的实现,我的 Windows 10 OS 上的 C:\WINDOWS\SysWOW64\msftedit.dll 具有名为 [=18= 的函数](或 public: int __thiscall CTxtSelection::Home(int,int) demangled)在映射偏移 0x3FC00 处,硬编码调用 MessageBeep(MB_OK),或 exactly 我要消除的东西:

如果您查看上面屏幕截图中的地址 0x6B64FD38,就会发现有一种内置的方法可以绕过它,看起来像是标志 0x800.

所以在深入研究 msftedit.dll 之后,似乎有一个名为 ?OnAllowBeep@CTxtEdit@@QAEJH@Z 的函数(或 public: long __thiscall CTxtEdit::OnAllowBeep(int) demangled)可以修改此标志:

经过更多研究后,我发现 RICHEDIT 控件中内置了 COM 接口,例如 ITextServicesITextHost 将 [=43] 中的标志引用为 TXTBIT_ALLOWBEEP =]方法。

不幸的是,我似乎无法找到直接更改 TXTBIT_ALLOWBEEP 标志的方法(COM 不是我的强项。)我尝试研究实施 ITextHost,但它有很多虚方法与我想要实现的目标无关,我不知道如何实现。

有人知道如何清除 TXTBIT_ALLOWBEEP 标志吗?


PS。这就是为什么我没有走覆盖按键的路线: 只是给你举个例子。比如说,如果我覆盖 VK_HOME 按键。我需要确保光标不在行的开头,而且没有选择。但是,我需要确保在光标位于 window 最顶部的情况下 Ctrl 键没有按下。然后与 Shift 键相同,我什至不确定 Alt 对它做了什么......等等。哦,这只是 Home 键。还有上、下、左、右、PageUp、PageDown、End、Delete、Backspace。 (这就是我所知道的。可能还有更多,而且我什至没有谈论 IME 或其他键盘布局等)换句话说,它变得一团糟! 所以,最终我意识到预期击键 不是 的方法。

首先我们需要发送 EM_GETOLEINTERFACE 消息到 rich edit window - 这是检索 IRichEditOle 对象,客户端可以使用该对象访问 rich edit 控件的组件对象模型 (COM) 功能。

然后为了检索 ITextServices 指针,在 EM_GETOLEINTERFACE 返回的私有 IUnknown 指针上调用 QueryInterface

这里存在有趣的一点 - IID_ITextServices 不为人知但需要在运行时从 Msftedit.dll

获取

来自 About Windowless Rich Edit Controls

Msftedit.dll exports an interface identifier (IID) called IID_ITextServices that you can use to query the IUnknown pointer for the ITextServices interface.

在我们得到 ITextServices pointer - we simply can call OnTxPropertyBitsChangeTXTBIT_ALLOWBEEP 掩码后

代码示例:

#include <textserv.h>

if (HMODULE hmodRichEdit = LoadLibrary(L"Msftedit.dll"))
{
    // create richedit window
    if (HWND hwndRich = CreateWindowExW(0, MSFTEDIT_CLASS, ...))
    {
        if (IID* pIID_ITS = (IID*) GetProcAddress(hmodRichEdit, "IID_ITextServices"))
        {
            IUnknown* pUnk;
            if (SendMessageW(hwndRich, EM_GETOLEINTERFACE, 0, (LPARAM)&pUnk))
            {
                ITextServices* pTxtSrv;
                HRESULT hr = pUnk->QueryInterface(*pIID_ITS, (void**)&pTxtSrv);
                pUnk->Release();
                if (0 <= hr)
                {
                    pTxtSrv->OnTxPropertyBitsChange(TXTBIT_ALLOWBEEP, 0);
                    pTxtSrv->Release();
                }
            }
        }
    }
}