积累的VC编程小技巧之属性页,多标签视图类CTabView的设计实现
分类:微服架构

(1)创建时传入字符串资源ID
m_pPage1 = new  CMyPropertyPage(IDS_MYCAPTION);
(2)修改CPropertyPage.m_psp
m_pPage1->m_psp.dwFlags |= PSP_USETITLE;
m_pPage1->m_psp.pszTitle = _T("My Caption");
注意:这种修改只能在CPropertySheet的Create和DoModal之前进行;
          如果是从文件中读出一个字符串来进行设置,最好把这个字符串放在一个静态字符数组里面,例如
static char strTitle[32];
m_pPage1->m_psp.pszTitle = strTitle;
(3)CTabCtrl::SetItem()
CTabCtrl* pTab = GetTabControl();
TC_ITEM ti;
ti.mask = TCIF_TEXT;
ti.pszText = pszText;
pTab->SetItem (nPage, &ti);
注意:这种方法可以在CPropertySheet的实例被创建之后进行

1.属性页的添加:

创建对话框的类,该类要从CpropertyPage继承;然后在要添加该对话框为属性页的类(头文件)里创建CpropertySheet类的一个对象m_tabsheet和新创建的对话框类的对象m_skatch;最后,在.cpp文件里的OnInitDialog()之类的函数里实现如下代码:

m_tabsheet.Create(this, WS_CHILD | WS_VISIBLE, 0);

   //使选项卡的按钮在下面

   if(m_tabsheet.m_hWnd)

   m_tabsheet.ShowWindow(SW_MAXIMIZE);//显示选项卡

//加入标签,标签名由各个子对话框的标题栏决定

   m_tabsheet.AddPage(&m_skatch);

   //用Create来创建一个属性页

   m_tabsheet.Create(this, WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT);

  

RECT rect;

   m_tabsheet.GetWindowRect(&rect);

   int width = rect.right - rect.left;

   int height = rect.bottom - rect.top;

  

   //调整属性页的大小和位置

   m_tabsheet.SetWindowPos(NULL, 225, 225, width-82, height,SWP_NOACTIVATE);

属性页的添加完成。如果要添加多个属性页,则只需要增加多个对象,如下:m_tabsheet.AddPage(&m_skatch1);

    m_tabsheet.AddPage(&m_skatch2);

. . . . . .

 

当一个基于对话框的程序中有相当多的控件时,你一定会想到使用属性页来将这些控件分类放置。本文针对这种方法来讨论几种可能实现的方案。

在MFC9(在vc2008和vc2010中,已经有了CTabView的现成类)以前的版本中,有CListView,CTreeView,CEditView,CRichEditView等控件视图类,但就是没有类似的CTabView类,因工作需要,最近在做一个简单的多标签IE浏览器,开发环境是vs2005,基本框架是sdi

2.更改属性页标题

void CProSheet::SetPageTitle(int nPage, int nImage, CString strTitle)

{

  TC_ITEM item;

  //item.mask = TCIF_TEXT|TCIF_IMAGE;    //设置图标+文字

  item.mask = TCIF_IMAGE;        //只设置图标

  item.iImage = nImage;

// item.pszText = strTitle.GetBuffer(0);    //设置文字

  GetTabControl ()->SetItem (nPage, &item);

//要设置文字时就将上面2行有注释符的代码前的注释符去掉

}

 

方案一
本方案的例子请见源代码打包文件中的Property1部分

  • chtmlview + ctabview,因此自行封装实现了CTabView,并把它作为一个重要的基础类来维护使用。先来说下我这个CTabView类的特点及功能:
    (1)CTabView本质上就是一种窗口容器,基于CCtrlView实现,能够容纳类型为CWnd的所有窗口,并作为它的子窗口(为描述方便,在这特称为视图页面),每个标签对应一个视图页面。
    (2)扩展了标准的标签项数据结构TC_ITEM,即自定义了一个TC_EXTRA_ITEM扩展数据结构,该结构除包含标准的数据结构(第1个成员)外,还包括视图页面对象、内部标志、视图页面标题、视图页面关联数据4种类型数据,扩展数据大小需要在创建后插入标签前调用SetItemExtra设定。
    (3)支持容纳外部窗口和创建内部窗口,前者是指容纳外面已经创建好的窗口;后者是根据窗口类型信息,由CTabView自己来管理创建视图页面。
    (4)对于内部窗口的管理,为方便区分并有利于消息映射处理,因此窗口ID是唯一的,即在创建时分派一个新的不同的ID,销毁时回收到ID数组中。而外部窗口ID则由外部管理。
    (5)提供激活视图页面后的自定义处理,表现为OnViewActivated虚函数。
    (6)提供右键单击Tab标签后的自定义处理,表现为OnTabContextMenu虚函数。
    (7)提供Tab标签提示信息的自定义处理,表现为UpdateTooltipText虚函数。
    (8)提供CTabView视图类的自绘处理(须设定TCS_OWNERDRAWFIXED样式),表现为DrawItem虚函数。
    (9)Tab标签的文本默认是简写的,即当长度超过最大值时,多余部分用省略号...代替,同时也支持自定义处理,表现为ShortenTitle虚函数。
    (10)对上述特点7,为了能支持派生类自定义处理,由于提示文本消息的发送者是CToolTipCtrl窗口,而不是CTabView本身,所以不能像TCN_SELCHANGE,NM_RCLICK等通知消息可以使用反射宏来实现,因此使用了窗口子类化来捕获父窗口的TTN_GETDISPINFO通知消息实现的。这一行为也可以通过UpdateTooltipText来定制,当其返回值是TRUE时,表示允许消息默认被父窗口接收处理,否则,反之。
    (11)对上述特点8,本可以重载OnChildNotify来实现WM_DRAWITEM消息的回调,但为了简洁,如同TTN_GETDISPINFO通知消息,也是使用窗口子类化来捕获实现的。
    (12)对上述10和11的消息捕获处理,使用了CBasicSubClassWnd类来实现,表现为重写其SubWindowProc虚函数。

3.属性页处理通知消息

CPropertyPageImpl有一个消息映射处理WM_NOTIFY。如果通知代码是PSN_*的值,OnNotify()就会调用相应的通知处理函数。这使用了编译阶段虚函数机制,从而使得派生类可以很容易的重载这些处理函数。

由于WTL 3和WTL 7设计的改变,从而存在两套不同的通知处理机制。在WTL 3中通知处理函数返回的值与PSN_*消息的返回值不同,例如,WTL 3是这样处理PSN_WIZFINISH的:

case PSN_WIZFINISH:
lResult = !pT->OnWizardFinish();
break;

OnWizardFinish()期望返回TRUE结束向导,FALSE阻止关闭向导。这个方法很简陋,但是IE5的通用控件对PSN_WIZFINISH处理的返回值添加了新解释,他返回需要获得焦点的窗口的句柄。WTL 3的程序将不能使用这个特性,因为它对所有非0的返回值都做相同的处理。

在WTL 7中,OnNotify() 没有改变 PSN_* 消息的返回值,处理函数返回任何文档中规定的合法数值和正确的行为。当然,为了向前兼容,WTL 3 仍然使用当前默认的工作方式,要使用WTL 7的消息处理方式,你必须在中including atldlgs.h一行之前添加一行定义:

#define _WTL_NEW_PAGE_NOTIFY_HANDLERS

编写新的代码没有理由不使用WTL 7的消息处理函数,所以这里就不介绍WTL 3的消息处理方式。

CPropertyPageImpl 为所有消息提供了默认的通知消息处理函数,你可以重载与你的程序有关的消息处理函数完成特殊的操作。默认的消息处理函数和相应的行为如下:

int OnSetActive() - 允许页面成为激活状态

BOOL OnKillActive() - 允许页面成为非激活状态

int OnApply() - 返回 PSNRET_NOERROR 表示应用操作成功完成

void OnReset() - 无相应的动作

BOOL OnQueryCancel() - 允许取消操作

int OnWizardBack() - 返回到前一个页面

int OnWizardNext() - 进行到下一个页面

INT_PTR OnWizardFinish() - 允许向导结束

void OnHelp() - 无相应的动作

BOOL OnGetObject(LPNMOBJECTNOTIFY lpObjectNotify) - 无相应的动作

int OnTranslateAccelerator(LPMSG lpMsg) - 返回 PSNRET_NOERROR 表示消息没有被处理

HWND OnQueryInitialFocus(HWND hWndFocus) - 返回 NULL 表示将按Tab Order顺序的第一个控件设为焦点状态

 

在对话框上放置一个Tab Control的控件,再在对话框上放置所需的控件(本例放置了2个按钮,试图在每个标签中显示一个)。然后利用Class Wizard来为Tab Control控件创建一个控件变量,该变量是CTabCtrl类的,再为其他控件也创建相应的控件类。 在主对话框的初始函数中CProperty1Dlg::OnInitDialog()加入如下代码:
//本例插入两个标签,实际运用中可通过循环插入所需个数的标签,运行后默认第一个标签被选中
m_tab.InsertItem( 0, _T("Tab1") );
m_tab.InsertItem( 1, _T("Tab2") );
//将不是第一个标签的控件隐藏掉,只留下你要的控件
m_button2.ShowWindow( SW_HIDE );
再利用ClassWizard处理Tab Control的 TCN_SELCHANGE 的消息。在消息处理函数中,利用CWnd::ShowWindow来使相应的控件显示和隐藏。 
void CProperty1Dlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult) 
{
//GetCurSel返回当前被选中的标签的索引号(以0为基础算起)
int sel = m_tab.GetCurSel();

   接下来看看CTabView类的接口定义,如下所示
  1#ifndef _TABVIEW_H
  2#define _TABVIEW_H
  3
  4#include <afxwin.h>
  5#include "basic_subclasswnd.h"
  6
  7#define WC_TABVIEWA "SysTabControl32"
  8#define WC_TABVIEWW L"SysTabControl32"
  9
 10#if (defined(_UNICODE)||defined(UNICODE))
 11#define WC_TABVIEW WC_TABVIEWW
 12#else
 13#define WC_TABVIEW WC_TABVIEWA
 14#endif
 15
 16class CTabView : public CCtrlView,private CBasicSubClassWnd
 17{
 18    DECLARE_DYNCREATE(CTabView)
 19
 20    struct TAB_VIEW
 21    {
 22        CWnd*  pWnd;     // 视图页面窗口
 23        BOOL   bInner;   // 视图页面是否内部创建
 24        LPTSTR pszTitle; // 视图页面标题
 25        LPVOID pData;    // 视图页面数据
 26    };
 27
 28    //扩展TC_ITEM数据,第一个成员必须为TCITEMHEADER类型
 29    struct TC_EXTRA_ITEM
 30    {
 31        TCITEMHEADER header;
 32        TAB_VIEW  view;
 33        operator LPTCITEM() { return (LPTCITEM)this; }
 34    };
 35
 36public:
 37    CTabView();
 38    virtual ~CTabView();
 39
 40    CTabCtrl& GetTabCtrl() const;
 41
 42    //在最后增加视图页面
 43    CWnd* AddView(CRuntimeClass* pClass,CCreateContext* pContext=NULL,LPCTSTR pszTitle=NULL,
 44                  int iImage=-1,LPVOID pData=NULL,BOOL bActivate=TRUE);
 45    BOOL  AddView(CWnd* pWnd,LPCTSTR pszTitle=NULL,int iImage=-1,LPVOID pData=NULL,BOOL bActivate=TRUE);
 46    //在某处插入视图页面
 47    CWnd* InsertView(int iIndex,CRuntimeClass* pClass,CCreateContext* pContext=NULL,LPCTSTR pszTitle=NULL,
 48                    int iImage=-1,LPVOID pData=NULL,BOOL bActivate=TRUE);
 49    BOOL InsertView(int iIndex,CWnd* pWnd,LPCTSTR pszTitle=NULL,int iImage=-1,
 50                    LPVOID pData=NULL,BOOL bActivate=TRUE);
 51
 52    //移除视图页面
 53    void RemoveView(int iIndex,BOOL bDestroy=TRUE);
 54    void RemoveView(CWnd* pWnd,BOOL bDestroy=TRUE);
 55    //移除所有视图页面
 56    void RemoveAllView();
 57
 58    //获取活动视图页面
 59    CWnd* GetActiveView() const;
 60    int GetActiveViewIndex() const;
 61    //设置活动视图页面
 62    void SetActiveViewIndex(int iIndex);
 63    void SetActiveView(const CWnd* pWnd);
 64
 65    //按索引获取某个视图页面
 66    CWnd* GetView(int iIndex) const;
 67    //按窗口获取视图页面的索引
 68    int GetIndex(const CWnd* pWnd) const;
 69
 70    //获取视图页面数量
 71    int GetViewCount() const;
 72   
 73    //设置视图页面的标题
 74    BOOL SetViewTitle(int iIndex,LPCTSTR pszTitle);
 75    BOOL SetViewTitle(const CWnd* pWnd,LPCTSTR pszTitle);
 76    //获取视图页面的标题
 77    BOOL GetViewTitle(const CWnd* pWnd,LPTSTR& pszTitle) const;
 78    BOOL GetViewTitle(int iIndex,LPTSTR& pszTitle) const;
 79
 80    //设置视图页面的图像
 81    BOOL SetViewImage(const CWnd* pWnd,int iImage);
 82    BOOL SetViewImage(int iIndex,int iImage);
 83    //获取视图页面的图像
 84    BOOL GetViewImage(const CWnd* pWnd,int& iImage) const;
 85    BOOL GetViewImage(int iIndex,int& iImage) const;
 86
 87    //设置视图页面的数据
 88    BOOL SetViewData(int iIndex,LPVOID pData);
 89    BOOL SetViewData(const CWnd* pWnd,LPVOID pData);
 90    //获取视图页面的数据
 91    BOOL GetViewData(int iIndex,LPVOID& pData) const;
 92    BOOL GetViewData(const CWnd* pWnd,LPVOID& pData) const;
 93
 94    //是否为内部创建的视图页面
 95    BOOL IsInnerView(const CWnd* pWnd,BOOL& bInner) const;
 96    BOOL IsInnerView(int iIndex,BOOL& bInner) const;
 97
 98    //设置tab标签可显示的最大文本长度
 99    void SetTabMaxTextLen(size_t len);
100    //获取tab标签可显示的最大文本长度
101    size_t GetTabMaxTextLen() const;
102
103    virtual void ShortenTitle(LPCTSTR pszTitle,CString& strShortTitle);
104
105protected:
106    BOOL SetViewInner(const CWnd* pWnd,BOOL bInner);
107    BOOL SetViewInner(int iIndex,BOOL bInner);
108    DWORD GetUniqueId();
109    BOOL IsValidViewIndex(int iIndex) const;
110    void UpdateLayout();
111   
112protected:
113    virtual BOOL SubWindowProc(UINT uMsg,WPARAM wParam,LPARAM lParam);
114    virtual int  CalcTabHeight();
115    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItem);
116    virtual void OnViewActivated(int iIndex);
117    virtual void OnTabContextMenu(int iIndex,CPoint point);
118    virtual BOOL UpdateTooltipText(LPNMTTDISPINFO pTTDI);
119
120protected:
121    DECLARE_MESSAGE_MAP()
122    afx_msg int OnCreate(LPCREATESTRUCT lpcs);
123    afx_msg void OnDestroy();
124    afx_msg void OnNMRclick(NMHDR *pNMHDR, LRESULT *pResult);
125    afx_msg void OnTcnSelChange(NMHDR* pNMHDR,LRESULT *pResult);
126    afx_msg void OnSize(UINT nType, int cx, int cy);
127    afx_msg void OnSetFocus(CWnd* pOldWnd);
128    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
129
130public:
131    virtual void OnInitialUpdate();
132
133private:
134    CArray<UINT,UINT> m_UniqueIDs;
135    int m_iActiveView;
136    int m_cyTabHeight;
137    size_t m_nMaxTabTextLen;   
138};
139
140#endif
   从上可知,CTabView主要包括2大类操作方法,一是针对视图页面窗口,二是针对Tab标签扩展数据。说明如下:1)增加视图页面:AddIView,有2个重载形式,其中带CRuntimeClass和CCreateConext类型参数的用于创建内部窗口,成功返回对应的窗口,否则为NULL;带CWnd参数的用于容纳外部窗口,成功返回TRUE,否则返回FALSE。2)插入视图页面:InsertView,有2个重载形式,其中带CRuntimeClass和CCreateConext类型参数的用于创建内部窗口,成功返回对应的窗口,否则为NULL;带CWnd参数的用于容纳外部窗口,成功返回TRUE,否则返回FALSE。3)移除某个视图页面:RemoveView,有2个重载形式,其中int类型参数的用于按索引移除,CWnd类型的用于按窗口对象移除。4)移除所有视图页面:RemoveAllView。5)获取活动视图页面:有2个方法,GetActiveView如果无活动视图页面,则返回NULL,否则返回对应的窗口;GetActiveViewIndex如果无活动视图页面,则返回-1,否则返回>=0的索引值。5)设置活动视图页面:有2个方法:SetActiveView按窗口来设置,SetActiveViewIndex按索引来设置。6)视图页面窗口与索引的相互转换:GetView由窗口得到其索引,操作失败返回-1,否则返回>=0的索引值。GetIndex由索引得到窗口,失败返回NULL,否则返回对应的窗口。7)获取视图页面的数量:GetViewCount。8)设置视图页面的标题:SetViewTitle,有2个重载形式,int类型参数的按索引设置,CWnd类型参数的按窗口设置,成功返回TRUE,否则返回FALSE。9)获取视图页面的标题:GetViewTitle,有2个重载形式,int类型参数的按索引获取,CWnd类型参数的按窗口获取,成功返回TRUE,否则返回FALSE。10)设置视图页面的图像:有2个重载形式,int类型参数的按索引设置,CWnd类型参数的按窗口设置,成功返回TRUE,否则返回FALSE。11)获取视图页面的图像:有2个重载形式,int类型参数的按索引获取,CWnd类型参数的按窗口获取,成功返回TRUE,否则返回FALSE。12)设置视图页面的数据:有2个重载形式,int类型参数的按索引设置,CWnd类型参数的按窗口设置,成功返回TRUE,否则返回FALSE。13)获取视图页面的数据:有2个重载形式,int类型参数的按索引获取,CWnd类型参数的按窗口获取,成功返回TRUE,否则返回FALSE。14)视图页面内部标志查询:有2个重载形式,int类型参数的按索引查询,CWnd类型参数的按窗口查询,成功返回TRUE,否则返回FALSE,当操作成功时,参数bInner为TRUE表示为CTabView内部创建的窗口,否则表示外部窗口。

4.属性页标题改名

我用CPropertySheet创建属性页,用的CPropertyPage对象只有一个,也就是每个属性页的内容一样.现在的问题是:这样每个属性页的标题都是一样的,是对话框的标题!怎样动态的改变这个标题,使每个属性页的标签的名称都不同??

CTabCtrl * pCtrl = pSheet->GetTabControl();
TCITEM tc;
tc.mask = TCIF_TEXT;
tc.pszText = "新标题";
pCtrl->SetItem(0,&tc);//0即是你要改的TAb的索引

 

switch(sel)
{
case 0:
m_button1.ShowWindow( SW_SHOW );
m_button2.ShowWindow( SW_HIDE );
break;
case 1:
m_button2.ShowWindow( SW_SHOW );
m_button1.ShowWindow( SW_HIDE );
break;
}

   最后列出主要部分的实现代码,如下所示

5.怎样去掉属性页的Apply与Help按钮?

//去掉Help
    m_psh.dwFlags |= PSH_HASHELP ;
    m_psh.dwFlags &= ~PSH_HASHELP ;

//除掉应用按钮  m_psh.dwFlags|=PSH_NOAPPLYNOW;

*pResult = 0;
}
这样做以后就可以使界面上的控件在不同的标签中显示了,但是这个方案也有很多弊病。

  1CTabCtrl& CTabView::GetTabCtrl() const
  2{
  3    return *(CTabCtrl*)this;
  4}
  5
  6CWnd* CTabView::AddView(CRuntimeClass* pClass,CCreateContext* pContext,LPCTSTR pszTitle,
  7                        int iImage,LPVOID pData,BOOL bActivate)
  8{
  9    return InsertView(GetViewCount(),pClass,pContext,pszTitle,iImage,pData,bActivate);
 10}
 11
 12BOOL CTabView::AddView(CWnd* pWnd,LPCTSTR pszText,int iImage,LPVOID pData,BOOL bActivate)
 13{
 14    return InsertView(GetViewCount(),pWnd,pszText,iImage,pData,bActivate);
 15}
 16
 17CWnd* CTabView::InsertView(int iIndex,CRuntimeClass* pClass,CCreateContext* pContext,
 18                           LPCTSTR pszTitle,int iImage,LPVOID pData,BOOL bActivate)
 19{
 20    ASSERT(::IsWindow(CCtrlView::m_hWnd));
 21    ASSERT(pClass!=NULL);
 22    ASSERT(pClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)));
 23    ASSERT(AfxIsValidAddress(pClass,sizeof(CRuntimeClass),FALSE));
 24
 25    CWnd* pWnd = (CWnd*)pClass->CreateObject();
 26    if (NULL==pWnd) return NULL;
 27    ASSERT(pWnd->IsKindOf(RUNTIME_CLASS(CWnd)));
 28
 29    CCreateContext context;
 30    if(pContext==NULL)
 31    {
 32        CView* pOldView=(CView*)GetActiveView();
 33        if(pOldView!=NULL && pOldView->IsKindOf(RUNTIME_CLASS(CView)))
 34        {
 35            ASSERT(context.m_pCurrentFrame==NULL);
 36            context.m_pLastView=pOldView;
 37            context.m_pCurrentDoc=pOldView->GetDocument();
 38            if(context.m_pCurrentDoc!=NULL)
 39            {
 40                context.m_pNewDocTemplate=context.m_pCurrentDoc->GetDocTemplate();
 41            }
 42        }
 43        pContext=&context;
 44    }
 45    if (!pWnd->Create(NULL,NULL,WS_CHILD,CRect(0,0,0,0),this,GetUniqueId(),pContext))
 46    {
 47        delete pWnd; return NULL;
 48    }
 49    if (!InsertView(iIndex,pWnd,pszTitle,iImage,pData,bActivate))
 50    {
 51        pWnd->DestroyWindow(); return NULL;
 52    }
 53    SetViewInner(iIndex,TRUE);
 54    return pWnd;
 55}
 56
 57BOOL CTabView::InsertView(int iIndex,CWnd* pWnd,LPCTSTR pszTitle,int iImage,LPVOID pData,BOOL bActivate)
 58{
 59    ASSERT(::IsWindow(CCtrlView::m_hWnd));
 60    ASSERT(pWnd&&::IsWindow(pWnd->m_hWnd));
 61
 62    CString str;
 63    ShortenTitle(pszTitle,str);
 64
 65    TC_EXTRA_ITEM ti;
 66    ti.header.mask = TCIF_PARAM|TCIF_TEXT|TCIF_IMAGE;
 67    ti.header.pszText = (LPTSTR)(LPCTSTR)str;
 68    if (pszTitle)
 69    {
 70        ti.view.pszTitle = new TCHAR[_tcslen(pszTitle)+1];
 71        if (NULL==ti.view.pszTitle)
 72            return FALSE;
 73        _tcscpy(ti.view.pszTitle,pszTitle);
 74    }
 75    else
 76    {
 77        ti.view.pszTitle = NULL;
 78    }
 79    ti.header.iImage = iImage;
 80    ti.view.pWnd = pWnd;
 81    ti.view.pData = pData;
 82    if (-1==GetTabCtrl().InsertItem(iIndex,ti))
 83    {
 84        if (pszTitle) delete []ti.view.pszTitle;
 85        return FALSE;
 86    }
 87    pWnd->SetParent(this);
 88    if (bActivate)
 89    {
 90        SetActiveViewIndex(iIndex);
 91        OnViewActivated(iIndex);
 92    }
 93    return TRUE;
 94}
 95
 96void CTabView::RemoveView(CWnd* pWnd,BOOL bDestroy)
 97{
 98    RemoveView(GetIndex(pWnd),bDestroy);
 99}
100
101void CTabView::RemoveView(int iIndex,BOOL bDestroy)
102{
103    ASSERT(::IsWindow(CCtrlView::m_hWnd));
104    ASSERT(IsValidViewIndex(iIndex));
105
106    CWnd* pWnd = GetView(iIndex);
107    ASSERT(pWnd);
108
109    BOOL bInner;
110    if (IsInnerView(iIndex,bInner) && bInner)
111        m_UniqueIDs.Add(pWnd->GetDlgCtrlID());
112    bDestroy ? pWnd->DestroyWindow() : pWnd->ShowWindow(SW_HIDE);
113   
114    LPTSTR pszTitle;
115    if (GetViewTitle(iIndex,pszTitle))
116        delete []pszTitle;
117       
118    ATLVERIFY(GetTabCtrl().DeleteItem(iIndex));
119    if(m_iActiveView == iIndex)
120    {
121        m_iActiveView = -1;
122        if(iIndex > 0)
123        {
124            SetActiveViewIndex(iIndex-1);
125        }
126        else if(GetViewCount() > 0)
127        {
128            SetActiveViewIndex(iIndex);
129        }
130        else
131        {
132            SetRedraw(TRUE);
133            Invalidate();
134            UpdateWindow();
135        }
136    }
137    else
138    {
139        iIndex = (iIndex < m_iActiveView) ? (m_iActiveView - 1) : m_iActiveView;
140        m_iActiveView = -1;
141        SetActiveViewIndex(iIndex);
142    }
143    if (-1!=m_iActiveView)
144    {
145        OnViewActivated(m_iActiveView);
146    }
147}
148
149void CTabView::RemoveAllView()
150{
151    LPTSTR pszTitle;
152    for (int iIndex=0;iIndex<GetViewCount();++iIndex)
153    {
154        GetView(iIndex)->DestroyWindow();
155        if (GetViewTitle(iIndex,pszTitle))
156            delete []pszTitle;
157    }
158    GetTabCtrl().DeleteAllItems();
159}
160
161int CTabView::GetViewCount() const
162{
163    ASSERT(::IsWindow(CCtrlView::m_hWnd));
164    return GetTabCtrl().GetItemCount();
165}
166
167CWnd* CTabView::GetView(int iIndex) const
168{
169    ASSERT(::IsWindow(CCtrlView::m_hWnd));
170    ASSERT(IsValidViewIndex(iIndex));
171
172    TC_EXTRA_ITEM ti;
173    ti.header.mask = TCIF_PARAM;
174    if (!GetTabCtrl().GetItem(iIndex,ti))
175        return NULL;
176    return ti.view.pWnd;
177}
178
179int CTabView::GetIndex(const CWnd* pWnd) const
180{
181    ASSERT(::IsWindow(CCtrlView::m_hWnd));
182
183    int count = GetTabCtrl().GetItemCount();
184    for (int i=0;i<count;++i)
185    {
186        if (GetView(i)==pWnd)
187            return i;
188    }
189    return -1;
190}
191
192CWnd* CTabView::GetActiveView() const
193{
194    ASSERT(::IsWindow(CCtrlView::m_hWnd));
195    return (-1==m_iActiveView) ? NULL : GetView(m_iActiveView);
196}
197
198int CTabView::GetActiveViewIndex() const
199{
200    return m_iActiveView;
201}
202
203void CTabView::SetActiveView(const CWnd* pWnd)
204{
205    SetActiveViewIndex(GetIndex(pWnd));
206}
207
208void CTabView::SetActiveViewIndex(int iIndex)
209{
210    ASSERT(::IsWindow(CCtrlView::m_hWnd));
211    ASSERT(IsValidViewIndex(iIndex));
212
213    if (iIndex==m_iActiveView)
214        return;
215
216//    SetRedraw(FALSE);
217    if (-1!=m_iActiveView)
218    {
219        GetView(m_iActiveView)->ShowWindow(SW_HIDE);
220    }
221    GetTabCtrl().SetCurSel(iIndex);
222    m_iActiveView = iIndex;
223    GetView(m_iActiveView)->ShowWindow(SW_SHOWNORMAL);
224
225    UpdateLayout();
226   
227    //SetRedraw(TRUE);
228    //RedrawWindow(NULL, NULL, RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW|RDW_ALLCHILDREN);
229
230    if (GetFocus()!=this)
231    {
232        GetView(iIndex)->SetFocus();
233    }   
234}
235
236BOOL CTabView::SetViewTitle(const CWnd* pWnd,LPCTSTR pszTitle)
237{
238    return SetViewTitle(GetIndex(pWnd),pszTitle);
239}
240
241BOOL CTabView::SetViewTitle(int iIndex,LPCTSTR pszTitle)
242{
243    ASSERT(::IsWindow(CCtrlView::m_hWnd));
244    ASSERT(IsValidViewIndex(iIndex));
245
246    TC_EXTRA_ITEM ti;
247    ti.header.mask = TCIF_PARAM;
248    if (!GetTabCtrl().GetItem(iIndex,ti))
249        return FALSE;
250    delete []ti.view.pszTitle;
251
252    CString str;
253    ShortenTitle(pszTitle,str);
254
255    ti.header.mask = TCIF_TEXT|TCIF_PARAM;
256    ti.header.pszText = (LPTSTR)(LPCTSTR)str;
257    if (pszTitle)
258    {
259        ti.view.pszTitle = new TCHAR[_tcslen(pszTitle)+1];
260        if (NULL==ti.view.pszTitle)
261            return FALSE;
262        _tcscpy(ti.view.pszTitle,pszTitle);
263    }
264    if (!GetTabCtrl().SetItem(iIndex,ti))
265    {
266        if (pszTitle) delete []ti.view.pszTitle;
267        return FALSE;
268    }
269    return TRUE;
270}
271
272BOOL CTabView::GetViewTitle(const CWnd* pWnd,LPTSTR& pszTitle) const
273{
274    return GetViewTitle(GetIndex(pWnd),pszTitle);
275}
276
277BOOL CTabView::GetViewTitle(int iIndex,LPTSTR& pszTitle) const
278{
279    ASSERT(IsValidViewIndex(iIndex));
280
281    TC_EXTRA_ITEM ti;
282    ti.header.mask = TCIF_PARAM;
283    if (!GetTabCtrl().GetItem(iIndex,ti))
284        return FALSE;
285    pszTitle = ti.view.pszTitle;
286    return TRUE;
287}
288
289BOOL CTabView::SetViewImage(const CWnd* pWnd,int iImage)
290{
291    return SetViewImage(GetIndex(pWnd),iImage);
292}
293
294BOOL CTabView::SetViewImage(int iIndex,int iImage)
295{
296    ASSERT(IsValidViewIndex(iIndex));
297
298    TC_EXTRA_ITEM ti;
299    ti.header.mask = TCIF_IMAGE;
300    ti.header.iImage = iImage;
301    return GetTabCtrl().SetItem(iIndex,ti);
302}
303
304BOOL CTabView::GetViewImage(const CWnd* pWnd,int& iImage) const
305{
306    return GetViewImage(GetIndex(pWnd),iImage);
307}
308
309BOOL CTabView::GetViewImage(int iIndex,int& iImage) const
310{
311    ASSERT(IsValidViewIndex(iIndex));
312
313    TC_EXTRA_ITEM ti;
314    ti.header.mask = TCIF_IMAGE;
315    if (!GetTabCtrl().GetItem(iIndex,ti))
316        return FALSE;
317    iImage = ti.header.iImage;
318    return TRUE;
319}
320
321BOOL CTabView::SetViewData(const CWnd* pWnd,LPVOID pData)
322{
323    return SetViewData(GetIndex(pWnd),pData);
324}
325
326BOOL CTabView::SetViewData(int iIndex,LPVOID pData)
327{
328    ASSERT(IsValidViewIndex(iIndex));
329
330    TC_EXTRA_ITEM ti;
331    ti.header.mask = TCIF_PARAM;
332    if (!GetTabCtrl().GetItem(iIndex,ti))
333        return FALSE;
334    ti.header.mask = TCIF_PARAM;
335    ti.view.pData = pData;
336    return GetTabCtrl().SetItem(iIndex,ti);
337}
338
339BOOL CTabView::GetViewData(const CWnd* pWnd,LPVOID& pData) const
340{
341    return GetViewData(GetIndex(pWnd),pData);
342}
343
344BOOL CTabView::GetViewData(int iIndex,LPVOID& pData) const
345{
346    TC_EXTRA_ITEM ti;
347    ti.header.mask = TCIF_PARAM;
348    if (!GetTabCtrl().GetItem(iIndex,ti))
349        return FALSE;
350    pData = ti.view.pData;
351    return TRUE;
352}
353
354BOOL CTabView::IsInnerView(const CWnd* pWnd,BOOL& bInner) const
355{
356    return IsInnerView(GetIndex(pWnd),bInner);
357}
358
359BOOL CTabView::IsInnerView(int iIndex,BOOL& bInner) const
360{
361    ASSERT(IsValidViewIndex(iIndex));
362
363    TC_EXTRA_ITEM ti;
364    ti.header.mask = TCIF_PARAM;
365    if (!GetTabCtrl().GetItem(iIndex,ti))
366        return FALSE;
367    bInner = ti.view.bInner;
368    return TRUE;
369}

所有的控件仍然在一个对话框内,在使用对话框编辑器进行编辑时,操作很不方便。 
为了能分类显示控件,必须用ClassWizard为每一个控件创建一个控件变量,以便利用各控件变量的CWnd基类的ShowWindow函数来显示和隐藏。有时为了使用DDX和DDV机制来进行数据交换,还要创建一些存放值的变量,这样就使得整个对话框类变得相当庞大难以操作。 
当然你也可以使用数组来存放那些控件变量或值变量,但是这样并不是最好,有时一些不相关的控件变量放入一个数组中,通过没有实际意义的数组索引号来访问控件,对程序的编写会造成麻烦。 最好能将所有控件进行分类,放入不通对话框类中,这些对话框作为子对话框出现在主对话框中。可以。现在看看方案二。

摘自 天道酬勤

方案二
本方案的例子请见源代码打包文件中的Property2部分
这个方案中,我将使用MFC中现成的CPropertySheet和CPropertyPage类来完成将控件分散到各个对话框类中。

...

首先加入两个(或数个)对话框资源。修改各对话框资源的属性,将对话框的Caption属性改为你要在标签上所显示的文字。将对话框的Style属性改为:Child, Border属性改为:Thin, 只选中Title Bar复选框,去掉其他复选框。然后你可以在这些对话框中加入要分开显示的各个控件。

为上述对话框资源分别制作一个对话框类,该对话框类是从CPropertyPage继承。这样一来各子对话框类就好了,主对话框类可以直接使用CPropertySheet类。使用如下代码即可: 
CPropertySheet sheet("属性页对话框");
CPage1 page1;
CPage2 page2;
//加入子对话框作为一个属性页
sheet.AddPage(&page1);
sheet.AddPage(&page2);
//产生一个模态对话框,也可以使用Create方法来产生一个非模态对话框(具体参见MSDN)
sheet.DoModal();

这样这个对话框效果如下:

但是会有人问,如何在主对话框中放置其他控件呢?如果直接使用CPropertySheet的话,是不可以的,但是别忘了我们可以从CPropertySheet类继承自己的类啊!下面来看看方案三的做法。

方案三
本方案的例子请见源代码打包文件中的Property3部分

首先还是要创建那些要在属性页中的显示的子对话框类,创建步骤和方案二一样,都是从CPropertyPage继承。

这次我们将从CPropertySheet类继承自己的类(假设类名为CMySheet)。我们要在这里放上一个button控件。那么现在先在CMySheet中加入一个CButton类的成员变量m_button。

在CMySheet类中的OnInitDialog()函数里,这样写:
BOOL bResult = CPropertySheet::OnInitDialog();

//取得属性页的大小
CRect rectWnd;
GetWindowRect(rectWnd);
//调整对话框的宽度
SetWindowPos(NULL, 0, 0,rectWnd.Width() + 100,rectWnd.Height(),SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
CRect rectButton(rectWnd.Width() + 25, 25,rectWnd.Width()+75, 75);
//用程序创建一个按钮
m_button.Create("Button", BS_PUSHBUTTON, CRect(rectWnd.Width(), 25,rectWnd.Width()+75, 50) , this, 1);
//显示这个按钮
m_button.ShowWindow( SW_SHOW );
CenterWindow();
return bResult;
效果如下:

使用方案三虽然能在主对话框中加入控件,但是也比较麻烦,首先所加的控件只能在属性页的右边或下边。并且用程序来产生控件比较烦琐,位置与大小不易控制。那么还有其他方法,既能在对话框中加入属性页,又能在主对话框随意添加控件?还是有的,看看方案四。

方案四
本方案的例子请见源代码打包文件中的Property4部分

这次我们不从CPropertySheet继承自己的类,还是直接使用它。各属性页的子对话框类还是需要的,创建方法和上述两个方案相同。

首先我们新建一个基于对话框的工程。在编辑已有的一个主对话框中可以自由加一些所需的控件,但是得留出一定的空间用于放置属性页。

在主对话框类里加入一个CPropertySheet类的一个成员变量(m_sheet)代表整个属性页。再加入一些各子对话框类的实例作为成员变量(m_page1、m_page2……)。

在主对话框类的OnInitDialog()函数中加入:
//加入标签,标签名由各个子对话框的标题栏决定
m_sheet.AddPage(&m_page1);
m_sheet.AddPage(&m_page2);
//用Create来创建一个属性页
m_sheet.Create(this, WS_CHILD | WS_VISIBLE, WS_EX_CONTROLPARENT);

RECT rect;
m_sheet.GetWindowRect(&rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;

//调整属性页的大小和位置
m_sheet.SetWindowPos(NULL, 20, 50, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);

效果如下:

这个方案可以自由在主对话框中加一些必要的控件,而且属性页中的控件也都分散在了各个子对话框类中,使用非常方便。

但是这样也有一些缺陷:主对话框不能处理属性页上标签的消息,即点击标签时无法通知主对话框。(可能笔者水平有限,理论上应该可以,但笔者尚未解决这个问题)

方案五
本方案的例子请见源代码打包文件中的Property5部分

这次我们仍然要使用Tab Control,并且从CTabCtrl控件类继承自己的类(CTabSheet)来处理。(此方法来自CodeGuru的一篇文章,本人稍做修改使其使用更简便)

首先我先介绍一下如何使用CTabSheet。

先要制作子对话框类,这次的子对话框类不要从CPropertyPage继承,而是直接从CDialog继承。并且各个子对话框资源的属性应设置为:Style为Child, Border为None。

在主对话框资源中,加入一个Tab Control,并且适当调整位置和大小。利用ClassWizard来为这个Tab Control创建一个CTabSheet的控件变量。

在主对话框的OnInitDialog()加入:
m_sheet.AddPage("tab1", &m_page1, IDD_DIALOG1);
m_sheet.AddPage("tab2", &m_page2, IDD_DIALOG2);
m_sheet.Show();
就这样就可以在对话框上制作出一个完美的属性页了。效果和上图完全一样。

下面我就来讲讲CTabSheet类的细节内容。

CTabSheet是从CTabCtrl继承来的,用于Tab Control的控件类。在类中有一个成员变量用来记录各子对话框的指针CDialog* m_pPages[MAXPAGE]; MAXPAGE是该类所能加载的标签的最大值。

类中有一个AddPage方法,用于记录子对话框的指针和所使用对话框资源的ID号。
BOOL CTabSheet::AddPage(LPCTSTR title, CDialog *pDialog,UINT ID)
{
if( MAXPAGE == m_nNumOfPages )
return FALSE;

//保存目前总的子对话框数
m_nNumOfPages++;

//记录子对话框的指针、资源ID、要在标签上显示的文字
m_pPages[m_nNumOfPages-1] = pDialog;
m_IDD[m_nNumOfPages-1] = ID;
m_Title[m_nNumOfPages-1] = title;

return TRUE;
}

在使用AddPage加入了若干子对话框后,必须调用CTabSheet的Show方法来真正生成标签和子对话框。 
void CTabSheet::Show()
{
//利用CDialog::Create来创建子对话框,并且使用CTabCtrl::InsertItem来加上相应的标签
for( int i=0; i < m_nNumOfPages; i++ )
{
m_pPages[i]->Create( m_IDD[i], this );
InsertItem( i, m_Title[i] );
}

//由于对话框显示时默认的是第一个标签被选中,所以应该让第一个子对话框显示,其他子对话框隐藏
m_pPages[0]->ShowWindow(SW_SHOW);
for( i=1; i < m_nNumOfPages; i++)
m_pPages[i]->ShowWindow(SW_HIDE);

SetRect();

}

生成好标签和子对话框后,调用CTabSheet::SetRect来计算并调整属性页的大小。
void CTabSheet::SetRect()
{
CRect tabRect, itemRect;
int nX, nY, nXc, nYc;

//得到Tab Control的大小
GetClientRect(&tabRect);
GetItemRect(0, &itemRect);

//计算出各子对话框的相对于Tab Control的位置和大小
nX=itemRect.left;
nY=itemRect.bottom+1;
nXc=tabRect.right-itemRect.left-2;
nYc=tabRect.bottom-nY-2;

//利用计算出的数据对各子对话框进行调整
m_pPages[0]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_SHOWWINDOW);
for( int nCount=1; nCount < m_nNumOfPages; nCount++ )
m_pPages[nCount]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_HIDEWINDOW);

}

在单击标签栏后,应该是相应的子对话框显示,正在显示的子对话框应该隐藏。因此利用ClassWizard来处理WM_LBUTTONDOWN消息。
void CTabSheet::OnLButtonDown(UINT nFlags, CPoint point) 
{
CTabCtrl::OnLButtonDown(nFlags, point);

//判断是否单击了其他标签
if(m_nCurrentPage != GetCurFocus())
{
//将原先的子对话框隐藏
m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);
m_nCurrentPage=GetCurFocus();
//显示当前标签所对应的子对话框
m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);
}

}

这样利用CTabSheet这个类就可以轻松地在对话框上放置自己的属性页了,并且控件都分散在各子对话框类中,符合对象封装的思想。而且用这个方法来制作属性页就可以利用ClassWizard来轻松地生成消息映射处理Tab Control的消息了。例如:可以处理TCN_SELCHANGE消息来对切换了标签时进行一些动作。

本文由10bet手机官网发布于微服架构,转载请注明出处:积累的VC编程小技巧之属性页,多标签视图类CTabView的设计实现

上一篇:C语言二叉树非递归遍历问题,判断二叉树是否为完全二叉树 下一篇:没有了
猜你喜欢
热门排行
精彩图文