I don't have enough understanding of messages in Windows applications.
I have this button handler:
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
}
}
}
}
I have removed code (replaced with the two AfxMessageBox
calls) but in principle I want to re-start the same event handler as if the user had clicked it on the File menu. Is it OK to use PostMessage
when the current instance of the message has not yet terminated?
I have not yet tried the answer provided, but I have encountered an issue when I introduce two PostMessage
calls:
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);
}
}
}
}
After the actions have been performed and it re-displays the window, and I then click Cancel I get:
If I change it and directly call the button handlers, like this:
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);
}
}
}
}
This latter approach works. I should point out that these two button handlers update a HTML file and redraw it in the web browser control (which is actually on the parent dialog).
Is it acceptable to directly call those button handlers like this?
Using the answer provided (thank you) this is working well:
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();
}
}
}
}
I had to use the two OnBnClickedMfcbuttonPreviousDate
/ OnBnClickedMfcbuttonNextDate
button handlers directly without using PostMessage
to simulate button clicks. I assume that is acceptable?
As written, this is safe. PostMessage
generates a queued message that doesn't get observed until the current thread gets around to calling a message-retrieval function (like GetMessage
) again. The message loop, however, is blocked, waiting for the current message handler to return control back to it.
Unfortunately, the (main) message loop provided by MFC isn't the only code that potentially dispatches messages. Dialogs (like MessageBox
es) fire up their own message loop, for example. As do menus, or the window resizing implementation. Lots of opportunities to revert back to the reentrancy you were trying to guard against.
A more robust solution could postpone the PostMessage
call to just before the function returns. C++ provides all the tools required to implement this: 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_;
};
You'd use it something like this:
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.
}