4

紹介と関連情報:

のような符号付き10 進数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;
}

そして、ここにsubclassing 手順があります- 私はマイナス記号を考慮していません:

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);
}

1 つの重要な注意:この質問への回答のおかげで、アプリケーションに現在のユーザー ロケール設定を読み込むことができます。

質問:

符号付き 10 進数のみを受け入れ、ロケールを認識するエディット コントロールを実装するより良い方法はありますか?

subclassing が唯一の方法である場合、コードをさらに改善/最適化できますか?

お時間をいただき、ありがとうございました。

よろしくお願いします。

付録:

さらに役立つように、エディット コントロールを作成し、subclass10 進数のみを受け入れるようにする小さなデモ アプリケーションを次に示します。ここでも、マイナス記号の部分は実装していません

#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

2 に答える 2

5

ロケール固有の設定を考慮する

確かに自分ですべてを行うことができますが、使用するオプションVarI4FromStrや、汚いことを行う同様の API があります。糸を入れたらLONG出ます。ロケール対応。

「受け入れるしかない」

コントロールがこれを正確に実施する方法を指定しません。入力文字列が有効でない場合はどうなりますか? たとえば、文字列がまだ有効になっておらず、ユーザーがまだ入力中であるため、コントロールはまだそれを受け入れる必要があります。OKボタンが押されたときなど、外部ハンドラーで入力を検証している場合は、サブクラス化する必要さえありません。入力が変更されるたびにチェックしたい場合はEN_CHANGE、親に通知があるため、サブクラス化する必要もありません。ただし、他の理由でサブクラス化することもできます。

テキストの変更時または入力の検証時に、任意の入力を受け入れてから、何らかの方法で (無効な場合は赤で下線を引くなど) 有効性を示すことはユーザーフレンドリーです。

于 2014-02-16T15:14:05.550 に答える
2

clipboard からテキストを取得したら、ある文字列を別の文字列に挿入するために必要な Adviceのコードを考慮した後、要件を満たすサブクラス化手順を立てることができました。

私の解決策のポイントは、その投稿で述べたように編集コントロールの動作をシミュレートし、結果のテキストを検証することです。

を処理するVK_DELETEと、選択したテキストが削除され、結果が解析されて、有効な 10 進形式が残っているかどうかがチェックされます。すべてが OK の場合、メッセージはデフォルトの手順に渡されます。それ以外の場合は破棄されます。同じメソッドが for WM_CUTWM_CLEAR、および forハンドラー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);
}
于 2014-03-16T19:24:50.440 に答える