新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → VC实现类似Excel文件夹式样的标签控制 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 4334 个阅读者浏览上一篇主题  刷新本主题   平板显示贴子 浏览下一篇主题
     * 贴子主题: VC实现类似Excel文件夹式样的标签控制 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 VC实现类似Excel文件夹式样的标签控制

    众所周知,微软的Excel中的工作簿可以有多个工作表单(worksheet),每个表单可以通过左下角的标签控制灵活切换,Visual C++也有类似的控制,如在"Output"窗口中设置有:Build,Debug,Find in Files和Results等标签控制。本例中我们将这种界面称为文件夹式样的标签控制,以下简称标签控制,而将MFC中的Tab Control称为标签控件。那么标签控制是如何实现的呢?MFC中有没有现成的控件可以利用?看了本文以后,读者朋友对这个问题应该能找到一个圆满的答案。

      MFC固然给编程带来了极大的方便,但是它并不能代替[URL=http://dev.yesky.com/]程序[/URL]员的编程,MFC只是提供了一个编程框架,应用的实质性代码还是必须由程序员自己来写。同时,MFC的问题也是显而易见的,那就是其GUI素材太丰富,以至于程序员们过分依赖MFC,当想要实现MFC中没有的GUI特性时便不知所措。对于如何实现文件夹式样的标签控制界面,有人可能想到了从现成的标签控件TabControl入手,但是经验证明:如果自己创建一个窗口类,能够让你完全控制代码的修改,不必顾及因现有控件版本的变化而对自己的代码造成的巨大影响和麻烦,微软的开发人员肯定也是这么做,如果用Spy++查看一下Excel和Visual C++的界面,你就会发现其文件夹式样的标签控制并不是TabControl,而是另外创建的窗口类。本实例通过定义一个CFolderTabCtrl实现了我们所要的界面功能。程序运行后的界面效果如图一所示:

    按此在新窗口浏览图片
    图一、类似EXCEL的文件夹式样的标签

      一、实现方法

      有关CFolderTabCtrl的实现细节请参考源代码。其头文件为Ftab.h,实现文件为Ftab.cpp。在分析CFolderTabCtrl的实现原理之前,让我先来说明一下这个类的使用方法。当FldrTab程序的InitInstance()函数获得控制权时,它创建一个主对话框的实例CMyDialog dlg;,并运行这个对话框。CMyDialog有两个控制:一个是m_wndStaticInfo,另一个是m_wndFolderTab,顾名思义,第一个控制为一个静态文本窗口,它显示选中的标签,第二个是标签控制本身,即CFolderTabCtrl实例。通过在CMyDialog::OnInitDialog()函数中调用SubclassDlgItem()函数,对话框以常规方式子类化静态文本,遗憾的是它不能子类化标签控制,因为对话框中并没有实际的标签控制窗口。在OnDialogInit()函数中通过调用一个特殊的函数m_wndFolderTab.CreateFromStatic(IDC_FOLDERTAB, this),在程序运行时将静态文本替换成标签控制。

      CFolderTabCtrl::CreateFromStatic()函数不仅在静态文本控件的位置上创建一个标签控制,它还负责删除静态文本控件。在调用Create()函数之前,CreateFromStatic()调用CFolderTab::GetDesiredHeight()来获得控制的高度,而忽略静态文本控件的高度。在非对话框应用中不能调用CreateFromStatic();而是要直接调用CFolderTab::Create()。创建了标签控制后,接下来必须设置标签名字。这里是在CMyDialog中调用m_wndFolderTab.Load(IDR_FOLDERTABS) 函数来装载字符串资源。其中 IDR_FOLDERTABS是字符串资源的ID,它是一个包含新行指示符("\n")分割的标签名。

      仅仅通过上述处理CfolderTabCtrl类还不能做任何事情,你还必须处理它的通知消息,当用户按下一个标签时,CFolderTabCtrl便用特殊代码FTN_TABCHANGED向对话框发送一个WM_NOTIFY消息。然后对话框处理这条消息,也就是在上面的静态文本控件中显示一条信息:

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

      NMFOLDERTAB结构在FTab.h文件中定义:

    struct NMFOLDERTAB:public NMHDR {
     int iItem; // 项目索引
     const CFolderTab* pItem; // 标签
    };  

      这个结构除了NMHDR所包含的成员之外,还有项目索引和指向当前标签CFolderTab的指针,它与CFolderTabCtrl有所不同,从CFolderTab中你可以得到标签的文本。以上就是CFolderTabCtrl的使用方法。

      下面我们就来揭示这个C++类的实现原理。前面已经对CreateFromStatic()进行了描述,那么CFolderTabCtrl::Load是个什么样的函数呢,这个函数的功能是加载一个串标签名,这个串是用新行指示符("\n")分割的字符串,吸取其中的子串,并调用CFolderTabCtrl::AddItem()将它添加到每一个标签上:

    int CFolderTabCtrl::AddItem(LPCTSTR lpszText)
    {
     m_lsTabs.AddTail(new CFolderTab(lpszText));
     return m_lsTabs.GetCount() - 1;
    }  

      就这么简单,创建一个新的CFolderTab对象并将它添加到一个列表中。与AddItem()相对的是RemoveItem()函数,它们的实现都在Ftab.cpp文件中,这两个函数分别负责动态添加和删除标签页,而不是存取资源串。然后是GetItem()和GetItemCount()函数,一看它们的名字你就应该明白它们的作用,前者用来获取CFolderTab标签的索引号(从0开始),后者则返回m_lsTabs.GetCount,即总共有多少标签。此外,还需要有个函数来获取和设置标签文本,没问题,每一个CFolderTab对象都有一个m_sText成员变量来存储标签名,存取方法是GetText()和SetText()。

      接下来要做的事情很重要,首先是绘制标签,CFolderTabCtrl::OnPaint()函数在循环中遍历所有标签,对每一个标签调用CFolderTab::Draw()函数来进行绘制处理。这里有两个技巧:一个是必须在最后绘制当前选中的标签(m_iCurItem),以便它看起来重叠在最上面。另一个是要绘制其它标签,必须让每个标签知道自己的位置--也就是定义标签的梯形坐标。这是此标签控制的重点所在。下面就来看看实际代码是怎么做的。

      CFolderTabCtrl有一个RecomputeLayout()函数,它计算所有标签的位置。只要你改变控制的版面,则必须调用它,如添加或删除某个标签以及修改某个标签的名字(它会影响标签大小)等操作。RecomputeLayout()函数的关键代码如下:

    int x = 0;
    for (int i=0; i<GetItemCount(); i++) {
     CFolderTab* pTab = GetTab(i);
     if (pTab) x += pTab->ComputeRgn(dc, x) - CXOFFSET;
    }  

      RecomputeLayout()为每一个标签调用CfolderTab类的成员函数ComputeRgn()。ComputeRgn()计算出标签的梯形大小并返回算出的宽度,RecomputeLayout()将它与当前x轴坐标相加,然后作为下一个标签的起始x轴坐标进行参数传递,最后减去形状修饰因子CXOFFSET,使得它们看起来有重叠的效果。之所以这么做是因为给定的标签只能决定其大小,不能决定其绝对位置,它需要更多的x轴信息。 一旦ComputeRgn()有了x轴坐标,它就可以计算出一个足够大的梯形来容纳标签文本,注意要加一些边空,使文本的显示不会产生混乱。用DT_CALCRECT 调用CDC::DrawText()来计算文本所占的矩形,然后用结果计算梯形的大小。私有函数GetTrapezoid()计算与文本矩形相配的梯形。当CFolderTab::ComputeRgn()计算出梯形的坐标,它调用CRgn::CreatePolygonRgn函数强行创建一个多边形区域。

    int CFolderTab::ComputeRgn(CDC& dc, int x)
    {
     CRect& rc = m_rect;
     dc.DrawText(m_sText, &rc, DT_CALCRECT);
     // tweak rc to add margins
     ……
     CPoint pts[4];
     GetTrapezoid(rc, pts);
     m_rgn.CreatePolygonRgn(pts, 4, WINDING);
     return rc.Width();
    }  

      当标签的区域确定后,CFolderTab::Draw()函数对选中标签(选中和未选中状态)要显示的颜色和字体进行处理。因为标签将梯形数据存储在CRgn对象中,因此只要调用CDC::FillRgn即可绘制标签。然后用MoveTo() 和LineTo()以适当的颜色绘制线条。最后调用DrawText绘制文本。注意线条有的是黑色,有的是灰色以便呈现3D效果,选中标签为白色(COLOR_WINDOW)并且顶边没有黑色边线。这样做便于与上面的视窗融于一体。为了简单起见,例子程序没有创建这样的视窗,但是在实际应用中,一般都会象Excel和Visual Studio那样有一个甚至多个视窗与每个选中的标签对应,这在实例二中会慢慢扩充。

      例子程序里选中标签的另外一个特点是使用了小字体,这是从Visual Studio借鉴过来。说到字体,到底应该使用哪一种呢?这里CFolderTabCtrl默认为Arial,你完全可以改用其它的字体,为此CFolderTabCtrl类中提供了一个改变字体的成员函数 CFolderTabCtrl::SetFonts()。

      以上讨论的都是标签的绘制问题,下面来看看事件及行为。有关标签区域计算的重点和难点问题都已经解决,剩下的问题就简单多了。当CFolderTabCtrl获悉OnLButtonDown事件,它调用函数HitTest()找出鼠标位于哪一个标签上,HitTest()函数在每一个CFolderTab对象上循环,调用CFolderTab的同名函数CFolderTab::HitTest,这个函数再用已经计算好的的梯形坐标调用CRgn::PtInRegion()。CRgn必须在这里做好自己的工作。如果HitTest()函数返回TRUE,则CFolderTabCtrl::HitTest()函数返回标签的索引,此时OnLButtonDown调用的另一个函数是CFolderTabCtrl::SelectItem(),以此选中标签。SelectItem()没有什么玄机,它将新的值赋给m_iCurItem并使的值失效,选中新标签后重画。完成SelectItem()函数的调用后,OnLButtonDown创建一个NMFOLDERTAB结构并将信息填写到结构,然后向父窗口发送WM_NOTIFY消息。对话框和主应用就是这样把握着所发生的一切。

      二、编程步骤

      1、 启动Visual C++6.0,生成一个基于对话框的应用程序,将该程序命名为"FldrTab;

      2、 在程序的对话框模板中添加两个静态控件,ID分别定义为IDC_STATICINFO 、IDC_FOLDERTAB;

      3、 在程序中定义CFolderTabCtrl、CfolderTab类和结构struct NMFOLDERTAB;

      4、 在程序中定义消息FTN_TABCHANGED;

      5、 添加代码,编译运行程序。
    三、程序代码

    //////////////////////////////////////////////////////
    #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);
    }

      四、小结

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


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/9/15 16:09:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/27 23:31:37

    本主题贴数1,分页: [1]

     *树形目录 (最近20个回帖) 顶端 
    主题:  VC实现类似Excel文件夹式样的标签控制(21030字) - 一分之千,2007年9月15日

    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    70.313ms