如何从RICHEDIT控件中消除MessageBeep声音?

9

RichEdit控件有一个非常让人讨厌的特性。每当用户试图将光标移动到其“结束点”之后时,它就会发出嗡嗡声。例如,您可以使用实现了RICHEDIT的WordPad进行测试。打开它,输入一些文本,然后按下Home键。如果光标不在行首:

enter image description here

按下 Home 键将把光标移动到开头,但再次按下 Home 键会发出哔声。
乍一看,似乎覆盖 WM_KEYDOWNWM_KEYUP 消息并阻止 RICHEDIT 可能产生的哔声是一个解决方案... 直到我真正开始实现它。不幸的是,事情没有听起来那么简单,因为该控件在很多情况下都会发出哔声!因此,我的按键阻塞代码实际上膨胀到了300多行,而且我仍然看到有一些按键我没有考虑到,或者更糟糕的是,我可能已经覆盖了一些有用的行为。(请阅读下面的详细信息。)
然后我决定查看RICHEDIT控件本身的实现。如果我们看一下Home键按下的实现,例如在我的Windows 10操作系统上的C:\WINDOWS\SysWOW64\msftedit.dll中,具有被称为?Home@CTxtSelection@@QAEHHH@Z(或已解码的public:int __thiscall CTxtSelection :: Home(int,int))的函数,映射偏移量为0x3FC00,硬编码为调用MessageBeep(MB_OK),或者正是我试图消除的内容。

enter image description here

如果你看一下上面截图中的地址0x6B64FD38,会发现有一种内置方法可以绕过它,使用标志0x800

所以深入研究了一下msftedit.dll后,似乎有一个名为?OnAllowBeep@CTxtEdit@@QAEJH@Z(或者已解码的public: long __thiscall CTxtEdit::OnAllowBeep(int))的函数可以修改这些标志:

enter image description here

经过进一步的研究,我发现RICHEDIT控件内置了COM接口,例如ITextServicesITextHost,这些接口在ITextServices::OnTxPropertyBitsChange方法中引用了标志TXTBIT_ALLOWBEEP

不幸的是,我似乎找不到直接更改TXTBIT_ALLOWBEEP标志的方法(COM不是我的专长)。我尝试着实现ITextHost,但它有很多虚拟方法与我试图实现的内容无关,我不知道如何实现。

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


< p > < em > PS. 这就是为什么我没有选择覆盖按键的路线:举个例子,如果我覆盖 < code > VK_HOME 键。我需要确保光标不在行首,但也没有选择。然而,在光标位于窗口顶部的情况下,我需要确保 < code > Ctrl 键没有按下。然后同样适用于 < code > Shift 键,而我甚至不确定 < code > Alt 与此有何关系......等等。哦,这只是 < code > Home 键。还有上下左右、PageUp、PageDown、End、Delete、Backspace。 (这就是我知道的。可能还有更多,而且我甚至没有谈论 IME 或其他键盘布局等问题。)换句话说,这变得一团糟! 所以,最终我意识到,预测按键不是正确的方法。


1
实际上,我们需要将 EM_GETOLEINTERFACE 发送到 richedit 窗口,然后查询 ITextServices 接口并调用 pTxtSrv->OnTxPropertyBitsChange(TXTBIT_ALLOWBEEP, 0);。这一切都很简单,但在链接的问题下没有这样的答案。 - RbMm
我在这里发布了答案(https://dev59.com/HVPTa4cB1Zd3GeqPil96#55885061)。 不知道是否需要在此问题下再次复制它,或者如何操作。 - RbMm
1个回答

11

首先,我们需要向富文本编辑窗口发送 EM_GETOLEINTERFACE 消息-这将检索客户端可以使用的 IRichEditOle 对象,以访问富文本控件的组件对象模型 (COM) 功能。

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

这里有一个有趣的点-IID_ITextServices 不太出名,但需要在运行时从 Msftedit.dll 中获取。

来自 关于无窗体富文本编辑控件

Msftedit.dll 导出了一个称为 IID_ITextServices 的接口标识符 (IID),您可以使用它查询 IUnknown 指针以获取 ITextServices 接口。

当我们得到 ITextServices 指针后,我们只需带有 TXTBIT_ALLOWBEEP 掩码调用 OnTxPropertyBitsChange 即可。

代码示例:

#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();
                }
            }
        }
    }
}

哦,伙计,你又帮我解决了大麻烦!可是我差点就自己完成了。我在IDA中深入挖掘了所有内部信息,但是那个COM让我束手无策。无论如何,感谢你的帮助! - c00000fd
@c00000fd - 我认为在调试器下更容易找到MessageBeep被调用的位置以及我们查询哪个对象成员,它实现了哪些接口 - 与静态代码工具不同,这只需要几分钟。然后主要是了解EM_GETOLEINTERFACE - RbMm
哦,你的意思是在我发布这个问题后很快就解决了? - c00000fd
@c00000fd - 是的,需要大约5-10分钟进行研究,并在代码中进行相同时间的测试。 - RbMm
真厉害,兄弟! - c00000fd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接