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.

      Reply
    Reply
  • Great

    Posted by yoyokits on 02/16/2007 10:04am

    Thank you for the great code :-)

    Reply
  • I was just looking for something like that!

    Posted by sjeffh on 02/01/2007 06:16am

    I was just thinking about achieving those results but was not quite sure how to go about it. Thanx

    Reply
  • 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.

      Reply
    • 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.

      Reply
    Reply
  • 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.

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds