у меня есть edit control
что должен принимать только подписанный десятичные числа-что-то вроде -123.456
, Также, это должно быть с учетом местных особенностей, поскольку десятичный разделитель не одинаков для каждой страны, в США используется точка, в то время как в Европе это запятая и так далее.
До сих пор я использовал subclassing
реализовать это. Вот моя логика для реализации subclassing
, выраженный через псевдокод:
if ( ( character is not a [ digit,separator, or CTRL/Shift... ] OR
( char is separator and we already have one ) )
{
discard the character;
}
Сначала я сделал вспомогательную функцию, которая определяет, имеет ли массив char десятичный разделитель, например:
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;
}
А вот и subclass
ПроцедураЯ не учел знак минус:
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);
}
Одно важное замечание: Я могу загрузить текущие настройки языка пользователя в моем приложении, благодаря ответам на этот вопрос.
Есть ли лучший способ реализовать элемент управления для редактирования, который принимает только десятичные числа со знаком и знает ли локаль?
Если subclass
Это единственный способ, может ли мой код быть улучшен / оптимизирован?
Спасибо за ваше время и помощь.
С наилучшими пожеланиями.
Чтобы помочь вам еще больше, вот небольшое демонстрационное приложение, которое создает элемент управления редактирования и subclass
Если принять только десятичные числаопять же, я не реализовал часть для знака минус:
#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;
}
Вы, конечно, можете делать все самостоятельно, однако у вас есть возможность использовать VarI4FromStr
или аналогичный API, который делает грязные вещи для вас. Вы вводите строку, вы получаете LONG
из. Локаль в курсе.
Вы не указываете, как именно контроль должен обеспечивать это. Что если входная строка недопустима? Контроль должен все еще принимать его, потому что, например, строка просто еще не действительна, и пользователь все еще печатает. Если вы проверяете ввод во внешнем обработчике, например, когда нажата кнопка OK, вам даже не нужно создавать подклассы. Если вы хотите проверять ввод каждый раз, когда он изменяется, вам не нужно создавать подклассы, так как у вас есть EN_CHANGE
уведомления на родителя. Возможно, вы захотите создать подкласс по другим причинам.
Удобно принимать любой ввод и затем каким-либо образом указывать достоверность (например, подчеркивание красным, если оно недействительно) либо при изменении текста, либо при проверке ввода.
После учета кода от Требуется вставить одну строку в другую после получения текста из буфера обмена., Мне удалось создать процедуру создания подклассов, которая отвечает требованию.
Суть моего решения состоит в том, чтобы смоделировать поведение элемента управления редактирования, как упомянуто в этом посте, а затем проверить полученный текст.
При обращении VK_DELETE
выбранный текст удаляется, а затем результат анализируется, чтобы проверить, оставлен ли действительный десятичный формат. Если все в порядке, сообщение передается процедуре по умолчанию, в противном случае отбрасывается. Тот же метод выполняется для WM_CUT
, WM_CLEAR
, и для backspace
в WM_CHAR
обработчик (здесь мы должны защитить себя от сбоя приложения, получая доступ к элементу строки с помощью порядкового номера -1
Вот почему я добавил строку if ( start > 0 )
).
При обращении WM_PASTE
мы объединить текст элемента управления редактирования с текстом буфера обмена и затем мы анализируем полученную строку, чтобы проверить ее правильность. Опять же, если все в порядке, мы пропускаем сообщение, иначе мы отбрасываем его.
То же самое относится и к WM_CHAR
за исключением того, что здесь мы вставляем символ в выбранную часть текста элемента управления редактирования, а затем выполняем проверку достоверности.
Так как введенный текст всегда будет правильным, нам не нужно обрабатывать WM_UNDO
,
Наконец, вот код:
LRESULT CALLBACK Decimalni( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_KEYDOWN:
{
if( wParam == VK_DELETE )
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
if( end > start )
buffer.erase( start, end - start );
else
buffer.erase( start, 1 );
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
bool IsTextValid = true; // indicates validity of inputed text
// TODO: parse buffer
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: indicate error
return FALSE;
}
}
}
return ::DefSubclassProc( hwnd, message, wParam, lParam);;
break;
case WM_CLEAR:
case WM_CUT:
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
if( end > start )
buffer.erase( start, end - start );
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
// TODO: parse buffer
bool IsTextValid = true;
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: Indicate error
return FALSE;
}
}
break;
case WM_PASTE:
{
int len = GetWindowTextLength(hwnd);
std::wstring clipboard, wndtxt( len, 0 );
if( len > 0 )
GetWindowText( hwnd, &wndtxt[0], len + 1 );
if( !OpenClipboard(hwnd) )
return FALSE;
HANDLE hClipboardData;
if( hClipboardData = GetClipboardData(CF_UNICODETEXT) )
{
clipboard = (wchar_t*)GlobalLock(hClipboardData);
GlobalUnlock(hClipboardData);
}
CloseClipboard();
if( clipboard.empty() )
return FALSE;
DWORD start, end;
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
// merge strings into one
if( end > start )
wndtxt.replace( start, end - start, clipboard );
else
wndtxt.insert( start, clipboard );
// TODO: parse the text
bool ITextValid = true;
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
// TODO: indicate error
return FALSE;
}
}
break;
case WM_CHAR:
{
DWORD start, end;
int len = GetWindowTextLength(hwnd);
std::wstring buffer( len, 0 );
// get current window text
if( len > 0 )
GetWindowText( hwnd, &buffer[0], len + 1 );
// get current selection
SendMessage( hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end );
// allow copy/paste but leave backspace for special handler
if( ( wParam < 0x020 ) && ( wParam != 0x08 ) )
return ::DefSubclassProc( hwnd, message, wParam, lParam);}
// process backspace
if( wParam == 0x08 )
{
if( end > start )
buffer.erase( start, end - start );
else
if( start > 0 ) // it is safe to move back one place
buffer.erase( start - 1, 1 );
else // start-1 < 0 , can't access buffer[-1] !!
return FALSE;
if( buffer.empty() )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
// TODO: parse buffer
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
//TODO: indicate error
return FALSE;
}
}
// insert character and parse text
if( end > start )
buffer.replace( start, end - start, 1, (wchar_t)wParam );
else
buffer.insert( start, 1, (wchar_t)wParam );
// TODO: parse text
// process the result
if( IsTextValid )
return ::DefSubclassProc( hwnd, message, wParam, lParam);
else
{
//TODO: indicate error
return FALSE;
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, Decimalni, 0 );
break;
}
return ::DefSubclassProc( hwnd, message, wParam, lParam);
}