Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

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 C_SubclassedWindow and C_SignalSlot modules.  You can download these on our web site.

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, and the entered number is closely checked for each change.  This way only positive whole numbers can be entered by the user of the specified size and/or below the specified maximum value.  If an invalid key is pressed, or if the pressed key would generate a number that is above the pre-set upper limit, the event CNumberEditBoxIllegalEntryEvent will be raised.  Any paste operation is also checked in the same manner.  In this event's handler you can decide to handle the character press yourself.  When you do not handle it, the user will receive the standard Windows error beep.

Notes / todo's

  • Add support for negative numbers.

Files

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

NumberEditBox.h

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

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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"
#include "..\C_SignalSlot\Signal.h"

class CNumberEditBox;

// All sorts of illegal entry types
enum class ENumberEditBoxIllegalEntryType {
  eDigit,
  eChar,
  ePaste
};

// Raised when an illegal character is pressed
// Return true if the character has been processed anyway by the handler;
// this will prevent CNumberEditBox to generate a beep sound
struct CNumberEditBoxIllegalEntryEvent: public CEventTypeBoolOr {
  // The control itself
  CNumberEditBox* poControl;

  // The illegal character(s)
  ENumberEditBoxIllegalEntryType eType;
  std::wstring sIllegalEntry;
};

class CNumberEditBox:
  private CSubclassedWindow,
  public CSignal<CNumberEditBoxIllegalEntryEvent> {
public:
  // Special return values
  static const long cnError;

  // 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;
  void SetToEmpty() const;

  // Gets the content
  long Get() 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);

  // Gets the content
  bool Get(std::wstring& sValue) const;

  // Gets the textual content on the clipboard
  bool GetClipboardContent(std::wstring& sContent);

  // Validates the entry of the given text is valid
  bool EntryValid(
    const std::wstring& sText,
    bool bCertifiedNumeric,
    ENumberEditBoxIllegalEntryType eEntryType,
    bool& bEntryEaten
  );
};

#endif // INCLUDE_TWOLOGS_COMMON_NUMBEREDITBOX_H

NumberEditBox.cpp

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

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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.

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

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

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

// 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 {
  wchar_t sNumber[65];
  _ltow(nNumber, sNumber, 10);
  SetWindowText(m_hEditBox, sNumber);
}
void CNumberEditBox::SetToEmpty() const {
  SetWindowText(m_hEditBox, L"");
}

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

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

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

  // And return our status
  return bSuccess;
}

// Gets the content
long CNumberEditBox::Get() const {
  // Get the textual representation
  std::wstring sNumber;
  long nNumber;
  if (Get(sNumber)) {
    // Done -> extract the number from the text
    if (sNumber.size() == 0) {
      nNumber = 0;
    } else {
      nNumber = _wtol(sNumber.c_str());
    }
  } else {
    // Error -> note
    nNumber = cnError;
  }

  // And return the number
  return nNumber;
}

// Processes the given message
void CNumberEditBox::ProcessMessage(CMessage& oMsg) {
  // Look what we have here
  bool bMsgValid = true;
  bool bMsgEaten = false;
  if (oMsg.eCode == WM_PASTE) {
    // A paste -> get the text that will be pasted
    bMsgValid = false;
    std::wstring sToPaste;
    if (GetClipboardContent(sToPaste)) {
      // Done -> look if it contains illegal chars
      bMsgValid = EntryValid(
        sToPaste,
        false,
        ENumberEditBoxIllegalEntryType::ePaste,
        bMsgEaten
      );
    }
  } else if (oMsg.eCode == WM_CHAR) {
    // A character -> look what char is pressed
    bMsgValid = false;
    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 -> get the existing value
        std::wstring sEntry(1, oMsg.nWParam);
        bMsgValid = EntryValid(
          sEntry,
          true,
          ENumberEditBoxIllegalEntryType::eDigit,
          bMsgEaten
        );
        break;
      }
      case 3:         // Ctrl-C
      case 24:        // Ctrl-X
      case 26:        // Ctrl-Z
      case 22:        // Ctrl-V
      case VK_BACK: { // Backspace
        // Valid edit key -> pass through
        bMsgValid = true;
        break;
      }
      default: {
        // Key not allowed -> raise the event
        bMsgValid = false;
        CNumberEditBoxIllegalEntryEvent oEvent;
        oEvent.poControl = this;
        oEvent.sIllegalEntry = oMsg.nWParam;
        oEvent.eType = ENumberEditBoxIllegalEntryType::eChar;
        bMsgEaten =
          CSignal<CNumberEditBoxIllegalEntryEvent>::RaiseEvent(oEvent);
        break;
      }
    }
  }

  // And look if to allow the message
  oMsg.bStop = !bMsgValid;
  if (!bMsgValid) {
    // No -> notify the user, if needed
    if (!bMsgEaten) {
      MessageBeep(MB_ICONEXCLAMATION);
    }
  }
}

// Gets the textual content on the clipboard
bool CNumberEditBox::GetClipboardContent(std::wstring& sContent) {
  bool bRetrievedText = false;
  if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
    if (OpenClipboard(m_hEditBox)) {
      HGLOBAL hData = GetClipboardData(CF_UNICODETEXT);
      if (hData != NULL) {
        LPCWSTR sData = (LPCWSTR)GlobalLock(hData);
        if (sData != nullptr) {
          sContent = sData;
          GlobalUnlock(hData);
          bRetrievedText = true;
        }
      }
      CloseClipboard();
    }
  }
  return bRetrievedText;
}

// Validates the entry of the given text is valid
bool CNumberEditBox::EntryValid(
  const std::wstring& sText,
  bool bCertifiedNumeric,
  ENumberEditBoxIllegalEntryType eEntryType,
  bool& bEntryEaten
) {
  // Get the existing value
  std::wstring sCurrentValue;
  Get(sCurrentValue);

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

  // Replicate what the entry would result in
  std::wstring sNewValue = sCurrentValue;
  if (nSelEnd > nSelStart) {
    sNewValue.erase(
      sNewValue.begin() + nSelStart,
      sNewValue.begin() + nSelEnd
    );
  }
  sNewValue.insert(nSelStart, sText);

  // Determine if the entry is allowed
  if (!bCertifiedNumeric) {
    bCertifiedNumeric =
      sText.find_first_not_of(L"0123456789") == sText.npos;
  }
  long nNewValue = _wtol(sNewValue.c_str());
  bool bEntryValid = bCertifiedNumeric && nNewValue <= m_nUpperBound;
  if (bEntryValid) {
    // Yes -> indicate it's not eaten
    bEntryEaten = false;
  } else {
    // No -> raise the event
    CNumberEditBoxIllegalEntryEvent oEvent;
    oEvent.poControl = this;
    oEvent.sIllegalEntry = sText;
    oEvent.eType = eEntryType;
    bEntryEaten =
      CSignal<CNumberEditBoxIllegalEntryEvent>::RaiseEvent(oEvent);
  }

  // And return the verdict
  return bEntryValid;
}