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


Comments

  • Add Numeric Limits to the NumEdit Control

    Posted by sschindler on 10/02/2009 11:28am

    add the following member data:
        bool use_limits;
        double min_allowed;
        double max_allowed;
     
    and the following methods:
        void set_type(eType type) { myType = type; };
        void set_min_limit(double min) { min_allowed = min; };
        void set_max_limit(double max) { max_allowed = max; };
        void set_to_use_limits(bool use_them){ use_limits = use_them; };
    
    Then modify the following method:
    
    void CNumEdit::OnUpdate() 
    {
    	if (! myRejectingChange)
    	{
    		CString aValue;
    		GetWindowText(aValue);
    		LPTSTR aEndPtr;
    		union
    		{
    			long aLongValue;
    			double aDoubleValue;
    		};
    
    		errno = 0;
                    bool within_limits = false;
    		switch (myType)
    		{
    		case TypeLong:
    			aLongValue = _tcstol(aValue, &aEndPtr, 10);
                if (( use_limits && aLongValue >= min_allowed && aLongValue <= max_allowed )
                    ||( !use_limits ))
                {
                    within_limits = true;
                }
    			break;
    		case TypeDouble:
    			aDoubleValue = _tcstod(aValue, &aEndPtr);
                if (( use_limits && aDoubleValue >= min_allowed && aDoubleValue <= max_allowed )
                    ||( !use_limits ))
                {
                    within_limits = true;
                }
    			break;
    		}
    
    		if (!(*aEndPtr) 
                &&( errno != ERANGE )
                &&( within_limits ))
    		{
    		    myLastValidValue = aValue;
    		}
            else
            {
    			myRejectingChange = true;
    			SetWindowText(myLastValidValue);
    			myRejectingChange = false;
    			SetSel(myLastSel);
    		}
    	}
    }

    Reply
  • slight modification required

    Posted by ASanjay on 08/06/2004 01:38pm

    If u exceed the range in edit control, it wont allow to do,so far so good. Now this sets the errono and if you try backspace or delete it will fail. So each time it goes in else statement set errno=0 Sanjay

    Reply
  • few notes

    Posted by Legacy on 09/07/2002 12:00am

    Originally posted by: Bojan

    I did similar control some time ago. And I folowed the same logic.
    This is a very basic control which only checks if text is a valid number. Such low level controling can be achieved through OnChar() only, to keep it simple. But if you need some higher level of control like limiting the value, limiting decimal places etc, you need to include few more things:
    1) Clipboard support (right mouse click) for cut, paste and delete actions.
    2) VK_DELETE is a special key. Use WM_KEYDOWN to intercept it.
    3) Sometimes the parent of this control uses EN_CHANGE instead of KILLFOCUS message to retrieve the value from this control at every typed char. When restoring the latest valid value through SetWindowText(), an aditional EN_CHANGE is fired and there is danger of retrieving the invalid number.

    bojan.banko@pu.hinet.hr

    Reply
  • Does not MFC already do that/?

    Posted by Legacy on 09/05/2002 12:00am

    Originally posted by: Saeed

    .

    Beside is not Numeric value a property of the edit box that can be assigned when you introduve the ctrl?on the dialog?

    Reply
  • it IS MFC

    Posted by Legacy on 09/05/2002 12:00am

    Originally posted by: Xin

    Refer to Jeff's "Windows Programming in MFC, Second Edition". There's an almost identical example there.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds