Skip navigation links

Source code for C_NumberEditBox

Download

The source files for this module are listed below.  You can also download the module C_NumberEditBox as a zip archive; this archive contains all the source files and documentation.

Description

CNumberEditBox allows you to transform a regular Windows edit control into a numbers-only edit box.

Information

This source code also uses our CSubclassedWindow class.  You can also download it at our website.

To transform a regular Windows edit box into a numbers-only edit box, you have to create an instance of the CNumberEditBox class.  To transform an edit box, initialization can be done in two ways; either pass in the window handle of the dialog it is on and the resource ID of the edit box, or pass in the edit box control's handle directly.  You also have to specify the numeric upper limit that may be accepted in the edit box and the maximum number of digits to allow.

Once the edit box is set up, you can set it's value using the SetTo method.  This method puts the number you specify into the edit box itself.  Use the method Get to consecutively get the current value of the edit box.

There are two special 'numeric' values defined that the SetTo and Get methods use.  The value cnError is returned when the Get method encounters an error when retrieving the value from the edit box itself.  The value cnNoValue is returned by Get to indicate no value was entered in the edit box (an empty edit box).  You can opt to have '0' returned instead by specifying this as a parameter to the Get method.  The SetTo method also accepts the special value cnNoValue; it will empty the edit box.

The user is only allowed to enter the digits 0-9, and no other characters.  Pasting into the edit box is also disabled.  If an invalid key is pressed, or if the pressed key would generate a number that is above the pre-set upper limit, the user will receive the standard Windows error beep.  This way only positive whole numbers can be entered by the user of the specified size and/or below the specified maximum value.

Notes / todo's

  • Add support for pasting into the edit box.
  • Add support for negative numbers.

Files

Each file belonging to this source code module is listed below.

NumberEditBox.h

/*******************************************************************************

  Version: 2
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  http://www.twologs.com/en/resources/sourcecode.asp

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/
#ifndef INCLUDE_TWOLOGS_COMMON_NUMBEREDITBOX_H
#define INCLUDE_TWOLOGS_COMMON_NUMBEREDITBOX_H

#include "..\C_SubclassedWindow\SubclassedWindow.h"

class CNumberEditBox: private CSubclassedWindow {
public:
  // Special return values
  static const long cnError;
  static const long cnNoValue;

  // Con- & destructor
  CNumberEditBox();
  virtual ~CNumberEditBox();

  // Initializes the edit box
  void Initialize(
    HWND hDlg,
    DWORD nEditBoxID,
    long nUpperBound,
    long nMaxDigits
  );
  void Initialize(
    HWND hControl,
    long nUpperBound,
    long nMaxDigits
  );

  // Sets the content to the given number
  void SetTo(long nNumber) const;

  // Gets the content
  long Get(bool bNoValueIsNul = false) const;

private:
  // The upper bound to use
  long m_nUpperBound;

  // The edit box's window handle
  HWND m_hEditBox;

  // Processes the given message
  void ProcessMessage(CMessage& oMsg);
};

#endif // INCLUDE_TWOLOGS_COMMON_NUMBEREDITBOX_H

NumberEditBox.cpp

#include "NumberEditBox.h"
#include <string>

// Special return values
const long CNumberEditBox::cnError = (long)0xFFFFFFFF;
const long CNumberEditBox::cnNoValue = (long)0xFFFFFFFE;

// Con- & destructor
CNumberEditBox::CNumberEditBox():
 m_nUpperBound(0),
 m_hEditBox(NULL) {
}
CNumberEditBox::~CNumberEditBox() {
}

// Initializes the edit box
void CNumberEditBox::Initialize(
  HWND hDlg,
  DWORD nEditBoxID,
  long nUpperBound,
  long nMaxDigits) {
  Initialize(GetDlgItem(hDlg, nEditBoxID), nUpperBound, nMaxDigits);
}
void CNumberEditBox::Initialize(
  HWND hControl,
  long nUpperBound,
  long nMaxDigits) {
  // Note the restraints on the edit box
  m_nUpperBound = nUpperBound;

  // Make us control the edit box
  m_hEditBox = hControl;
  Subclass(m_hEditBox);

  // And apply the maximum number of digits to allow
  SendMessage(m_hEditBox, EM_LIMITTEXT, nMaxDigits + 1, 0);
}

// Sets the content to the given number
void CNumberEditBox::SetTo(long nNumber) const {
  char sNumber[30];
  if (nNumber == cnNoValue) {
    sNumber[0] = '\0';
  } else {
    ltoa(nNumber, sNumber, 10);
  }
  SetWindowText(m_hEditBox, sNumber);
}

// Gets the content
long CNumberEditBox::Get(bool bNoValueIsNul) const {
  // Get the textual representation
  bool bRetrieving = true;
  bool bSuccess = true;
  std::string sNumber;
  DWORD nTextSize = 256;
  do {
    // Make room for the current size
    sNumber.resize(nTextSize + 1);

    // Get the text
    DWORD nRetrievedSize = GetWindowText(
      m_hEditBox,
      const_cast<char*>(sNumber.c_str()),
      nTextSize
    );
    if (nRetrievedSize == 0) {
      // Possible error -> check
      bSuccess = GetLastError() == 0;
      bRetrieving = false;
      sNumber = "";
    } else if (nRetrievedSize == nTextSize) {
      // Possible truncation -> enlarge
      nTextSize = nTextSize * 2;
    } else {
      // Done -> truncate the content to the correct size
      sNumber.resize(nRetrievedSize);

      // And exit retrieving
      bRetrieving = false;
    }
  } while (bRetrieving && bSuccess);

  // Look if successfull
  long nNumber;
  if (bSuccess) {
    // Yes -> extract the number from the text
    if (sNumber.size() == 0) {
      if (bNoValueIsNul) {
        nNumber = 0;
      } else {
        nNumber = cnNoValue;
      }
    } else {
      nNumber = atol(sNumber.c_str());
    }
  } else {
    // No -> note
    nNumber = cnError;
  }

  // And return the number
  return nNumber;
}

// Processes the given message
void CNumberEditBox::ProcessMessage(CMessage& oMsg) {
  // Look if it's a char message
  oMsg.bStop = false;
  if (oMsg.eCode == WM_CHAR) {
    // Yes -> look what char is pressed
    switch (oMsg.nWParam) {
      case 0x30:    // 0
      case 0x31:    // 1
      case 0x32:    // 2
      case 0x33:    // 3
      case 0x34:    // 4
      case 0x35:    // 5
      case 0x36:    // 6
      case 0x37:    // 7
      case 0x38:    // 8
      case 0x39: {  // 9
        // Numeric, but possibly invalid end result -> get the existing value
        char sBuffer[30];
        int nResult = GetWindowText(m_hEditBox, sBuffer, 29);
        std::string sOldValue = nResult == 0? "": sBuffer;

        // Look where this char will be entered
        DWORD nSelStart;
        DWORD nSelEnd;
        SendMessage(m_hEditBox, EM_GETSEL, (WPARAM)&nSelStart, (LPARAM)&nSelEnd);

        // Replicate what would happen if this digit was entered
        if (nSelEnd > nSelStart) {
          sOldValue.erase(
            sOldValue.begin() + nSelStart,
            sOldValue.begin() + nSelEnd
          );
        }
        char sNewDigit[2];
        sNewDigit[0] = oMsg.nWParam;
        sNewDigit[1] = '\0';
        sOldValue.insert(nSelStart, sNewDigit);

        // And determine if the new digit is allowed
        int nNewValue = atol(sOldValue.c_str());
        oMsg.bStop = nNewValue > m_nUpperBound;
        break;
      }
      case 3:           // Ctrl-C
      case 24:          // Ctrl-X
      case 26:          // Ctrl-Z
      case VK_END:      // End
      case VK_HOME:     // Home
      case VK_PRIOR:    // Page up
      case VK_NEXT:     // Page down
      case VK_LEFT:     // Cursor left
      case VK_UP:       // Cursor up
      case VK_RIGHT:    // Cursor right
      case VK_DOWN:     // Cursor down
      case VK_INSERT:   // Insert
      case VK_BACK:     // Backspace
      case VK_DELETE: { // Delete
        // Valid key -> pass through
        break;
      }
      default: {
        // Key not allowed -> do not pass
        oMsg.bStop = true;
        break;
      }
    }
  }

  // And look if to allow the message
  if (oMsg.bStop) {
    // No -> notify the user
    MessageBeep(MB_ICONEXCLAMATION);
  }
}