The correct CChildFrame is not activated after the predecessor is closed.

Flaviu_ 1,071 Reputation points
2025-07-21T11:59:20.9766667+00:00

MFC MDI application. Opened several CChildFrame s. (To illustrate better my issue I identified every CChildFrame with an unique id.)

Opened, lets say, 5 CChildFrame. 1, 2, 3, 4, 5.

List the order of the CChildFrame by menu: About: List Childs:

User's image

C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 5
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 4
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 3
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 2
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 1

Now, change their focus, by clicking on their tabs: click 1, then 4, then 2, then 3. List childs again:

User's image

C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 3
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 2
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 4
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 1
C:\Project\TT\MainFrm.cpp(237) : atlTraceGeneral - Child: 5

So, the focus is on CChildFrame 3.

Now, close this tab. Now, the logic will be the focus should move on CChildFrame 2 (the previous focused CChildFrame). But the focus is going to CChildFrame 5. Not ok. How can I change this illogical behaviour?

The test application could be found here: https://1drv.ms/u/c/dedcb6ef190b8fd4/EcmqdcH4nBhCoP0Dsn51opIB5PKcAQ_ZmoJkZs0tN-Tm2w?e=qY4him

Developer technologies | C++
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. RLWA32 50,066 Reputation points
    2025-07-21T16:08:28.58+00:00

    It appears that when Child Window 3 is closed MFC activates the next MDI child window, not the previous one.

    Following subclass procedure for mdi client window appears to achieve your desired result. It hasn't been tested extensively.

    LRESULT CMainFrame::MDIClientProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uiSubclass, DWORD_PTR dwRefData)
    {
    	if (msg == WM_MDIACTIVATE)
    	{
    		_RPT1(_CRT_WARN,"WM_MDIACTIVATE Activate Child 0x%p\n", (HWND)wParam);
    	}
    	else if (msg == WM_MDINEXT)
    	{
    		_RPT2(_CRT_WARN,"WM_MDINEXT Current Child HWND: 0x%p, Activate %s\n", (HWND)wParam, lParam == 0 ? "Next" : "Previous");
    	}
    	else if (msg == WM_MDIDESTROY)
    	{
    		_RPT1(_CRT_WARN, "WM_MDIDESTROY Destroy Child 0x%p\n", (HWND)wParam);
    		CMainFrame* p = (CMainFrame*)dwRefData;
    		p->MDIPrev();
    	}
    	else if (msg == WM_NCDESTROY)
    		RemoveWindowSubclass(hwnd, MDIClientProc, uiSubclass);
    
    	return DefSubclassProc(hwnd, msg, wParam, lParam);
    }
    

  2. Varsha Dundigalla(INFOSYS LIMITED) 795 Reputation points Microsoft External Staff
    2025-07-23T06:28:22.8866667+00:00

    Thank you for reaching out. Please find the answer below:
    Activate the Last-Focused MDI Child Instead of Z-Order Default

    Problem
    In an MFC MDI application, when a child window is closed, MFC activates the next window based on Z-order. This can lead to unintuitive behavior—for example, closing tab 3 activates tab 5 instead of tab 2, which was previously focused.

    Fix: Track Activation History

    To resolve this, subclass the MDI client window and maintain your own activation history. This allows you to activate the last-focused child window when the current one is closed.

    Implementation

    In CMainFrame.h:

    std::deque m_activationHistory;
    static LRESULT CALLBACK MDIClientProc(HWND, UINT, WPARAM, LPARAM, UINT_PTR, DWORD_PTR);
    

    In CMainFrame::OnCreate:

    SetWindowSubclass(m_hWndMDIClient, MDIClientProc, 0, (DWORD_PTR)this);
    

    Subclass Procedure:

    LRESULT CALLBACK CMainFrame::MDIClientProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
                                               UINT_PTR uiSubclass, DWORD_PTR dwRefData)
    {
        auto* pMain = reinterpret_cast(dwRefData);
    
        switch (msg)
        {
            case WM_MDIACTIVATE:
            {
                HWND h = (HWND)lParam;
                if (::IsWindow(h)) {
                    auto& hist = pMain->m_activationHistory;
                    hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
                    hist.push_front(h);
                }
                break;
            }
    
            case WM_MDIDESTROY:
            {
                HWND h = (HWND)wParam;
                auto& hist = pMain->m_activationHistory;
                hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
    
                for (HWND prev : hist) {
                    if (::IsWindow(prev)) {
                        ::SendMessage(hwnd, WM_MDIACTIVATE, (WPARAM)prev, 0);
                        break;
                    }
                }
                break;
            }
    
            case WM_NCDESTROY:
                RemoveWindowSubclass(hwnd, MDIClientProc, uiSubclass);
                break;
        }
    
        return DefSubclassProc(hwnd, msg, wParam, lParam);
    }
    

    Result

    • When a tab is closed, the last-focused child window is activated, providing a more intuitive user experience.
      Let us know if the issue persists after following these steps. We’ll be happy to assist further if needed.

  3. RLWA32 50,066 Reputation points
    2025-07-24T15:57:53.3866667+00:00

    @Flaviu_ Give the following a try -

    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\n", h);
    		auto &hist = pMain->m_activationHistory;
    		if (hist.empty() || h != hist.front())
    		{
    			if (!InDestroy)
    			{
    				hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
    				hist.push_front(h);
    				for (auto& f : hist)
    				{
    					CMDIChildWndEx* pChild = DYNAMIC_DOWNCAST(CMDIChildWndEx, FromHandle(f));
    					_RPT1(_CRT_WARN, "Activate Focus Child 0x%p, %d\n", f, (DYNAMIC_DOWNCAST(CTTDoc, pChild->GetActiveDocument()))->m_nDocId);
    				}
    			}
    		}
    		break;
    	}
    
    	case WM_MDIDESTROY:
    	{
    		HWND h = (HWND)wParam;
    		_RPT1(_CRT_WARN, "WM_MDIDESTROY Destroy Child 0x%p\n", h);
    		InDestroy = true;
    		auto result = DefSubclassProc(hwnd, msg, wParam, lParam);
    		InDestroy = false;
    		auto& hist = pMain->m_activationHistory;
    		hist.erase(std::remove(hist.begin(), hist.end(), h), hist.end());
    
    		if (!hist.empty())
    		{
    			::SendMessage(hwnd, WM_MDIACTIVATE, (WPARAM)hist.front(), 0);
    		}
    
    		for (auto& f : hist)
    		{
    			CMDIChildWndEx* pChild = DYNAMIC_DOWNCAST(CMDIChildWndEx, FromHandle(f));
    			_RPT1(_CRT_WARN, "Destroy Focus Child 0x%p %d\n", f, (DYNAMIC_DOWNCAST(CTTDoc, pChild->GetActiveDocument()))->m_nDocId);
    		}
    
    		return result;
    
    		break;
    	}
    
    	case WM_NCDESTROY:
    		RemoveWindowSubclass(hwnd, MDIClientProc, uiSubclass);
    		break;
    	}
    
    	return DefSubclassProc(hwnd, msg, wParam, lParam);
    }
    

    The idea was to prevent the order of the focused windows from being changed by an extra WM_MDIACTIVATE message sent by the framework. After it handles a message it prints the focus order so you can tell where focus should go upon closing a child window.


  4. RLWA32 50,066 Reputation points
    2025-08-10T16:19:27.9833333+00:00

    @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. :)


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.