Завершение вложенного цикла сообщений пользовательского диалога - PullRequest
1 голос
/ 04 ноября 2019

У меня есть диалоговый класс, созданный из шаблона диалога в памяти, который запускает свой собственный вложенный цикл сообщений (в дополнение к основному циклу сообщений приложения).

Для создания диалога я использую CreateDialogIndirectParamW, который возвращает дескриптордиалоговое окно,

Поскольку диалоговое окно полностью настроено, вся обработка и создание сообщения выполняется вручную.

Я пропустил процедуру диалога, но в основном она вызывает нижеприведенные соответствующие обработчики сообщений, как толькодиалог закрывается.

Обратите внимание, что мы не можем использовать функцию EndDialog для уничтожения диалога и завершения цикла сообщений, потому что для CreateDialogIndirectParamW мы должны явно использовать DestroyWindow.

Я ищу эффективный способ завершения диалогового цикла.

Я пытался реализовать этот цикл , но проблема в том, что этот подход потребляет слишком много ресурсов ЦП, поскольку forцикл в примере кода будет просто работать глупо, пока не появится сообщение, или если for опущено, то PeekMessage немедленно остановит цикл, а это не то, что я не хочу.

Соответствующее объявление класса:

class Dialog :
    public ContainerWindow,
    public MessageWindow,
    public SuperClassWindow
{
    // ...

public:
    /** Process messages for dialog window */
    [[nodiscard]] int RunMessageLoop() override;

protected:
    /** Handler for WM_NCDESTROY */
    std::uint32_t OnNcDestroy(const UINT& uMsg, const WPARAM& wParam, const LPARAM& lParam) override;

    /** Handler for WM_DESTROY */
    inline void OnDestroy() const noexcept override;

    /** Handler for WM_CLOSE */
    inline virtual bool OnClose() noexcept;

    // ...

protected:
        HWND mhWnd;                 /** Window handle of derived component */
    }

Упрощенное определение класса:

std::uint32_t Dialog::OnNcDestroy(
    [[maybe_unused]] const UINT& uMsg,
    [[maybe_unused]] const WPARAM& wParam,
    [[maybe_unused]] const LPARAM& lParam)
{
    // ...
    delete this; // note we can't set this pointer to nullptr!
    // ...
    return count;
}

void Dialog::OnDestroy() const noexcept
{
    PostQuitMessage(0);
}

bool Dialog::OnClose() noexcept
{
    return DestroyWindow(mhWnd);
}

А вот цикл сообщений для диалога: мне нужно добавить проверочный код в цикл, чтобы проверить, является ли диалог допустимым объектом, то есть как-то остановить цикл, если объект Dialog был удален

После того, какОбработчик OnNcDestroy вызван, IsDialogMessageW ниже произойдет сбой, см. Комментарий.

Похоже, GetMessageW продолжит работать после отправки WM_NCDESTROY, цикл все еще ожидает WM_QUIT, отправленный *Обработчик 1045 *, поэтому цикл msg продолжит работать после удаления объекта Dialog, что приведет к сбою IsDialogMessageW(mhWnd, &msg) ниже. поскольку mhWnd больше не существует.

int Dialog::RunMessageLoop()
{
    EnableWindow(mhWndParent, FALSE);

    MSG msg{ };
    BOOL result = FALSE;

    while ((result = GetMessageW(&msg, nullptr, 0, 0)) != FALSE)
    {
        if (result == -1)
        {
            ShowError(ERR_BOILER); // error checking function.
            break;
        }

        // once OnNcDestroy is called "mhWnd" is invalid memory
        // and this will off course cause access violation!
        if (!IsDialogMessageW(mhWnd, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    EnableWindow(mhWndParent, TRUE);

    return static_cast<int>(msg.wParam);
}

обратите внимание, что мы не можем if (this), поскольку this не является nullptr и не может быть установлено на nullptr в OnNcDestroy обработчик.

1 Ответ

0 голосов
/ 04 ноября 2019

ОК, после некоторого времени экспериментирования я предложил следующее изменение в коде, которое заставляет все работать как надо! (см. комментарии для получения дополнительной информации)

кстати. Я оставлю этот вопрос открытым, надеюсь, есть более эффективный способ для этого, в настоящее время IsWindow вызывается для каждого сообщения, которое, возможно, является проблемой производительности.

int Dialog::RunMessageLoop()
{
    EnableWindow(mhWndParent, FALSE);

    MSG msg{ };
    BOOL result = FALSE;

    // first we save the handle to the window
    HWND hwnd = mhWnd;
    if (!IsWindow(hwnd)) // make sure the handle is valid dialog window
        std::abort();

    while ((result = GetMessageW(&msg, nullptr, 0, 0)) != FALSE)
    {
        if (result == -1)
        {
            ShowError(ERR_BOILER);
            break;
        }

        // if object is deleted the handle is no longer valid!
        // because prior to OnNcDestroy handler DestroyWindow has been called
        if (!IsWindow(hwnd))
            goto invalid;

        if (!IsDialogMessageW(mhWnd, &msg))
        {
        invalid: // non dialog message processing
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    // NOTE that we removed EnableWindow(mHwndParent) here!

    return static_cast<int>(msg.wParam);
}

Это само по себе будет работать, но один дополнительныйшаг состоит в том, чтобы включить окно владельца (главное окно) до уничтожения диалога, поэтому обновите обработчик:

bool Dialog::OnClose() noexcept
{
    // otherwise first window in Z order (such as Desktop) will get focus!
    EnableWindow(mhWndParent, TRUE); 

    return DestroyWindow(mhWnd);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...