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

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.

MY EFFORTS TO SOLVE THIS:

So far I have used subclassing to implement this. Here is my logic for implementing the subclassing, expressed through pseudo code:

C++
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:

C++
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 subclassing procedure-I haven't taken minus sign into account:
C++
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.

QUESTION:

Is there a better way to implement an edit control that accepts signed decimal numbers only, and is locale aware?

If subclassing is the only way, can my code be further improved/optimized ?

Thank you for your time and help.

Best regards.

APPENDIX:

To help you even further, here is a small demo application that creates an edit control and subclasses it to accept only decimal numbers-again, I haven't implemented the part for the minus sign:
C++
#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;
}
Posted
Updated 16-Feb-14 14:43pm
v2

(a) Consider doing the GetLocaleInfo calls only once and store the result in a member variable.

(b) The while loop in HasDecimalSeparator is almost unreadable. How about writing it like this:
C++
for (size_t i = 0; i < wcslen (test); ++i)
    if (test[i] == szBuffer[0])
        return true;
return false;
 
Share this answer
 
Comments
AlwaysLearningNewStuff 16-Feb-14 9:53am    
Thank you for your suggestions.

I will implement your suggestion for while loop.

As for storing the locale info into a member variable, I have a question:

I do not have a class, since this is Win32 API I do everything "C-style".

I do not mind using the class, but so far there was no need for one.

Should I use static global variable to store locale info?

Thank you again.

Best regards.
nv3 16-Feb-14 13:54pm    
You could use a static structure to keep such data; but I don't like global structures hanging around. They often become a maintenance headache.

You are welcome!
AlwaysLearningNewStuff 16-Feb-14 16:41pm    
I agree with you about not using global variables, but how else can I solve this?

Taking the above program into consideration ( see APPENDIX section ), can you provide a better solution for me to do this ? One that does not constantly call GetLocaleInfo or has global static structure ?

Sorry for being so annoying, but I really want to do this as best as I can.

Best regards.
nv3 16-Feb-14 18:21pm    
I would either stick with calling GetLocaleInfo every time, or afford a single global pointer to a structure, which contains the locale info and perhaps additional data. This structure could later be converted into the members of a class, should you ever choose to migrate to an object oriented design.
AlwaysLearningNewStuff 16-Feb-14 18:39pm    
Thank you for your advices. Please be patient, since I will need time to implement all of this. Until we "speak" again-which I hope should be soon-I wish you best regards!
I solved this problem in my article CGridCellNumeric - A numeric cell class for the MFC Grid[^]. While this edit control is designed to work with Chris Maunders MFC grid control, the logic is identical. The key to making it work is to do your validating/checking in the EN_UPDATE message handler. This way you validate all input channels, including cut and paste.
 
Share this answer
 
Comments
AlwaysLearningNewStuff 17-Feb-14 3:08am    
Code is pretty big, and hard for me to follow since I do not know MFC yet, but I think I have managed to grasp the concept:

You set Updating flag in WM_CHAR to indicate if user is typing or not, and do all the validation checking in WM_NOTIFY handler only if user is not typing. Did I get this right?

One more thing:

I do not fully comprehend your validation code in OnNotify function.

In the demo application submitted in the article, user can type multiple separators, and minus signs can be placed anywhere.

I must ask you where in your code do you secure the proper input ? I have failed to find the handler/mechanism for correction of input mistakes made by the user.

Best regards.
PJ Arends 17-Feb-14 13:34pm    
The validating is done in CInPlaceNumEdit::OnUpdate()

This function handles the EN_UPDATE message. This message is generated by the edit control just before it displays the text. What I did is validate the text in the control, and if it is not valid I restore the last previously valid text. This way it does not matter what is typed into the edit control, it will just appear to the user that all invalid text is rejected.

The tricky part for you now is that the CInPlaceNumEdit class works in concert with the CGridCellNumeric class. When the user finishes editing it ends up calling CGridCellNumeric::SetText() with the user entered text, which in turn calls CGridCellNumeric::Format() to format the final text in a locale specific format.

That should now be as clear as mud. Just look at CInPlaceNumEdit::OnUpdate and CGridCellNumeric::Format()
PJ Arends 17-Feb-14 13:56pm    
I should also add, that the only the top left cell in the demo grid has this functionality, the rest of the cells are simple text edit controls.
AlwaysLearningNewStuff 18-Feb-14 6:01am    
I have ran into problems with the recursion when responding to EN_UPDATE message.

Can you please instruct me how to handle it properly ?

I am using C++ and pure Win32 API.

Thank you.

Best regards.
PJ Arends 18-Feb-14 10:54am    
Yes, that is where the Updating flag comes in. It is used to prevent just this problem.
Further to my comment earlier, I've assembled a rough implementation of a flexible validation system. You could easily add a handler for a custom message that would allow you to change the required separator at run-time, in response to a WM_SETTINGCHANGE message being received by the main window. You would need to send this custom-message to the edit-controls, in order to get access to the control's instance data that holds the separator type.

My aim was to make the same subclass function able to validate data of different forms, with the validation logic de-coupled from the subclass function. You can see that the one function validateEditControl is used for each of the 3 edit controls - whether it be a number containing a period, or a number containing a comma or even alphabetical text.

It also demonstrates a means by which you can avoid having your code littered with static variables or (worse) global variables. It's well after midnight here and my best work was done many, many hours ago today. :unsure:

Finally, the code handles the checking in response to an EN_UPDATE message sent to the parent window, by forwarding (reflecting) this message back onto the control that generated the notification in the first place. When validation fails, a sound is issued and the text is modified. You could simply copy the text back into the control without the last character, if the text was typed in. If the text was copied-in, you may have more than 1 invalid character. This/these character(s) could also appear at any place in the string. I've glossed-over this fact, electing to simply replace the entire contents with some text that will pass the validation method used for each control.

It's been rather fun to be honest - who'd have thunk javascript coding would influence the way I coded c++? :grin:

***Please don't be dissapointed at my poor number 'validation' logic, a good implementation was not my aim. PJ Arrends PJ Arends seems to have covered that point already***

The code:

main.cpp
C++
#define _WIN32_WINNT 0x0501     // for subclassing stuff.

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <ctype.h>
#include "resource.h"

HINSTANCE hInst;

typedef enum seperator_t{period=0, comma};

typedef bool (*pfnValidator2)(char *inputText, void *instanceData);

typedef struct editData_t
{
    seperator_t curSep;
    pfnValidator2 isValidFunc;
    char *replacement;
} *pEditData;

bool isNumberValid2(char *text, void *instData)
{
   int i;
   pEditData mDat = (pEditData)instData;

   char seperatorChar;

   if (mDat->curSep == period)
        seperatorChar = '.';
   else
        seperatorChar = ',';

   // _very_ rough, clumsy and *incorrect* validation method.
   // provided for the sake of illustration only.
   // **this** is where the real logic to validate the number should be placed.
   // I.e - (-) symbol can only occur as the first character
   // only one instance of the separator is allowed.
   for (i=0; i<strlen(text); i++)
   {
        if ( (isdigit(text[i]) == false) && (text[i] != seperatorChar) && (text[i] != '-') )
            return false;
   }
   return true;
}

// provided for the sake of demonstrating a flexible approach to validating content of
// different forms.
bool isTextValid(char *text, void *instData)
{
    int i, n;
    bool bResult;
    pEditData mDat = (pEditData)instData;

    n = strlen(text);
    bResult = true;
    for (i=0; i<n; i++)
    {
        if ( tolower(text[i]) != tolower(mDat->replacement[i]) )
            bResult = false;
    }
    return bResult;
}

LRESULT CALLBACK validateEditControl(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    pEditData myData = (pEditData)dwRefData;
    switch (uMsg)
    {
        case EN_UPDATE:
        {
            char *curText, *updatedText;
            int curTextLen;

            curTextLen = GetWindowTextLength(hwnd);
            curText = new char[curTextLen+1];
            memset(curText, 0, curTextLen+1);
            GetWindowText(hwnd, curText, curTextLen+1);

            if (myData->isValidFunc(curText, (void*)myData) != true)
            {
                SetWindowText(hwnd, myData->replacement);
                MessageBeep(MB_ICONERROR);
            }
            delete curText;
        }
        break;
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND editWnd = GetDlgItem(hwndDlg, IDC_EDIT1);
            pEditData refData1 = new editData_t;
            *refData1 = (editData_t){period, isNumberValid2, "123.456"};
            SetWindowSubclass(editWnd, validateEditControl, 1, (DWORD_PTR)refData1);

            HWND editWnd2 = GetDlgItem(hwndDlg, IDC_EDIT2);
            pEditData refData2 = new editData_t;
            *refData2 = (editData_t){comma, isNumberValid2, "654,321"};
            SetWindowSubclass(editWnd2, validateEditControl, 1, (DWORD_PTR)refData2);

            HWND editWnd3 = GetDlgItem(hwndDlg, IDC_EDIT3);
            pEditData refData3 = new editData_t;
            *refData3 = (editData_t){comma, isTextValid, "eNhZfLeP"};
            SetWindowSubclass(editWnd3, validateEditControl, 1, (DWORD_PTR)refData3);
        }
        return TRUE;

        case WM_CLOSE:
        {
            EndDialog(hwndDlg, 0);
        }
        return TRUE;

        case WM_COMMAND:
        {
            if (HIWORD(wParam) == EN_UPDATE)
            {
                SendMessage( (HWND)lParam, EN_UPDATE, 0, 0);
                return 0;
            }
        }
        return TRUE;
    }
    return FALSE;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    hInst=hInstance;
    InitCommonControls();
    return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}


resource.h
C++
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif

#define DLG_MAIN                                100
#define IDC_EDIT3                               1000
#define IDC_EDIT1                               1002
#define IDC_EDIT2                               1004



resource.rc
C++
// Generated by ResEdit 1.5.11
// Copyright (C) 2006-2012
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"

//
// Dialog resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
DLG_MAIN DIALOG 0, 0, 178, 65
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "Ms Shell Dlg"
{
    EDITTEXT        IDC_EDIT1, 81, 7, 90, 14, ES_AUTOHSCROLL
    RTEXT           "Period, Digits, '-'", IDC_STATIC, 14, 10, 57, 8, SS_RIGHT
    RTEXT           "Comma, Digits, '-'", IDC_STATIC, 14, 28, 57, 8, SS_RIGHT
    EDITTEXT        IDC_EDIT2, 82, 25, 89, 14, ES_AUTOHSCROLL
    RTEXT           "enhzflep", IDC_STATIC, 14, 47, 57, 8, SS_RIGHT
    EDITTEXT        IDC_EDIT3, 82, 44, 89, 14, ES_AUTOHSCROLL
}
 
Share this answer
 
v4
Comments
AlwaysLearningNewStuff 20-Feb-14 10:37am    
Wow, what a Twilight Zone moment ! :)))

I was pondering about this method myself!

I too, unfortunately, do not know where is appropriate to release memory.

Further more, I just need to adapt my locale settings to the user default, so I will need only one instance of the structure and it will be passed to all numeric edit controls.

Thank you so much for taking time to try and help-I really appreciate it-and thank you for that.

You and Mr.Kryukov have helped me so much in the last half year period of time and always have had patience for my inexperience. I wish if there is something I could do to repay you in the same manner...

I hope you are coping a little better with the scorching heat "down there" :)

By the way, I get error reports around the code that initializes your structure, specifically here:

*refData1 = (editData_t){period, isNumberValid2, "123.456"};

The error code is C2059. Browsing through Internet for this error, I have found an MSDN example that states this might be an error in initializing the structure, which can be solved with a constructor. After observing your code I see no constructor, but I am unable to rework it to compile. Can you help me please?

Until next time, best regards!
AlwaysLearningNewStuff 22-Feb-14 4:39am    
OK, it seems that everything works now after I alter the structure initialization per your instructions!

The only thing left is to properly delete data when removing subclassing so memory leaks can be avoided.

As for text validation-> "no sweat" I can do this on my own, and the example from second solution is great as well!

As soon as I find out how to safely unsubclass I will officially accept all three solutions! So far I have 5ed all of the answers!

Hopefully we shall somehow solve this last piece of the puzzle.

Best regards.
enhzflep 22-Feb-14 5:23am    
Brilliant.

Well, the following works just fine. I modified the instance-data struct to include a 1024kb byte array, so I could easily monitor memory usage from Task Manager. I then added a couple of buttons to allow me to remove/re-create one of the edit controls. Clicking the remove button drops memory usage by 1mb, clicking the add button brings memory usage back to the value immediately after program initialization. So, that seems to be that one solved.

I then tried to call RemoveWindowSubclass after freeing the memory, printing the result. I get back a non-zero value, indicating success. So, it seems that it was really straight forward - I just had to give it a try.

So to do both of these functions - free the memory and un-subclass the window, I just had to add a handler for the WM_NCDESTROY message to the subclass function.

LRESULT CALLBACK validateEditControl(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
pEditData myData = (pEditData)dwRefData;
switch (uMsg)
{
case EN_UPDATE:
{
// do validation stuff here
}
break;

case WM_NCDESTROY:
{
delete myData;
bool success = RemoveWindowSubclass(hwnd, validateEditControl, uIdSubclass);
}
break;
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}


Hopefully that allows you to move on to the next challenge. :)

EDIT: While it works, I've just wondered if you should actually call DefWindowProc in the WM_NCDESTROY handler, rather than breaking from it and calling DefSubclassProc. The DefSubclassProc 'helper' function may do this internally if the control isn't subclassed, I don't know. :unsure:
AlwaysLearningNewStuff 22-Feb-14 12:04pm    
I do believe that a simple break; will work. After breaking, it goes to DefSubclassProc anyway and that should take care of it.

No one demonstrated passing this structure to the dialog via LPARAM but I have managed to make it work.

Also, I have made 2 edit controls and got error when shut down the dialog, which is expected in my opinion.

You see we send a pointer to a structure to subclassing procedure, so when dialog box is performing destruction he will destroy first edit control.

Edit control will remove its subclassing and delete the local structure but since this structure is a pointer to main windows structure, you will also delete that structure as well!

Now when dialog box destroys the second edit control, the edit control will remove its subclassing and try to delete deleted structure which will result in an error!

To solve this I have made a small demo application submitted here[^] so I do not clog this post.

Please give it a look, and try to verify my findings. If you can suggest an improvement I will more than happily consider it.

Best regards!
enhzflep 22-Feb-14 16:31pm    
One would think that calling DebSubclassProc for a window that isn't subclassed is a trivial situation to catch and that MS should have taken care of that. I atually wondered last night if I should have simply followed the documentation for WM_NCDESTROY - that is, return 0.

I checked the example code you linked to, noting particularly the position of the comment "should I delete structure here?". My answer to that question is a firm no.

1. The memory is a static in WndProc - which it needs to be if you'd like to be able to change it there when the locale changes, rather than telling the edit-control to update the data itself.
2. The biggest reason is that as you mention, you're sharing this instance data between two instances of an edit-control. Destruction of the first edit-control will delete the memory, which will cause problems when the second edit control is destroyed.

Reviewing point 1 - that WndProc maintains a copy of the pointer to this memory, I feel it's appropriate that WndProc frees this memory, as you've done in the WM_CLOSE handler. As mentioned by Helix on SO, you really should do it in WM_DESTROY rather than WM_CLOSE, since you'll always get a WM_DESTROY, but wont necessarily get a WM_CLOSE.

The demo you've shown seems perfectly sensible to me and is the way I would approach it too.

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