@Flaviu_ It seems to me that MFC's MDI Client class handles selecting and activating the next MDI child in its OnMDIDestroy handler.
I didn't see any convenient way to override functions (they weren't virtual) and the data structures that MFC uses to track MDI child windows/tabs just aren't accessible.
So instead of trying to change how MFC activates MDI child windows/tabs I decided to work around the flash problem by letting MFC do its thing while preventing screen updating and then activating the desired MDC child from the z order history. Even though screen updating was prevented MFC still changes the z order so I added some additional code to keep the activation history and the z order in sync.
The next iteration of MDIClientProc -
LRESULT CALLBACK CMainFrame::MDIClientProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
UINT_PTR uiSubclass, DWORD_PTR dwRefData)
{
auto* pMain = reinterpret_cast<CMainFrame*>(dwRefData);
static bool InDestroy = false;
switch (msg)
{
case WM_MDIACTIVATE:
{
HWND h = (HWND)wParam;
_RPT1(_CRT_WARN, "WM_MDIACTIVATE Child 0x%p, InDestroy: %s\n", h, InDestroy ? "true" : "false");
auto &hist = pMain->m_activationHistory;
// If Indestroy = true then MFC has changed the z order to bring its choice to the top
// We're going to activate our own MDI Child so put MFC's choice back in the activation history z order
if (InDestroy)
{
// Find previous window from history to use with SetWindowPos
for (auto pos = 0; pos < hist.size(); pos++)
{
if (h == hist.at(pos))
{
--pos;
if (pos >= 0) // Can't have a negative index into deque
{
HWND prev = hist.at(pos);
::SetWindowPos(h, prev, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
break;
}
}
}
else
{
// Handle ordinary activation
if (hist.empty() || h != hist.front())
{
hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
hist.push_front(h);
}
}
break;
}
case WM_MDIDESTROY:
{
HWND h = (HWND)wParam;
InDestroy = true; // Set flag that a tab has been closed
_RPT1(_CRT_WARN, "Before: WM_MDIDESTROY Destroy Child 0x%p, InDestroy: %s\n", h, InDestroy ? "true" : "false");
auto& hist = pMain->m_activationHistory;
hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
if (!hist.empty())
{
CMDIChildWndEx* pChild = DYNAMIC_DOWNCAST(CMDIChildWndEx, FromHandle(hist.front()));
// Let MFC handle the MDI child close but prevent screen updating of the Child activated by MFC
pMain->SetRedraw(FALSE);
auto result = DefSubclassProc(hwnd, msg, wParam, lParam);
// MDI Child was destroyed by DefSubclassProc so reset the flag
// MDI Child z order was changed by MFC -- handled in WM_MDIACTIVATE
InDestroy = false;
_RPT1(_CRT_WARN, "After: WM_MDIDESTROY Destroy Child 0x%p, InDestroy: %s\n", h, InDestroy ? "true" : "false");
// Active the desired MDI Child from the activation history
pChild->MDIActivate();
// Redraw the updated windows
pMain->SetRedraw(TRUE);
pMain->RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
return result;
}
else
InDestroy = false;
break;
}
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, MDIClientProc, uiSubclass);
break;
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
I also added some code to display the MDI child window z order and also the activation history so you can see that they are synchronized.
void CMainFrame::OnHelpListchilds()
{
// TODO: Add your command handler code here
HWND hChild = ::GetWindow(m_hWndMDIClient, GW_CHILD);
CWnd* pWnd = CWnd::FromHandle(hChild);
while (pWnd)
{
if (pWnd->IsKindOf(RUNTIME_CLASS(CMDIChildWndEx)))
{
auto c = static_cast<CMDIChildWndEx*>(pWnd);
auto ptc = c->GetRelatedTabGroup();
auto tab = ptc->GetTabFromHwnd(c->GetSafeHwnd());
CTTDoc* pDoc = static_cast<CTTDoc*>(c->GetActiveDocument());
_RPTN(_CRT_WARN, "Child: HWND: 0x%p index: %d, id: %d\n", c->GetSafeHwnd(), tab, pDoc->m_nDocId);
}
pWnd = pWnd->GetNextWindow();
}
for (auto& f : m_activationHistory)
{
CMDIChildWndEx* pChild = DYNAMIC_DOWNCAST(CMDIChildWndEx, FromHandle(f));
auto ptc = pChild->GetRelatedTabGroup();
auto tab = ptc->GetTabFromHwnd(f);
_RPT2(_CRT_WARN, "History: Child 0x%p, index: %d, id: %d\n", f, tab, (DYNAMIC_DOWNCAST(CTTDoc, pChild->GetActiveDocument()))->m_nDocId);
}
}
I look forward to the results of your next round of testing. :)