3

假设您刚刚在启用拼写检查的富编辑控件中设置了一些文本,并且该文本有一些拼写错误。一瞬间,拼写检查将启动,然后拼写错误的文本将加下划线。但是你猜怎么着:富编辑控件实际上EN_CHANGE只会为下划线事件发送一个通知(假设您已经通过 do 注册了通知SendMessage(hwnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_CHANGE))。

有没有一种解决方法可以避免这种行为?我有一个带有一些启用拼写检查的富编辑控件的对话框。而且我还想知道编辑事件何时发生,所以我知道何时启用“保存”按钮。EN_CHANGE因此,仅针对拼写检查下划线事件获取通知是一个问题。

我考虑过的一个选项是 EN_CHANGE完全禁用通知,然后在子类富编辑控件中自行触发它们。例如,当有 a 时WM_CHAR,它会显式发送 EN_CHANGE通知等。但这似乎是个问题,因为有许多类型的事件应该触发更改,如删除、复制/粘贴等,我可能会没有正确捕获所有这些。

我考虑过的另一个选项是EN_CHANGE动态启用和禁用通知。例如,仅在有焦点时启用它们,并在焦点被杀死时禁用它们。但这似乎也有问题,因为富编辑在设置其文本时可能已经具有焦点。然后会出现拼写检查下划线,并且EN_CHANGE会发送不需要的通知。

我想也可以使用计时器,但我认为这很容易出错。

有人有其他想法吗?

这是一个可重现的示例。只需运行它,它就会说发生了一些变化:

#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
#include <Richedit.h>

class CMyWindow :
    public CWindowImpl<CMyWindow, CWindow, CWinTraits<WS_VISIBLE>>
{
public:
    CMyWindow()
    {
    }

BEGIN_MSG_MAP(CMyWindow)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    COMMAND_CODE_HANDLER(EN_CHANGE, OnChange)
END_MSG_MAP()

private:
    LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL& bHandled)
    {
        bHandled = FALSE;

        LoadLibrary(L"Msftedit.dll");

        CRect rc;
        GetClientRect(&rc);
        m_wndRichEdit.Create(MSFTEDIT_CLASS, m_hWnd, &rc,
            NULL, WS_VISIBLE | WS_CHILD | WS_BORDER);

        INT iLangOpts = m_wndRichEdit.SendMessage(EM_GETLANGOPTIONS, NULL, NULL);
        iLangOpts |= IMF_SPELLCHECKING;
        m_wndRichEdit.SendMessage(EM_SETLANGOPTIONS, NULL, (LPARAM)iLangOpts);

        m_wndRichEdit.SetWindowText(L"sdflajlf adlfjldsfklj dfsl");
       
        m_wndRichEdit.SendMessage(EM_SETEVENTMASK, 0, (LPARAM)ENM_CHANGE);
      
        return 0;
    }

    LRESULT OnChange(WORD, WORD, HWND, BOOL&)
    {
        MessageBox(L"changed", NULL, NULL);
        return 0;
    }

private:
    CWindow m_wndRichEdit;
};


int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    CMyWindow wnd;
    CRect rc(0, 0, 200, 200);
    wnd.Create(NULL, &rc);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

此外,似乎使用EM_SETMODIFYEM_GETMODIFY没有帮助。我猜拼写检查下划线会导致 a EM_SETMODIFY,因此在处理程序中检查该标志是无用的。

4

3 回答 3

0

使用EM_CANUNDO(也许也是EM_CANREDO)来验证内容是否已更改。我希望拼写检查器不会添加任何撤消信息。

于 2021-05-10T04:49:32.537 回答
0

因为关于CHANGENOTIFY(必须包含与 EN_CHANGE 通知代码相关的信息,但不是.. ) 的文档是错误的 - 只有研究存在。

在我的测试中,我认为EN_CHANGE仅在富编辑处理WM_TIMER消息时收到与拼写检查相关的内容。所以解决方案是下一个 - 子类richedit并记住(保存在类成员变量中) - 当它在里面时WM_TIMER。比,当我们处理EN_CHANGE- 检查是在 Richedit 里面WM_TIMER

部分 POC 代码。我特别展示了更复杂的情况 - 如果框架或对话框窗口中存在多个(多个)子 Richedit

#include <richedit.h>

class RichFrame : public ZFrameMultiWnd
{
    enum { richIdBase = 0x1234 };
    bool _bInTimer[2] = {};

public:
protected:
private:
    static LRESULT WINAPI SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
    {
        if ((uIdSubclass -= richIdBase) >= _countof(_bInTimer))
        {
            __debugbreak();
        }

        bool bTimerMessage = uMsg == WM_TIMER;
        
        if (bTimerMessage)
        {
            reinterpret_cast<RichFrame*>(dwRefData)->_bInTimer[uIdSubclass] = TRUE;
        }
        
        lParam = DefSubclassProc(hWnd, uMsg, wParam, lParam);

        if (bTimerMessage)
        {
            reinterpret_cast<RichFrame*>(dwRefData)->_bInTimer[uIdSubclass] = false;
        }

        return lParam;
    }

    virtual BOOL CreateClient(HWND hWndParent, int nWidth, int nHeight, PVOID /*lpCreateParams*/)
    {
        UINT cy = nHeight / _countof(_bInTimer), y = 0;

        UINT id = richIdBase;
        ULONG n = _countof(_bInTimer);

        do 
        {
            if (HWND hwnd = CreateWindowExW(0, MSFTEDIT_CLASS, 0, WS_CHILD|ES_MULTILINE|WS_VISIBLE|WS_BORDER, 
                0, y, nWidth, cy, hWndParent, (HMENU)id, 0, 0))
            {
                SendMessage(hwnd, EM_SETLANGOPTIONS, 0, 
                    SendMessage(hwnd, EM_GETLANGOPTIONS, 0, 0) | IMF_SPELLCHECKING);

                SetWindowText(hwnd, L"sdflajlf adlfjldsfklj d");
                SendMessage(hwnd, EM_SETEVENTMASK, 0, ENM_CHANGE);

                if (SetWindowSubclass(hwnd, SubclassProc, id, reinterpret_cast<ULONG_PTR>(this)))
                {
                    continue;
                }
            }

            return FALSE;

        } while (y += cy, id++, --n);
        
        return TRUE;
    }

    virtual LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_COMMAND:
            if (EN_CHANGE == HIWORD(wParam))
            {
                if ((wParam = LOWORD(wParam) - richIdBase) >= _countof(_bInTimer))
                {
                    __debugbreak();
                }
                
                DbgPrint("EN_CHANGE<%x> = %x\n", wParam, _bInTimer[wParam]);
            }
            break;

        case WM_DESTROY:
            {
                UINT id = richIdBase;
                ULONG n = _countof(_bInTimer);
                do 
                {
                    RemoveWindowSubclass(GetDlgItem(hwnd, id), SubclassProc, id);
                } while (id++, --n);
            }
            break;

        case WM_NCDESTROY:
            PostQuitMessage(0);
            break;
        }
        return __super::WindowProc(hwnd, uMsg, wParam, lParam);
    }
};
于 2021-05-10T12:12:42.217 回答
0

我最近尝试在没有子类化的情况下解决这个问题,但它只是有点成功。

我的替代解决方法包括将整个文档标记为CFE_PROTECTED. 在EN_PROTECTED处理程序ENPROTECTED::msg中,WM_NULL当来自拼写检查器时,您可以设置一个标志,告诉自己忽略下一个EN_CHANGE。要允许从上下文菜单进行实际拼写更正,您还需要跟踪菜单。

这感觉相当脆弱,但跟踪也是如此WM_TIMER

于 2021-08-06T16:34:20.917 回答