Question

I'll go ahead and give a summary to this, how can I use a dialog procedure that is a member of a class? I am creating a window wrapper class, but CreateDialogParam needs a global dialog procedure, so I tried this workaround:

I have done a bit of searching on this topic. I am making a Dialog class which I am subclassing to make a CMainWnd and then instantiating that. In the Dialog class I have a member function defined as INT_PTR CALLBACK Dialog::cb_proc(HWND,UINT,WPARAM,LPARAM). Now, I know that windows must have a global function as a callback procedure.

So I made a std::map<HWND,Dialog*> DlgProcs map to associate the dialogs window handle with its Dialog class pointer.

And a INT_PTR CALLBACK DlgMainProc(HWND,UINT,WPARAM,LPARAM) so I could pass that to CreateDialogParam(). In the body of DlgMainProc(...) I search the map for using the hWnd parameter to find the Dialog* and return its cb_proc(..) member.

My problem is that none of the messages get processed, this is because the member procedure in my Dialog class never gets called. Even though when I put a MessageBox() in DlgMainProc inside a if (DlgProcs.find(hWnd) != DlgProcs.end()) { statement, the messagebox is displayed, over and over again until I have to abort the program from Visual Studio 2008. Which tells me that it is finding the hWnd in my map. The weird thing is it also does this if I put it in the else statement after that, which contradictingly tells me it is NOT finding the hWnd in the map.

If I put a messagebox in the cb_proc member function it does not get displayed at all. But during this I never get any compiler, linker, or runtime errors. When I remove the messagebox from it (as to not have to abort the program, it was just for debugging purposes) the program runs but no messages get processed, the X button does not close the program, button clicks do nothing.


Here is the PasteBin code: http://pastebin.com/GsGUBpZU Btw, I have no problem subclassing this, my window is created fine, just no messages are processed, cb_proc just never gets called.

EDIT: Here is the relevant parts of the code

map<HWND,Dialog*> g_DlgProcs;

INT_PTR CALLBACK g_MainDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        if (g_DlgProcs.find(hWnd) != g_DlgProcs.end()) {
                Alert("blah"); // Gets executed repeatedly
                return g_DlgProcs[hWnd]->cb_proc(hWnd, msg, wParam, lParam);
        } else {
                Alert("blah"); // Removing the above alert, this gets
                               // executed repeatedly, erm, as well.. O.o strange
                return FALSE;
        }
}

Dialog::Dialog(int id, HWND parent /* = HWND_DESKTOP */) {
        _id = id;
        _parent = parent;

        // Tried this before CreateDialogParam
        g_DlgProcs.insert(make_pair(_handle, this));

        _handle = CreateDialogParam(
                (HINSTANCE)GetModuleHandle(NULL),
                MAKEINTRESOURCE(id), _parent,
                (DLGPROC)g_MainDlgProc, NULL
        );

        // Then tried it after CreateDialogParam
        g_DlgProcs.insert(make_pair(_handle, this));
}

INT_PTR CALLBACK Dialog::cb_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        Alert("blah"); // Never gets executed

        bool handled = true;

        switch (msg)
        {
        case WM_INITDIALOG:
                OnInitialize();
                break;
        case WM_COMMAND:
                if (HIWORD(wParam) == 0 || HIWORD(wParam) == 1) {
                        OnMenuCommand((HIWORD(wParam) == 1), (int)LOWORD(wParam));
                } else {
                        OnCtrlCommand((int)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam);
                }
                break;
        case WM_NOTIFY:
                {
                        LPNMHDR head = (LPNMHDR)lParam;
                        OnNotification(head->code, head->idFrom, head->hwndFrom);
                }
                break;
        case WM_CLOSE:
                OnClose(); // DestroyWindow(_handle)
                break;
        case WM_DESTROY:
                OnDestroy(); // PostQuitMessage(0)
        default:
                handled = ProcessMsg(msg, wParam, lParam);
        }

        // Convert bool to Windows BOOL enum
        return ((handled == true) ? TRUE : FALSE);
}

Does anybody know why it never gets called? Or maybe just guide me to another way to use a member function as a DLGPROC?

Was it helpful?

Solution

I tried your code and it worked: cb_proc gets called. You will miss any messages (e.g. WM_INITDIALOG) that get sent before CreateDialogParam returns.

You can fix the latter problem by adding the window handle and the object to the map in g_MainDlgProc. If you get a message for an unknown window, you know it belongs to the window you're creating; put the object in a global and you can add the handle/object to the map.

OTHER TIPS

The standard solution is to pass your this pointer as the last parameter to Create,DialogParam, stash it in DWLP_USER in your WM_INITDIALOG handler, and retrieve it from DWLP_USER thereafter. Basically you use DWLP_USER as your map.

I'm just adding this here in case someone finds it useful; using the magic of C++11 lambdas and templates you can have a simple wrapper template that means you don't have to continually rewrite the boiler-plate code for saving and loading userdata in window and dialog box procedures.

Here's an example for the DialogBoxParam function, but the same technique can be applied to CreateDialogParam and CreateWindowEx as well.

template <typename T, INT_PTR (T::*P)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)>
INT_PTR DialogBoxThis(T* pThis, HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent)
{
    return ::DialogBoxParam(hInstance, lpTemplateName, hWndParent, [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> INT_PTR {
        if (uMsg == WM_INITDIALOG) SetWindowLongPtr(hWnd, DWLP_USER, lParam);
        T* pThis = reinterpret_cast<T*>(GetWindowLongPtr(hWnd, DWLP_USER));
        return pThis ? (pThis->*P)(hWnd, uMsg, wParam, lParam) : FALSE;
    }, reinterpret_cast<LPARAM>(pThis));
}

You would use it like this:

class MyClass
{
    INT_PTR MyDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
};

// from inside MyClass, we can show a dialog that uses member function MyDlgProc as the dialog procedure
// note it is NOT a static function

DialogBoxThis<MyClass, &MyClass::MyDlgProc>(this, hInstance,
    MAKEINTRESOURCE(IDD_MYDIALOG), hWndParent);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top