Click here to Skip to main content
15,897,891 members
Articles / Desktop Programming / WTL

A WTL Tab Control for Managing Tab Views

Rate me:
Please Sign up or sign in to vote.
4.54/5 (11 votes)
21 Jun 20024 min read 120K   5.2K   36  
This article describes a tab control that automates the management of tab windows.
//==============================================================================
/**
 *		@file		WTLTabViewCtrl.h
 *
 *		Interface for the CWTLTabViewCtrl class.
 *
 *		@author		Stephen Jones (Stephen@Jones-net.com)
 *		@date		June 13th, 2002
 *	
 *		Copyright 2002, Stephen Jones 
 *
 *		History:
 *		--------
 *
 *		06/18/2002	Creation
 */
//==============================================================================

//==============================================================================
/**
 *		@mainpage CWTLTabViewCtrl
 *
 *		@htmlinclude .\CodeProject\TabViewCtrl.html
 */
//==============================================================================

#ifndef INCLUDED_WTLTABVIEWCTRL
#define INCLUDED_WTLTABVIEWCTRL


//==============================================================================
//	Includes
//==============================================================================

#ifndef __ATLMISC_H__
	#include <atlmisc.h>
#endif

#ifndef __ATLCTRLS_H__
	#include <atlctrls.h>
#endif

#ifndef __ATLCRACK_H__
	#include <atlcrack.h>
#endif

 
//==============================================================================
/**
 *		@class CWTLTabViewCtrl 
 *
 *		A WTL class that encapsulates the functionality for managing a tab 
 *		control and associated tab windows.
 *
 *		The standard tab control does not provide facilities for managing the 
 *		child windows associated with each tab. This class	encapsulates the 
 *		functionality for automatically managing each tab.
 *
 *		Parent windows must have REFLECT_NOTIFICATIONS() in the message map
 *		to pass along the TCN_SELCHANGE message.
 *
 *		This class started out as a port of a similar MFC class 
 *		(CSizingTabCtrlBar - author unknown), but turned into a total rewrite. 
 */
//==============================================================================
class CWTLTabViewCtrl : public CWindowImpl< CWTLTabViewCtrl, CTabCtrl >
{
	//==============================================================================
	//	Constants
	//==============================================================================

	private:

		const LONG TABVIEW_BORDER;			///< Border Width
		const LONG TABVIEW_EDGE;			///< Distance of tab from content


	//==============================================================================
	//	Fields
	//==============================================================================

	private:

		CSimpleArray<CWindow>		m_Views;			///< An array of views for the tab
		LONG						m_ActiveTabIndex;	///< The index of the active tab
		CWindow						m_ActiveTabWindow;	///< The active tab window
		CFont						m_HorizFont;		///< Top/Bottom font used by tab control
		CFont						m_LeftFont;			///< Left font used by tab control
		CFont						m_RightFont;		///< Right font used by tab control
			

	//==============================================================================
	//	Construction
	//==============================================================================

	public:

		//==============================================================================
		/**
		 *		Constructor:	
		 */
		//==============================================================================
		CWTLTabViewCtrl() :	
			TABVIEW_BORDER		( 3 ),
			TABVIEW_EDGE		( 5 ),
			m_ActiveTabIndex	( -1 ) 
		{ 
		}

		//==============================================================================
		/**
		 *		Destructor:	
		 */
		//==============================================================================
		virtual ~CWTLTabViewCtrl()
		{
		}

	//==============================================================================
	//	Public Interface
	//==============================================================================

	public:

		//==============================================================================
		/**
		 *		AddTab:	Append a tab to the end of the tab control.
		 *
		 *		@param	inLabel
		 *				[in] The label to appear on the tab control.
		 *		@param	inTabWindow
		 *				[in] The child window to use as the view for the tab. The window
		 *				must have the WS_CHILD style bit set and the WS_VISIBLE style bit not set.
		 *		@param	inActiveFlag
		 *				[in, optional] TRUE to make the tab active, FALSE to just add the tab.
		 *		@param	inImage
		 *				[in, optional] The index into the image list for the image to display by the tab label.
		 *		@param	inParam
		 *				[in, optional] The param value to associate with the tab.
		 *		@return	Zero based index of the new tab, -1 on failure
		 */
		//==============================================================================
		BOOL AddTab( LPCTSTR inLabel, HWND inTabWindow, BOOL inActiveFlag = TRUE, int inImage = -1, LPARAM inParam = 0 )
		{
			BOOL	theReturn;
			CWindow theTabWindow( inTabWindow );

			// Make sure it's a real window
			ATLASSERT( theTabWindow.IsWindow( ) );

			// Make sure it's a child window and is not visible
			ATLASSERT( ( theTabWindow.GetStyle() & WS_CHILD ) != 0 );
			ATLASSERT( ( theTabWindow.GetStyle() & WS_VISIBLE ) == 0 );

			// Hide the view window
			theTabWindow.EnableWindow( FALSE );
			theTabWindow.ShowWindow( SW_HIDE );

			// Store the required data for the list
			m_Views.Add( theTabWindow );

			// Add the tab to the tab control
			TC_ITEM theItem = {0};
			theItem.mask = TCIF_TEXT;
			theItem.pszText = const_cast<char*>( inLabel );

			// Add an image for the tab
			if ( inImage != -1 )
			{
				theItem.mask |= TCIF_IMAGE;
				theItem.iImage = inImage;
			}

			// Add the param for the tab
			if ( inParam != 0 )
			{
				theItem.mask |= TCIF_PARAM;
				theItem.lParam = inParam;
			}

			// Insert the item at the end of the tab control
			theReturn = InsertItem( 32768, &theItem );

			// Set the position for the window
			CRect rcChild;
			CalcViewRect( &rcChild );
			theTabWindow.MoveWindow( rcChild );

			// Select the tab that is being added, if desired
			LONG theTabIndex = GetTabCount( ) - 1; 
			if ( inActiveFlag || theTabIndex == 0 )
			{
				SetActiveTab( theTabIndex );
			}

			return theReturn;
		}

		//==============================================================================
		/**
		 *		GetTabCount:	Return the current number of tabs.
		 *
		 *		@return	The current number of tabs.
		 */
		//==============================================================================
		LONG GetTabCount( )
		{
			return m_Views.GetSize();
		}

		//==============================================================================
		/**
		 *		GetTab:	Get the view window associated with the tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab to return the view window.
		 *		@return	The handle of the view window.
		 */
		//==============================================================================
		HWND GetTab( int inTab )
		{
			HWND theTabHwnd = NULL;
			
			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				m_Views[ inTab ];
			}

			return theTabHwnd;
		}

		//==============================================================================
		/**
		 *		OnTabRemoved:	Virtual method that is called when a tab is removed.
		 *
		 *		@param	inTabIndex
		 *				[in] The index of the tab to return the view window.
		 *		@return	The handle of the view window.
		 */
		//==============================================================================
		virtual void OnTabRemoved( int inTabIndex )
		{
			UNREFERENCED_PARAMETER( inTabIndex );
		}

		//==============================================================================
		/**
		 *		RemoveTab:	Remove the specified tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab to remove.
		 *		@return	Returns the HWND of the deleted view window.
		 */
		//==============================================================================
		HWND RemoveTab( int inTab )
		{	
			HWND theTabHwnd = NULL;
			LONG theNewTab = -1;

			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				// Select a new tab if the tab is active
				if ( m_ActiveTabIndex == inTab )
				{
					m_ActiveTabIndex = -1;
					m_ActiveTabWindow = NULL;

					if ( GetTabCount( ) > 1 )
					{
						theNewTab = ( inTab > 0 ) ? ( inTab - 1 ) : 0;
					}
				}

				// Save the window that is begin removed
				theTabHwnd = m_Views[ inTab ];

				// Virtual method to notify subclasses that a tab was removed
				OnTabRemoved( inTab );

				// Remove the item from the view list
				m_Views.RemoveAt( inTab );

				// Remove the tab
				if ( IsWindow() )
				{
					DeleteItem( inTab );
				}

				SetActiveTab( theNewTab );
			}

			return theTabHwnd;
		}

		//==============================================================================
		/**
		 *		RemoveAllTabs:	Remove all the tabs from the tab control.
		 *
		 *		@return	void
		 */
		//==============================================================================
		void RemoveAllTabs( )
		{
			LONG theCount = GetTabCount( );
			for ( LONG theIndex = theCount - 1; theIndex >= 0; theIndex-- ) 
			{
				RemoveTab( theIndex );
			}
		}
		
		//==============================================================================
		/**
		 *		SetActiveTab:	Activate the specified tab.
		 *
		 *		@param	inNewTab
		 *				Select the zero based tab at this index.
		 *		@return	void
		 */
		//==============================================================================
		void SetActiveTab( int inNewTab )
		{
			if ( inNewTab >= 0 && inNewTab < GetTabCount( ) &&	// Validate the tab index range
				 inNewTab != m_ActiveTabIndex )					// Don't select if already selected
			{
				// Disable the old tab
				if ( m_ActiveTabWindow.IsWindow( ) ) 
				{
					m_ActiveTabWindow.EnableWindow( FALSE );
					m_ActiveTabWindow.ShowWindow( SW_HIDE );
				}

				// Enable the new tab
				m_ActiveTabWindow = m_Views[ inNewTab ];

				m_ActiveTabWindow.EnableWindow( TRUE );
				m_ActiveTabWindow.ShowWindow( SW_SHOW );
				m_ActiveTabWindow.SetFocus( );

				m_ActiveTabWindow.Invalidate( TRUE );
				
				m_ActiveTabIndex = inNewTab;

				// Select the tab (if tab programmatically changed)
				SetCurSel( m_ActiveTabIndex );
			}
		}

		//==============================================================================
		/**
		 *		GetActiveTab:	Return the HWND to the active tab.
		 *
		 *		@return	The HWND to the active view.
		 */
		//==============================================================================
		HWND GetActiveTab( )
		{
			return GetTab( m_ActiveTabIndex );
		}

		//==============================================================================
		/**
		 *		GetActiveTabIndex:	Return the index of the active tab.
		 *
		 *		@return	The index of the active tab.
		 */
		//==============================================================================
		LONG GetActiveTabIndex( )
		{
			return m_ActiveTabIndex;
		}

		//==============================================================================
		/**
		 *		GetTabText:	Return the label of the specified tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab for which to return the text.
		 *		@param	inSize
		 *				[in] The size of the text string buffer.
		 *		@param	outText
		 *				[out] The text string buffer.
		 *		@return	void
		 */
		//==============================================================================
		void GetTabText( int inTab, int inSize, char* outText )
		{
			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				// Get tab item info
				TCITEM tci;
				tci.mask = TCIF_TEXT;
				tci.pszText = outText;
				tci.cchTextMax = inSize;
				GetItem( inTab, &tci );
			}
		}

	#ifdef __ATLSTR_H__

		//==============================================================================
		/**
		 *		GetTabText:	Return the label of the specified tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab for which to return the text.
		 *		@return	CString containing the tab text.
		 */
		//==============================================================================
		const CString GetTabText( int inTab )
		{
			CString theTabText;

			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				// Get tab item info
				char theText[128];

				GetTabText( inTab, sizeof( theText ), theText );
				theTabText = theText;
			}

			return theTabText;
		}
	
	#endif

		//==============================================================================
		/**
		 *		GetTabParam:	Return the param of the specified tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab for which to return the param.
		 *		@return	LPARAM
		 */
		//==============================================================================
		LPARAM GetTabParam( int inTab )
		{
			TCITEM tci = {0};
			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				// Get tab item info
				tci.mask = TCIF_PARAM;
				GetItem( inTab, &tci );
			}

			return tci.lParam;
		}

		//==============================================================================
		/**
		 *		GetTabImage:	Return the param of the specified tab.
		 *
		 *		@param	inTab
		 *				[in] The index of the tab for which to return the image.
		 *		@return	int
		 */
		//==============================================================================
		int GetTabImage( int inTab )
		{
			TCITEM tci = {0};
			if ( inTab >= 0 && inTab < GetTabCount( ) )
			{
				// Get tab item info
				tci.mask = TCIF_IMAGE;
				GetItem( inTab, &tci );
			}

			return tci.iImage;
		}

		//==============================================================================
		/**
		 *		ModifyTabStyle:	This method modifies the window styles of the CWindow 
		 *			object. Styles to be added or removed can be combined by using the 
		 *			bitwise OR ( | ) operator.
		 *
		 *		@param	dwRemove
		 *				[in] Specifies the window styles to be removed during style modification.
		 *		@param	dwAdd
		 *				[in] Specifies the window styles to be added during style modification.
		 *		@param	nFlags
		 *				[in] Window-positioning flags. For a list of possible values, 
		 *				see theSetWindowPos function in the Win32 SDK.
		 *
		 *		If nFlags is nonzero, ModifyStyle calls the Win32 function SetWindowPos, 
		 *		and redraws the window by combining nFlags with the following four flags: 
		 *
		 *		SWP_NOSIZE   Retains the current size.
		 *		SWP_NOMOVE   Retains the current position.
		 *		SWP_NOZORDER   Retains the current Z order.
		 *		SWP_NOACTIVATE   Does not activate the window. 
		 *		To modify a window's extended styles, call ModifyStyleEx.
		 *
		 *		@return	TRUE if the window styles are modified; otherwise, FALSE.
		 */
		//==============================================================================
		BOOL ModifyTabStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags )
		{
			// Modify the style
			BOOL theReturn = ModifyStyle( dwRemove, dwAdd, nFlags );
			
			// Update all the views in case the window positions changes
			UpdateViews( );

			// Set the font in case the tab position changed
			SetTabFont( dwAdd );

			return theReturn;
		}


	//==============================================================================
	//	Message Map
	//==============================================================================

	public:

		BEGIN_MSG_MAP_EX(CWTLTabViewCtrl)
			MESSAGE_HANDLER(WM_CREATE, OnCreate)
			MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
			MESSAGE_HANDLER(WM_WINDOWPOSCHANGED, OnWindowPosChanged)
			REFLECTED_NOTIFY_CODE_HANDLER_EX(TCN_SELCHANGE, OnSelectionChanged)
		END_MSG_MAP()


	//==============================================================================
	//	Windows Message Handlers
	//==============================================================================

	protected:

		//==============================================================================
		/**
		 *		UpdateViews:	Update the position of all the contained views.
		 *
		 *		@return	void
		 */
		//==============================================================================
		void UpdateViews( )
		{
			CRect rcChild;
			CalcViewRect( &rcChild );

			LONG theCount = GetTabCount( );
			for ( LONG theIndex = 0; theIndex < theCount; theIndex++ ) 
			{
				m_Views[ theIndex ].MoveWindow( rcChild );
			}
		}

		//==============================================================================
		/**
		 *		OnCreate:	Called in response to the WM_CREATE message
		 *
		 *		@return	0
		 */
		//==============================================================================
		LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& )
		{
			LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);

			// Get the log font.
			NONCLIENTMETRICS ncm;
			ncm.cbSize = sizeof(NONCLIENTMETRICS);
			::SystemParametersInfo(SPI_GETNONCLIENTMETRICS,	sizeof(NONCLIENTMETRICS), &ncm, 0);
			
			// Top and Bottom Tab Font
			m_HorizFont.CreateFontIndirect(&ncm.lfMessageFont);
		
			// Left Tab Font
			ncm.lfMessageFont.lfOrientation = 900;
			ncm.lfMessageFont.lfEscapement  = 900;
			m_LeftFont.CreateFontIndirect(&ncm.lfMessageFont);

			// Right Tab Font	
			ncm.lfMessageFont.lfOrientation = 2700;
			ncm.lfMessageFont.lfEscapement  = 2700;
			m_RightFont.CreateFontIndirect(&ncm.lfMessageFont);

			// Check styles to determine which font to set
			SetTabFont( GetStyle( ) );

			return lRet;
		}

		//==============================================================================
		/**
		 *		OnDestroy:	Called in response to the WM_DESTROY message
		 *
		 *		@return	0
		 */
		//==============================================================================
		LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled)
		{
			RemoveAllTabs( );

			bHandled = FALSE;

			return 0;
		}

		//==============================================================================
		/**
		 *		OnWindowPosChanged:	Called in response to the WM_WNDPOSCHANGED message
		 *
		 *		@return	0
		 */
		//==============================================================================
		LRESULT OnWindowPosChanged(UINT, WPARAM, LPARAM, BOOL& bHandled )
		{
			UpdateViews( );

			bHandled = FALSE;

			return 0;
		}

		//==============================================================================
		/**
		 *		OnSelectionChanged:	Called in response to the TCN_SELCHANGE message
		 *
		 *		@return	0
		 */
		//==============================================================================
		LRESULT OnSelectionChanged( LPNMHDR )
		{
			SetActiveTab( GetCurSel( ) );
		
			return 0;
		}


	//==============================================================================
	//	Implementation
	//==============================================================================

	protected:

		//==============================================================================
		/**
		 *		CalcViewRect:	Calculate the client rect for contained views.
		 *
		 *		@param	pRect
		 *				[out] Returns the new view rect.
		 *		@return	void
		 */
		//==============================================================================
		void CalcViewRect(CRect* pRect)
		{
			GetClientRect( (*pRect) );

			if ( pRect->Height() > 0 && pRect->Width() > 0 )
			{
				// Calculate the Height (or Width) of the tab . . .
				// cause it could be Multiline
				CRect theTabRect;
				GetItemRect( 0, &theTabRect );

				LONG theRowCount = GetRowCount( );
				LONG theEdgeWidth = ( theTabRect.Width() * theRowCount ) + TABVIEW_EDGE;
				LONG theEdgeHeight = ( theTabRect.Height() * theRowCount ) + TABVIEW_EDGE;

				// Set the size based on the style
				DWORD dwStyle = GetStyle();
				if ((dwStyle & TCS_BOTTOM) && !(dwStyle & TCS_VERTICAL)) {		// Bottom
					(*pRect).top	+= TABVIEW_BORDER;
					(*pRect).left	+= TABVIEW_BORDER;
					(*pRect).right	-= TABVIEW_BORDER;
					(*pRect).bottom	-= theEdgeHeight;
				}
				else if ((dwStyle & TCS_RIGHT) && (dwStyle & TCS_VERTICAL)) {	// Right
					(*pRect).top	+= TABVIEW_BORDER;
					(*pRect).left	+= TABVIEW_BORDER;
					(*pRect).right	-= theEdgeWidth;
					(*pRect).bottom	-= TABVIEW_BORDER;
				}
				else if (dwStyle & TCS_VERTICAL) {								// Left
					(*pRect).top	+= TABVIEW_BORDER;
					(*pRect).left	+= theEdgeWidth;
					(*pRect).right	-= TABVIEW_BORDER;
					(*pRect).bottom	-= TABVIEW_BORDER;
				}
				else {															// Top
					(*pRect).top	+= theEdgeHeight;
					(*pRect).left	+= TABVIEW_BORDER;
					(*pRect).right	-= TABVIEW_BORDER;
					(*pRect).bottom	-= TABVIEW_BORDER;
				}
			}
		}

		//==============================================================================
		/**
		 *		SetTabFont:	Set the font to be used by the tab control.
		 *
		 *		@param	inStyleBits
		 *				[in] The style bits to use to calculate the font to use. 
		 *		@return	void
		 */
		//==============================================================================
		void SetTabFont( DWORD inStyleBits )
		{
			if ( inStyleBits & TCS_VERTICAL ) 
			{
				if ( inStyleBits & TCS_RIGHT )
				{
					SetFont( m_RightFont );
				}

				else
				{
					SetFont( m_LeftFont );
				}
			}

			else
			{
				SetFont( m_HorizFont );
			}
		}
}; 

#endif // INCLUDED_WTLTABVIEWCTRL

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Stephen Jones is a Lead Software Engineer for Anark Corporation in Boulder, Colorado.


Comments and Discussions