Click here to Skip to main content
15,868,016 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
INTRODUCTION AND RELEVANT INFORMATION:

I have MS Access 2007 database which I fill using ADO.

Among other data types ( string, integer...) I also have a double.

Since I work on Windows XP and use pure Win32 API to create GUI, I collect data from edit controls with GetDlgItemText API and then I convert that text into double using _wtof_l.

PROBLEM:

Everything works well if the user sets English or Serbian ( we use European notation for decimal and group separator ) locale and then starts the program, but the problem occurs when user changes locale settings while the program is working.

Let me demonstrate this on a small example:

Let us assume that user has English locale set.

Then user starts my application.

Then user decides to change the locale ( Control Panel->Regional and Language Settings for Windows XP ) before he hits the "Save" button.

After the changes apply he then enters data and then hits "save".

The error in converting text to double must occur ( _wtof_l will now truncate 1.25 to 1 ), since my program uses default ANSI "C" locale and did not adapt it to reflect users modification.

MY EFFORTS TO SOLVE THE PROBLEM:

To prevent this I need to adapt my program to the possibility described above-I need to set my locale to the one user selected before executing query.

To do so I use message from answer to my previous question[^] to detect when user changes settings in Control Panel->Regional and Language Options.

Then I use _wsetlocale(LC_ALL,L"") to set my applications locale to the one selected by the user.

However, wrong conversion from text to decimal number described above still occurs.

This only happens when I change the locale during my program's work. If I leave locale untouched ( as 99.9% of users will ) everything seems to work fine.

To further help the community I have made a demo application that illustrates the problem. It will be presented in the APPENDIX section at the end of this post.

QUESTION:

How can I respond to WM_SETTINGCHANGE message to set my application's locale to the one currently selected by the user so my "save" button handler can perform proper conversion from string to double with _wtof_l function?

Thank you.

Best regards.

APPENDIX:

Steps to create the demo application that illustrates my problem:

1.Create default Win32 project in Visual Studio.

2.Add the following WM_CREATE handler:
C++
case WM_CREATE:
    {
        _wsetlocale( LC_ALL, L"" ); //set current locale at window creation
            
        INITCOMMONCONTROLSEX iccex;
        memset( &iccex, 0, sizeof(INITCOMMONCONTROLSEX) );
        iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
        iccex.dwICC = ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_BAR_CLASSES;
        InitCommonControlsEx( &iccex );
              
        // text
            
        HWND hEdit = CreateWindowEx( 0, L"Edit", L"",
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
            50, 50, 150, 25, hWnd, (HMENU)8002, hInst, 0 );
                
        // decimal number
             
        HWND hEdit1 = CreateWindowEx( 0, L"Edit", L"", 
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
            250, 50, 150, 25, hWnd, (HMENU)8003, hInst, 0 );
              
        HWND hButton = CreateWindowEx( 0, L"Button", L"Save", 
            WS_CHILD | WS_VISIBLE | WS_BORDER | BS_PUSHBUTTON,
            50, 150, 150, 25, hWnd, (HMENU)8004, hInst, 0 );
                               
        SendMessage( hEdit, EM_LIMITTEXT, (WPARAM)9, (LPARAM)0 );
        SendMessage( hEdit1, EM_LIMITTEXT, (WPARAM)4, (LPARAM)0 );
    
    }
    return 0L;


3.Add the following handler to detect when user changes locale settings
C++
case WM_SETTINGCHANGE:
if( !wParam && !wcscmp( (wchar_t*)lParam, L"intl" ) )
    {
        _wsetlocale( LC_ALL, L"" ); //set it to current locale
        return 0L; // "say" we handled it
    }
    else
        break; // pass it to default window procedure

4.In WM_COMMAND handler add the following cases:

C++
case 8004:
    {
        // initialize COM
        HRESULT hr = CoInitialize(NULL);

        // format connection string
        wchar_t *bstrConnect= L"Provider=Microsoft.ACE.OLEDB.12.0; \
            Data Source = .\\test.accdb";
        
        try
        {
        
            ADODB::_ConnectionPtr pConn("ADODB.Connection");
     
            hr = pConn->Open(bstrConnect, L"admin", L"", ADODB::adConnectUnspecified);

            if ( SUCCEEDED(hr) )
            {
                wchar_t text[10], number[5];
                
                memset( &text, L'\0', sizeof(text) );
                memset( &number, L'\0', sizeof(number) );
                
                GetDlgItemText( hWnd, 8002, text, 10 );    // text
                GetDlgItemText( hWnd, 8003, number, 5 );   // double
                
                ADODB::_CommandPtr pCmd("ADODB.Command");
                
                pCmd->ActiveConnection = pConn;
                pCmd->CommandText = 
                    L" insert into MyTable ( field1, field2 ) values ( ?, ? );";
                
                pCmd->Parameters->Append( pCmd->CreateParameter( "?",
                    ADODB::adDouble,
                    ADODB::adParamInput, sizeof(double),
                    _wtof_l( number, _get_current_locale() ) ) );
                
                pCmd->Parameters->Append( pCmd->CreateParameter( "?",
                    ADODB::adVarWChar, ADODB::adParamInput, 
                    wcslen(text), text ) );
                
                pCmd->Execute( NULL, NULL, ADODB::adCmdText );
                
                pConn->Close(); // close connection
                
                CoUninitialize(); // uninitialize COM
            }
            else
                throw _com_error(hr); //something failed-report it
        }
        catch(_com_error& e)
        {
            MessageBox(hWnd, (LPWSTR)(e.Description()), L"Error", MB_OK | 
                MB_ICONERROR );
            
            CoUninitialize();
        }
        
    }
    break;


5.Create MS Access 2007 database in the project folder.

6.Create table MyTable and add 2 fields field1 as TEXT field, and add field2 which is double.

I apologize for small irregularities in formatting of the code.
Posted
Updated 2-Feb-14 14:06pm
v2
Comments
chaau 2-Feb-14 20:00pm    
I think after setting to current locale in the "case WM_SETTINGCHANGE:" code you will need to re-initialise all the edit boxes with double values by re-reading from database
AlwaysLearningNewStuff 2-Feb-14 20:09pm    
Very good point!

But there are other problems too.

After I perform several switches from US to European locale, WM_SETTINGCHANGE gets "stuck" and stops recognizing the change of loxale!
chaau 2-Feb-14 20:21pm    
I think you are performing unrealistic test scenarios. Who is going to change the settings back in forth in real life? Perhaps Windows is trying to protect your and others applications against unnecessary changes
AlwaysLearningNewStuff 2-Feb-14 20:55pm    
I was thinking about that too, but just want to be on the safe side. Maybe I being too paranoid?

I don't see where did you try to set the locale. Perhaps you don't really need to respond to the change of locale. You can apply the locate you need to the current thread. To related functions are:
http://msdn.microsoft.com/en-us/library/dd374053%28VS.85%29.aspx[^],
http://msdn.microsoft.com/en-us/library/dd374051%28VS.85%29.aspx[^].

See also:
http://msdn.microsoft.com/en-us/library/dd319081%28VS.85%29.aspx[^],
http://msdn.microsoft.com/en-us/library/dd319074%28VS.85%29.aspx[^].

More generally, it all depends on what you want to achieve. You can do one of the two: 1) try to use the locale defined OS-wide; then you need to follow the rules of this locale consistently; then your application will behave differently for different OS settings, but will behave consistently; 2) you can try to do all the job in "neutral culture" (in .NET terminology) manner, using fixed locale ignoring OS settings.

This is really the architectural big-picture type of solution which should take into consideration the prolonged lifetime of the application. In particular, you store some data permanently in a database or some files, or pass those data in a stream (network, any transport, whatever) to another machine. What does it mean? If one computer wrote this data in one locale, another computer will try to read this data in some other locate and, naturally, fail. Hence, there is a serious need in a culture-neutral approach. From the other hand, UI may require to respond to the national settings set by the user — numeric format, currency, date/time in the form convenient for a particular user. This is a matter of culture, which does not mean just the language, country and associated culture, but, ultimately, the personal preference of each user.

—SA
 
Share this answer
 
Comments
AlwaysLearningNewStuff 2-Feb-14 23:26pm    
I was preparing to go to sleep and gave a quick look to this question.

Thank you for replying!

Locale is set with _wsetlocale first in my WM_CREATE handler and then in handler for WM_SETTINGCHANGE.

The point of responding to locale change was to properly create INSERT INTO queries that have decimal numbers as arguments. European and US locale have different decimal separator.

After I use GetWindowText API to get data from edit control I must convert text to double by using _wtof_L function.
It truncates the result if the decimal separator is not the right one and this scenario can happen when user changes the locale settings during the work of my program.

That is why I have asked my previous question and this one.

Again, thanks again-we shall speak tomorrow.

I got an idea how to try solve this and will test it after getting some sleep.

Until then best regards Mr.Kryukov.
AlwaysLearningNewStuff 3-Feb-14 13:22pm    
Mr.Kryukov,

I was setting the locale in a wrong way, the accepted answer pointed it out well.

Still, the API you suggested was the one I should have used only I failed to do so right.

Because of that, and your thorough and comprehensive coverage you have earned my 5.

Thank you again for helping me-I highly appreciate it.

Best regards.
Sergey Alexandrovich Kryukov 3-Feb-14 15:04pm    
Sure. But are you saying that you still have this problem, cannot accept the answer..?
—SA
AlwaysLearningNewStuff 3-Feb-14 16:07pm    
I forgot that on this site you can accept multiple answers...

I have formally accepted your answer as well.

Until next time best regards.
Sergey Alexandrovich Kryukov 3-Feb-14 16:13pm    
Very good.
Good luck, call again.
—SA
From within your WM_SETTINGCHANGE handler you are calling
C++
_wsetlocale( LC_ALL, L"" );

This will set the locale to the default, which is the user-default ANSI code page obtained from the operating system, but not the current LCID when it has been changed in the control panel.

To set the locale from the current user LCID you can use:
TCHAR lpszLocale[64+64+16+3] = _T(""); // max. len: language, country, code page
TCHAR lpszVal[128];

LCID nLCID = ::GetUserDefaultLCID(); // current LCID for user
if (::GetLocaleInfo(nLCID, LOCALE_SENGLANGUAGE, lpszVal, 128))
{
    _tcscat(lpszLocale, lpszVal); // language
    if (::GetLocaleInfo(nLCID, LOCALE_SENGCOUNTRY, lpszVal, 128))
    {
        _tcscat(lpszLocale, _T("_")); // append country/region
        _tcscat(lpszLocale, lpszVal);
        if (::GetLocaleInfo(nLCID, LOCALE_IDEFAULTANSICODEPAGE, lpszVal, 128))
        { 
            // missing code page or page number 0 is no error (e.g. with Unicode)
            int nCPNum = _tstoi(lpszVal);
            if (nCPNum >= 10)
            {
                _tcscat(lpszLocale, _T(".")); // append code page
                _tcscat(lpszLocale, lpszVal);
            }
        }
    }
}
// set locale and LCID
_tsetlocale(LC_ALL, lpszLocale);
::SetThreadLocale(nLCID);


You might also want to call the above upon program start because the default locale is "C".

As already noted in the comments, all visible program output containing floating point values must be also updated when the decimal point changes.

In one of my applications I have added a configuration option to optional replace periods by commas in floating point input fields when the decimal point from the current settings is the comma. So users must not care about the decimal point.
 
Share this answer
 
Comments
AlwaysLearningNewStuff 3-Feb-14 13:19pm    
Thank you so much!

My 5!

Best regards!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900