I have an edit control
that should accept only signed decimal numbers-something like -123.456
. Also, it should be locale aware, since decimal separator is not the same for every country-in US dot is used, while in Europe it is comma and so on.
So far I have used subclassing
to implement this. Here is my logic for implementing the subclassing
, expressed through pseudo code:
if ( ( character is not a [ digit,separator, or CTRL/Shift... ] OR
( char is separator and we already have one ) )
{
discard the character;
}
First I have made a helper function that determines if the char array already has a decimal separator, like this:
bool HasDecimalSeparator( wchar_t *test )
{
// get the decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
bool p = false; // text already has decimal separator?
size_t i = 0; // needed for while loop-iterator
// go through entire array and calculate the value of the p
while( !( p = ( test[i] == szBuffer[0] ) ) && ( i++ < wcslen(test) ) );
return p;
}
And here is the subclass
ing procedure-I haven't taken minus sign into account:
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
// get decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
wchar_t t[50]; // here we store edit control's current text
memset( &t, L'\0', sizeof(t) );
// get edit control's current text
GetWindowText( hwnd, t, 50 );
// if ( ( is Not a ( digit,separator, or CTRL/Shift... )
// || ( char is separator and we already have one ) )
// discard the character
if( ( !( isdigit(wParam) || ( wParam == szBuffer[0] ) )
&& ( wParam >= L' ' ) ) // digit/separator/... ?
|| ( HasDecimalSeparator(t) // has separator?
&& ( wParam == szBuffer[0] ) ) )
{
return 0;
}
}
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
One important note: I am able to load current user locale settings in my application, thanks to the answers to this question.
Is there a better way to implement an edit control that accepts signed decimal numbers only, and is locale aware?
If subclass
ing is the only way, can my code be further improved/optimized ?
Thank you for your time and help.
Best regards.
To help you even further, here is a small demo application that creates an edit control and subclass
es it to accept only decimal numbers-again, I haven't implemented the part for the minus sign:
#include <windows.h>
#include <commctrl.h>
#include <stdlib.h>
#include <locale.h>
#pragma comment( lib, "comctl32.lib")
const wchar_t g_szClassName[] = L"myWindowClass";
bool HasDecimalSeparator( wchar_t *test )
{
// get the decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
bool p = false; // text already has decimal separator?
size_t i = 0; // needed for while loop-iterator
// go through entire array and calculate the value of the p
while( !( p = ( test[i] == szBuffer[0] ) ) && ( i++ < wcslen(test) ) );
return p;
}
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
// get decimal separator
wchar_t szBuffer[5];
GetLocaleInfo ( LOCALE_USER_DEFAULT,
LOCALE_SDECIMAL,
szBuffer,
sizeof(szBuffer) / sizeof(szBuffer[0] ) );
wchar_t t[50]; // here we store edit control's current text
memset( &t, L'\0', sizeof(t) );
// get edit control's current text
GetWindowText( hwnd, t, 50 );
// if ( ( is Not a ( digit,separator, or CTRL/Shift... )
// || ( char is separator and we already have one ) )
// discard the character
if( ( !( isdigit(wParam) || ( wParam == szBuffer[0] ) )
&& ( wParam >= L' ' ) ) // digit/separator/... ?
|| ( HasDecimalSeparator(t) // has separator?
&& ( wParam == szBuffer[0] ) ) )
{
return 0;
}
}
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
/************* load current locale settings *************/
// max. len: language, country, code page
wchar_t lpszLocale[64+64+16+3] = L"";
wchar_t lpszVal[128];
LCID nLCID = ::GetUserDefaultLCID(); // current LCID for user
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGLANGUAGE, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, lpszVal ); // language
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGCOUNTRY, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, L"_" ); // append country/region
wcscat_s( lpszLocale, 147, lpszVal );
if ( ::GetLocaleInfo( nLCID,
LOCALE_IDEFAULTANSICODEPAGE, lpszVal, 128 ) )
{
// missing code page or page number 0 is no error
// (e.g. with Unicode)
int nCPNum = _wtoi(lpszVal);
if (nCPNum >= 10)
{
wcscat_s( lpszLocale, 147, L"." ); // append code page
wcscat_s( lpszLocale, 147, lpszVal );
}
}
}
}
// set locale and LCID
_wsetlocale( LC_ALL, lpszLocale );
::SetThreadLocale(nLCID);
/*************************************************/
HWND hEdit1;
hEdit1 = CreateWindowEx(0, L"EDIT", L"",
WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
50, 100, 100, 20,
hwnd, (HMENU)8001, GetModuleHandle(NULL), NULL);
SetWindowSubclass( hEdit1, Decimalni, 0, 0);
}
break;
case WM_SETTINGCHANGE:
if( !wParam && !wcscmp( (wchar_t*)lParam, L"intl" ) )
{
// max. len: language, country, code page
wchar_t lpszLocale[64+64+16+3] = L"";
wchar_t lpszVal[128];
LCID nLCID = ::GetUserDefaultLCID(); // current LCID for user
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGLANGUAGE, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, lpszVal ); // language
if ( ::GetLocaleInfo( nLCID, LOCALE_SENGCOUNTRY, lpszVal, 128 ) )
{
wcscat_s( lpszLocale, 147, L"_" ); // append country/region
wcscat_s( lpszLocale, 147, lpszVal );
if ( ::GetLocaleInfo( nLCID,
LOCALE_IDEFAULTANSICODEPAGE, lpszVal, 128 ) )
{
// missing code page or page number 0 is no error
// (e.g. with Unicode)
int nCPNum = _wtoi(lpszVal);
if (nCPNum >= 10)
{
wcscat_s( lpszLocale, 147, L"." ); // append code page
wcscat_s( lpszLocale, 147, lpszVal );
}
}
}
}
// set locale and LCID
_wsetlocale( LC_ALL, lpszLocale );
::SetThreadLocale(nLCID);
return 0L;
}
else
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
0,
g_szClassName,
L"theForger's Tutorial Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 480, 320,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
You certainly can do everything yourself, however you have an option to use VarI4FromStr
or similar API which does dirty stuff for you. You put string in, you get LONG
out. Locale aware.
You don't specify how the control should enforce this exactly. What if the input string is not valid? Control should be still accepting it because, for example, string is just not yet valid and user is still typing. If you are validating the input in external handler, such as when OK button is pressed, then you don't even need to subclass. If you want to check input every time it changes, you don't need to subclass either since you have EN_CHANGE
notifications on parent. You might want to subclass for other reasons though.
It is user-friendly to accept any input and then indicate validity somehow (such as underlining with red if invalid) either on text change or on input validation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With