1
votes

I am using Visual Studio 2013 and making MFC Dialog based application. I am running into strange issue with Kill Focus of Edit Control.

Please see below:

==========================================================================

In my application, I have two Edit Controls on Dialog Box.

1st Edit Control -> IDC_EDIT_QUALITY1
2nd Edit Control -> IDC_EDIT_QUALITY2

I have handled both's EN_KILLFOCUS event to validate the value.

BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
    ON_EN_KILLFOCUS(IDC_EDIT_QUALITY1, &CTestDlg::OnQuality1EditKillFocus)
    ON_EN_KILLFOCUS(IDC_EDIT_QUALITY2, &CTestDlg::OnQuality2EditKillFocus)
END_MESSAGE_MAP()

void CTestDlg::OnQuality1EditKillFocus()
{
    ValidateQualityParams(IDC_EDIT_QUALITY1);
}

void CTestDlg::OnQuality2EditKillFocus()
{
    ValidateQualityParams(IDC_EDIT_QUALITY2);
}

#define MIN_QUALITY_VALUE     1     
#define MAX_QUALITY_VALUE   100

void CTestDlg::ValidateQualityParams(int qualityParamID)
{
    CString strQuality1;
    if (IDC_EDIT_QUALITY1 == qualityParamID)
    {
        m_ctrlQuality1.GetWindowText(strQuality1);
        if ((_ttoi(strQuality1) < MIN_QUALITY_VALUE) || (_ttoi(strQuality1) > MAX_QUALITY_VALUE))
        {
            CString strMessage;
            strMessage.Format(_T("Quality1 value must be between %d to %d."), MIN_QUALITY_VALUE, MAX_QUALITY_VALUE);
            **AfxMessageBox(strMessage);**
            m_ctrlQuality1.SetSel(0, -1);
            m_ctrlQuality1.SetFocus();
            return;
        }
    }

    CString strQuality2;
    if (IDC_EDIT_QUALITY2 == qualityParamID)
    {
        m_ctrlQuality2.GetWindowText(strQuality2);
        if ((_ttoi(strQuality2) < MIN_QUALITY_VALUE) || (_ttoi(strQuality2) > MAX_QUALITY_VALUE))
        {
            CString strMessage;
            strMessage.Format(_T("Quality2 value must be between %d to %d."), MIN_QUALITY_VALUE, MAX_QUALITY_VALUE);
            AfxMessageBox(strMessage);
            m_ctrlQuality2.SetSel(0, -1);
            m_ctrlQuality2.SetFocus();
            return;
        }
    }
}

Now, the issue happens when, after changing the value in 1st Edit Control (IDC_EDIT_QUALITY1), say entering 0 in it and pressing TAB key, the flow goes as below:

  • void CTestDlg::OnQuality1EditKillFocus() is called.
  • It calls ValidateQualityParams(IDC_EDIT_QUALITY1)
  • Inside ValidateQualityParams, it goes to if (IDC_EDIT_QUALITY1 == qualityParamID) condition.
  • As the value I entered is less than MIN_QUALITY_VALUE, so it shows the Message by calling AfxMessageBox.
    - Now, even from the callstack of AfxMessageBox, it hits void CTestDlg::OnQuality2EditKillFocus() internally.

Although callstack of OnQuality1EditKillFocus is NOT finished yet, OnQuality2EditKillFocus gets called from the callstack of AfxMessageBox.

I don't understand the cause of this issue. Has anyone encountered such issue before?

In my resource.h, I have two distinct values for IDC_EDIT_QUALITY1 and IDC_EDIT_QUALITY2

 #define IDC_EDIT_QUALITY1               1018
 #define IDC_EDIT_QUALITY2               1020

Please help on this issue.

2
Why not just use the standard validation properties provided with MFC? When you add your variable to the control you can tell it the valid ranges and then the system uses it? - Andrew Truckle

2 Answers

0
votes

When you pressed TAB key, IDC_EDIT_QUALITY2 got focus. But because value entered was out of bound, the program called m_ctrlQuality1.SetFocus(), which in turn caused OnQuality2EditKillFocus() to get called. Add a member variable says m_bQuality1OutOfBound and set it to true right before calling m_ctrlQuality1.SetFocus(). In OnQuality2EditKillFocus(), when m_bQuality1OutOfBound is true, set it to false and don't call ValidateQualityParams(IDC_EDIT_QUALITY2).

0
votes

I believe the EN_KILLFOCUS notification for the IDC_EDIT_QUALITY2 control you are receiving is caused not by the m_ctrlQuality1.SetFocus() call, but instead by the AfxMessageBox() call.

When you press the [Tab] key IDC_EDIT_QUALITY1 loses the focus, and IDC_EDIT_QUALITY2 gets the focus. Then you receive the EN_KILLFOCUS notification for IDC_EDIT_QUALITY1. You display the error-message, which causes the application to "yield" (start processing messages again), while the message-box is displayed. The m_ctrlQuality1.SetFocus() call won't take place before the AfxMessageBox() returns, ie before you close the message-box, and therefore the EN_KILLFOCUS notification for IDC_EDIT_QUALITY2 can't be the result of that call. I guess it's the result of displaying the message-box (IDC_EDIT_QUALITY2 has got the focus, but the message-box makes it lose it).

You may work around it by adding a memeber variable, as Staytuned123 suggested, but in a different setting: name it, say m_bKillFocusProcessing, and set it to TRUE while you are processing ANY EN_KILLFOCUS notification (AfxMessageBox() plus SetFocus()), and to FALSE when you are done processing it; if it's already TRUE exit without doing anything. That is, only one EN_KILLFOCUS notification may be processed at a time.

However, such a user-interface (displaying a message-box on exiting a field) is rather weird. And why reinvent the wheel and not instead use the DDX/DDV feature, which MFC already offers? You can define member variables associated with controls, and perform various checks, including range-check. Call UpdateData(TRUE) to perform the checks (for all controls on the dialog) and transfer the data to the member variables. Or you can put some error-displaying controls (usually in red color), activated when an error is found, like in .net or the web.