科技时代新浪首页 > 科技时代 > 学园 > 正文

VC实现类似Excel文件夹式样的标签控制(2)


http://www.sina.com.cn 2006年01月26日 09:48 天极yesky

  三、程序代码

//////////////////////////////////////////////////////
#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

////////////////////////////////////////////////////////////////////////
#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 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();
 dc.DrawText(m_sText, &rc, DT_CALCRECT); // calculate desired text rectangle
 rc.right += 2*CXOFFSET + 2*CXMARGIN; // add margins
 rc.bottom = rc.top + GetSystemMetrics(SM_CYHSCROLL); // ht = scrollbar ht
 rc += CPoint(x,0); // shift right
 CPoint pts[4]; // create trapezoid region
 GetTrapezoid(rc, pts);
 m_rgn.CreatePolygonRgn(pts, 4, WINDING);
 return rc.Width();
}

//////////////////// Given the boundint rect, compute trapezoid region.
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));
 CPoint pts[4]; // Fill trapezoid
 CRect rc = m_rect;
 GetTrapezoid(rc, pts);
 CPen* pOldPen = dc.SelectObject(&blackPen);
 dc.FillRgn(&m_rgn, &brush);
 // Draw edges. This is requires two corrections:
 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);
 rc.DeflateRect(CXOFFSET + CXMARGIN, CYMARGIN); // draw text
 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;
 LOGFONT lf; // initialize fonts
 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;
 int n = GetItemCount();// draw all the normal (non-selected) tabs
 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;
 }
 if (pCurTab) // draw selected tab last so it will be "on top" of the others
  pCurTab->Draw(dc, m_fontSelected, TRUE);
  CRect rcCurTab(0,0,0,0); // draw border: line along the top edge, excluding seleted tab
  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;
 }
}

////////////////////////////////////////////////////////////////
BOOL CFldrTabDlg::OnInitDialog()
{
 CDialog::OnInitDialog();
 m_wndStaticInfo.SubclassDlgItem(IDC_STATICINFO, this); // hook info control
 m_wndFolderTab.CreateFromStatic(IDC_FOLDERTAB, this);
 m_wndFolderTab.Load(IDR_FOLDERTABS); // Load strings
 SetIcon(m_hIcon, TRUE); // Set big icon
 SetIcon(m_hIcon, FALSE); // Set small icon
 return TRUE; // return TRUE unless you set the focus to a control
}

void CFldrTabDlg::OnChangedTab(NMFOLDERTAB* nmtab, LRESULT* pRes)
{
 CString s;
 s.Format(_T("选中 %d: %s"), nmtab->iItem,
 nmtab->pItem->GetText());
 m_wndStaticInfo.SetWindowText(s);
}

  四、小结

  上述的例子程序虽然粗糙,但所要的功能基本都实现了。如果实现键盘切换标签(这一点可以在主应用中以加速键的方式实现),以及标签的禁用--即防止用选中某个标签,读者朋友们可以在此基础上自行解决。

 

[上一页] [1] [2]

发表评论

爱问(iAsk.com) 相关网页共约3,770,000篇。



评论】【论坛】【收藏此页】【 】【多种方式看新闻】【下载点点通】【打印】【关闭




科技时代意见反馈留言板 电话:010-82628888-5595   欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

Copyright © 1996 - 2006 SINA Corporation, All Rights Reserved

新浪公司 版权所有