Подклассы управления редактированием с учетом локали для десятичных чисел (формат [знак] [xxx …] [разделитель десятичных чисел] [гг …])

ВВЕДЕНИЕ И СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ:

у меня есть 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;
}

4

Решение

Принимая во внимание специфичные для региона настройки

Вы, конечно, можете делать все самостоятельно, однако у вас есть возможность использовать VarI4FromStr или аналогичный API, который делает грязные вещи для вас. Вы вводите строку, вы получаете LONG из. Локаль в курсе.

«Должен принимать только»

Вы не указываете, как именно контроль должен обеспечивать это. Что если входная строка недопустима? Контроль должен все еще принимать его, потому что, например, строка просто еще не действительна, и пользователь все еще печатает. Если вы проверяете ввод во внешнем обработчике, например, когда нажата кнопка OK, вам даже не нужно создавать подклассы. Если вы хотите проверять ввод каждый раз, когда он изменяется, вам не нужно создавать подклассы, так как у вас есть EN_CHANGE уведомления на родителя. Возможно, вы захотите создать подкласс по другим причинам.

Удобно принимать любой ввод и затем каким-либо образом указывать достоверность (например, подчеркивание красным, если оно недействительно) либо при изменении текста, либо при проверке ввода.

5

Другие решения

После учета кода от Требуется вставить одну строку в другую после получения текста из буфера обмена., Мне удалось создать процедуру создания подклассов, которая отвечает требованию.

Суть моего решения состоит в том, чтобы смоделировать поведение элемента управления редактирования, как упомянуто в этом посте, а затем проверить полученный текст.

При обращении 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);
}
2

По вопросам рекламы [email protected]