Figure 3   CFolderTab

FTab.h


 ////////////////////////////////////////////////////////////////
 // 1999 Paul DiLascia
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // 
 #ifndef FTAB_H
 #define FTAB_H
 
 // style flags
 #define FTS_FULLBORDER  0x1             // draw full border
 
 class CFolderTab {
 private:
    CString  m_sText; // tab text
    CRect    m_rect;        // bounding rect
    CRgn     m_rgn;         // polygon region to fill (trapezoid)
 
    int      ComputeRgn(CDC& dc, int x);
    int      Draw(CDC& dc, CFont& font, BOOL bSelected);
    BOOL     HitTest(CPoint pt)         { return m_rgn.PtInRegion(pt); }
    CRect    GetRect() const            { return m_rect; }
    void     GetTrapezoid(const CRect& rc, CPoint* pts) const;
 
    friend class CFolderTabCtrl;
 
 public:
    CFolderTab(LPCTSTR lpszText) : m_sText(lpszText) { }
    LPCTSTR  GetText() const            { return m_sText; }
    void     SetText(LPCTSTR lpszText)  { m_sText = lpszText; }
 };
 
 enum { FTN_TABCHANGED = 1 };            // notification: tab changed
 
 struct NMFOLDERTAB : public NMHDR {     // notification struct
    int iItem;                              // item index
    const CFolderTab* pItem;                // ptr to data, if any
 };
 
 //////////////////
 // Folder tab control, similar to tab control
 //
 class CFolderTabCtrl : public CWnd {
 protected:
    CPtrList    m_lsTabs;                // array of CFolderTabs
    DWORD       m_dwFtabStyle;           // folder tab style flags
    int         m_iCurItem;              // current selected tab
    CFont       m_fontNormal;            // current font, normal ntab
    CFont       m_fontSelected;          // current font, selected tab
    int         m_nDesiredWidth;         // exact fit width
 
    // helpers
    void InvalidateTab(int iTab, BOOL bErase=TRUE);
    void DrawTabs(CDC& dc, const CRect& rc);
    CFolderTab* GetTab(int iPos);
 
 public:
    CFolderTabCtrl();
    virtual ~CFolderTabCtrl();
 
    BOOL CreateFromStatic(UINT nID, CWnd* pParent);
 
    virtual BOOL Create(DWORD dwWndStyle, const RECT& rc,
       CWnd* pParent, UINT nID, DWORD dwFtabStyle=0);
    virtual BOOL Load(UINT nIDRes);
 
    CFolderTab* GetItem(int iPos)    { return (CFolderTab*)GetTab(iPos); }
    int   GetSelectedItem()          { return m_iCurItem; }
    int   GetItemCount()             { return m_lsTabs.GetCount(); }
    int   GetDesiredWidth()          { return m_nDesiredWidth; }
    int   GetDesiredHeight()         { return GetSystemMetrics(SM_CYHSCROLL); }
    BOOL  AddItem(LPCTSTR lpszText);
    BOOL  RemoveItem(int iPos);
    void  RecomputeLayout();
    int   HitTest(CPoint pt);
    int   SelectItem(int iTab);
    void  SetFonts(CFont& fontNormal, CFont& fontSelected);
 
 protected:
    afx_msg void OnPaint();
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    DECLARE_DYNAMIC(CFolderTabCtrl);
    DECLARE_MESSAGE_MAP()
 };
 
 #endif // FTAB_H
FTab.cpp

 ////////////////////////////////////////////////////////////////
 // 1999 Paul DiLascia
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 #include "stdafx.h"
 #include "ftab.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 const CXOFFSET = 8;     // defined pitch of trapezoid slant
 const CXMARGIN = 2;     // left/right text margin
 const CYMARGIN = 1;     // top/bottom text margin
 const CYBORDER = 1;     // top border thickness
 
 //////////////////
 // Compute the points, rect and region for a tab.
 // Input x is starting x pos.
 //
 int CFolderTab::ComputeRgn(CDC& dc, int x)
 {
    m_rgn.DeleteObject();
 
    CRect& rc = m_rect;
    rc.SetRectEmpty();
 
    // calculate desired text rectangle
    dc.DrawText(m_sText, &rc, DT_CALCRECT);
    rc.right  += 2*CXOFFSET + 2*CXMARGIN;                 // add margins
    rc.bottom = rc.top + GetSystemMetrics(SM_CYHSCROLL);  // ht = scrollbar ht
    rc += CPoint(x,0);                                    // shift right
 
    // create trapezoid region
    CPoint pts[4];
    GetTrapezoid(rc, pts);
    m_rgn.CreatePolygonRgn(pts, 4, WINDING);
 
    return rc.Width();
 }
 
 //////////////////
 // Given the boundint rect, compute trapezoid region.
 // Note that the right and bottom edges not included in rect or
 // trapezoid; these are normal rules of geometry. 
 //
 void CFolderTab::GetTrapezoid(const CRect& rc, CPoint* pts) const
 {
    pts[0] = rc.TopLeft();
    pts[1] = CPoint(rc.left + CXOFFSET,   rc.bottom);
    pts[2] = CPoint(rc.right- CXOFFSET-1, rc.bottom);
    pts[3] = CPoint(rc.right-1, rc.top);
 }
 
 
 //////////////////
 // Draw tab in normal or highlighted state
 //
 int CFolderTab::Draw(CDC& dc, CFont& font, BOOL bSelected)
 {
    COLORREF bgColor = GetSysColor(bSelected ? COLOR_WINDOW     : COLOR_3DFACE);
    COLORREF fgColor = GetSysColor(bSelected ? COLOR_WINDOWTEXT : COLOR_BTNTEXT);
 
    CBrush brush(bgColor);               // background brush
    dc.SetBkColor(bgColor);              // text background
    dc.SetTextColor(fgColor);            // text color = fg color
 
    CPen blackPen(PS_SOLID, 1, RGB(0,0,0));   // black
    CPen shadowPen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW));
 
    // Fill trapezoid
    CPoint pts[4];
    CRect rc = m_rect;
    GetTrapezoid(rc, pts);
    CPen* pOldPen = dc.SelectObject(&blackPen);
    dc.FillRgn(&m_rgn, &brush);
 
    // Draw edges. This requires two corrections:
    // 1) Trapezoid dimensions don't include the right and bottom edges,
    // so must use one pixel less on bottom (cybottom)
    // 2) the endpoint of LineTo is not included when drawing the line, so
    // must add one pixel (cytop)
    //
    pts[1].y--;       // correction #1: true bottom edge y-coord
    pts[2].y--;       // ...ditto
    pts[3].y--;       // correction #2: extend final LineTo
    dc.MoveTo(pts[0]);                  // upper left
    dc.LineTo(pts[1]);                  // bottom left
    dc.SelectObject(&shadowPen);        // bottom line is shadow color
    dc.MoveTo(pts[1]);                  // line is inside trapezoid bottom
    dc.LineTo(pts[2]);                  // ...
    dc.SelectObject(&blackPen);         // upstroke is black
    dc.LineTo(pts[3]);                  // y-1 to include endpoint
    if (!bSelected) {
       // if not highlighted, upstroke has a 3D shadow, one pixel inside
       pts[2].x--;    // offset left one pixel
       pts[3].x--;    // ...ditto
       dc.SelectObject(&shadowPen);
       dc.MoveTo(pts[2]);
       dc.LineTo(pts[3]);
    }
    dc.SelectObject(pOldPen);
 
    // draw text
    rc.DeflateRect(CXOFFSET + CXMARGIN, CYMARGIN);
    CFont* pOldFont = dc.SelectObject(&font);
    dc.DrawText(m_sText, &rc, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
    dc.SelectObject(pOldFont);
 
    return m_rect.right;
 }
 
 //////////////////////////////////////////////////////////////////
 // CFolderTabCtrl
 
 IMPLEMENT_DYNAMIC(CFolderTabCtrl, CWnd)
 BEGIN_MESSAGE_MAP(CFolderTabCtrl, CWnd)
    ON_WM_PAINT()
    ON_WM_LBUTTONDOWN()
 END_MESSAGE_MAP()
 
 CFolderTabCtrl::CFolderTabCtrl()
 {
    m_iCurItem = 0;
    m_dwFtabStyle = 0;
    m_nDesiredWidth = 0;
 }
 
 CFolderTabCtrl::~CFolderTabCtrl()
 {
    while (!m_lsTabs.IsEmpty())
       delete (CFolderTab*)m_lsTabs.RemoveHead();
 }
 
 //////////////////
 // Create folder tab control from static control.
 // Destroys the static control. This is convenient for dialogs
 //
 BOOL CFolderTabCtrl::CreateFromStatic(UINT nID, CWnd* pParent)
 {
    CStatic wndStatic;
    if (!wndStatic.SubclassDlgItem(nID, pParent))
       return FALSE;
    CRect rc;
    wndStatic.GetWindowRect(&rc);
    pParent->ScreenToClient(&rc);
    wndStatic.DestroyWindow();
    rc.bottom = rc.top + GetDesiredHeight();
    return Create(WS_CHILD|WS_VISIBLE, rc, pParent, nID);
 }
 
 //////////////////
 // Create folder tab control.
 //
 BOOL CFolderTabCtrl::Create(DWORD dwStyle, const RECT& rc,
    CWnd* pParent, UINT nID, DWORD dwFtabStyle)
 {
    ASSERT(pParent);
    ASSERT(dwStyle & WS_CHILD);
 
    m_dwFtabStyle = dwFtabStyle;
 
    static LPCTSTR lpClassName = _T("PDFolderTab");
    static BOOL bRegistered = FALSE; // registered?
    if (!bRegistered) {
       WNDCLASS wc;
       memset(&wc, 0, sizeof(wc));
       wc.style = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
       wc.lpfnWndProc = (WNDPROC)::DefWindowProc; // will get hooked by MFC
       wc.hInstance = AfxGetInstanceHandle();
       wc.hCursor = LoadCursor(NULL, IDC_ARROW);
       wc.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_3DFACE));
       wc.lpszMenuName = NULL;
       wc.lpszClassName = lpClassName;
       if (!AfxRegisterClass(&wc)) {
          TRACE("*** CFolderTabCtrl::AfxRegisterClass failed!\n");
          return FALSE;
       }
       bRegistered = TRUE;
    }
    if (!CWnd::CreateEx(0, lpClassName, NULL, dwStyle, rc, pParent, nID))
       return FALSE;
 
    // initialize fonts
    LOGFONT lf;
    memset(&lf, 0, sizeof(lf));
    lf.lfHeight = GetSystemMetrics(SM_CYHSCROLL)-CYMARGIN;
    lf.lfWeight = FW_NORMAL;
    lf.lfCharSet = DEFAULT_CHARSET;
    _tcscpy(lf.lfFaceName, _T("Arial"));
    m_fontNormal.CreateFontIndirect(&lf);
    lf.lfHeight -= 2;
    m_fontSelected.CreateFontIndirect(&lf);
 
    return TRUE;
 }
 
 //////////////////
 // copy a font
 //
 static void CopyFont(CFont& dst, CFont& src)
 {
    dst.DeleteObject();
    LOGFONT lf;
    VERIFY(src.GetLogFont(&lf));
    dst.CreateFontIndirect(&lf);
 }
 
 //////////////////
 // Set normal, selected fonts
 //
 void CFolderTabCtrl::SetFonts(CFont& fontNormal, CFont& fontSelected)
 {
    CopyFont(m_fontNormal, fontNormal);
    CopyFont(m_fontSelected, fontSelected);
 }
 
 //////////////////
 // Paint function
 //
 void CFolderTabCtrl::OnPaint() 
 {
    CPaintDC dc(this); // device context for painting
 
    CRect rc;
    GetClientRect(&rc);
 
    CFolderTab* pCurTab = NULL;
 
    // draw all the normal (non-selected) tabs
    int n = GetItemCount();
    for (int i=0; i<n; i++) {
       CFolderTab* pTab = GetTab(i);
       ASSERT(pTab);
       if (i==m_iCurItem) {
          pCurTab = pTab;
       } else if (pTab->Draw(dc, m_fontNormal, FALSE) > rc.right)
          break;
    }
    // draw selected tab last so it will be "on top" of the others
    if (pCurTab)
       pCurTab->Draw(dc, m_fontSelected, TRUE);
 
    // draw border: line along the top edge, excluding seleted tab
    CRect rcCurTab(0,0,0,0);
    if (pCurTab)
       rcCurTab = pCurTab->GetRect();
 
    CPen blackPen(PS_SOLID, 1, RGB(0,0,0));   // black
    CPen* pOldPen = dc.SelectObject(&blackPen);
    dc.MoveTo(rcCurTab.right, rcCurTab.top);
    dc.LineTo(rc.right, rc.top);
    if (m_dwFtabStyle & FTS_FULLBORDER) {
       dc.MoveTo(rc.right-1, rc.top);
       dc.LineTo(rc.right-1, rc.bottom-1);
       dc.LineTo(rc.left,  rc.bottom-1);
       dc.LineTo(rc.left,  rc.top);
    } else {
       dc.MoveTo(rc.left, rc.top);
    }
    dc.LineTo(rcCurTab.TopLeft());
    dc.SelectObject(pOldPen);
 }
 
 //////////////////
 // Handle mouse click: select new tab, if any. Notify parent, of course
 //
 void CFolderTabCtrl::OnLButtonDown(UINT nFlags, CPoint pt)
 {
    int iTab = HitTest(pt);
    if (iTab>=0 && iTab!=m_iCurItem) {
       SelectItem(iTab);
       NMFOLDERTAB nm;
       nm.hwndFrom = m_hWnd;
       nm.idFrom = GetDlgCtrlID();
       nm.code = FTN_TABCHANGED;
       nm.iItem = iTab;
       nm.pItem = GetTab(iTab);
       CWnd* pParent = GetParent();
       pParent->SendMessage(WM_NOTIFY, nm.idFrom, (LPARAM)&nm);
    }
 }
 
 //////////////////
 // Find which tab is under mouse, -1 if none
 //
 int CFolderTabCtrl::HitTest(CPoint pt)
 {
    CRect rc;
    GetClientRect(&rc);
    if (rc.PtInRect(pt)) {
       int n = GetItemCount();
       for (int i=0; i<n; i++) {
          if (GetTab(i)->HitTest(pt))
             return i;
       }
    }
    return -1;
 }
 
 //////////////////
 // Select ith tab. Returns index selected
 //
 int CFolderTabCtrl::SelectItem(int iTab)
 {
    if (iTab<0 || iTab>=GetItemCount())
       return -1;     // bad
    if (iTab==m_iCurItem)
       return iTab;   // already selected
 
    InvalidateTab(m_iCurItem);    // invalidate old tab (repaint)
    m_iCurItem = iTab;            // set new selected tab
    InvalidateTab(m_iCurItem);    // repaint new tab
    
    return m_iCurItem;
 }
 
 //////////////////
 /// Invalidate a tab: invaldate its rect
 //
 void CFolderTabCtrl::InvalidateTab(int iTab, BOOL bErase)
 {
    InvalidateRect(GetTab(iTab)->GetRect(), bErase);
 }
 
 BOOL CFolderTabCtrl::Load(UINT nIDRes)
 {
    CString s;
    if (!s.LoadString(nIDRes))
       return FALSE;
 
    CString sTab;
    for (int i=0; AfxExtractSubString(sTab, s, i); i++) {
       AddItem(sTab);
    }
    RecomputeLayout();
    return TRUE;
 }
 
 int CFolderTabCtrl::AddItem(LPCTSTR lpszText)
 {
    m_lsTabs.AddTail(new CFolderTab(lpszText));
    return m_lsTabs.GetCount() - 1;
 }
 
 BOOL CFolderTabCtrl::RemoveItem(int iPos)
 {
    POSITION pos = m_lsTabs.FindIndex(iPos);
    if (pos) {
       CFolderTab* pTab = (CFolderTab*)m_lsTabs.GetAt(pos);
       m_lsTabs.RemoveAt(pos);
       delete pTab;
    }
    return pos!=NULL;
 }
 
 CFolderTab* CFolderTabCtrl::GetTab(int iPos)
 {
    POSITION pos = m_lsTabs.FindIndex(iPos);
    return pos ? static_cast<CFolderTab*>(m_lsTabs.GetAt(pos)) : NULL;
 }
 
 void CFolderTabCtrl::RecomputeLayout()
 {
    CClientDC dc(this);
    CFont* pOldFont = dc.SelectObject(&m_fontNormal);
    int x = 0;
    int n = GetItemCount();
    CFolderTab* pTab;
    for (int i=0; i<n; i++) {
       pTab = GetTab(i);
       if (pTab) 
          x += pTab->ComputeRgn(dc, x) - CXOFFSET;
    }
    dc.SelectObject(pOldFont);
 
    if (pTab) {
       CRect rc = pTab->GetRect();
       m_nDesiredWidth = rc.right;
    }
 }

Figure 4   FLDRTAB.cpp


 ////////////////////////////////////////////////////////////////
 // FLDRTAB 1996 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 
 #include "stdafx.h"
 #include "ftab.h"
 #include "StatLink.h"
 #include "tracewin.h"
 #include "resource.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 class CApp : public CWinApp {
 public:
    virtual BOOL InitInstance();
 } theApp;
 
 // dialog for main app window
 class CMyDialog : public CDialog {
 public:
    CMyDialog(CWnd* pParent = NULL);
 protected:
    CFolderTabCtrl m_wndFolderTab;       // the folder tab control
    CStatic        m_wndStaticInfo;      // report selection here
 
    // these two for web links
    CStaticLink    m_wndLink1;
    CStaticLink    m_wndLink2;
 
    virtual BOOL   OnInitDialog();
    DECLARE_MESSAGE_MAP()
    afx_msg void OnChangedTab(NMFOLDERTAB* nmtab, LRESULT* pRes);
 };
 
 BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_NOTIFY(FTN_TABCHANGED, IDC_FOLDERTAB, OnChangedTab)
 END_MESSAGE_MAP()
 
 CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/) 
    : CDialog(IDD_DIALOG1, pParent)
 {
 }
 
 BOOL CMyDialog::OnInitDialog()
 {
    if (!CDialog::OnInitDialog())
       return FALSE;
 
    // hook info control
    m_wndStaticInfo.SubclassDlgItem(IDC_STATICINFO, this);
 
    // Create folder tab and load it. Control is created in same position
    // as static control with IDC_FOLDERTAB, which will be deleted.
    // This is just a trick so I can design the dialog with the resource editor.
    //
    m_wndFolderTab.CreateFromStatic(IDC_FOLDERTAB, this);
    m_wndFolderTab.Load(IDR_FOLDERTABS); // Load strings
 
    // initialize static web links
    m_wndLink1.SubclassDlgItem(IDC_STATICMSJ, this,
       _T("http://www.microsoft.com/msj"));
    m_wndLink2.SubclassDlgItem(IDC_STATICPD, this,
       _T("http://pobox.com/~askpd"));
 
    return TRUE;
 }
 
 void CMyDialog::OnChangedTab(NMFOLDERTAB* nmtab, LRESULT* pRes)
 {
    CString s;
    s.Format(_T("Selected item %d: %s"), nmtab->iItem,
       nmtab->pItem->GetText());
    m_wndStaticInfo.SetWindowText(s);   
 }
 
 BOOL CApp::InitInstance()
 {
    PxlTraceInit();      // TraceWin tracing
    CMyDialog dlg;       // create dialog object
    m_pMainWnd = &dlg;   // make MFC happy
    dlg.DoModal();       // run it
    return FALSE;        // bye
 }