Размещение диалога в Win32 C++

Я пытаюсь создать диалоговое окно, чтобы оно всегда открывалось в середине окна моего приложения. Это обычное поведение диалога, созданного с помощью чего-то вроде MessageBox(). Однако если окно приложения перемещается до открытия диалогового окна, диалоговое окно откроется в середине того места, где окно БЫЛО, а не там, где оно находится сейчас. Итак, что-то за кулисами явно кэширует положение окна.

Можно ли как-нибудь обновить эту позицию, чтобы указать, где на самом деле находится окно? Проблема в том, что с помощью MessageBox() у вас нет возможности позиционировать диалог, поэтому я не могу легко его правильно расположить.

Здесь есть интересный ответ на вопрос: Как изменить положение MessageBox в C++ и я попробовал предложенное решение, и, честно говоря, использование TaskDialogIndirect() почти работает. Вы действительно можете установить положение (X, Y) диалогового окна, но положение X немного смещено, и вы не можете изменить размер диалогового окна.

Поэтому я решил создать диалоговое окно самостоятельно и подумал, что проще всего создать подкласс моего главного окна приложения.

LRESULT CALLBACK DialogWndProc(HWND wnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, [[maybe_unused]] DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
        case WM_CREATE:
            CreateWindowEx(NULL, L"BUTTON", L"Okay", WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE, 50, 50, 100, 40, wnd, (HMENU)1002, GetModuleHandle(NULL), nullptr);
            break;
        case WM_CLOSE:
        {
            HWND owner = GetWindow(wnd, GW_OWNER);
            EnableWindow(owner, true);
            SetFocus(owner);
            DestroyWindow(wnd);
            return 0;
        }
        case WM_NCDESTROY:
            RemoveWindowSubclass(wnd, DialogWndProc, uIdSubclass);
            break;
    }
    return DefSubclassProc(wnd, uMsg, wParam, lParam);
}

// hwd is the HANDLE of the main application window
void CustomDialog(HWND hwd, const std::wstring title, const std::wstring body, size_t xp, size_t yp, size_t xs, size_t ys)
{
    // Get the class name from the parent window
    std::vector<wchar_t> class_name(256);
    class_name.resize(static_cast<size_t>(GetClassName(hwd, class_name.data(), static_cast<int>(class_name.size()))));

    // Subclass the window and change WNDPROC
    BOOL t = SetWindowSubclass(hwd, DialogWndProc, 1, 0);

    // Create the modal dialog window
    HWND dlg = CreateWindowEx(WS_EX_DLGMODALFRAME, class_name.c_str(), title.c_str(), WS_POPUPWINDOW | WS_CAPTION | DS_MODALFRAME | DS_NOIDLEMSG, xp, yp, xs, ys, hwd, NULL, GetModuleHandle(NULL), nullptr);
    ShowWindow(dlg, SW_SHOW);
    EnableWindow(hwd, false);
}

Проблема, которую я обнаружил, заключается в том, что WM_CREATE никогда не получен, и я не понимаю, почему. Конечно, во время вызова CreateWindowEx() новый WNDPROC получит WM_CREATE?

Или мне придется зарегистрировать совершенно новый атом класса окна для этого окна?

Создание подкласса требует, чтобы окно уже было создано. Очевидно, что вы не получите сообщение WM_CREATE в оконной процедуре, которую вы прикрепили к окну после его создания. Я не знаю, почему вы вообще решили использовать подклассы. Вместо этого вы можете вызвать CreateDialog() и друзьям, и все сообщения с самого начала делегируются процедуре диалога.

IInspectable 19.06.2024 14:50

@IInspectable Совершенно верно, и если бы у меня был шаблон диалогового окна, я бы не задавал этот вопрос. Я даже думал о программном создании шаблона, но это большая работа, поскольку все должно быть выровнено по словам. Кроме того, я не хочу с вами не соглашаться, но я не нашел ничего, что говорило бы, что окно необходимо создать перед созданием подклассов. Вы определяете WNDPROC при регистрации класса без использования окна. Как бы вы справились с созданием пользовательских элементов, если бы вы создали подклассы после создания окна? Очевидно, вы не получили бы сообщение WM_CREATE, если бы окно уже существовало.

The Welder 19.06.2024 15:38

Если вы не хотите создавать шаблон диалогового окна в памяти, вы можете создать пустой шаблон диалогового окна в скрипте ресурса, чтобы использовать его в качестве заполнителя.

IInspectable 19.06.2024 16:01

@TheWelder «Я не нашел ничего, что говорило бы о том, что окно необходимо создать перед созданием подкласса» - хм, тот факт, что для создания подкласса требуется целевой HWND, не является подсказкой? Ваш код является подклассом главного окна приложения, а не диалогового окна. «Как бы вы справились с созданием пользовательских элементов, если бы вы создавали подклассы после создания окна?» - вам следует либо создать собственный шаблон диалогового окна, либо зарегистрировать собственный класс окна, но если вы не хотите этого делать, тогда единственный оставшийся вариант - использовать перехватчик локального потока для перехвата создания.

Remy Lebeau 19.06.2024 16:58

@RemyLebeau Но это то, что я пытаюсь сделать. Подкласс моего главного окна, чтобы затем создать диалог CreateWindowEx() без необходимости регистрации нового атома класса. WNDPROC является членом WNDCLASSEX, а не окна, окно просто использует этот класс. Целевой HWND будет использоваться только для поиска класса окна и его подкласса. Если вы создадите подкласс существующего окна перед выполнением еще одного CreateWindowEx(), я думал, вы получите сообщения WM_CREATE. Я не пробовал ваш метод WinHook, чтобы получить структуру Create, которая, вероятно, сработает, просто это кажется немного странным.

The Welder 20.06.2024 02:41

@TheWelder Очевидно, вы не читали документацию . Ваше понимание неверно. SetWindowSubclass() заменяет wndproc одного существующего HWND, он НЕ изменяет wndproc новых HWNDs типа класса подкласса окна. Вместо этого вы можете подумать о SetClassLongPtr().

Remy Lebeau 20.06.2024 04:10

@TheWelder В любом случае создание подкласса вашего главного окна не поможет вам расположить диалоговое окно там, где вы хотите. Когда вы создаете диалоговое окно, если оно еще не позиционируется в середине вашего главного окна, вам просто нужно сначала получить текущую позицию главного окна, затем создать диалоговое окно и переместить его туда, куда вы хотите. Для этого вам не нужны подклассы. Но если это не тот визуальный эффект, который вам нужен после создания диалога, вам придется подключить создание самого диалога.

Remy Lebeau 20.06.2024 04:10

«WNDPROC является членом WNDCLASSEX, а не окна, окно просто использует класс». - В этом недоразумении и кроется суть вопроса. Класс окна служит шаблоном при создании окна. Во время построения значения по умолчанию из класса копируются в память каждого окна, доступную через GetWindowLongPtrW(). Оконная процедура хранится по индексу GWLP_WNDPROC. За всю свою жизнь окно никогда больше не вернется в класс окна.

IInspectable 20.06.2024 10:29

@IInspectable Это действительно мое недоразумение. Я использую GetWindowLongPtr() для различных целей, включая изменение WNDPROC, но я ошибочно подумал, что это происходит с WNDCLASSEX, и теперь я думаю об этом... немного глупо с моей стороны так думать.

The Welder 20.06.2024 12:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
95
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы все делаете неправильно.

Во-первых, вы не создаете подкласс нового диалогового окна, вы создаете подкласс самого главного окна. Таким образом, вы будете получать сообщения, отправленные только в главное окно, а не в диалоговое окно. И вы никогда не получите WM_CREATE в подклассе, так как вы не можете создать подкласс окна с помощью SetWindowSubclass() до того, как оно будет создано. Для этого вы должны зарегистрировать собственный класс окна или использовать SetWindowHookEx() или SetWinEventHook() для обнаружения новых HWND созданий в вызывающем потоке.

И во-вторых, в этой ситуации вам вообще не нужен подкласс/хук. Вы можете просто вычислить желаемую позицию при создании диалога, например:

// hwd is the HANDLE of the main application window
void CustomDialog(HWND hwd, const std::wstring title, const std::wstring body, int width, int height)
{
    RECT r;
    GetWindowRect(hwd, &r);

    int x = r.left + ((r.right - r.left) / 2) - (width / 2);
    int y = r.top + ((r.bottom - r.top) / 2) - (height / 2);

    // Create the modal dialog window
    HWND dlg = CreateWindowEx(WS_EX_DLGMODALFRAME, L"ClassName", title.c_str(), WS_POPUPWINDOW | WS_CAPTION | DS_MODALFRAME | DS_NOIDLEMSG, x, y, width, height, hwd, NULL, GetModuleHandle(NULL), nullptr);
    ShowWindow(dlg, SW_SHOW);
    EnableWindow(hwd, false);
}

Или вы можете переместить диалоговое окно после его создания и перед его отображением, например:

// hwd is the HANDLE of the main application window
void CustomDialog(HWND hwd, const std::wstring title, const std::wstring body, int width, int height)
{
    // Create the modal dialog window
    HWND dlg = CreateWindowEx(WS_EX_DLGMODALFRAME, L"ClassName", title.c_str(), WS_POPUPWINDOW | WS_CAPTION | DS_MODALFRAME | DS_NOIDLEMSG, 0, 0, width, height, hwd, NULL, GetModuleHandle(NULL), nullptr);

    RECT mainRect;
    GetWindowRect(hwd, &mainRect);

    RECT dlgRect;
    GetWindowRect(dlg, &dlgRect);

    int x = mainRect.left + ((mainRect.right - mainRect.left) / 2) - ((dlgRect.right - dlgRect.left) / 2);
    int y = mainRect.top + ((mainRect.bottom - mainRect.top) / 2) - ((dlgRect.bottom - dlgRect.top) / 2);
    SetWindowPos(dlg, NULL, x, y, 0, 0, SWP_NOSIZE);

    ShowWindow(dlg, SW_SHOW);
    EnableWindow(hwd, false);
}

Мое недоразумение заключается в следующем: я ЗНАЮ, что создаю подкласс моего главного окна, но я думал, что произойдет что-то вроде нового WNDCLASSEX, только с изменением WNDPROC на новую процедуру подкласса. Вот почему я создал подкласс перед созданием окна. Я также знаю, как переместить окно, заставить клиента прямо в главном окне открыть диалоговое окно в нужном месте. С чем я боролся, так это без WM_CREATE, ни одна из кнопок и вещей не появлялась в диалоговом окне. Очевидно, что если вы создадите подкласс после создания окна, вы все равно не получите WM_CREATE.

The Welder 20.06.2024 11:57

Другие вопросы по теме