Модальное диалоговое окно MFC не отображается из-за неактивных проверок в CWnd::RunModalLoop

Ниже я поставил источник CWnd::RunModal, который является циклом сообщений, запускаемым при вызове CDialog::DoModal - он вступает во владение как вложенный цикл сообщений, пока диалог не закончится.

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

Это приводит к тому, что в некоторых случаях в нашем приложении диалог не появляется в течение многих секунд, когда DoModal называется. Если я отлаживаю код и ставлю точки останова, я вижу, что цикл фазы 1 не достигнут до этого времени. Однако, если я создаю тот же диалог немодально (вызов Create затем ShowWindow это появляется мгновенно) - но это было бы неловким изменением, чтобы просто исправить ошибку, не понимая ее хорошо.

Есть ли способ избежать этой проблемы? Возможно я могу позвонить ShowWindow явно в какой-то момент, например, или опубликовать сообщение для запуска бездействия? Я прочитал "Старая новая вещь - модальность", которая была очень информативной, но не ответила на этот вопрос, и я могу только найти ее редко упоминаемой в Интернете, без успешного разрешения.

wincore.cpp: CWnd:: RunModalLoop

int CWnd::RunModalLoop(DWORD dwFlags)
{
    ASSERT(::IsWindow(m_hWnd)); // window must be created
    ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state

    // for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
    BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
    HWND hWndParent = ::GetParent(m_hWnd);
    m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
    MSG *pMsg = AfxGetCurrentMessage();

    // acquire and dispatch messages until the modal state is done
    for (;;)
    {
        ASSERT(ContinueModal());

        // phase1: check to see if we can do idle work
        while (bIdle &&
            !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
        {
            ASSERT(ContinueModal());

            // show the dialog when the message queue goes idle
            if (bShowIdle)
            {
                ShowWindow(SW_SHOWNORMAL);
                UpdateWindow();
                bShowIdle = FALSE;
            }

            // call OnIdle while in bIdle state
            if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
            {
                // send WM_ENTERIDLE to the parent
                ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
            }
            if ((dwFlags & MLF_NOKICKIDLE) ||
                !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
            {
                // stop idle processing next time
                bIdle = FALSE;
            }
        }

        // phase2: pump messages while available
        do
        {
            ASSERT(ContinueModal());

            // pump message, but quit on WM_QUIT
            if (!AfxPumpMessage())
            {
                AfxPostQuitMessage(0);
                return -1;
            }

            // show the window when certain special messages rec'd
            if (bShowIdle &&
                (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
            {
                ShowWindow(SW_SHOWNORMAL);
                UpdateWindow();
                bShowIdle = FALSE;
            }

            if (!ContinueModal())
                goto ExitModal;

            // reset "no idle" state after pumping "normal" message
            if (AfxIsIdleMessage(pMsg))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }

        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
    }

ExitModal:
    m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
    return m_nModalResult;
}

3 ответа

Поэтому, чтобы ответить на мой собственный вопрос, решение, которое я нашел, состояло в том, чтобы явно вызвать следующие два метода:

ShowWindow(SW_SHOWNORMAL);
UpdateWindow();

CWnd::RunModalLoop должен вызывать их, но только когда он обнаруживает, что очередь сообщений пуста / неактивна. Если этого не происходит, то диалоговое окно существует и блокирует ввод в другие окна, но не отображается.

Из сообщений отслеживания я нашел WM_ACTIVATE было последнее сообщение, отправленное до того, как что-то застряло, поэтому я добавил OnActivate() обработчик моего класса Dialog.

Я обнаружил очень похожую проблему в своем приложении. Это произошло только тогда, когда приложение было под большой нагрузкой для рисования (пользователь может открывать множество представлений, представления показывают данные в реальном времени, поэтому они постоянно обновляются, и каждое представление должно быть нарисовано независимо, а процесс рисования занимает много времени. времени). Итак, если в этом сценарии пользователь пытается открыть какой-либо модальный диалог (скажем, диалоговое окно «О программе», или если приложению необходимо отобразить какой-либо модальный диалог, например ), он "зависает", и диалоговое окно отображается только после нажатия клавиши ALT. Анализируя, я пришел к такому же выводу, что и вы, то есть первый цикл (названный «фаза1» в комментариях к коду) никогда не вызывается, так как ему нужно, чтобы очередь сообщений была пустой, а это никогда не бывает; затем приложение попадает во второй цикл, phase2, из которого оно никогда не выходит, потому что вызов в конце фазы 2 никогда не возвращает ноль (очередь сообщений очень занята из-за постоянного обновления множества представлений). Поскольку этот код находится в wincore.cpp, моим решением было найти ближайшую функцию, которая могла быть перегружена, и мне повезло, что я нашел виртуальную. Поскольку у меня уже был класс, производный от который я всегда использую в качестве замены, мне нужно было только определить в этом классе a переменная и перегрузка:

      BOOL CMyDialog::ContinueModal()
{
    // Begin extra code by Me
    if (m_nFlags & WF_CONTINUEMODAL)
    {
        if (!m_bShown && !(GetStyle() & WS_VISIBLE))
        {
            ShowWindow(SW_SHOWNORMAL);
            UpdateWindow();
            m_bShown = TRUE;
        }
    }
    // End extra code
    return m_nFlags & WF_CONTINUEMODAL;
}

Это вызывает чтобы фактически показать окно, если оно никогда раньше не показывалось. Я установил как в его декларации, так и в , так что он прибывает в , и установите его на сразу после показа окна, чтобы отключить небольшой дополнительный фрагмент кода. Это решило проблему для меня и, предположительно, должно решить ее для всех. Остались только сомнения: 1) вызывается из другого места в MFC, что может конфликтовать с этой модификацией? и 2) Почему Microsoft запрограммировала этот странный способ оставить там эту вопиющую дыру, которая может привести к зависанию приложения без какой-либо видимой причины?

Недавно у меня была такая же проблема. Я решил, отправив недокументированное сообщение 0x118 перед вызовом DoModal(), который обрабатывается на этапе 2.

... PostMessage(0x118, 0, 0); return CDialog::DoModal();

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