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.
TNumberEditBox allows you to transform a regular Windows edit control into a numbers-only edit box.
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.
Each file belonging to this source code module is listed below.
/*******************************************************************************
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
/*******************************************************************************
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;
}