One of the problems that is intrinsic to Windows is the fact that an ordinary Edit control, MFC class
CEdit, will generate
EN_CHANGE notifications any time the text changes, even if the text is changed programmatically by the application.
This can lead to awkward situations; for example, if two edit controls have some invariant which must be maintained, responding to an
EN_CHANGE event in one may necessitate changing the text of the other; but, since a change in the other requires a change in the first, setting that text triggers another
EN_CHANGE, during which the handler for the second control changes the contents of the first control, which triggers an
EN_CHANGE which causes the application to then respond by changing the second, and so on, until there is a stack overflow due to the infinite recursion that goes on.
In this article, I will show how to avoid
EN_CHANGE notifications in ordinary
CEdit controls, and also how much simpler it is to implement the same functionality in a Rich Edit Control.
Avoiding it in CEdit
EN_CHANGE notifications in a
CEdit control, you must create a subclass of the control. In my case, it was called
To this subclass, and a "Reflected event handler" for
EN_CHANGE, which is indicated by the
Next, hand-edit the resulting header file and implementation file.
In the Message Map, change the line that says:
This requires a change in the prototype for the method, so change:
afx_msg void OnEnChange();
afx_msg BOOL OnEnChange();
Note that I not only change the result type from
BOOL, but I also change the scope to be
protected. There is not now, and never has been in the entire history of MFC, a sensible reason to make these methods
public, since any program that ever attempted to call them from outside the class would represent such an egregious breach of sensible OO methodology that no one would ever do it. I always change my handlers to be
protected. (This represents one of the numerous blunders of Visual Studio .NET, which has been more of an exercise in destroying the usability of the IDE than of fixing ridiculous bugs and design flaws.)
Then, I add a new method:
void SetWindowTextNoNotify(LPCTSTR s);
and add a variable:
notify variable in the constructor:
notify = TRUE;
The implementation of
void CNoNotifyEdit::SetWindowTextNoNotify(LPCTSTR s)
if(old == s)
return; BOOL previous = notify;
notify = FALSE;
notify = previous;
As an optimization, I simply check to see if the existing string is the same as the string I'm about to set, and if they are, I do nothing. If you had edit controls with massive amounts of text, you might choose to eliminate this step because of its impact on memory fragmentation.
Finally, the implementation of the reflected
EN_CHANGE handler is quite simple:
The way a reflected handler works is that if the handler returns
TRUE, it means the handler has done everything necessary to handle the event and it will not be sent to the parent window. So, in this case, if
FALSE (no notification desired), the handler returns
TRUE. But, if
TRUE, then the handler returns
FALSE, which MFC interprets as "Please pass this event to the parent window as usual".
Avoiding it in CRichEditCtrl
The problem is a little different in a Rich Edit control. First, Rich Edit controls do not normally generate
EN_CHANGE notifications. Whenever you subclass an
EN_CHANGE handler in a rich edit control, you get comments that say:
Now, this message is more than a little strange. For example, the
CEdit class would not be involved at all. It would be the
CRichEditCtrl class. And, neither of these have an
OnInitDialog handler, because that is only a member of the
CDialog class. You would not call
CRichEditCtrl().SetEventMask() because it would make no sense to apply the
SetEventMask call to a constructor! It is not clear why you would get this for a
CEdit control at all, since the ClassWizard knows the class of the control, and therefore would not need to put these comments in except for classes derived from
CRichEdit. It also does not explain what is meant by "ORed into the mask", since what is required to OR something in is to have a value in the first place into which to OR it. So, other than the fact that the comment has approximately one deep and fundamental bug per line, it makes perfect sense. Not.
What does this comment really mean?
Well, if the control is in a
CDialog-derived class, including dialog bars and property pages, you would typically enable the events as part of the
OnInitDialog handler. If it is part of a
CFormView-derived class, you would typically enable the events as part of the
But, what if you always want to get these events for a rich edit control, and don't want to be bothered doing this for every rich edit control you add?
In that case, what you do is handle it in the
PreSubclassWindow handler of your
PreSubclassWindow is a good place to do lots of useful and interesting "default initializations" that require an actual
HWND object exist (as opposed to a constructor, which can be executed far, far before there is an
HWND associated with the control). I discuss this in a separate article.
CFormView, etc., your enabling of
EN_CHANGE events would be done by the following sequence of operations, assuming that
c_MyEdit is a control variable that is bound to the
c_MyEdit.SetEventMask(ENM_CHANGE | c_MyEdit.GetEventMask());
By the way, has it struck you as a little odd that
CRichEditCtrl::GetEventMask returns a
CRichEditCtrl::SetEventMask requires a
DWORD? Do you wonder if anyone ever actually looked at these specifications before publishing them?
Now, we can add a
SetWindowTextNoNotify method to our
void CMyRichEditCtrl::SetWindowTextNoNotify(LPCTSTR s)
DWORD oldmask = CRichEditCtrl::GetEventMask();
DWORD newmask = oldmask & ~ENM_CHANGE;
Because we are able to avoid even generating any
EN_CHANGE notification, there is no need to have a reflected handler that will cause them to be ignored. They simply won't happen at all.
In one case, I overrode the
SetWindowText method by adding a new method:
void SetWindowText(LPCTSTR s, BOOL notify = TRUE);
and implemented it as:
void CMyRichEditCtrl::SetWindowTAext(LPCTSTR s, BOOL notify )
oldmask = CRichEditCtrl::GetEventMask();
newmask = oldmask &~ENM_CHANGE;
I have a slight preference for this latter approach, and it is left as an Exercise For The Reader to implement the corresponding function in the ordinary