Tab Bar Control

It looks like one question is asked quite frequently (or is it my perception) on the VC++ forum: How do you create more than one view in a frame and have all views wired to the same document object, with the ability to switch views at will, making one as visible and active?

I have posted many samples showing how that can be done, using different techniques. Finally, I thought: Why not to create a control that will manage view swapping making life easier?

After strenuous thinking (I know it may prove to be dangerous, but I took a risk), I came to the following solution:

It should be a bar that can be created by a frame window, should behave like a toolbar, and have the functionality of a tab control. I have decided to derive the class using CControlBar. Why not a CDialogBar with tab control? One major reason is that it would require additional resources whereas the above implementation does not require it. The other is that it would allow docking that I think is not desirable and would require writing code to disable it. The Tab Bar should always be placed at the top of the client area of the frame and allow other bars to dock to it.

Tab Bar does not support docking; moreover, there were certain pitfalls. Some different styles for the control bar and tab control are defined as the same value, but they are interpreted differently. For example, CBRS_ALIGN_TOP is for the Windows tab control TCS_OWNERDRAWFIXED; you do not need that.

That is how a tab control bar was born. To ease the burden of coding, I re-used small parts of the MFC code.

Tab bar fully supports all functionality of the tab control with addition of adding, inserting, removing, and swapping views. It maintains selected view visibility. Tab bar does not send selection changed notification to a parent window; it uses it to maintain views. However, it sends a selection changing notification allowing the parent (frame window), preventing selection change if needed. Each added view is bound to a tab item.

The project was written using VS 6.0, SP 6 using VC++ because there are still many programmers using this version of Studio. It is very easy to use it with newer versions of VS.

The sample demo program also serves as the test program. It allows you to insert, add, and remove a single or all tabs. See the screen shot of the sample application.

All is needed to add Tab Bar to any application is to include the header and implementation files: TabBarCtrl.h and TabBarCtrl.cpp. I have also included a Word doc file documenting functions that are specific to Tab Bar.

Architecture Design

In a nutshell, as I have already mentioned above, Tab maintains the view's IDs. Because removing, adding, or inserting tabs changes the tab item's number, Item number to view ID mapping is not possible. That is why I have used a map template class to dynamically maintain available IDs, removing and adding ID values as tab items are added or removed. This method finds the first available ID, searching this map to be used for a newly created view. All views' IDs, with the exception of AFX_IDW_PANE_FIRST, are irrelevant because they are not used for anything else but temporary view/id/tab item dynamic mapping.

Adding a Tab Bar item creates a new view and assigns an appropriate ID. Adding a tab requires runtime information about the view and pointer to a CCreateContext structure. Passing a pContext pointer from OnCreateClient member of the frame enables Tab Bar to add a newly created view to a document's internal list of views. This in turn allows using UpdateAllView as illustrated in the included sample.

View swapping does not destroy the view. Instead, the selected view's ID is changed to AFX_IDW_PANE_FIRST. This special ID is used by MFC frame windows to retrieve the pointer to a view that is to be resized after all other children (bars, for example) are resized.

View pointers and the view ID are stored as Tab Bar item data. Both require 8 bytes of tab data storage in addition to the traditional 4 bytes of data for lParam. All 12 extra bytes are requested from the tab control by using a TCM_SETITEMEXTRA message when Tab Bar is created. That approach requires using a custom defined structure to retrieve and set the items' data. However, the way it is implemented, it is transparent. All public member functions still require the well-known MFC TCITEM structure. Tab Bar translates TCITEM data into an internal data type and passes it down in calls to helper functions. I have decided to implement it this way for two major reasons:

Tab Bar's internal structures are not exposed, limiting dependencies to a minimum.

Tab Bar usage is not much different from the MFC implementation of the CTabCtrl.

Tab creation in OnCreateClient is not mandatory, but it seems natural because views are usually created here. Therefore, a sample Tab Bar is created in OnCreateClient, before all views are created. It is needed because wrappers use Windows native APIs and messages and that require valid window handle. This is similar to using CSplitterWnd that is created first and, after creation, all panes are created. This approach also assures that all windows created here will receive the WM_INITIALUPDATE message from the framework. It happens shortly after OnCreateClient is called.

Tab Bar control is initialized when all children receive the WM_INITIALUPDATE message.

All functionality and operation are very similar to the familiar CTabCtrl. I hope this will allow using the CTabBarCtrl class with ease.

About the Author

John Z. Czopowik VC++ MVP

Microsoft VC++ MVP

Downloads

Comments

how to save and load data on to Tabs

Posted by kishoresajja
on 05/22/2009 05:47pm

Hi John,
This is great article. I am just started to looking into this. But I have one quick question for you. Say for example I created 4 CFormView's and inserted into Tabs. Each FormView has some controls like checkbox's,radiobox etc etc. Now I all want to is store the control values when application exit and reload the control once the application lanched agian. How can we do this ...?
please let me know how can we achive the above senario..
I think preserving values between switching forms logic is already in place in your application, Isn't it ?

how to save and load data on to Tabs

Posted by JohnCz
on 05/25/2009 08:08am

Thanks for the kind words.
It does not matter what window (view) you tell a tab bar to insert. It just maintains visibility of the windows you add and nothing else. Swapping view, does not kill and recreate window as seen in many examples. Tab bar does not react with any window; hence, window behavior depends on default or your implementation.
I do not know your appbs architecture. You would have to implement data saving yourself, possibly using document or view serialization functionality. If you are not sure how to do it, search VC++ forum, I am sure I have a sample showing how to save data from a form view.

I was just looking for something like that!

TabBar in MDI child?

Posted by stefand
on 01/29/2007 05:20am

Is this possible? I've tried, and i do get the tabs for each view that i add. But nothing happens when i select the different tabs.
Would some simple modification of the code make it work, or is it simply not possible to use the TabBar inside a MDI child window?

My fault =)

Posted by stefand
on 02/12/2007 05:59am

Thank you. It was just a stupid mistake from my side =) The OnCreateClient should return TRUE. I completely missed that when i moved the code to my MDI application.
Thank you for your code, this is great stuff.

Yes, it works

Posted by JohnCz
on 01/30/2007 08:50pm

Yes, of course it is possible. I have tested both: SDI and MDI applications before submitting this article and it works well in both.
You should be able to copy relevant code from main frame of the SDI app to child frame of the MDI app and it will work.
If you could post your project I will ba able to take a look at it.

Changing position of TabBar

Posted by wolamp
on 01/24/2007 08:53am

Hi John,
I like your work very much !
However, one thing I didn't like, that is the TabBar is placed *above* the ToolBar.
One simple way to change this, is to change the z-order of the FrameWnd children.
this can be done by:
m_wndTabBar.SetWindowPos(&m_wndToolBar, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
after creating the ToolBar and StatusBar.
This will place the TabBar behind the ToolBar in Z-order, and then the LayoutManager
does the desired placing.
but this works only if the ToolBar is not dockable. (deleting the last three lines of code)
which I normaly do.

Changing position of TabBar

Posted by JohnCz
on 01/24/2007 03:34pm

Well, that is by design. I envisioned Tab Bar (since is not floating)) to be on the top of all other toolbars. This approach does not change Tab Bar position when other bars are floating. You can also place it at the bottom of the frame.
However I think this is just a matter of personal preference and you are free to change anything that meet your preference.
The main reason for this article was to show how it can be done and if it helps to understand how it works: Z order as well as using MFC layout mechanism. I think I have reached my goal at least in your case.
Thanks for the comment it will allow other to have a choice.

Top White Papers and Webcasts

As virtualization becomes the norm throughout organizations of nearly all sizes, and as more organizations look to private cloud solutions, IT decision makers are increasingly in need of ways to keep storage costs and complexity under control in the face of often-runaway virtual machine (VM) sprawl. Application-aware storage is designed to help achieve these important goals. Read this white paper to learn how application-aware storage allows you to gain VM-level visibility into application performance and …