一旦消息的当前实例在 MFC 中完成,如何 post 一条消息?

How to post a message once the current instance of the message has finished in MFC?

我对 Windows 应用程序中的消息没有足够的理解。

我有这个按钮处理程序:

void CChristianLifeMinistryEditorDlg::OnFilePublicTalk()
{
    CWeekendMeetingDlg dlgPublicTalk(this);

    if (m_pEntry != nullptr)
    {
        dlgPublicTalk.SetPublicTalkInfo(m_pEntry->GetPublicTalkInfo());
        dlgPublicTalk.SetCircuitVisitMode(m_iIncludeMode == kIncludeServiceTalk); // AJT v17.0.7
        // AJT v20.0.1
        dlgPublicTalk.SetSongInfo(
            CChristianLifeMinistryUtils::UseSingOutJoyfullyToJehovah(m_datFirstMonday), m_eForeignLang);
        auto iResult = dlgPublicTalk.DoModal();
        if (iResult == IDOK || iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate) ||
                               iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
        {
            m_pEntry->SetPublicTalkInfo(dlgPublicTalk.GetPublicTalkInfo());

            SetModified(true);
            UpdatePreview(m_iDateIndex);
            m_pHtmlPreview->Refresh2(REFRESH_COMPLETELY); // Ensure it has refreshed

            // Padlock
            // Reminder
            // Disable controls

            if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate))
            {
                AfxMessageBox(_T("Move to previous week."));
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);

                // It can't process this message until the current instance one has finished

            }
            else if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
            {
                AfxMessageBox(_T("Move to next week."));
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);

                // It can't process this message until the current instance one has finished
            }
        }
    }
}

我删除了代码(替换为两个 AfxMessageBox 调用)但原则上我想重新启动同一个事件处理程序,就好像用户在文件菜单上单击它一样。在消息的当前实例尚未终止时使用 PostMessage 是否可以?


我还没有尝试提供的答案,但是我在引入两个 PostMessage 调用时遇到了问题:

void CChristianLifeMinistryEditorDlg::OnFilePublicTalk()
{
    CWeekendMeetingDlg dlgPublicTalk(this);

    if (m_pEntry != nullptr)
    {
        dlgPublicTalk.SetPublicTalkInfo(m_pEntry->GetPublicTalkInfo());
        dlgPublicTalk.SetCircuitVisitMode(m_iIncludeMode == kIncludeServiceTalk); // AJT v17.0.7
        // AJT v20.0.1
        dlgPublicTalk.SetSongInfo(
            CChristianLifeMinistryUtils::UseSingOutJoyfullyToJehovah(m_datFirstMonday), m_eForeignLang);

        // AJT v20.1.8
        dlgPublicTalk.SetPreviousNextDateButtonStates(m_btnMovePrevious.IsWindowEnabled(),
                                                      m_btnMoveNext.IsWindowEnabled());
        auto iResult = dlgPublicTalk.DoModal();
        if (iResult == IDOK || iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate) ||
                               iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
        {
            m_pEntry->SetPublicTalkInfo(dlgPublicTalk.GetPublicTalkInfo());

            SetModified(true);
            UpdatePreview(m_iDateIndex);
            m_pHtmlPreview->Refresh2(REFRESH_COMPLETELY); // Ensure it has refreshed

            if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate))
            {
                PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_PREVIOUS_DATE, BN_CLICKED),
                    (LPARAM)m_btnMovePrevious.GetSafeHwnd());
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
            }
            else if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
            {
                PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_NEXT_DATE, BN_CLICKED),
                    (LPARAM)m_btnMoveNext.GetSafeHwnd());
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
            }
        }
    }
}

执行完操作后,它会重新显示 window,然后我单击“取消”,我得到:

如果我更改它并直接调用按钮处理程序,如下所示:

void CChristianLifeMinistryEditorDlg::OnFilePublicTalk()
{
    CWeekendMeetingDlg dlgPublicTalk(this);

    if (m_pEntry != nullptr)
    {
        dlgPublicTalk.SetPublicTalkInfo(m_pEntry->GetPublicTalkInfo());
        dlgPublicTalk.SetCircuitVisitMode(m_iIncludeMode == kIncludeServiceTalk); // AJT v17.0.7
        // AJT v20.0.1
        dlgPublicTalk.SetSongInfo(
            CChristianLifeMinistryUtils::UseSingOutJoyfullyToJehovah(m_datFirstMonday), m_eForeignLang);

        // AJT v20.1.8
        dlgPublicTalk.SetPreviousNextDateButtonStates(m_btnMovePrevious.IsWindowEnabled(),
                                                      m_btnMoveNext.IsWindowEnabled());
        auto iResult = dlgPublicTalk.DoModal();
        if (iResult == IDOK || iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate) ||
                               iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
        {
            m_pEntry->SetPublicTalkInfo(dlgPublicTalk.GetPublicTalkInfo());

            SetModified(true);
            UpdatePreview(m_iDateIndex);
            m_pHtmlPreview->Refresh2(REFRESH_COMPLETELY); // Ensure it has refreshed

            if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate))
            {
                //PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_PREVIOUS_DATE, BN_CLICKED),
                //  (LPARAM)m_btnMovePrevious.GetSafeHwnd());
                OnBnClickedMfcbuttonPreviousDate();
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
            }
            else if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
            {
                //PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_NEXT_DATE, BN_CLICKED),
                //  (LPARAM)m_btnMoveNext.GetSafeHwnd());
                OnBnClickedMfcbuttonNextDate();
                PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
            }
        }
    }
}

后一种方法有效。我应该指出,这两个按钮处理程序更新了一个 HTML 文件并在 Web 浏览器控件(实际上在父对话框中)中重新绘制它。

像这样直接调用那些按钮处理程序是否可以接受?


使用提供的答案(谢谢)效果很好:

void CChristianLifeMinistryEditorDlg::OnFilePublicTalk()
{
    delay_post_msg dpm{ this };
    CWeekendMeetingDlg dlgPublicTalk(this);

    if (m_pEntry != nullptr)
    {
        dlgPublicTalk.SetPublicTalkInfo(m_pEntry->GetPublicTalkInfo());
        dlgPublicTalk.SetCircuitVisitMode(m_iIncludeMode == kIncludeServiceTalk); // AJT v17.0.7
        // AJT v20.0.1
        dlgPublicTalk.SetSongInfo(
            CChristianLifeMinistryUtils::UseSingOutJoyfullyToJehovah(m_datFirstMonday), m_eForeignLang);

        // AJT v20.1.8
        dlgPublicTalk.SetPreviousNextDateButtonStates(m_btnMovePrevious.IsWindowEnabled(),
                                                      m_btnMoveNext.IsWindowEnabled());
        auto iResult = dlgPublicTalk.DoModal();
        if (iResult == IDOK || iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate) ||
                               iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
        {
            m_pEntry->SetPublicTalkInfo(dlgPublicTalk.GetPublicTalkInfo());

            SetModified(true);
            UpdatePreview(m_iDateIndex);
            m_pHtmlPreview->Refresh2(REFRESH_COMPLETELY); // Ensure it has refreshed

            if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::PreviousDate))
            {
                //PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_PREVIOUS_DATE, BN_CLICKED),
                //  (LPARAM)m_btnMovePrevious.GetSafeHwnd());
                OnBnClickedMfcbuttonPreviousDate();
                //PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
                dpm.set_active();
            }
            else if (iResult == to_underlying(CWeekendMeetingDlg::EndResult::NextDate))
            {
                //PostMessage(WM_COMMAND, MAKEWPARAM(IDC_MFCBUTTON_NEXT_DATE, BN_CLICKED),
                //  (LPARAM)m_btnMoveNext.GetSafeHwnd());
                OnBnClickedMfcbuttonNextDate();
                //PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
                dpm.set_active();
            }
        }
    }
}

我不得不直接使用两个 OnBnClickedMfcbuttonPreviousDate / OnBnClickedMfcbuttonNextDate 按钮处理程序,而没有使用 PostMessage 来模拟按钮点击。我认为这是可以接受的?

正如所写,这是安全的。 PostMessage 生成一个 queued message 直到当前线程再次调用消息检索函数(如 GetMessage)后才会被观察到。然而,消息循环被阻塞,等待当前消息处理程序 return 控制权返回给它。

不幸的是,MFC 提供的(主)消息循环并不是可能调度消息的唯一代码。例如,对话框(如 MessageBoxes)启动它们自己的消息循环。与菜单或 window 调整大小的实现一样。有很多机会恢复到您试图防止的重入状态。

更强大的解决方案可以将 PostMessage 调用推迟到函数 return 之前。 C++ 提供了实现这一点所需的所有工具:Destructors!

struct delay_post_msg {
    delay_post_msg(CWnd* w) : w_{ w }, active_{ false } {}
    void set_active() { active_ = true; }
    ~delay_post_msg()
    {
        if (active_) w_->PostMessage(WM_COMMAND, ID_FILE_PUBLIC_TALK, 0);
    }
private:
    CWnd* w_;
    bool active_;
};

你会像这样使用它:

void CChristianLifeMinistryEditorDlg::OnFilePublicTalk()
{
    // Objects are destroyed in the opposite order they are created.
    // If this object's d'tor needs to run last, it has to be created first.
    delay_post_msg dpm{ this };

    CWeekendMeetingDlg dlgPublicTalk(this);

    // ...

    if (some_condition)
    {
        dpm.set_active();
    }

    // ...

    // If active, the d'tor of dpm posts the message just before leaving this function.
}