Creating a Numeric Edit Box

Environment: Windows + Visual C++ + MFC

Hi. I have a proposal for an Edit control that accepts numeric input only. The code is quite simple and, unlike most articles and comments on CodeGuru concerning this topic, it solves problems such as "do not accept more than one . and - characters", "- has to be the very first character, if present" and "Paste of non-numeric data". Moreover, the functions used are locale-aware (works with UNICODE). Finally, you can easily choose whether the control accepts only integer numbers or floating-point ones as well.

The basic idea is to create a new class based on CEdit, which catches an EN_UPDATE message. The message is sent to a control whenever the user changes the control's text. The important thing is that the message arrives before the change has been visualized. This gives us a chance to check the value. If it is correct, it will be set into the edit field. Otherwise, the latest valid value, which has been saved in an advisory variable, will be restored. All the hard work of testing the validity of the actual control's text remains on standard string-to-number conversion functions.

One side effect of restoring the latest valid value (by means of the SetWindowText() method) is that the caret is moved to the beginning of the text. Therefore, it was necessary to preserve the latest selection before the change took place. In addition, the SetWindowText() method will invoke one extra EN_UPDATE. A boolean member variable will be used to detect and ignore it.

Please find the important parts of the code below. Please add a method to set the number type that you need to be accepted or alter the constructor. The code needs to be somewhat polished to allow the user to enter an exponent in a natural way...

Important part of the .H file:

class CNumEdit : public CEdit
{
public:
  enum eType
  {
    TypeLong,
    TypeDouble
  };

protected:
  eType myType;
  CString myLastValidValue;
  UINT myLastSel;
  bool myRejectingChange;

  ...
}

Important part of the .CPP file:

#include <errno.h>

...

CNumEdit::CNumEdit()
{
  myType = TypeDouble;
  myRejectingChange = false;
}

...

BEGIN_MESSAGE_MAP(CNumEdit, CEdit)
  //{{AFX_MSG_MAP(CNumEdit)
  ON_CONTROL_REFLECT(EN_UPDATE, OnUpdate)
  ON_WM_CHAR()
  ...
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

...

void CNumEdit::OnUpdate()
{
  if (! myRejectingChange)
  {
    CString aValue;
    GetWindowText(aValue);
    LPTSTR aEndPtr;
    union
    {
      long aLongValue;
      double aDoubleValue;
    };

    errno = 0;
    switch (myType)
    {
    case TypeLong:
      aLongValue = _tcstol(aValue, &aEndPtr, 10);
      break;
    case TypeDouble:
      aDoubleValue = _tcstod(aValue, &aEndPtr);
      break;
    }

    if (! (*aEndPtr) && errno != ERANGE)
    {
      myLastValidValue = aValue;
    }
    else
    {
      myRejectingChange = true;
      SetWindowText(myLastValidValue);
      myRejectingChange = false;
      SetSel(myLastSel);
    }
  }
}


void CNumEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  switch (nChar)
  {
  case VK_BACK:
  case VK_RETURN:
  case 0x0A:               // Shift+Enter (= linefeed)
  case VK_ESCAPE:
  case VK_TAB:
    break;
  default:
    myLastSel = GetSel();
    GetWindowText(myLastValidValue);
    break;
  }

  CEdit::OnChar(nChar, nRepCnt, nFlags);
}

Downloads

Download demo project source - 11 Kb