Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Custom Tab Controls, Tabbed Frame and Tabbed MDI

, 13 Jul 2005
An extensible framework for creating customized tabs in ATL/WTL, with a VS.NET-like tab control implementation, tabbed frames, tabbed MDI, and more.
tabbingframework_demo.zip
TabbedSDISplitter
Release
TabbedSDISplitter.exe
res
Doc.ico
folder_c.ico
folder_o.ico
TabbedSDISplitter.exe.manifest
TabbedSDISplitter.ico
toolbar.bmp
TabbedSDISplitter.dsp
TabbedSDISplitter.dsw
TabDemo
Release
TabDemo.exe
res
error.ico
folder_closed.ico
folder_open.ico
info.ico
log.ico
TabDemo.exe.manifest
TabDemo.ico
TabDemodoc.ico
toolbar.bmp
warning.ico
TabDemo.dsp
TabDemo.dsw
TabbingFramework_ScreenShot.png
DockingDemo
DockingDemo.dsp
DockingDemo.dsw
Release
DockingDemo.exe
res
DockingDemo.exe.manifest
DockingDemo.ico
DockingDemodoc.ico
error.ico
folder_closed.ico
folder_open.ico
info.ico
log.ico
msdev_tab_icons.bmp
toolbar.bmp
warning.ico
include
Sergey Klimov
SimpleTabbedMDIDemo
Release
SimpleTabbedMDIDemo.exe
res
SimpleTabbedMDIDemo.exe.manifest
SimpleTabbedMDIDemo.ico
SimpleTabbedMDIDemodoc.ico
toolbar.bmp
SimpleTabbedMDIDemo.dsp
SimpleTabbedMDIDemo.dsw
tabbingframework_history.zip
tabbingframework_priorhistory.zip
tabbingframework_src.zip
/////////////////////////////////////////////////////////////////////////////
// Written by Daniel Bowen (dbowen@es.com)
// Copyright (c) 2002 Daniel Bowen.
// WTL Docking windows

#ifndef __WTL_DW__VCLIKECAPTION_H__
#define __WTL_DW__VCLIKECAPTION_H__

namespace dockwins{


class CVC7LikeExCaption : public CCaptionBase
{
	typedef CVC7LikeExCaption thisClass;
	typedef CCaptionBase	baseClass;
public:
	enum{fntSpace=4,cFrameSpace=1,btnSpace=1,cMarginTop=2,cMarginBottom=2};
protected:
	typedef baseClass::CButton CButtonBase;
	struct CButton : CButtonBase
	{
		typedef enum tagButtonState
		{
			eButtonState_Normal = 0,
			eButtonState_Pressed = 1,
			eButtonState_Hot = 2,
		} ButtonState;

		virtual void CalculateRect(CRect& rc,bool bHorizontal)
		{
			CopyRect(rc);
			if(bHorizontal)
			{
				top+=cMarginTop;
				bottom-=cMarginBottom;
			}
			else
			{
				left+=cMarginTop;
				right-=cMarginBottom;
			}

			DeflateRect(cFrameSpace+btnSpace,cFrameSpace+btnSpace);
			if(bHorizontal)
			{
				left=right-Height();
				rc.right=left+btnSpace;
			}
			else
			{
				bottom=top+Width();
				rc.top=bottom+btnSpace;
			}
		}
		virtual void Draw (CDC& dc)=0;
		virtual void Draw (HWND hWnd, CDC& dc, ButtonState eState)=0;
		virtual void Press(HWND hWnd)
		{
			CWindowDC dc(hWnd);
			Draw(hWnd, dc, eButtonState_Pressed);
			dc.DrawEdge(this,BDR_SUNKENOUTER/*|BF_ADJUST*/ ,BF_RECT); //look like button push
		}
		virtual void Release(HWND hWnd)
		{
			CWindowDC dc(hWnd);
			Draw(hWnd, dc, eButtonState_Normal);
		}
		virtual void Hot(HWND hWnd)
		{
			CWindowDC dc(hWnd);
			Draw(hWnd, dc, eButtonState_Hot);
			dc.DrawEdge(this,BDR_RAISEDINNER/*|BF_ADJUST*/ ,BF_RECT); //look like button raise
		}
	};
	class CCloseButton: public CButton
	{
	public:
		virtual void Draw(CDC& dc)
		{
			Draw(dc.WindowFromDC(), dc, eButtonState_Normal);
		}

		virtual void Draw(HWND hWnd, CDC& dc, ButtonState eState)
		{
			CDWSettings settings;

			int cxOffset = 0, cyOffset = 0;
			if(eState == eButtonState_Pressed)
			{
				cxOffset++;
				cyOffset++;
			}

			const int nMaxHeightSingleWidth = 15;

			// Erase Background
#ifndef DF_FOCUS_FEATURES
			BOOL bDescendantHasFocus = FALSE;
#else
			BOOL bDescendantHasFocus = ::IsChild(hWnd, ::GetFocus());
#endif
			dc.FillRect(this,::GetSysColorBrush(bDescendantHasFocus ? COLOR_ACTIVECAPTION : COLOR_3DFACE));

			// Paint Internal of Button

			if(settings.CYSmCaption() > nMaxHeightSingleWidth)
			{
				// Note: This may look funny, but DrawFrameControl
				//  actually uses a similar algorithm
				//  (with the Marlett font, etc.)
				CFont fontClose;
				fontClose.CreatePointFont(Height()*7,_T("Marlett"),dc);

				CFontHandle fontOld = dc.SelectFont(fontClose);
				COLORREF colorOld = dc.SetTextColor(::GetSysColor(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT));
				int bkModeOld = dc.SetBkMode(TRANSPARENT);

				CSize size;
				dc.GetTextExtent(_T("r"), 1, &size);

				CRect rcDraw(this);
				rcDraw.OffsetRect(cxOffset,cyOffset);

				dc.DrawText(_T("r"),1,&rcDraw,DT_SINGLELINE|DT_CENTER|DT_VCENTER);

				if(bkModeOld != TRANSPARENT)
					dc.SetBkMode(bkModeOld);
				if(colorOld != ::GetSysColor(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT))
					dc.SetTextColor(colorOld);

				dc.SelectFont(fontOld);
				// fontClose gets destroyed when going out of scope
			}
			else
			{
				CPen pen;
				pen.CreatePen(PS_SOLID, 1, ::GetSysColor(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT));

				HPEN hPenOld = dc.SelectPen(pen);
				const int sp=2;
				dc.MoveTo(left+cxOffset+sp, top+cyOffset+sp +1);
				dc.LineTo(right+cxOffset-sp -1, bottom+cyOffset-sp);
				dc.MoveTo(left+cxOffset+sp, bottom+cyOffset - sp-1);
				dc.LineTo(right+cxOffset-sp -1, top+cyOffset +sp );

				dc.SelectPen(hPenOld);
			}
		}
	};
#ifdef DF_AUTO_HIDE_FEATURES
	class CPinButton : public CButton
	{
	public:
		typedef CPinIcons CIcons;
		CPinButton():m_state(CIcons::sPinned)
		{
		}
		void State(CIcons::States state)
		{
			m_state=state;
		}
		virtual void Draw(CDC& dc)
		{
			Draw(dc.WindowFromDC(), dc, eButtonState_Normal);
		}

		virtual void Draw(HWND hWnd, CDC& dc, ButtonState eState)
		{
			CDWSettings settings;

			int cxOffset = 0, cyOffset = 0;
			if(eState == eButtonState_Pressed)
			{
				cxOffset++;
				cyOffset++;
			}

			// Erase Background
#ifndef DF_FOCUS_FEATURES
			BOOL bDescendantHasFocus = FALSE;
#else
			BOOL bDescendantHasFocus = ::IsChild(hWnd, ::GetFocus());
#endif
			dc.FillRect(this,::GetSysColorBrush(bDescendantHasFocus ? COLOR_ACTIVECAPTION : COLOR_3DFACE));

			// Paint Internal of Button

			// This approximates the VS.NET algorithm.  The pin in
			// VS.NET depends on the small caption height.  This code
			// does as well, but doesn't always exactly match the drawing
			// code of VS.NET.  For example, the width of the lines
			// based on SmCaption are 1 (0-36), 2 (37-58), 3 (59-79), 4 (80-100+)
			int cySmCaption = settings.CYSmCaption();
			int nPenWidth = cySmCaption/20;
			if(nPenWidth < 1) nPenWidth = 1;

			LOGBRUSH lb = {BS_SOLID, ::GetSysColor(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT), 0};
			CPen pen;
			pen.CreatePen(PS_SOLID|PS_GEOMETRIC|PS_ENDCAP_SQUARE|PS_JOIN_BEVEL, nPenWidth, &lb);

			int nHCenter = left+Width()/2;
			int nVCenter = top+Height()/2;
			HPEN hPenOld = dc.SelectPen(pen);
			if(m_state == CPinIcons::sUnPinned)
			{
				// Note: there are slight differences in coord. because of our ExtCreatePen
				int nSegment = (cySmCaption/5);
				int nLeftSp = (cySmCaption<20) ? 1 : nPenWidth*3;
				//int nRightSp = (cySmCaption<16) ? 2 : ((cySmCaption<20) ? (cySmCaption-13) : nPenWidth*4);
				int nLip = cySmCaption/10;
				int nBoxWidth = (cySmCaption<20) ? 1 : (nLip-1);

				int nPinLeft = left+nLeftSp;
				int nLineLeft = nPinLeft + nSegment;
				int nPinRight = nLineLeft + 2*nSegment -1;
				//int nPinRight = right-nRightSp;
				int nLineTop = nVCenter-nSegment;
				int nLineBottom = nVCenter+nSegment;

				int nPinBodyTop = nVCenter+nLip-nSegment;
				int nPinBodyBottom = nVCenter-nLip+nSegment;

				dc.MoveTo(nPinLeft+cxOffset, nVCenter+cyOffset);
				dc.LineTo(nLineLeft+cxOffset, nVCenter+cyOffset);
				dc.MoveTo(nLineLeft+cxOffset, nLineTop+cyOffset);
				dc.LineTo(nLineLeft+cxOffset, nLineBottom+cyOffset);

				dc.MoveTo(nLineLeft+cxOffset, nPinBodyTop+cyOffset);
				dc.LineTo(nPinRight+cxOffset, nPinBodyTop+cyOffset);
				dc.LineTo(nPinRight+cxOffset, nPinBodyBottom+cyOffset);
				dc.LineTo(nLineLeft+cxOffset, nPinBodyBottom+cyOffset);

				RECT rcPinRight = {
					nLineLeft+cxOffset+1,
					nPinBodyBottom-nBoxWidth+cyOffset,
					nPinRight+cxOffset,
					nPinBodyBottom+cyOffset};
				dc.FillRect(&rcPinRight, ::GetSysColorBrush(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT));
			}
			else
			{
				// Works better one pixel to the left
				cxOffset--;

				// Note: there are slight differences in coord. because of our ExtCreatePen
				int nSegment = (cySmCaption/5);
				int nBottomSp = (cySmCaption<20) ? 2 : nPenWidth*3;
				//int nTopSp = (cySmCaption<16) ? 2 : ((cySmCaption<20) ? (cySmCaption-13) : nPenWidth*4);
				int nLip = cySmCaption/10;
				int nBoxWidth = (cySmCaption<20) ? 1 : (nLip-1);

				int nPinBottom = bottom-nBottomSp;
				int nLineBottom = nPinBottom - nSegment;
				int nPinTop = nLineBottom - 2*nSegment +1;
				//int nPinTop = top+nTopSp;
				int nLineLeft = nHCenter-nSegment;
				int nLineRight = nHCenter+nSegment;

				int nPinBodyLeft = nHCenter+nLip-nSegment;
				int nPinBodyRight = nHCenter-nLip+nSegment;

				dc.MoveTo(nHCenter+cxOffset, nPinBottom+cyOffset);
				dc.LineTo(nHCenter+cxOffset, nLineBottom+cyOffset);
				dc.MoveTo(nLineLeft+cxOffset, nLineBottom+cyOffset);
				dc.LineTo(nLineRight+cxOffset, nLineBottom+cyOffset);

				dc.MoveTo(nPinBodyLeft+cxOffset, nLineBottom+cyOffset);
				dc.LineTo(nPinBodyLeft+cxOffset, nPinTop+cyOffset);
				dc.LineTo(nPinBodyRight+cxOffset, nPinTop+cyOffset);
				dc.LineTo(nPinBodyRight+cxOffset, nLineBottom+cyOffset);
				RECT rcPinRight = {
					nPinBodyRight-nBoxWidth+cxOffset,
					nPinTop+cyOffset+1,
					nPinBodyRight+cxOffset,
					nLineBottom+cyOffset};
				dc.FillRect(&rcPinRight, ::GetSysColorBrush(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT));
			}

			dc.SelectPen(hPenOld);
		}
	protected:
		CIcons::States	m_state;
	};
public:
	void SetPinButtonState(CPinButton::CIcons::States state)
	{
		m_btnPin.State(state);
	}
#endif
public:
	CVC7LikeExCaption():baseClass(0,false)
	{
		SetOrientation(!IsHorizontal());
	}

	void UpdateMetrics()
	{
		CDWSettings settings;

		m_thickness=settings.CYSmCaption()+cFrameSpace+cMarginBottom+cMarginTop;
	}

    void SetOrientation(bool bHorizontal)
    {
		if(IsHorizontal()!=bHorizontal)
		{
			baseClass::SetOrientation(bHorizontal);
			UpdateMetrics();
		}
    }

	bool CalculateRect(CRect& rc,bool bTop)
	{
		bool bRes=baseClass::CalculateRect(rc,bTop);
		CRect rcSpace(*this);
		m_btnClose.CalculateRect(rcSpace,IsHorizontal());
#ifdef DF_AUTO_HIDE_FEATURES
		m_btnPin.CalculateRect(rcSpace,IsHorizontal());
#endif
		return bRes;
	}
	void Draw(HWND hWnd,CDC& dc)
	{
#ifndef DF_FOCUS_FEATURES
		BOOL bDescendantHasFocus = FALSE;
#else
		BOOL bDescendantHasFocus = ::IsChild(hWnd, ::GetFocus());
#endif
		CRect rcBorder(this);
		if(IsHorizontal())
		{
			rcBorder.top+=cMarginTop;
			rcBorder.bottom-=cMarginBottom;
		}
		else
		{
			rcBorder.left+=cMarginTop;
			rcBorder.right-=cMarginBottom;
		}

		if(bDescendantHasFocus)
		{
			dc.FillRect(this,::GetSysColorBrush(COLOR_3DFACE));
			dc.FillRect(&rcBorder,::GetSysColorBrush(COLOR_ACTIVECAPTION));
		}
		else
		{
			dc.FillRect(this,::GetSysColorBrush(bDescendantHasFocus ? COLOR_ACTIVECAPTION : COLOR_3DFACE));

			//dc.FrameRect(&rcBorder,::GetSysColorBrush(COLOR_BTNSHADOW));
			rcBorder.InflateRect(-1,0);
			dc.DrawEdge(&rcBorder,EDGE_ETCHED,BF_TOP|BF_BOTTOM|BF_FLAT);
			rcBorder.InflateRect(1,-1);
			dc.DrawEdge(&rcBorder,EDGE_ETCHED,BF_LEFT|BF_RIGHT|BF_FLAT);
		}

		int len=GetWindowTextLength(hWnd)+1;
		TCHAR* sText=new TCHAR[len];
		if(GetWindowText(hWnd,sText,len)!=0)
		{
			HFONT hFont = NULL;
			CDWSettings settings;
			CRect rc(rcBorder);
			if(IsHorizontal())
			{
				rc.left+=fntSpace+cFrameSpace;
#ifdef DF_AUTO_HIDE_FEATURES
				rc.right=m_btnPin.left-cFrameSpace-btnSpace;
#else
				rc.right=m_btnClose.left-cFrameSpace-btnSpace;
#endif
				hFont = settings.HSmCaptionFont();
			}
			else
			{
				rc.bottom-=fntSpace-cFrameSpace;
#ifdef DF_AUTO_HIDE_FEATURES
				rc.top=m_btnPin.bottom+cFrameSpace+btnSpace;
#else
				rc.top=m_btnClose.bottom+cFrameSpace+btnSpace;
#endif
				hFont = settings.VSmCaptionFont();
			}
			dc.SetTextColor(::GetSysColor(bDescendantHasFocus ? COLOR_CAPTIONTEXT : COLOR_BTNTEXT));
			dc.SetBkMode(TRANSPARENT);
			HFONT hFontOld = dc.SelectFont(hFont);
			if( (rc.left<rc.right) && (rc.top<rc.bottom))
				DrawEllipsisText(dc,sText,(int)_tcslen(sText),&rc,IsHorizontal());
			dc.SelectFont(hFontOld);
		}
		m_btnClose.Draw(hWnd, dc, CButton::eButtonState_Normal);
#ifdef DF_AUTO_HIDE_FEATURES
		m_btnPin.Draw(hWnd, dc, CButton::eButtonState_Normal);
#endif

		delete [] sText;
	}

	LRESULT HitTest(const CPoint& pt) const
	{
		LRESULT lRes=HTNOWHERE;
		if(PtInRect(pt))
		{
			lRes=HTCAPTION;
			if(m_btnClose.PtInRect(pt))
				lRes=HTCLOSE;
#ifdef DF_AUTO_HIDE_FEATURES
			else
			{
				if(m_btnPin.PtInRect(pt))
					lRes=HTPIN;
			}
#endif
		}
		return lRes;
	}

	bool HotTrack(HWND hWnd,unsigned int nHitTest)
	{
		bool bRes=true;
		CButton* pbtn;
		switch(nHitTest)
		{
			case HTCLOSE:
				pbtn=&m_btnClose;
				break;
#ifdef DF_AUTO_HIDE_FEATURES
			case HTPIN:
				pbtn=&m_btnPin;
				break;
#endif
			default:
				return false;
		}
		CHotBtnTracker<thisClass> tracker(*pbtn,*this,hWnd,nHitTest);
		TrackDragAndDrop(tracker,hWnd);
		if(tracker)
			::SendMessage(hWnd,WM_NCLBUTTONUP,nHitTest,GetMessagePos());
		return bRes;
	}

protected:
#ifdef DF_AUTO_HIDE_FEATURES
	CPinButton		m_btnPin;
#endif
	CCloseButton	m_btnClose;
};
struct CVC7LikeCaption :  CVC7LikeExCaption
{
	void SetOrientation(bool /*bHorizontal*/)
	{
		// horizontal only
	}
};


typedef CDockingWindowTraits<CVC7LikeCaption,
								WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE |
								WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_TOOLWINDOW>
							 CVC7LikeTitleDockingWindowTraits;

typedef CDockingWindowTraits<CVC7LikeExCaption,
								WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE |
								WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_TOOLWINDOW>
							 CVC7LikeExTitleDockingWindowTraits;

typedef CDockingBoxTraits<CVC7LikeCaption,
								WS_OVERLAPPEDWINDOW | WS_POPUP/* WS_CHILD*/ |
								/*WS_VISIBLE |*/ WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
								WS_EX_TOOLWINDOW/* WS_EX_CLIENTEDGE*/> CVC7LikeDockingBoxTraits;

typedef CDockingBoxTraits<CVC7LikeExCaption,
								WS_OVERLAPPEDWINDOW | WS_POPUP/* WS_CHILD*/ |
								/*WS_VISIBLE |*/ WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
								WS_EX_TOOLWINDOW/* WS_EX_CLIENTEDGE*/> CVC7LikeExDockingBoxTraits;

typedef CDockingWindowTraits<CVC7LikeCaption,
								WS_CAPTION | WS_CHILD |
								WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_TOOLWINDOW>
								CVC7LikeAutoHidePaneTraits;
typedef CBoxedDockingWindowTraits<CVC7LikeCaption, CTabDockingBox<CVC7LikeDockingBoxTraits>,
									WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE |
									WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_TOOLWINDOW>
								CVC7LikeBoxedDockingWindowTraits;

typedef CBoxedDockingWindowTraits<CVC7LikeExCaption, CTabDockingBox<CVC7LikeExDockingBoxTraits>,
									WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE |
									WS_CLIPCHILDREN | WS_CLIPSIBLINGS,WS_EX_TOOLWINDOW>
								CVC7LikeExBoxedDockingWindowTraits;

#ifdef DF_AUTO_HIDE_FEATURES
typedef CDockingFrameTraitsT< CVC7LikeAutoHidePaneTraits,CSimpleSplitterBar<5>,
		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
		WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CVC7LikeDockingFrameTraits;

typedef CDockingFrameTraitsT<CVC7LikeAutoHidePaneTraits, CSimpleSplitterBarEx<6>,
		WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,0> CVC7LikeDockingSiteTraits;
#endif


}//namespace dockwins
#endif // __WTL_DW__VCLIKECAPTION_H__

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

Share

About the Author

Daniel Bowen
Architect
United States United States
Daniel Bowen used to work as a Software Engineer for Evans & Sutherland in Salt Lake City, Utah working on the modeling tools for high end flight simulators. He then worked for the startup company WiLife Inc. in Draper, Utah working on the software portion of an easy to use and affordable digital video surveillance system. WiLife Inc. is now part of Logitech Inc.

| Advertise | Privacy | Mobile
Web03 | 2.8.140926.1 | Last Updated 14 Jul 2005
Article Copyright 2002 by Daniel Bowen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid