在VC中使用TabCtrl无疑是一件相当令人头疼的事情,而偏偏网上的相关资料又比较稀少,一些现成解决方案也多少存在一些问题,于是参考一些现成的TabCtrl类经过糅合修改成以下的一个类:CTabSheet
该类以vckbase的一篇文章《 在对话框中加入属性页》中提到的“方案五”的CTabSheet类为模板,并参考 CodeProject的CXTabCtrl、XPTabCtrl以及网上的一些其它资料修改而成。
该类具有以下特点:每个标签页都使用一个对话框以设计该页界面,可以随意设置禁用某页,可以设置隐藏TAB控件(用各页的对话框遮盖),可以随意设置选项卡的位置(顶部、底部、左边、右边),可以自由的添加或删除某页,可以为某个选项卡添加图标。
使用方法:
添加对话框资源,并且各个子对话框资源的属性应设置为:Style为Child,Border为None。为这些对话框建立类(直接从CDialog继承)如CPage1、CPage2……
在主对话框的类中添加成员变量:CPage1 m_Page1;、CPage2 m_Page2;……
在主对话框资源中,加入一个Tab Control,并且适当调整位置和大小。利用ClassWizard来为这个Tab Control创建一个CTabSheet的控件变量m_TabSheet。
在主对话框的OnInitDialog()加入:
m_TabSheet.AddPage("tab1", &m_page1, IDD_DIALOG1);
m_TabSheet.AddPage("tab2", &m_page2, IDD_DIALOG2);
……
如果要给标签加上图标,在AddPage之前设置好ImageList:
//为TabCtrl控件添加图标
m_imageList.Create(16, 16, ILC_COLOR32, 1, 1);
CBitmap bitmap1,bitmap2;
bitmap1.LoadBitmap(IDB_BITMAP1);
bitmap2.LoadBitmap(IDB_BITMAP2);
m_imageList.Add(&bitmap1, RGB(0,0,0));
m_imageList.Add(&bitmap2, RGB(0,0,0));
m_TabSheet.SetImageList(&m_imageList);
//给TabCtrl添加页
m_TabSheet.AddPage(_T("Page1"), &m_Page1, m_Page1.IDD, 0);
m_TabSheet.AddPage(_T("第二页"), &m_Page2, m_Page2.IDD, 1);
很不可思议的是,我在测试中,如果在工程中没有把TabCtrl的标签设置成左边或右边的话,那么在运行时修改标签的位置为左边或右边时会出现问题,但是只要曾经设置过TabCtrl的标签为左边或右边后,以后运行不管工程中的TabCtrl的标签是怎样设置的,在运行时都可以正确的修改其标签的位置。
示例工程文件见我的网络硬盘:http://wooddoor.ys168.com/
以下是该CTabSheet类的源码:
//------------------------------TabSheet.h---------------------------------------///
//------------------------------TabSheet.cpp---------------------------------------///
该类以vckbase的一篇文章《 在对话框中加入属性页》中提到的“方案五”的CTabSheet类为模板,并参考 CodeProject的CXTabCtrl、XPTabCtrl以及网上的一些其它资料修改而成。
该类具有以下特点:每个标签页都使用一个对话框以设计该页界面,可以随意设置禁用某页,可以设置隐藏TAB控件(用各页的对话框遮盖),可以随意设置选项卡的位置(顶部、底部、左边、右边),可以自由的添加或删除某页,可以为某个选项卡添加图标。
使用方法:
添加对话框资源,并且各个子对话框资源的属性应设置为:Style为Child,Border为None。为这些对话框建立类(直接从CDialog继承)如CPage1、CPage2……
在主对话框的类中添加成员变量:CPage1 m_Page1;、CPage2 m_Page2;……
在主对话框资源中,加入一个Tab Control,并且适当调整位置和大小。利用ClassWizard来为这个Tab Control创建一个CTabSheet的控件变量m_TabSheet。
在主对话框的OnInitDialog()加入:
m_TabSheet.AddPage("tab1", &m_page1, IDD_DIALOG1);
m_TabSheet.AddPage("tab2", &m_page2, IDD_DIALOG2);
……
如果要给标签加上图标,在AddPage之前设置好ImageList:
//为TabCtrl控件添加图标
m_imageList.Create(16, 16, ILC_COLOR32, 1, 1);
CBitmap bitmap1,bitmap2;
bitmap1.LoadBitmap(IDB_BITMAP1);
bitmap2.LoadBitmap(IDB_BITMAP2);
m_imageList.Add(&bitmap1, RGB(0,0,0));
m_imageList.Add(&bitmap2, RGB(0,0,0));
m_TabSheet.SetImageList(&m_imageList);
//给TabCtrl添加页
m_TabSheet.AddPage(_T("Page1"), &m_Page1, m_Page1.IDD, 0);
m_TabSheet.AddPage(_T("第二页"), &m_Page2, m_Page2.IDD, 1);
很不可思议的是,我在测试中,如果在工程中没有把TabCtrl的标签设置成左边或右边的话,那么在运行时修改标签的位置为左边或右边时会出现问题,但是只要曾经设置过TabCtrl的标签为左边或右边后,以后运行不管工程中的TabCtrl的标签是怎样设置的,在运行时都可以正确的修改其标签的位置。
示例工程文件见我的网络硬盘:http://wooddoor.ys168.com/
以下是该CTabSheet类的源码:
//------------------------------TabSheet.h---------------------------------------///
- #if !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_)
- #define AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_
- #if _MSC_VER > 1000
- #pragma once
- #endif // _MSC_VER > 1000
- // TabSheet.h : header file
- //
- /
- // CTabSheet window
- class CTabSheet : public CTabCtrl
- {
- // Construction
- public:
- CTabSheet();
- virtual ~CTabSheet();
- // Attributes
- public:
- // Operations
- public:
- // Overrides
- // ClassWizard generated virtual function overrides
- //{{AFX_VIRTUAL(CTabSheet)
- protected:
- virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
- // override to draw text only; eg, colored text or different font
- virtual void OnDrawText(CDC& dc, CRect rc, CString sText, BOOL bDisabled, UINT format);
- virtual void PreSubclassWindow();
- //}}AFX_VIRTUAL
- // Implementation
- public:
- int GetCurSel();
- int SetCurSel(int nItem);
- //void Show();
- BOOL AddPage(LPCTSTR title, CDialog *pDialog, UINT ID, int nImage=-1);
- void EnableTab(int iIndex, BOOL bEnable = TRUE);//Index从0开始计数
- void EnableAllTabs(BOOL bEnable = TRUE);
- void DeleteAllTabs();
- void DeleteTab(int iIndex);
- virtual BOOL IsTabEnabled(int iIndex);
- BOOL HideTab(BOOL bHide = FALSE);
- BOOL IsTabHided();
- enum ITEMPOS{TOP,BOTTOM,LEFT,RIGHT};//设置选项卡的位置:顶、底、左、右
- void SetItemPos(ITEMPOS nItemPos);//设置选项卡的位置:顶、底、左、右
- protected:
- //void SetRect();
- void SetRect(int iIndex);
- // Generated message map functions
- protected:
- CArray<BOOL, BOOL> m_arrayStatusTab; //** enabled Y/N
- BOOL m_bHideTab;
- CStringArray m_Title;
- CUIntArray m_IDD;
- typedef CDialog* PDIALOG;
- CArray<PDIALOG, PDIALOG> m_pPages;
- int m_nCurrentPage;
- //{{AFX_MSG(CTabSheet)
- afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
- //}}AFX_MSG
- DECLARE_MESSAGE_MAP()
- };
- /
- //{{AFX_INSERT_LOCATION}}
- // Microsoft Visual C++ will insert additional declarations immediately before the previous line.
- #endif // !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_)
//------------------------------TabSheet.cpp---------------------------------------///
- // TabSheet.cpp : implementation file
- //
- #include "stdafx.h"
- #include "TabSheet.h"
- #ifdef _DEBUG
- #define new DEBUG_NEW
- #undef THIS_FILE
- static char THIS_FILE[] = __FILE__;
- #endif
- /
- // CTabSheet
- CTabSheet::CTabSheet()
- {
- //m_nNumOfPages = 0;
- m_nCurrentPage = 0;
- m_bHideTab=FALSE;
- }
- CTabSheet::~CTabSheet()
- {
- m_arrayStatusTab.RemoveAll();
- m_pPages.RemoveAll();
- m_IDD.RemoveAll();
- m_Title.RemoveAll();
- }
- BEGIN_MESSAGE_MAP(CTabSheet, CTabCtrl)
- //{{AFX_MSG_MAP(CTabSheet)
- ON_WM_LBUTTONDOWN()
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
- /
- // CTabSheet message handlers
- //将字符串从半角转到全角(bDBC=FALSE)或从全角转到半角(bDBC=TRUE)
- CString strConv(const CString& strIn, BOOL bDBC /* = TRUE*/)
- {
- CString strTmp = strIn;
- LPTSTR szText;
- if(!strTmp.IsEmpty())
- {
- szText = strTmp.GetBuffer(0);
- size_t nLen = _tcslen(szText);
- if(bDBC)
- {
- for(size_t i=0;i<nLen;i++)
- {
- if(12288==szText[i])
- szText[i] = 32;
- else
- {
- if(szText[i]>65280 && szText[i]<65375)
- szText[i] -= 65248;
- }
- }
- }
- else
- {
- for(size_t i=0;i<nLen;i++)
- {
- if(32==szText[i])
- szText[i] = 12288;
- else
- {
- if(szText[i]<127)
- szText[i] += 65248;
- }
- }
- }
- strTmp.ReleaseBuffer();
- }
- return strTmp;
- }
- BOOL CTabSheet::AddPage(LPCTSTR title, CDialog *pDialog, UINT ID, int nImage)
- {
- int iIndex = static_cast<int>( m_pPages.Add(pDialog) );
- m_IDD.Add(ID);
- //m_Title.Add(title);
- m_Title.Add(strConv(title,FALSE));//将标签的标题字符串全部转化成全角字符,以便标签在左或右时标题能够竖直排列
- m_pPages[iIndex]->Create( m_IDD[iIndex], this );
- InsertItem( iIndex, m_Title[iIndex], nImage );
- SetRect(iIndex);
- m_pPages[iIndex]->ShowWindow(iIndex ? SW_HIDE : SW_SHOW);
- //** the initial status is enabled
- m_arrayStatusTab.Add(TRUE);
- return TRUE;
- }
- void CTabSheet::SetRect(int iIndex)
- {
- ASSERT(iIndex < m_pPages.GetCount());
- CRect tabRect, itemRect;
- int nX, nY, nXc, nYc;//左、顶、宽、高
- GetClientRect(&tabRect);//获取整个TAB控件的位置大小。
- GetItemRect(0, &itemRect);//item指的只是标签页,因此这里是获得标签页的位置大小
- if (m_bHideTab)
- {
- nX=tabRect.left;//使用tabRect.left、tabRect.top即可将各页显示的子对话框遮住TAB控件的标签。
- nY=tabRect.top;
- nXc=tabRect.Width();
- nYc=tabRect.Height();
- }
- else
- {
- DWORD style = GetStyle();
- #define offset 2
- if(style & TCS_VERTICAL)
- {//选项卡在TAB控件的侧边
- nY = tabRect.top + offset;
- nYc = tabRect.bottom -nY - (offset + 1);
- if (style & TCS_BOTTOM)
- {//选项卡在右边
- nX = tabRect.left + offset;
- nXc = itemRect.left - nX - (offset + 1);
- }
- else
- {//选项卡在左边
- nX = itemRect.right + offset;
- nXc = tabRect.right - nX - (offset + 1);
- }
- }
- else
- {//选项卡在TAB控件的顶部或底部
- nX = tabRect.left + offset;
- nXc = tabRect.right - nX - (offset + 1);
- if (style & TCS_BOTTOM)
- {//选项卡在底部
- nY = tabRect.top + offset;
- nYc = itemRect.top - nY - (offset + 1);
- }
- else
- {//选项卡在顶部
- nY = itemRect.bottom + offset;
- nYc = tabRect.bottom - nY - (offset + 1);
- }
- }
- }
- m_pPages[iIndex]->MoveWindow(nX, nY, nXc, nYc);
- }
- void CTabSheet::OnLButtonDown(UINT nFlags, CPoint point)
- {
- CTabCtrl::OnLButtonDown(nFlags, point);
- int selectedPage = GetCurFocus();
- if (!m_arrayStatusTab[selectedPage])
- {
- CTabCtrl::SetCurSel(m_nCurrentPage);
- return;
- }
- if(m_nCurrentPage != selectedPage)
- {
- m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);
- m_nCurrentPage=selectedPage;
- m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);
- }
- }
- int CTabSheet::SetCurSel(int nItem)
- {
- if( nItem < 0 || nItem >= m_pPages.GetCount())
- return -1;
- int ret = m_nCurrentPage;
- if(m_nCurrentPage != nItem )
- {
- m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);
- m_nCurrentPage = nItem;
- m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);
- CTabCtrl::SetCurSel(nItem);
- }
- return ret;
- }
- int CTabSheet::GetCurSel()
- {
- return CTabCtrl::GetCurSel();
- }
- void CTabSheet::EnableTab(int iIndex, BOOL bEnable)
- {
- ASSERT(iIndex < m_arrayStatusTab.GetSize());
- //** if it should change the status ----
- if (m_arrayStatusTab[iIndex] != bEnable)
- {
- m_arrayStatusTab[iIndex] = bEnable;
- m_pPages[iIndex]->EnableWindow(bEnable);
- //** redraw the item -------
- CRect rect;
- GetItemRect(iIndex, &rect);
- InvalidateRect(rect);
- }
- }
- void CTabSheet::EnableAllTabs(BOOL bEnable)
- {
- for (int i=0; i<m_arrayStatusTab.GetCount(); i++)//CArray数组类的GetCount()与GetSize()是相同的,都是返回数组的元素个数。
- {
- EnableTab(i, bEnable);
- }
- }
- void CTabSheet::DeleteAllTabs()
- {
- m_arrayStatusTab.RemoveAll();
- DeleteAllItems();
- m_pPages.RemoveAll();
- m_IDD.RemoveAll();
- m_Title.RemoveAll();
- }
- void CTabSheet::DeleteTab(int iIndex)
- {
- ASSERT(iIndex < m_pPages.GetCount());
- m_arrayStatusTab.RemoveAt(iIndex);
- DeleteItem(iIndex);
- m_pPages.RemoveAt(iIndex);
- m_IDD.RemoveAt(iIndex);
- m_Title.RemoveAt(iIndex);
- }
- BOOL CTabSheet::IsTabEnabled(int iIndex)
- {
- ASSERT(iIndex < m_pPages.GetCount());
- return m_arrayStatusTab[iIndex];
- }
- //
- // Draw the tab: mimic SysTabControl32, except use gray if tab is disabled
- //
- void CTabSheet::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
- {
- DRAWITEMSTRUCT& ds = *lpDrawItemStruct;
- int iItem = ds.itemID;
- // Get tab item info
- TCHAR text[128];
- TCITEM tci;
- tci.mask = TCIF_TEXT|TCIF_IMAGE;
- tci.pszText = text;
- tci.cchTextMax = sizeof(text);
- GetItem(iItem, &tci);
- // use draw item DC
- CDC dc;
- dc.Attach(ds.hDC);
- DWORD style = GetStyle();//取TAB控件的style,以判断是否设置有TCS_VERTICAL属性
- //** Draw the image
- CRect rect = ds.rcItem;
- rect.top += ::GetSystemMetrics(SM_CYEDGE);
- dc.SetBkMode(TRANSPARENT);
- //dc.FillSolidRect(rect, ::GetSysColor(COLOR_BTNFACE));
- CImageList* pImageList = GetImageList();
- if (pImageList && tci.iImage >= 0)
- {
- //rect.left += dc.GetTextExtent(_T(" ")).cx;
- rect.left += 2;
- rect.top += 1;
- IMAGEINFO info;
- pImageList->GetImageInfo(tci.iImage, &info);
- CRect ImageRect(info.rcImage);
- pImageList->Draw(&dc, tci.iImage, CPoint(rect.left, rect.top), ILD_TRANSPARENT);
- if(style & TCS_VERTICAL)
- {//选项卡在TAB控件的侧边
- rect.top += ImageRect.Height();
- }
- else
- {//选项卡在TAB控件的顶部或底部
- rect.left += ImageRect.Width();
- }
- }
- /*
- typedef struct tagLOGFONT {
- LONG lfHeight; // 高度
- LONG lfWidth; // 宽度
- LONG lfEscapement; // 打印角度,单位是0.1度,900垂直打印,0水平打印
- LONG lfOrientation; // 字体打印角度,1800上下倒置,900左右倒置.
- LONG lfWeight; // 字体粗细,默认是0,还常用400,700
- BYTE lfItalic; // 斜体字,默认0非斜体,1斜体.
- BYTE lfUnderline; // 下划线,默认0无.
- BYTE lfStrikeOut; // 字体被直线穿过,默认0无.
- BYTE lfCharSet; // 字符集,如宋体字,一般设置为DEFAUL_CHARSET.
- BYTE lfOutPrecision; // 符合度,看不明白?一般设置为OUT_DEFAUL_PRECIS
- BYTE lfClipPrecision; // 不懂,一般设置为CLIP_DEAFAUL_PRECIS
- BYTE lfQuality; // 字体图形质量,不管,设为DEFAUL_QUALITY
- BYTE lfPitchAndFamily; // 字间距,不管,设为DEFAUL_PITCH+FF_DONTCARE
- TCHAR lfFaceName[LF_FACESIZE]; // 所有字体式样数组,供字体回调函数调用
- } LOGFONT, *PLOGFONT;*/
- //Draw Text,必须设置使用LOGFONT,否则在切换选项卡的位置时选项卡的文字显示将出现问题。但是对于英文字符并不能置为竖排。
- LOGFONT logFont;
- memset(&logFont, 0, sizeof(LOGFONT));
- //logFont.lfEscapement = 0;
- //logFont.lfOrientation = 900;//如果注释掉此行,好像也没发现有何不同
- //logFont.lfHeight = 16;//如果注释掉此行,好像也没发现有何不同
- CFont newFont;
- newFont.CreateFontIndirect(&logFont);
- CFont *pOldFont = dc.SelectObject(&newFont);
- if(style & TCS_VERTICAL)
- {//选项卡在TAB控件的侧边
- OnDrawText(dc, rect, text, !IsTabEnabled(iItem), DT_WORDBREAK|DT_CENTER|DT_VCENTER);
- }
- else
- {//选项卡在TAB控件的顶部或底部
- OnDrawText(dc, rect, text, !IsTabEnabled(iItem), DT_SINGLELINE|DT_CENTER|DT_VCENTER);
- }
- dc.SelectObject(pOldFont);
- dc.Detach();
- }
- void CTabSheet::PreSubclassWindow()
- {
- // TODO: Add your specialized code here and/or call the base class
- CTabCtrl::PreSubclassWindow();
- ModifyStyle(0, TCS_OWNERDRAWFIXED);
- }
- //
- // Draw tab text. You can override to use different color/font.
- //
- void CTabSheet::OnDrawText(CDC& dc, CRect rc, CString sText, BOOL bDisabled, UINT format)
- {
- dc.SetTextColor(GetSysColor(bDisabled ? COLOR_3DHILIGHT : COLOR_BTNTEXT));
- dc.DrawText(sText, &rc, format);
- if (bDisabled)
- {// disabled: draw again shifted northwest for shadow effect
- rc += CPoint(-1,-1);
- dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT));
- dc.DrawText(sText, &rc, format);
- }
- }
- BOOL CTabSheet::HideTab(BOOL bHide)
- {
- m_bHideTab = bHide;
- for (int iIndex=0; iIndex<m_pPages.GetCount(); iIndex++)
- {
- SetRect(iIndex);
- }
- return m_bHideTab;
- }
- BOOL CTabSheet::IsTabHided()
- {
- return m_bHideTab;
- }
- void CTabSheet::SetItemPos(ITEMPOS nItemPos)
- {
- switch (nItemPos)
- {
- case TOP://Top
- ModifyStyle(TCS_VERTICAL|TCS_BOTTOM, 0);
- break;
- case BOTTOM://Bottom
- ModifyStyle(TCS_VERTICAL, TCS_BOTTOM);
- break;
- case LEFT://Left
- ModifyStyle(TCS_BOTTOM, TCS_VERTICAL);
- break;
- case RIGHT://Right
- ModifyStyle(0, TCS_BOTTOM|TCS_VERTICAL);
- break;
- default:
- break;
- };
- for (int iIndex=0; iIndex<m_pPages.GetCount(); iIndex++)
- {
- SetRect(iIndex);
- }
- }