• Download demo - 12 Kb
  • Download demo project - 32 Kb
  • Download source - 5 Kb

Overview

A feature I always missed in most MDI products (like Microsoft Word or Microsoft FrontPage) was a simple means to see which views/docs are currently open and to switch easily between them. Using the 'Window' menu can be quite cumbersome. I wanted something like Oz Solomonovich's Window Tabs Add-In. In my current project I have to do an MDI application with several different views and I don't want my users to become as frustrated as me about this topic. So I decided to make my own CTabCtrl derivation. As it turns out, it was very easy to do so.

CMDITabs is a little control that adds 'tabs' to an MFC MDI application with which the user can switch between views. The appearance can also be customized to display the view icons. This is useful if you have different types of views, otherwise I would suggest to turn the icons off. The tabs can be placed at the bottom or at the top of the views. The control is smart enough to reflect all changes in the views (open, close, new, change title/icon).

After writing this article I discovered, that there is a similar solution for tabbing between MDI views in the doc/viewsection of Code Project. First I was a little bit frustrated, thinking I had reinvented the wheel again ;-). But on a closer look I liked my MDITabs better because of three reasons:

  1. it looks nicer and hasn't got a space wasting double border frame
  2. it is more stable due to its simpler implementation ;-) (read the bug list in the comments)
  3. it is smaller, you have only one class to use, not three

So I sent this article int to the Code Project.

How to use

Using CMDITabs in your code is extremely simple. Add MDITabs.h and MDITabs.cpp to your project. In yourCMainFrame class add a new member m_wndMDITabs of class CMDITabs (don't forget to include 'MDITabs.h'). Insertm_mdiTabs.Create(this); in CMainFrame::OnCreate() after all toolbars and status bars have been set up created. It is important for proper layout that the MDI tabctrl is created last. To synchronize view operations with the MDI Tabs, it is necessary to override OnUpdateFrameTitle from the base class CMDIFrameWnd of our CMainFrameclass. After calling the base class implementation you have to call the Update() function of m_wndMDITabs

// MainFrm.hclass CMainFrame : public CMDIFrameWnd{  [...]  CMDITabs m_wndMDITabs;    virtual void OnUpdateFrameTitle(BOOL bAddToTitle);   [...]  };// MainFrm.cppvoid CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle){  CMDIFrameWnd::OnUpdateFrameTitle(bAddToTitle);  m_wndMDITabs.Update(); // sync the mditabctrl with all views}

That's it! Build and start your application, open some views and enjoy switching between them ;-)

Other features

  • Double clicking on a tab maximizes the view.
  • Right clicking on a tab displays the view's system menu. You can change the menu by simply changing the views system menu, if you like (see demo project CChildFrame::OnCreate()). If you need to supply a total different (independant) menu, your view can answer the WM_GETTABSYSMENU message.
  • The tabs are hidden when there are less than one (two) view(s) open. Use SetMinViews() or the styleMT_HIDEWLT2VIEWS to change this behaviour.
  • In the Create() function you can supply some styles to customize the appearance of the control:MT_BOTTOMtabs appear at bottomMT_TOPtabs appear at topMT_IMAGESuse view icons as imagesMT_HIDEWLT2VIEWShide tabs when there are less than two views (default is one view)
  m_wndMDITabs.Create(this, MT_TOP|MT_IMAGES|MT_HIDEWLT2VIEWS);

Internals

Layout

The private MFC message WM_SIZEPARENT provides a way to attach windows to the MDI client area of a doc/view app. CMDITabs implements OnSizeParent to attach itself at the bottom of the MDI client area. If you want another layout you need to change this function. The Z-order of the MDIClient siblings is important for the layout algorithm. Siblings are asked to position themselves in Z-order (search for WM_SIZEPARENT on MSDN to get more info). That's the reason why CMDITabs must be created after all other control and status bars have been done inCMainFrame::OnCreate(). Otherwise the status bar would appear above the tabs, destroying proper layout.

Synchronizing Views and Tabs

The tab control must always reflect the list of views. Instead of monitoring all possible view changing events (close, open, new, changing titles/icons) I hooked into the CMainFrame::OnUpdateFrameTitle() function. I discovered, that this function gets called when something view-related happens. Here you have to call CMDITabs::Update()which in response completely rebuilds its tab list. It does so by querying the child windows of the MDIClient window, circumventing the complex doc/view organization of MFC! The simpler the solution the robuster it works!