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.