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

TNumberEditBox 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 TNumberEditBox 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 TNumberEditBox::error is returned when the Get method encounters an error when retrieving the value from the edit box itself.  The value TNumberEditBox::noValue 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 TNumberEditBox::noValue; 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 TNumberEditBoxIllegalEntryEvent 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 TNumberEditBox;

// All sorts of illegal entry types
enum class TNumberEditBoxIllegalEntryType {
  digits,
  chars,
  paste
};

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

  // The illegal character(s)
  TNumberEditBoxIllegalEntryType m_type;
  std::wstring m_illegalEntry;
};

class TNumberEditBox:
  private TSubclassedWindow,
  public TSignal<TNumberEditBoxIllegalEntryEvent> {
public:
  // Special return values
  static const long error;
  static const long noValue;

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

  // Initializes the edit box
  void Initialize(
    HWND dlgWnd,
    DWORD editBoxID,
    long upperBound,
    long maxDigits
  );
  void Initialize(
    HWND controlWnd,
    long upperBound,
    long maxDigits
  );

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

  // Gets the content
  long Get(bool return0WhenEmpty = true) const;

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

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

  // Processes the given message
  void ProcessMessage(TMessage& msg);

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

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

  // Validates the entry of the given text is valid
  bool EntryValid(
    const std::wstring& text,
    bool certifiedNumeric,
    TNumberEditBoxIllegalEntryType entryType,
    bool& entryEaten
  );
};

#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 TNumberEditBox::error = (long)0xFFFFFFFF;
const long TNumberEditBox::noValue = (long)0xFFFFFFFE;

// Con- & destructor
TNumberEditBox::TNumberEditBox():
 m_upperBound(0),
 m_editBoxWnd(NULL) {
}
TNumberEditBox::~TNumberEditBox() {
}

// Initializes the edit box
void TNumberEditBox::Initialize(
  HWND dlgWnd,
  DWORD editBoxID,
  long upperBound,
  long maxDigits) {
  Initialize(GetDlgItem(dlgWnd, editBoxID), upperBound, maxDigits);
}
void TNumberEditBox::Initialize(
  HWND controlWnd,
  long upperBound,
  long maxDigits) {
  // Note the restraints on the edit box
  m_upperBound = upperBound;

  // Make us control the edit box
  m_editBoxWnd = controlWnd;
  Subclass(m_editBoxWnd);

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

// Sets the content to the given number
void TNumberEditBox::SetTo(long number) const {
  wchar_t numberText[65];
  if (number == noValue) {
    numberText[0] = L'\0';
  } else {
    _ltow(number, numberText, 10);
  }
  SetWindowText(m_editBoxWnd, numberText);
}
void TNumberEditBox::SetToEmpty() const {
  SetWindowText(m_editBoxWnd, L"");
}

// Gets the content
bool TNumberEditBox::Get(std::wstring& valueText) const {
  // Get the textual representation
  bool retrieving = true;
  bool allOK = true;
  DWORD textSize = 256;
  do {
    // Make room for the current size
    valueText.resize(textSize + 1);

    // Get the text
    DWORD retrievedSize = GetWindowText(
      m_editBoxWnd,
      const_cast<wchar_t*>(valueText.c_str()),
      textSize
    );
    if (retrievedSize == 0) {
      // Possible error -> check
      allOK = GetLastError() == 0;
      retrieving = false;
      valueText = L"";
    } else if (retrievedSize == textSize) {
      // Possible truncation -> enlarge
      textSize = textSize * 2;
    } else {
      // Done -> truncate the content to the correct size
      valueText.resize(retrievedSize);

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

  // And return our status
  return allOK;
}

// Gets the content
long TNumberEditBox::Get(bool return0WhenEmpty) const {
  // Get the textual representation
  std::wstring numberText;
  long number;
  if (Get(numberText)) {
    // Done -> extract the number from the text
    if (numberText.size() == 0) {
      number = return0WhenEmpty? 0: noValue;
    } else {
      number = _wtol(numberText.c_str());
    }
  } else {
    // Error -> note
    number = error;
  }

  // And return the number
  return number;
}

// Processes the given message
void TNumberEditBox::ProcessMessage(TMessage& msg) {
  // Look what we have here
  bool msgValid = true;
  bool msgEaten = false;
  if (msg.code == WM_PASTE) {
    // A paste -> get the text that will be pasted
    msgValid = false;
    std::wstring toPaste;
    if (GetClipboardContent(toPaste)) {
      // Done -> look if it contains illegal chars
      msgValid = EntryValid(
        toPaste,
        false,
        TNumberEditBoxIllegalEntryType::paste,
        msgEaten
      );
    }
  } else if (msg.code == WM_CHAR) {
    // A character -> look what char is pressed
    msgValid = false;
    switch (msg.wParam) {
      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 valueText
        std::wstring entry(1, msg.wParam);
        msgValid = EntryValid(
          entry,
          true,
          TNumberEditBoxIllegalEntryType::digits,
          msgEaten
        );
        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
        msgValid = true;
        break;
      }
      default: {
        // Key not allowed -> raise the event
        msgValid = false;
        TNumberEditBoxIllegalEntryEvent event;
        event.m_controlPtr = this;
        event.m_illegalEntry = msg.wParam;
        event.m_type = TNumberEditBoxIllegalEntryType::chars;
        msgEaten =
          TSignal<TNumberEditBoxIllegalEntryEvent>::RaiseEvent(event);
        break;
      }
    }
  }

  // And look if to allow the message
  msg.stop = !msgValid;
  if (!msgValid) {
    // No -> notify the user, if needed
    if (!msgEaten) {
      MessageBeep(MB_ICONEXCLAMATION);
    }
  }
}

// Gets the textual content on the clipboard
bool TNumberEditBox::GetClipboardContent(std::wstring& content) {
  bool retrievedText = false;
  if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
    if (OpenClipboard(m_editBoxWnd)) {
      HGLOBAL dataPtr = GetClipboardData(CF_UNICODETEXT);
      if (dataPtr != NULL) {
        LPCWSTR data = (LPCWSTR)GlobalLock(dataPtr);
        if (data != nullptr) {
          content = data;
          GlobalUnlock(dataPtr);
          retrievedText = true;
        }
      }
      CloseClipboard();
    }
  }
  return retrievedText;
}

// Validates the entry of the given text is valid
bool TNumberEditBox::EntryValid(
  const std::wstring& text,
  bool certifiedNumeric,
  TNumberEditBoxIllegalEntryType entryType,
  bool& entryEaten
) {
  // Get the existing valueText
  std::wstring currentValueText;
  Get(currentValueText);

  // Look where this entry will be entered
  DWORD selStart;
  DWORD selEnd;
  SendMessage(
    m_editBoxWnd,
    EM_GETSEL,
    (WPARAM)&selStart,
    (LPARAM)&selEnd
  );

  // Replicate what the entry would result in
  std::wstring newValueText = currentValueText;
  if (selEnd > selStart) {
    newValueText.erase(
      newValueText.begin() + selStart,
      newValueText.begin() + selEnd
    );
  }
  newValueText.insert(selStart, text);

  // Determine if the entry is allowed
  if (!certifiedNumeric) {
    certifiedNumeric =
      text.find_first_not_of(L"0123456789") == text.npos;
  }
  long newValue = _wtol(newValueText.c_str());
  bool entryValid = certifiedNumeric && newValue <= m_upperBound;
  if (entryValid) {
    // Yes -> indicate it's not eaten
    entryEaten = false;
  } else {
    // No -> raise the event
    TNumberEditBoxIllegalEntryEvent event;
    event.m_controlPtr = this;
    event.m_illegalEntry = text;
    event.m_type = entryType;
    entryEaten =
      TSignal<TNumberEditBoxIllegalEntryEvent>::RaiseEvent(event);
  }

  // And return the verdict
  return entryValid;
}