关闭

VC6.0 MFC添加树形控件CTreeCtrl

745人阅读 评论(0) 收藏 举报
分类:

前面这段转自:http://blog.csdn.net/kongfuxionghao/article/details/11585157

(一)树控制的主要功能

树控制和视(Tree Control&View)主要用来显示具有一定层次结构的数据项,如资源管理器中的磁盘目录等,以供用户在其中进行各种选择。树控制中的每个数据项包括数据项名称的文本字符串和用于表示该数据项的图像,每个数据项下面均可包含各种子项,整个结构就象目录树一样。对于包含各种子项的数据项,可通过鼠标双击来展开或合拢,这可以通过控制树的不同风格来实现树控制的不同显示形态。这些风格主要包括:

TVS_HASLINES表示用连线来连接父项和它下面的各个子项,这可以使树的显示层次结构更加清晰,但在无父项的各子项之间并没有连线;

TVS_LINESATROOT表示在无父项的各子项即根下面的各子项之间存在连线;

TVS_HASBUTTONS表示在带有子项的父项前面增加一个带“+”或“-”的按钮,这使得用户也可以通过单击这个小按钮来实现子项的展开和合拢,当存在子项时,按钮的初始状态为“+”,当子项被展开时,按小按钮由“+”变为“-”号,当子项合拢时,小按钮由“-”变为“+”号,这一风格同样对于根项无效,如果需要可通过组合TVS_LINESATROOT风格来实现;

TVS_EDITLABELS表示允许让用户单击具有输入焦点的数据项来修改其名称。

对于树控制,MFC中也以两种形式来封装,即树控制(CTREECTRL)和树视(CTREEVIEW),来满足用户的不同需求,对于一般要求的用户如在对话框中应用,使用树控制比较方便,而对于具有较高要求的用户,在使用树视时还具有视窗口的各种方便特性,可以更好地满足文档/视结构的要求。当在窗口中使用树视时,树视会占满两个窗口的客户区域并自动随窗口的框架结构的调整而调整,并能够很好地处理诸如菜单、加速键和工具条中的各种命令消息。在使用树视时只要利用其成员函数CtreeView取得其一个引用,就可以象树控制一样方便地应用:CtreeCtrl &treeCtrl = GetTreeCtrl()。

(二)树控制的对象结构

1、树控制的建立方法

CtreeCtrl&treeCtrl 建立树控制对象结构

Create 建立树控制并绑定对象

树控制CTreeCtrl::Create的调用格式如下:

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

其中参数dwStyle用来确定树控制的类型;rect用来确定树控制的大小和位置;pParentWnd用来确定树控制的父窗口,通用是一个对话框并且不能为NULL;nID用来确定树控制的标识。树控制的风格可以是下列值的组合:

TVS_HASLINES 表示树控制在各子项之间存在连线;

TVS_LINESATROOT 表示树控制在根项之间存在连线;

TVS_HASBUTTONS 表示树控制视在父项左侧存在展开合拢控制按钮;

TVS_EDITLABELS 表示可以控制鼠标单击修改树项的名称;

TVS_SHOWSELALWAYS 表示选中项即使在窗口失去输入焦点时仍然保持选中状态;

TVS_DISABLEDRAGDROP表示禁止树控制发送TVN_BEGINDRAG消息

2、树控制的属性类

树控制属性类包括取得树控制中项数GetCount、取得树控制中项相对于父项的偏移值GetIndent、取得树控制图像列表控制句柄GetImageList、设置树控制图像列表控制句柄SetImageList、取得匹配下一个树项GetNextItem、判断给定树项是否包含子项ItemHasChildren、取得树项子项GetChildItem、取得下一个同属树项GetNextSiblingItem、取得前一个同属树项GetPrevSiblingItem、取得父树项GetParentItem、取得第一个可视树项GetFirstVisibleItem、取得下一个可视树项GetNextVisible Item、取得前一个可视的树项GetPrevVisibleItem、取得被选中的树项GetSelectedItem、取得根树项GetRootItem、取得树项的属性GetItem、设置树项的属性SetItem、取得树项的状态GetItemState、设置树项的状态SetItemState、取得与树项关联图像GetItemImage、设置与树项关联图像SetItemImage、取得树项文本GetItemText、设置树项文本SetItemText和取得树项编辑控制句柄GetEditControl等。

3、树控制的操作方法

树控制的操作方法包括插入一个树项InsertItem、删除一个树项DeleteItem、删除所有树项DeleteAllItems、展开或合拢树项的子项Expand、选中特定树项SelectItem、选择一个树项作为第一个可视树项SelectSetFirstVisible、编辑一个可视的树项EditLabel和排序给定父树项的子树项SortChildren等。

(三)树控制的数据结构

在使用树控制时需要了解两个个非常重要的数据结构TV_ITEM和TV_INSERTSTRUCT,前一个数据结构是用来表示树控制的树项信息,后一个数据结构是用来定义将树项增加到数据控制中所需要的数据内容。另外,还需要NM_TREEVIEW、TV_DISPINFO和TV_HITTESTINFO三个数据结构,这几个数据结构的定义方法如下:

①基本数据项结构

typedef struct _TV_ITEM {

UINT mask; //结构成员有效性屏蔽位

HTREEITEM hItem; //数据项控制句柄

UINT state; //数据项状态

UINT stateMask; //状态有效性屏蔽位

LPSTR pszText; //数据项名称字符串

int cchTextMax; //数据项名称的最大长度

int iImage; //数据项图标索引号

int iSelectedImage;//选中数据项图标索引号

int cChildren; //子项标识

LPARAM lParam; //程序定义的32位数据

} TV_ITEM, FAR *LPTV_ITEM;

②插入树项结构

typedef struct _TV_INSER TSTRUCT {

HTREEITEM hParent; //父项控制句柄

HTREEITEM hInsertAfter; //插入树项的位置

TV_ITEM item; //数据项的结构

} TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;

其中插入的位置如果是TVI_FIRST 或TVI_LAST ,则分别插入到树控制的最前面或最后面,如果是TVI_SORT ,则插入的树项自动插入到合适的位置。

③树控制通知消息结构

typedef struct _NM_TREEVIEW {

NMHDR hdr; //通知消息句柄

UINT action; //通知消息标志

TV_ITEM itemOld; //原来的数据结构

TV_ITEM itemNew; //新的数据结构

POINT ptDrag; //拖动指针

} NM_TREEVIEW;

④取得或设置数据结构

typedef struct _TV_DISPINFO { tvdi

NMHDR hdr; //通知消息控制句柄

TV_ITEM item; //数据项结构

} TV_DISPINFO;

⑤指针测试数据结构

typedef struct _TVHITTESTINFO { tvhtst

POINT pt; //客户区域屏幕坐标指针

UINT flags; //存放测试结果的变量

HTREEITEM hItem; //测试的数据项结构

} TV_HITTESTINFO, FAR *LPTV_HITTESTINFO;

其中flags测试结果可以是如下值:

TVHT_ABOVE 在客户区域上面

TVHT_BELOW 在客户区域下面

TVHT_NOWHERE 在客户区域中并在最后一项下面

TVHT_ONITEM 在与树项关联的位图或标签内

TVHT_ONITEMBUTTON 在与树项关联的按钮上

TVHT_ONITEMICON 在与树项关联的位图上

TVHT_ONITEMINDENT 在与树项关联的联线上

TVHT_ONITEMLABEL 在与树项关联的标签上

TVHT_ONITEMRIGHT 在树项的右侧区域中

TVHT_ONITEMSTATEICON 在用户定义的状态图标上

TVHT_TOLEFT 在客户区域的左侧

TVHT_TORIGHT 在客户区域的右侧

(四)树控制的应用技巧示例

这里仍以基于对话框演示实例来具体介绍树控制及其和图像列表相结构的应用技巧:

通过“FILE->NEW->PROJECTS->MFC AppWizard(EXE)”建立名为VCTREE的工程,在建立过程中选择基于对话框(Dialog based)的应用;将对话框中的默认控件删除,并将所有对话框属性中的Language域设置为Chinese(P.R.C.),以使应用程序支持中文;建立两个图标IDI_PM和IDI_CJ,用来表示图标的选中和非选中状态,对于每个图标都应建立32X32和16X16两种大小,以保证程序的需要;在对话框窗口中添加树控制对象(TREE CONTROL),并设置五个按钮“增加|删除|查看|排序|关闭”,其对应标识分别如下:


控制名称 标题名称 标识符号


树控制 IDC_TREECTRL

按钮 增 加 IDC_ADD

删 除 IDC_DEL

查 看 IDC_VIEW

排 序 IDC_SORT

关 闭 IDOK


5、选中树控制控件,选择“VIEW->ClassWizard->Memory Variables。 骺刂艻DC_TREECTRL 引入成员变量,其变量类型为:

变量名 种类 变量类型

m_TreeCtrl Control CTreeCtrl

同时利用“MESSAGES MAP”为各命令按钮增加控制功能函数。

6、然后在代码文件VCTREEDlg.CPP中分别加入如下控制代码:

(1)在文件开始处增加图像列表定义

CImageList Cil1,Cil2;//大小图标像列表

(2)在初始化文件开始处增加代码

BOOL CVCTREEDlg::OnInitDialog()

{ CDialog::OnInitDialog();

......//原来其它代码

// TODO: Add extra initialization here

// 此处开始增加代码

CVCTREEApp *pApp=(CVCTREEApp *)AfxGetApp();//创建图象列表

Cil1.Create(16,16,ILC_COLOR,2,2);

Cil1.Add(pApp->LoadIcon(IDI_PM));

Cil1.Add(pApp->LoadIcon(IDI_CJ));

m_TreeCtrl.SetImageList(&Cil1,TVSIL_NORMAL); //设置图象列表

DWORD dwStyles=GetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE);//获取树控制原风格

dwStyles|=TVS_EDITLABELS|TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT;

SetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE,dwStyles);//设置风格

char * CJ[4]={"玉溪卷烟厂","云南卷烟厂","沈阳卷烟厂","成都卷烟厂"};//根数据名称

char * PM[4][5]={

{"红梅一","红梅二","红梅三","红梅四","红梅五"},//产品数据项

{"白梅一","白梅二","白梅三","白梅四","白梅五"},

{"绿梅一","绿梅二","绿梅三","绿梅四","绿梅五"},

{"青梅一","青梅二","青梅三","青梅四","青梅五"}};

int i,j;

HTREEITEM hRoot,hCur;//树控制项目句柄

TV_INSERTSTRUCT TCItem;//插入数据项数据结构

TCItem.hParent=TVI_ROOT;//增加根项

TCItem.hInsertAfter=TVI_LAST;//在最后项之后

TCItem.item.mask=TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE;//设屏蔽

TCItem.item.pszText="数据选择";

TCItem.item.lParam=0;//序号

TCItem.item.iImage=0;//正常图标

TCItem.item.iSelectedImage=1;//选中时图标

hRoot=m_TreeCtrl.InsertItem(&TCItem);//返回根项句柄

for(i=0;i<4;i++){//增加各厂家

TCItem.hParent=hRoot;

TCItem.item.pszText=CJ[i];

TCItem.item.lParam=(i+1)*10;//子项序号

hCur=m_TreeCtrl.InsertItem(&TCItem);

for(j=0;j<5;j++){//增加各产品

TCItem.hParent=hCur;

TCItem.item.pszText=PM[i][j];

TCItem.item.lParam=(i+1)*10+(j+1);//子项序号

m_TreeCtrl.InsertItem(&TCItem);

}

m_TreeCtrl.Expand(hCur,TVE_EXPAND);//展开树

}

m_TreeCtrl.Expand(hRoot,TVE_EXPAND);//展开上一级树

return TRUE; // return TRUE unless you set the focus to a control

}

(3)增加树项功能的实现

在增加树项功能时,除了需要定义和设置插入树项的数据结构之外,还需要注意的是新增树项的名称初始时均为“新增数据”,增加后允许用户给数据项设置自定义名称。在编程时应特别注意m_TreeCtrl.EditLabel(hInsert);后面不能跟任何其它程序命令,否则这条编辑指令无效。

void CVCTREEDlg::OnAdd()

{ //增加子项功能函数

HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选择项句柄

if(hSel==NULL) return;//无任何选项则返回

static int nAddNo=100;//编号大于100为新增数据

TV_INSERTSTRUCT TCItem;//定义插入项数据结构

TCItem.hParent=hSel; //设置父项句柄

TCItem.hInsertAfter=TVI_LAST;//在最后增加

TCItem.item.mask=TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE;//设屏蔽

TCItem.item.pszText="新增数据";

TCItem.item.lParam=nAddNo++;//索引号增加

TCItem.item.iImage=0;//正常图标

TCItem.item.iSelectedImage=1;//选中时图标

HTREEITEM hInsert=m_TreeCtrl.InsertItem(&TCItem);//增加

m_TreeCtrl.Expand(hSel,TVE_EXPAND);

m_TreeCtrl.EditLabel(hInsert);//修改增加的数据

}

(4)删除树项功能的实现

在实现删除功能时,应对存在子项的树项进行提示,以警告用户是否连同其子项一起删除。

void CVCTREEDlg::OnDel()

{ //删除子项功能函数

HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选项句柄;

if(hSel==NULL) return;//无任何选项则返回

if(m_TreeCtrl.ItemHasChildren(hSel))//判断是否有子项

if(MessageBox("厂家下存在品名,一同删除?","警告",MB_YESNO)==IDNO) return;

m_TreeCtrl.DeleteItem(hSel);

}

(5)排序功能的实现

排序功能是对所选中的树项的所有子项按字符中顺序进行排序,如果想要按照其它规则进行排序,应利用SortChildrenItemBC()函数进行自行开发排序程序,这个自行开发的函数与列表控制中实现的函数基本相同,可兴趣的读可以试验。

void CVCTREEDlg::OnSort()

{ //排序子项功能函数

HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选项句柄;

if(hSel==NULL) return;//无任何选项则返回

m_TreeCtrl.SortChildren(hSel);

}

(6)查看功能的实现

查看功能用来查看选中树项的有关信息,函数中中显示了树项的文本名称和标识号,可以将这两个信息作为查找关键字,来查看其它更详细的信息。

void CVCTREEDlg::OnView()

{ //查看选中项功能函数

HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选项句柄;

if(hSel==NULL) return;//无任何选项则返回

CString cText=m_TreeCtrl.GetItemText(hSel);//取得数据项名

LONG IDs=m_TreeCtrl.GetItemData(hSel);//取得数据项序号

char temp[100];

wsprintf(temp,"厂家:%s 编号:%05d",cText,IDs);

MessageBox(temp,"选择信息");

}

(7)修改功能的实现

如果不进行其它处理,当修改树项的文本名称后,就会发现其未被修改,这是因为程序中没有对修改结果进行保存处理,这就要利用TV_DISPINFO结构和SetItemText函数对TVN_ENDLABELEDIT进行处理,这样就可以正确地实现修改功能。

void CVCTREEDlg::OnEndlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult)

{ TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;

// TODO: Add your control notification handler code here

if(pTVDispInfo->item.pszText==0) return;//用户取消修改操作

m_TreeCtrl.SetItemText(pTVDispInfo->item.hItem,

pTVDispInfo->item.pszText);//设置新数据

*pResult = 0;

}

7、树视的演练技巧

树视的应用技巧在使用树视时,其方法与树控制基本相同,只不过树视是在窗口中来实现的而树控制是在对话框中实现,树视的各种功能是通过菜单来实现的而树控制是通过按钮等方式来实现的,树控制需要在对话框中创建树控制控件而树视直接占据整个窗口,在设计过程中只要将按钮和树控制设计过程变为菜单设计,并注意在功能函数是在类向导中是通过菜单命令来操作,同时在每个功能函数前面增加取得列表视引用的命令(CTreeCtrl& TreeCtrl = GetTreeCtrl()),而其余数据结构和代码均不需要修改,实现起来比较容易。

 

 

以下代码自己实现:

 

//手动添加树形控件代码 

m_treeTestInstance = new CTreeCtrl();
 if(!m_treeTestInstance->Create(WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_BORDER
  | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES
   | TVS_DISABLEDRAGDROP | TVS_NOTOOLTIPS | TVS_EDITLABELS | TVS_CHECKBOXES , Rect,this,IDC_TREE_TESTINSTANCE))
 {
  return FALSE;
 }
 
 ASSERT_VALID(m_treeTestInstance);

 //检查文件是否存在并获取工程名,这段代码与树无关
 CFileFind finder;
 BOOL isFind = finder.FindFile(m_sTestProjectPath);
 if ( FALSE == isFind)
 {
  return FALSE;
 }
 while (isFind)
 {
  isFind = finder.FindNextFile();
  m_testProjectName = finder.GetFileTitle();//仅获取文件名称
  ASSERT(NULL != pGlobleUnit);
  pGlobleUnit->m_csTestCasePath = finder.GetRoot();//获取除去文件名的路径
  pGlobleUnit->m_csTestProjectName = m_testProjectName;
 }

 


 //初始化树形控件节点
 TV_INSERTSTRUCT treeCtrlItem;
 HTREEITEM hTreeItem;
 HTREEITEM hTreeRoot;
 treeCtrlItem.hInsertAfter = TVI_LAST;//插入的新项之后的项的句柄,TVI_LAST 在列表的最后插入项
 treeCtrlItem.item.mask = TVIF_TEXT|TVIF_PARAM;//item是TVITEM结构,包含关于项添加的信息,TVIF_TEXT表示item结构中的pszText和cchTextMax成员有效,TVIF_PARAM表示item结构中的lParam成员有效
 //设置第一级根节点
 treeCtrlItem.hParent = TVI_ROOT;
 treeCtrlItem.item.pszText = (LPTSTR)(LPCTSTR)(m_testProjectName);//CString m_testProjectName
 treeCtrlItem.item.lParam = 1;  //根
 hTreeRoot = m_treeTestInstance->InsertItem(&treeCtrlItem);
 //循环解析
 for (int i=0;i<m_pTestSectionFrame.GetSize();i++)// CPtrArray  m_pTestSectionFrame

 {
     CStringArray *pTestSection;
  pTestSection = (CStringArray *)(m_pTestSectionFrame.GetAt(i));
  for(int j=0;j<(pTestSection->GetSize());j++)
  {
   CString csItemStr = pTestSection->GetAt(j);
   if ( 0 == j )// 二级节点的值存放在CStringArray的第一个位置 
   {
    //设置第二级节点
    treeCtrlItem.hParent = hTreeRoot;
    treeCtrlItem.item.pszText = (LPTSTR)(LPCTSTR)(csItemStr);
    treeCtrlItem.item.lParam = 2;
    hTreeItem = m_treeTestInstance->InsertItem(&treeCtrlItem);
   }
   else if( 2 < j )
   {
    //设置第三级节点
    treeCtrlItem.hParent = hTreeItem;
    treeCtrlItem.item.pszText = (LPTSTR)(LPCTSTR)(csItemStr);
    treeCtrlItem.item.lParam = 3;
    m_treeTestInstance->InsertItem(&treeCtrlItem);
   }
  }
 }

 

带复选框的CTreeCtrl响应复选消息(选中父项子项全部选中并展开,选中子项自动选中相应父项)

// (1)鼠标点击当前ITEM的CHECKBOX:引发NM_CLICK事件并传递TVHT_ONITEMSTATEICON。
// (2)鼠标点击当前ITEM的TEXT:引发NM_CLICK事件。
// (3)鼠标点击新ITEM的CHECKBOX:引发TVN_SELCHANGED事件、NM_CLICK事件并传递TVHT_ONITEMSTATEICON。
// (4)鼠标点击新ITEM的TEXT:引发NM_CLICK事件、TVN_SELCHANGED事件。

//点击ITEM会引发NM_CLICK事件。若点击CHECKBOX则传递TVHT_ONITEMSTATEICON。
  // 因此可不处理TVN_SELCHANGED事件而只处理NM_CLICK事件。

BEGIN_MESSAGE_MAP(CSelectTestInstance, CDialog)

          ON_NOTIFY(NM_CLICK, IDC_TREE_TESTINSTANCE, OnClickTreeCheckBox)

END_MESSAGE_MAP()


void CSelectTestInstance::OnClickTreeCheckBox(NMHDR* pNMHDR, LRESULT* pResult)
{
 // TODO: Add your control notification handler code here
 
 CPoint point;
    UINT uFlag;                                      //接收有关点击测试的信息的整数
    HTREEITEM hTree;
    BOOL bCheck;
    GetCursorPos(&point);                            //获取屏幕鼠标坐标
    m_treeTestInstance->ScreenToClient(&point);       //转化成客户坐标
    hTree = m_treeTestInstance->HitTest(point,&uFlag);//返回与m_treeTestInstance关联的光标的当前位置和句柄
    if((TVHT_NOWHERE & uFlag))
    {
        return;
    }
 
    if (hTree && (TVHT_ONITEMSTATEICON & uFlag))     //点中复选框
    {
        m_treeTestInstance->SelectItem(hTree);        //?
        CString temp = m_treeTestInstance->GetItemText(hTree) + "\r\n";
        TRACE(temp);
        bCheck = m_treeTestInstance->GetCheck(hTree);  //获取当前复选状态(点击CHECKBOX后会自动更新CheckBox的状态,而GetCheck函数返回也是其原始状态。)
        fnSetChildCheck(hTree,!bCheck);               //设置子项复选状态
        fnSetParentCheck(hTree,!bCheck);              //设置父项复选状态
    }

 *pResult = 0;
}

//设置子节点状态
void  CSelectTestInstance::fnSetChildCheck(HTREEITEM hTree,BOOL bCheck)
{
 m_treeTestInstance->Expand(hTree,TVE_EXPAND);     //展开树
    hTree = m_treeTestInstance->GetChildItem(hTree);  //获取子项句柄
    while (hTree)
    {
        m_treeTestInstance->SetCheck(hTree, bCheck);    
        fnSetChildCheck(hTree,bCheck);               //递归调用
        hTree = m_treeTestInstance->GetNextSiblingItem(hTree);//获取兄弟的句柄
    }
}
//设置父节点状态
void  CSelectTestInstance::fnSetParentCheck(HTREEITEM hTree,BOOL bCheck)
{
  HTREEITEM hParent = m_treeTestInstance->GetParentItem(hTree); //获得父项句柄
    if (hParent)
    {
        HTREEITEM hChild = m_treeTestInstance->GetChildItem(hParent);
        while (hChild)
        {
            if (hChild == hTree)
            {
                hChild = m_treeTestInstance->GetNextSiblingItem(hChild);
                continue;
            }
            BOOL bflag = m_treeTestInstance->GetCheck(hChild);
   //如果当前节点的状态为未选中,并且存在兄弟节点的状态与当前节点的状态不相等则直接返回;
            if ( (bCheck != bflag)&&(FALSE == bCheck) )
            {
                return;
            }
            hChild = m_treeTestInstance->GetNextSiblingItem(hChild);
        }
        m_treeTestInstance->SetCheck(hParent,bCheck);//设置父项的状态
        fnSetParentCheck(hParent,bCheck);            //循环调用   
    }
}

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:8220次
    • 积分:185
    • 等级:
    • 排名:千里之外
    • 原创:10篇
    • 转载:5篇
    • 译文:0篇
    • 评论:0条
    文章存档