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/view section 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:
- it looks nicer and hasn't got a space wasting double border frame
- it is more stable due to its simpler implementation ;-) (read the bug list
in the comments)
- 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 your
CMainFrame
class add a new member m_wndMDITabs
of
class CMDITabs
(don't forget to include 'MDITabs.h'). Insert
m_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 CMainFrame
class. After calling the base class implementation
you have to call the Update()
function of
m_wndMDITabs
class CMainFrame : public CMDIFrameWnd
{
[...]
CMDITabs m_wndMDITabs;
virtual void OnUpdateFrameTitle(BOOL bAddToTitle);
[...]
};
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CMDIFrameWnd::OnUpdateFrameTitle(bAddToTitle);
m_wndMDITabs.Update();
}
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 style MT_HIDEWLT2VIEWS
to change
this behaviour.
- In the
Create()
function you can supply some styles to
customize the appearance of the control:
MT_BOTTOM |
tabs appear at bottom |
MT_TOP |
tabs appear at top |
MT_IMAGES |
use view icons as images |
MT_HIDEWLT2VIEWS |
hide 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 in CMainFrame::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!