Let's say you've just set some text in a spellcheck-enabled rich edit control, and the text has some spelling errors. A split second will go by, spellcheck will kick in, and then the misspelled text will get underlined. But guess what: the rich edit control will actually send an EN_CHANGE
notification just for the underlining event (this is assuming you've registered for notifications by doing SendMessage(hwnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_CHANGE)
).
Is there a workaround to not get this type of behavior? I've got a dialog with some spellcheck-enabled rich edit controls. And I also want to know when an edit event has taken place, so I know when to enable the "Save" button. Getting an EN_CHANGE
notification merely for the spellcheck underlining event is thus a problem.
One option I've considered is disabling EN_CHANGE
notifications entirely, and then triggering them on my own in a subclassed rich edit control. For example, when there's a WM_CHAR
, it would send the EN_CHANGE
notification explicitly, etc. But that seems like a problem, because there are many types of events that should trigger changes, like deletes, copy/pastes, etc., and I'd probably not capture all of them correctly.
Another option I've considered is enabling and disabling EN_CHANGE
notifications dynamically. For example, enabling them only when there's focus, and disabling when focus is killed. But that also seems problematic, because a rich edit might already have focus when its text is set. Then the spellcheck underline would occur, and the undesirable EN_CHANGE
notification would be sent.
I suppose a timer could be used, too, but I think that would be highly error-prone.
Does anybody have any other ideas?
Here's a reproducible example. Simply run it, and it'll say something changed:
#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;
}
Also, it appears that using EM_SETMODIFY
and EM_GETMODIFY
don't help. I guess the spellcheck underlining results in a EM_SETMODIFY
, so checking that flag in the handler is of no avail.
because documentation about CHANGENOTIFY
( must contains information that is associated with an EN_CHANGE notification code, but not..) is wrong - only research exist.
in my test i view that EN_CHANGE
related to Spellcheck received only when rich edit handle WM_TIMER
message. so solution is next - subclass richedit and remember (save in class member variable) - when it inside WM_TIMER
. than, when we handle EN_CHANGE
- check are richedit inside WM_TIMER
.
partial POC code. i special show more complex case - if several (more than one) child richedit`s exist in frame or dialog winndow
#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);
}
};