Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / MFC
Article

Switch Between Documents

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
4 Jan 2020CPOL3 min read 8.2K   287   6   2
Switching between open documents in multi-document interface application

Image 1

Introduction

When you generate a multi-document interface application with Visual Studio Wizard, you can switch between several opened documents using key combination Ctrl+Tab. And every time you press Tab key while Ctrl key is down, you will switch the next open document. If you want to return on some previous open document, you can do it by walking through to already open documents. If you test this behavior on professional editors, including Visual Studio, you will notice that this behavior is a little optimized. In what way? If you release Ctrl key after every document switched and you press Ctrl+Tab again, you will activate the previous open document, not the next one. You can see this inside Visual Studio editor, Notepad++, and a lot of professional editors, even on some browsers, like Opera (this is one of the reason why I prefer Opera browser. :) At this time, Chrome doesn't have such behavior, or at least I didn't know to have it.

Background

Let's make things more clear. There are two cases:

  • Case 1. Ctrl+Tab->switch next document. Keep Ctrl key pressed. Press again Tab key->switch next document. Keep Ctrl key pressed. Press Tab key again->switch next document. And so on.
  • Case 2. Ctrl+Tab->switch next document. Release Ctrl key. Press again Ctrl+Tab->switch next document. Release Ctrl key. Press again Ctrl+Tab key->switch next document.

Those two cases reveal that even if you use different key combinations, you will have the same behavior on default MDI application. This will not happen on Visual Studio editor. In case 2, Visual Studio will activate the previous document, not the next one. This is more convenient. Why? Suppose you have 10 documents open. You activate document 3. Then you activate document 10. If you go back on document 3 and want to navigate to document 10 again, you will have to pass through all the documents from 3 to 10. And what if you have more than 10 documents open?

Using the Code

Ok. So, the professional editors can handle easily this document switching and activation behavior by delivering the last visited document to the user at Ctrl+Tab key combination. My solution adds this improvement at the default MDI application with some minimal changes.

These changes are:

  1. Adding in CMainFrame an array of pointers for opened childframe.
    C++
    CTypedPtrArray<CPtrArray, CMDIChildWnd*> m_arrChild;

    And then handling here adding, removing and synchronizing the opened childframes with this array of pointers.

    C++
        ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
        ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
        ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
        ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
    ...
    ...
    
    LRESULT CMainFrame::OnChildFrameAdded(WPARAM wParam, LPARAM lParam)
    {
        for (int i = 0; i < m_arrChild.GetSize(); ++i)
        {
            if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
            {
                return 0;
            }
        }
    
        m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
    
        return 1;
    }
    
    LRESULT CMainFrame::OnChildFrameRemoved(WPARAM wParam, LPARAM lParam)
    {
        for (int i = 0; i < m_arrChild.GetSize(); ++i)
        {
            if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
            {
                m_arrChild.RemoveAt(i);
                break;
            }
        }
    
        return 1;
    }
    
    LRESULT CMainFrame::OnChildFrameActivate(WPARAM wParam, LPARAM lParam)
    {
        for (int i = 0; i < m_arrChild.GetSize(); ++i)
        {
            if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
            {
                CMDIChildWnd* pMDIChild = m_arrChild.GetAt(i);
                if (pMDIChild->IsIconic())
                    pMDIChild->ShowWindow(SW_RESTORE);
                MDIActivate(pMDIChild);
                CMDIChildWndEx* pMDIChildEx = DYNAMIC_DOWNCAST(CMDIChildWndEx, pMDIChild);
                if (NULL != pMDIChildEx->GetSafeHwnd() && pMDIChildEx->IsTabbedMDIChild())
                {
                    CMFCTabCtrl* pTabCtrl = pMDIChildEx->GetRelatedTabGroup();
                    if (NULL != pTabCtrl->GetSafeHwnd())
                    {
                        const int nTab = pTabCtrl->GetTabFromHwnd(pMDIChildEx->GetSafeHwnd());
                        pTabCtrl->SetActiveTab(nTab);
                    }
                }
                break;
            }
        }
    
        return 1;
    }
    // put lParam as index 0 in m_arrChild
    LRESULT CMainFrame::OnChildFrameActivated(WPARAM wParam, LPARAM lParam)
    {
        for (int i = 0; i < m_arrChild.GetSize(); ++i)
        {
            if (reinterpret_cast<CMDIChildWnd*>(lParam) == m_arrChild.GetAt(i))
            {
                m_arrChild.RemoveAt(i);
                break;
            }
        }
    
        m_arrChild.InsertAt(0, (CMDIChildWnd*)lParam);
    
        return 1;
    }
  2. In childframe, send to mainframe the information about creating, destroying and activate the childframe.
    C++
    CChildFrame::CChildFrame() noexcept
    {
        // TODO: add member initialization code here
    
        ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), WMU_CHILDFRAMEADDED, 0, 
          reinterpret_cast<LPARAM>(this));
    }
    
    CChildFrame::~CChildFrame()
    {
        ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), WMU_CHILDFRAMEREMOVED, 
          0, reinterpret_cast<LPARAM>(this));
    }
    
    void CChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
    {
        // call the base class to let standard processing switch to
        // the top-level menu associated with this window
        CMDIChildWndEx::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
    
        if(bActivate)
            ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(), WMU_CHILDFRAMEACTIVATED, 
              0, reinterpret_cast<LPARAM>(this));
    }
  3. Add CWindowsManagerDialog class to your project for listing the opened childframes when you press Ctrl+Tab.

    CWindowsManagerDialog is an adaptation for CMFCWindowsManagerDialog, because this one has a different purpose.

  4. Handling PreTranslateMessage in CMainFrame and CWindowsManagerDialog to follow the user key presses.
    C++
    BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
    {
        // TODO: Add your specialized code here and/or call the base class
    
        if (WM_KEYDOWN == pMsg->message && VK_TAB == pMsg->wParam && 
            GetAsyncKeyState(VK_CONTROL) < 0 && m_arrChild.GetSize() > 1)
        {
            CWindowsManagerDialog* pDlg = new CWindowsManagerDialog;
            pDlg->Create(CWindowsManagerDialog::IDD, this);
            pDlg->ShowWindow(SW_SHOW);
            return TRUE;
        }
    
        return CMDIFrameWndEx::PreTranslateMessage(pMsg);
    }

With all of these changes, you will have the document switching and activation behavior like Visual Studio editor. This code will run without any problem if you have CChildFrame derived from CMDIChildWnd or CMDIChildWndEx as well, and your application has or not the tabbed childframe, the code will automatically know it.

Known issues: The attached zip file contain an example of this approach. And it is built in Visual Studio 2017. In CMainFrame is using the IsTabbedMDIChild() call. This call might be unavailable in older Visual Studio versions. Is this case, you should write it into your CMainFrame header like that:

C++
// Attributes
public:
    BOOL IsTabbedMDIChild() const { return NULL != GetSafeHwnd() && AreMDITabs(); }

and everything should be alright.

If you think this is useful, you can adapt it, modify it and implement it as you wish. Enjoy it!

Points of Interest

With the simple changes from above, anyone who has an MDI application can turn it into one that behaves like a professional editor does.

History

  • 5th January, 2020: Published article
  • 27th September, 2023: Corrected documents order at closing

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMFC outdated Pin
Ken M10-Jan-20 6:56
Ken M10-Jan-20 6:56 
AnswerRe: MFC outdated Pin
Ralf Wirtz16-Feb-21 22:20
Ralf Wirtz16-Feb-21 22:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.