传统地,Tab控件在切换Tab页时,需要切换页相对应的子窗体的隐藏与显示。单独对子窗体控制,略显繁杂。
本文采取如下思路: 将诸子窗体放到某个承载窗体(比如CWnd类型或者CDialog类型对象)上,在Tab切换时,操控承载窗体的显隐即可达到目的。
又传统地,程序需要维护一个对象数组,每个对象用于维护Tab和承载窗体或子窗体之间的关系;而本文采取如下方法:利用TCITEM结构体的lParam字段存储对应承载窗体的句柄。这样即可构建对应关系。
本例所需类:
1、CMainFrame
2、CChildView : public CWnd
3、CTabCtrl,此处亦作TabCtrl,因大部分属性对于Win32对象亦适用。
4、CDialog 作为承载窗体若干,用CDialog的好处在于,可以直接利用resource template,便于在工程rc中设计控件位置等。
步骤:
一、以VC的Application Wizard创建一个MFC exe-〉SDI-〉no Document/View,那么即具备以上1、2两对象,、
二、在2中定义一成员变量,类型为3、
三、在资源rc中画好对话框,并制定相应resource ID
四、重载2 (CChildView)的LRESULT CChildView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam),处理消息
WM_CREATE,创建TabCtrl、承载窗体,后者在此挂上资源;
WM_NOTIFY,遍历所有与TabCtrl相关的承载窗体,显示当前选中Tab对应的个体,其他个体隐藏;
WM_SIZE,调整TabCtrl的大小以适应CChildView,调整承载窗体的大小适应TabCtrl的Display Area
{
CRect rt;
TCITEM item;
int j,i;
TCITEM ItemTemp;
// TODO: Add your specialized code here and/or call the base class
switch(message)
{
case WM_CREATE:
if(!m_Tab.Create (/*TCS_OWNERDRAWFIXED|*/TCS_TABS|WS_CHILD|WS_VISIBLE, CRect(0,0,0,0),this,IDC_TABCTRL))
{
MessageBox(_T("Tab creation failed!"),_T("Error"));
}
//FormView1.Create (IDD_FORMVIEW1,this);
//FormView0.Create (IDD_FORMVIEW,this);
FormView1.Create (IDD_FORMVIEW1,&m_Tab);//MUST like this, if do like above, will occur some problem described in comment1
FormView0.Create (IDD_FORMVIEW,&m_Tab);//一定要这么做,若如上面代码,则会引发评论一所述及的问题
item.mask = TCIF_TEXT|TCIF_PARAM ;
item.pszText=_T("第一个");
item.lParam =(LPARAM)FormView0.m_hWnd ;
m_Tab.InsertItem (0,&item);
item.pszText=_T("第二个");
item.lParam =(LPARAM)FormView1.m_hWnd ;
m_Tab.InsertItem (1,&item);
break;
case WM_NOTIFY:
if(IDC_TABCTRL == (int) wParam)
{
LPNMHDR pnmh=(LPNMHDR) lParam;
if (pnmh->code ==TCN_SELCHANGE )
{
j=m_Tab.GetCurSel ();
//if(ItemTemp.lParam!=NULL)
PointA: if(m_Tab.GetItem(j,&ItemTemp))
if(IsWindow((HWND)ItemTemp.lParam))
{
::ShowWindow((HWND)ItemTemp.lParam,SW_SHOW);
Invalidate();//important!! Avoid some dialog unshowed!
PointB: }
for(i=0;i<m_Tab.GetItemCount ();i++)
{
if(m_Tab.GetCurSel ()==i)
continue;
if(m_Tab.GetItem (i,&ItemTemp))
if(IsWindow((HWND)ItemTemp.lParam))
::ShowWindow((HWND)ItemTemp.lParam,SW_HIDE);
}
PointC:
}
}
break;
case WM_SIZE:
GetClientRect(rt);
rt.DeflateRect (100,100,100,100);
m_Tab.MoveWindow (rt);
m_Tab.GetWindowRect (rt);
ScreenToClient(rt);
m_Tab.AdjustRect (FALSE,rt);
for(i=0;i<m_Tab.GetItemCount ();i++)
{
m_Tab.GetItem (/*m_Tab.GetCurSel ()*/i,&ItemTemp);
if(ItemTemp.lParam!=NULL && ::IsWindow ((HWND)ItemTemp.lParam))
::MoveWindow(((HWND)(ItemTemp.lParam)),rt.left ,rt.top ,rt.Width (),rt.Height(),FALSE);
}
break;
case WM_LBUTTONDBLCLK:
item.mask = TCIF_TEXT;
item.pszText=_T("第三个");
//item.lParam =;
m_Tab.InsertItem (m_Tab.GetItemCount (),&item);
break;
case WM_RBUTTONDBLCLK:
m_Tab.DeleteItem (m_Tab.GetItemCount ()-1);
break;
}
return CWnd ::WindowProc(message, wParam, lParam);
}
那么在模拟单击某个Tab页的动作,代码为:
m_wndView.m_Tab.SetCurSel(1);
NMHDR nh;
nh.hwndFrom=m_wndView.m_Tab.m_hWnd;
nh.idFrom=IDC_TABCTRL;
nh.code=TCN_SELCHANGE;
m_wndView.SendMessage(WM_NOTIFY,IDC_TABCTRL,(LPARAM)&nh);
假设动态地创建了多个Tab,其中一些可能对应某个Dialog派生类,那么如果需要查找某个特定的Dialog就必须仰赖MFC的RTTI和Dialog某甄别字段。如下例展示查找一个Dialog并将其杀掉,将界面切换至第1个(zero based index)Tab。
void CMainFrame::KillDialingPad(CString StrContactName)
{
CWnd* pWnd;
HWND hWnd;
TCITEM ItemTemp;
int i=0;
for (i=1;i<m_wndView.m_Tab.GetItemCount();i++)//start from 1, avoid searching from the CONTACT LIST
{
m_wndView.m_Tab.GetItem(i,&ItemTemp);
hWnd=(HWND)ItemTemp.lParam;
pWnd=CWnd::FromHandle(hWnd);//avoid to bring a new CWnd object;
if (pWnd->IsKindOf(RUNTIME_CLASS(CDialingBoxDlg)))
{
if(((CDialingBoxDlg *)pWnd)->m_StrContactName==StrContactName)
break;
}
}
if(i<m_wndView.m_Tab.GetItemCount())//found
{
pWnd->DestroyWindow();
delete pWnd;
pWnd=NULL;
m_wndView.m_Tab.DeleteItem(i);
}
m_wndView.m_Tab.SetCurSel(1);
NMHDR nh;
nh.hwndFrom=m_wndView.m_Tab.m_hWnd;
nh.idFrom=IDC_TABCTRL;
nh.code=TCN_SELCHANGE;
m_wndView.SendMessage(WM_NOTIFY,IDC_TABCTRL,(LPARAM)&nh);
}