Avoiding EN_CHANGE notifications

Home
Back To Tips Page

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 essay 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

To avoid EN_CHANGE notifications in a CEdit control, you must create a subclass of the control.  In my case, it was called CNoNotifyEdit.

To this subclass, and a "Reflected event handler" for EN_CHANGE, which is indicated by the =EN_CHANGE notification:

Next, hand-edit the resulting header file and implementation file.

In the Message Map, change the line that says

ON_CONTROL_REFLECT(EN_CHANGE, OnEnChange)

to read

ON_CONTROL_REFLECT_EX(EN_CHANGE, OnEnChange)

This requires a change in the prototype for the method, so change

public:
afx_msg void OnEnChange();

to

protected:
afx_msg BOOL OnEnChange();

Note that I not only change the result type from void to BOOL, but I also change the scope to be protected.  This is 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,

public:
void SetWindowTextNoNotify(LPCTSTR s);

and add a variable

protected:
BOOL notify;

Initialize the notify variable in the constructor:

CNoNotifyEdit::CNoNotifyEdit()
{
 notify = TRUE;
}

The implementation of SetWindowTextNoNotify is:

void CNoNotifyEdit::SetWindowTextNoNotify(LPCTSTR s)
    { 
     CString old;
     CEdit::GetWindowText(old);
     if(old == s)
        return; // do nothing, already set
     BOOL previous = notify;
     notify = FALSE;
     CEdit::SetWindowText(s);
     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:

BOOL CNoNotifyEdit::OnChange() 
{
 return !notify;
}

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 notify is FALSE (no notification desired), the handler returns TRUE.  But if notify is 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

    // TODO:  If this is a RICHEDIT control, the control will not
    // send this notification unless you override the CEdit::OnInitDialog()
    // function and call CRichEditCtrl().SetEventMask()
    // with the ENM_CHANGE flag ORed into the mask.

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 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 OnInitialUpdate handler.

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 CRichEditCtrl-derived subclass.  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 essay.

For a CDialog, CPropertyPage, 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 HWND

c_MyEdit.SetEventMask(ENM_CHANGE | c_MyEdit.GetEventMask());

By the way, has it struck you as a little odd that CRichEditCtrl::GetEventMask returns a long while CRichEditCtrl::SetEventMask requires a DWORD?  Do you wonder if anyone every actually looked at these specifications before publishing them?

Now we can add a SetWindowTextNoNotify method to our CRichEditCtrl-derived class:

void CMyRichEditCtrl::SetWindowTextNoNotify(LPCTSTR s)
   {
    DWORD oldmask = CRichEditCtrl::GetEventMask();
    DWORD newmask = oldmask & ~ENM_CHANGE;
    CRichEditCtrl::SetEventMask(newmask);
    CRichEditCtrl::SetWindowText(s);
    CRichEditCtrl::SetEventMask(oldmask);
   }

Because we are able to avoid even generating any EN_CHANGE notifications, 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 /* = TRUE */)
   {
    DWORD oldmask;
    DWORD newmask;
    if(!notify)
       {
        oldmask = CRichEditCtrl::GetEventMask();
        newmask = oldmask &~ENM_CHANGE;
        CRichEditCtrl::SetEventMask(newmask);
       }
    CRichEditCtrl::SetWindowText(s);
    if(!notify)
       CRichEditCtrl::SetEventMask(oldmask);
   }

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 CEdit control.

[Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright © 2006, The Joseph M. Newcomer Co. All Rights Reserved
Last modified: May 14, 2011