Использовать член класса как WNDPROC/DLGPROC с глобальным или без него.

Я продолжу и дам краткое изложение этого, как я могу использовать процедуру диалога, которая является членом класса? Я создаю класс-оболочку окна, но CreateDialogParam нужна процедура глобального диалога, поэтому я попробовал этот обходной путь:

Я немного поискал по этой теме. Я создаю класс Dialog, который я подклассифицирую, чтобы создать CMainWnd, а затем создаю его экземпляр. В классе Dialog у меня есть функция-член, определенная как INT_PTR CALLBACK Dialog::cb_proc(HWND,UINT,WPARAM,LPARAM). Теперь я знаю, что Windows должна иметь глобальную функцию в качестве процедуры обратного вызова.

Поэтому я сделал карту std::map<HWND,Dialog*> DlgProcs, чтобы связать дескриптор окна диалогов с его указателем класса Dialog.

И INT_PTR CALLBACK DlgMainProc(HWND,UINT,WPARAM,LPARAM), чтобы я мог передать это CreateDialogParam(). В теле DlgMainProc(...) я ищу карту для использования параметра hWnd, чтобы найти Dialog* и вернуть его член cb_proc(..).

Моя проблема в том, что ни одно из сообщений не обрабатывается, потому что процедура-член в моем классе Dialog никогда не вызывается. Несмотря на то, что когда я помещаю MessageBox() в DlgMainProc внутри инструкции if (DlgProcs.find(hWnd) != DlgProcs.end()) {, окно сообщения отображается снова и снова, пока мне не придется прервать программу из Visual Studio 2008. Это говорит мне, что она находит hWnd на моей карте. Странно то, что он также делает это, если я добавляю его в оператор else после этого, что противоречиво говорит мне, что он НЕ находит hWnd на карте.

Если я помещу окно сообщения в функцию-член cb_proc, оно вообще не будет отображаться. Но при этом я никогда не получаю ошибок компилятора, компоновщика или времени выполнения. Когда я удаляю из него окно сообщений (чтобы не прерывать программу, это было просто для целей отладки), программа запускается, но сообщения не обрабатываются, кнопка X не закрывает программу, нажатия кнопок ничего не делают.


Вот код PasteBin: http://pastebin.com/GsGUBpZU Кстати, у меня нет проблем с подклассом этого, мой окно создается нормально, просто сообщения не обрабатываются, cb_proc просто никогда не вызывается.

EDIT: Вот соответствующие части кода

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

Кто-нибудь знает, почему он никогда не вызывается? Или, может быть, просто указать мне другой способ использования функции-члена в качестве DLGPROC?


person Brandon Miller    schedule 25.09.2012    source источник
comment
Пожалуйста, вставьте сюда свой код, потому что нельзя полагаться на сторонние pastebins для целей сайта.   -  person ulidtko    schedule 25.09.2012
comment
Ну, это много кода. Почти все это имеет значение, я думал, что исключать части кода было плохой практикой, потому что это вполне могло быть частью того, что вызвало это. Что ж, я выберу наиболее важные части, которые я думаю.   -  person Brandon Miller    schedule 25.09.2012


Ответы (3)


Я попробовал ваш код, и он сработал: cb_proc вызывается. Вы пропустите все сообщения (например, WM_INITDIALOG), отправленные до возвращения CreateDialogParam.

Вы можете исправить последнюю проблему, добавив дескриптор окна и объект на карту в g_MainDlgProc. Если вы получаете сообщение для неизвестного окна, вы знаете, что оно принадлежит окну, которое вы создаете; поместите объект в глобальный, и вы можете добавить дескриптор/объект на карту.

person arx    schedule 25.09.2012
comment
ООН. Правдоподобно. Я тестировал свой код в общей сложности 7 раз. Кажется, я не использовал свой метод отладки маленького окна сообщений до тех пор, пока не переключился на сопоставление HWND и Dialog* ДО того, как я вызвал CreateDialogParam, что и было проблемой. Я передал указатель NULL на карту. И спасибо, я думаю, что я помещу код инициализации диалога в производный конструктор, так как я не знаю, как мне получить Dialog*, поскольку CreateDialogParam находится в конструкторе, поэтому объект еще не создан. - person Brandon Miller; 25.09.2012

Стандартное решение состоит в том, чтобы передать указатель this в качестве последнего параметра в Create,DialogParam, спрятать его в DWLP_USER в обработчике WM_INITDIALOG и затем извлечь из DWLP_USER. В основном вы используете DWLP_USER в качестве карты.

person Raymond Chen    schedule 25.09.2012

Я просто добавляю это сюда на случай, если кому-то это покажется полезным; Используя магию лямбда-выражений и шаблонов C++11, вы можете получить простой шаблон-оболочку, что означает, что вам не нужно постоянно переписывать шаблонный код для сохранения и загрузки пользовательских данных в оконных и диалоговых процедурах.

Вот пример для функции DialogBoxParam, но тот же метод можно применить и к CreateDialogParam и CreateWindowEx.

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

Вы бы использовали это так:

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);
person Jonathan Potter    schedule 21.07.2015