Objective Toolkit : Chapter 13 Tabbed Windows : Using SECTabWnd and SEC3DTabWnd
Using SECTabWnd and SEC3DTabWnd
 
To add SECTabWnd or SEC3DTabWnd to a frame window
The steps for adding SECTabWnd and SEC3DTabWnd are identical.
1. Add an SECTabWnd (orSEC3DTabWnd) data member to your CFrameWnd-derived class. For example:
 
SECTabWnd m_tabWnd;
If your application is:
Then:
SDI-based
Add the data member to your CMainFrame class.
MDI-based
Add the data member to your CMDIChildWnd descendant.
FDI-based
Add the data member to your SECFDIChildWnd descendant.
2. Override the OnCreateClient() method of the frame class and call the tabbed window Create() method to create the tabbed window. For example:
 
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs,
CCreateContext *pContext)
{
BOOL rtn_val;
lpcs; //UNUSED
 
rtn_val = m_tabWnd.Create(this);
...
}
3. In the OnCreateClient() frame class method, add the views or any CWnd-derived objects to the tabbed window object using the AddTab() method.
For example, the following line of code creates a new instance of a CView-derivative and inserts it into the tabbed window.
 
m_tabWnd.AddTab(RUNTIME_CLASS(CDemoView1), "Tab One", pContext,
nID);
The next line inserts any pre-existing CWnd-derived object into the tabbed window.
 
m_tabWnd.AddTab(pWnd, "Tab Two");
4. As the last step in your OnCreateClient() override, activate and scroll into view the tab that you want to be selected initially. For example:
 
m_tabWnd.ActivateTab(0);
m_tabWnd.ScrollToTab(0);
To add a tabbed window to a dialog
1. Edit the dialog resource in the resource editor.
2. Add an arbitrary control (for example, an edit control or a static control). Position and size it where you want the tabbed window, and give the control a unique control identifier.
3. In the OnInitDialog() handler for the dialog, retrieve the rectangle for the control and window you just added. For example:
 
CWnd* pWnd = GetDlgItem(uiControlID);
CEdit* pwndEdit=(CEdit*)pWnd;
// Retrieve the previous window in the tab order
// and the rectangle to use for the call to create
// (in parent client-area coordinates).
CWnd* pwndPrev = pwndEdit->GetWindow(GW_HWNDPREV);
CRect rc;
pWnd->GetWindowRect(&rc);
this->ScreenToClient(&rc);
 
// Now we no longer need the original window and can
// safely destroy it.
pWnd->DestroyWindow();
4. Now, call the tabbed window Create() method. Then, set the size, position, and tab order for the control.
 
if (m_tabWnd.Create(this))
{
SetWindowPos(pwndPrev, rc.TopLeft().x, rc.TopLeft().y,
rc.Width(), rc.Height(), 0);
}
Removing the 2D Tab Scroll Buttons
In the call to SECTabWnd::Create(), leave off the TWS_FULLSCROLL or TWS_LEFTRIGHTSCROLL flags.
To put 3D tabs on the side or top of the tabbed window
1. Ensure that the font used on the tabs is a True Type font, such Arial or Times New Roman. For more information, see “To change the font of a tab.”
2. In the call to SEC3DTabWnd::Create(), specify one of the following style flags:
TWS_TABS_ON_BOTTOM
TWS_TABS_ON_TOP
TWS_TABS_ON_LEFT
TWS_TABS_ON_RIGHT
For example:
 
rtn_val = m_tabWnd.Create(this,
WS_CHILD | WS_VISIBLE |
WS_HSCROLL | WS_VSCROLL | TWS_FULLSCROLL |
TWS_TABS_ON_LEFT);
To enable scroll bars in the 2D tabbed window
1. The contained object for the tab must be CScrollView-derived. Override CWnd::GetScrollBarCtrl() for the contained window class. For example:
 
CScrollBar* CMyScrollView::GetScrollBarCtrl(int nBar) const
{
ASSERT(GetParent()-> IsKindOf(RUNTIME_CLASS(SECTabWnd)));
return ((SECTabWnd*)GetParent())-> GetScrollBar(nBar);
}
2. For each tabbed window, pass WS_HSCROLL or WS_VSCROLL as required in the dwStyle parameter of the SECTabWndBase::SetScrollStyle() member function. For example:
 
m_tabWnd.SetScrollStyle(nTab, WS_HSCROLL | WS_VSCROLL);
To add keyboard accelerator support
1. Derive a class from SECTabWnd/SEC3DtabWnd and add handlers for the WM_CHAR and WM_KEYDOWN messages.
2. In OnChar() or OnKeyDown() or both, provide your own tab-activation code. Within the handler, you can use the SECTabWndBase::ActivateTab() method to activate a specific tab.
If the tab contains CView derived class objects:
The default message routing mechanism sends the keyboard messages to the window with the keyboard focus so the active view receives the messages.
Handle the message(s) in the view/control class contained in the tab to redirect the message(s) to the tabbed window. This ensures that your tab-window's handler is called.
 
void CDemoView1::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
nChar; //UNUSED
nRepCnt; //UNUSED
nFlags; //UNUSED
 
// Forward message to parent
ASSERT(GetParent()-> IsKindOf(RUNTIME_CLASS(SECTabWndBase)));
 
MSG msg = *GetCurrentMessage();
if (GetParent())
GetParent()->SendMessage( msg.message,
msg.wParam, msg.lParam);
}
If the tabbed window contains CDialog/CFormView derived class objects:
When a CDialog/CFormView derived object is the active window, the application sends keyboard messages to the control within the dialog that has the focus by default. You need to subclass the control and redirect the keyboard messages to the tabbed window.
To add a window to the tabbed window
Call the overloaded SECTabWndBase::AddTab() method with the following declaration.
 
void AddTab(CWnd* pWnd,
LPCTSTR lpszLabel,
HICON hIcon = NULL);
NOTE >> If the tab is removed at run time, don’t forget to destroy the associated CWnd. See “To remove a tab”.
To create and add a view to the tabbed window
Call the overloaded SECTabWndBase::AddTab() method with the following declaration.
 
CWnd* AddTab(CRuntimeClass* pViewClass,
LPCTSTR lpszLabel,
CCreateContext* pContext = NULL,
HICON hIcon = NULL,
UINT nID = -1);
If the tab creation is within CFrameWnd::OnCreateClient():
Use the pContext parameter passed to the OnCreateClient() method in the AddTab() method call.
If the tab creation is not occurring within CFrameWnd::OnCreateClient():
Create a CCreateContext on the stack and pass it to the AddTab() method call. For example:
 
void CMyFrameWnd::OnSheetNew()
{
CCreateContext context;
context.m_pCurrentFrame = this;
context.m_pCurrentDoc = GetDocToUse();
context.m_pNewViewClass = RUNTIME_CLASS(CMyView);
context.m_pNewDocTemplate = GetDocTemplateToUse();
m_wndTab.AddTab(RUNTIME_CLASS(CMyView), &context);
}
The implementations of the GetDocToUse() and GetDocTemplateToUse() methods are application-specific.
NOTE >> In general, you can embed a tabbed window anywhere. This is not true if the tabbed window contains views. You cannot embed a view in a tabbed window that is, in turn, embedded in a control bar (docking window) or a dialog. However, you can embed a tabbed window containing views into a view contained in a frame using the docking views architecture.
NOTE >> If the tab is removed at run time, don’t forget to destroy the associated CView. See “To remove a tab”.
To remove a tab
Call the RemoveTab() method. Don’t forget to destroy the contained CWnd. For example:
 
// Don't just delete the tab, destroy the associated
// window too.
if (m_tabWnd.GetTabInfo(nActiveTab, lpszLabel,
bSelected, pActiveWnd, pExtra))
{
pActiveWnd->ShowWindow(SW_HIDE);
pActiveWnd->SendMessage(WM_CLOSE);
}
To access the CWnd associated with a tab
Call the GetTabInfo() method, which is defined as follows:
 
// Returns information about the tab with the supplied index.
BOOL GetTabInfo(int nIndex,
LPCTSTR& lpszLabel,
BOOL& bSelected,
CWnd*& pWnd,
void*& pExtra);
 
For example:
 
m_tabWnd.GetTabInfo(nActiveTab, lpszLabel,
bSelected, pActiveWnd, pExtra);
To change the font of a tab
Call one of the following font accessor methods.
Font accessor method
Description
Get/SetFontActiveTab()
Sets an active tab's current font to the specified font.
Get/SetFontInactiveTab()
Sets an inactive tab's current font to the specified font.
NOTE >> If you are specifying the font for a tab on a 3D tabbed window and the tabs are placed on either the left or right side of the tabbed window, the font must be a True Type font.
To receive user event notifications from the tabbed window
Add a message-map entry and message-handler member function to the parent class for each message. See “Tab Control Notification Messages”. Each message-map macro entry takes the following form:
 
ON_MESSAGE( <tab control message id>, memberFxn )
memberFxn is the name of the parent member function you wrote to handle the notification and <tab control message id> is one of the following:
TCM_TABSEL
TCM_TABDBLCLK
TCM_TABSELCLR
TCM_TABREACTIVATE
The parent's function prototype is as follows:
 
afx_msg LRESULT memberFxn(WPARAM wParam, LPARAM lParam);
wParam parameter contains the index of the activated tab. If this handler is called in response to a SelectTab() event, then the wParam is the index of the currently active tab.
To get a pointer to the SECTabWnd from a contained view
Use the following code:
 
pTabWnd = (SECTabWnd*)pView->GetParent();
ASSERT_KINDOF(SECTabWnd, pTabWnd);
To insert a splitter window into a tabbed window
1. Create the tabbed window.
 
m_tabWnd.Create(this);
2. Create the splitter window. Specify the tabbed window as its parent.
 
m_wndSplitter.CreateStatic(&m_tabWnd, 1, 2);
3. Insert your child windows into the splitter window.
 
m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CDemoView2),
CSize(225,100), pContext);
m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CDemoView2),
CSize(225,100), pContext);
4. Insert the splitter window into the tabbed window.
 
m_tabWnd.AddTab(&m_wndSplitter, "Tab Two");
NOTE >> There is a known limitation in SECTabWnd, which is particularly problematic if you are using the SECTabWnd class in conjunction with a splitter window. You can create the tabbed window with or without scroll bars, but they cannot be dynamically shown or hidden. If one tab requires scroll bars and another does not, the SECTabWnd class does not show and hide the scroll bars appropriately.
Problem with Tabbed Windows in Docking Views
If you are using a tabbed window in conjunction with the docking views component of Objective Toolkit, you may experience a problem with view activation. SECTabWndBase::Create() asserts if the tab window is being added as a child of a docking view that already has an ID of AFX_ID_PANE_FIRST. A tab window is expected to have this ID if the tab window consumes the client area of an MDI child frame. In other words, it is to act as a container for one or more views that would normally otherwise have this ID. The same is true if the tab window consumes the client area in the SDI scenario.
When calculating the layout of the client area, MFC looks for a window with an ID of AFX_ID_PANE_FIRST. In a MDI application, the view window is typically the one with this ID. If multiple windows with this ID exist, view activation behavior becomes erratic.
To fix this problem, create a unique ID for the tab window and specify it during the call to Create(). For example:
 
VERIFY (m_wndTab.Create(this,
WS_CHILD|WS_VISIBLE|TWS_TABS_ON_BOTTOM,
ID_MYTAB)); // some unique id