关键字:VC++ CTreeCtrl类 储存 ini文件读写 递归调用
在此之前,先引用一段别人的文章,是建立树的:(来自<VC知识库精华珍藏版在线杂志合订本>中《PWIN98/95下演练CTree》的节选)
如果你已经会建立一棵树,请跳过这一段。
通过“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()),而其余数据结构和代码均不需要修改,实现起来比较容易。
我是通过这篇文章了解到了树的基本操作的,拜一个先~
之后,我发现我要将建立的树保存到一个文件,之后再在另一个程序里打开,生成这棵数。怎么保存好呢……?这个时候,我找到了一篇把数
据保存到ini文件里的教程,知道了三个函数:(会的跳过)
写入:
BOOL WritePrivteProfileString(
LPCTSTR lpAppName, //INI文件中的一个字段名
LPCTSTR lpKeyName, //lpAppName下的一个键名
LPCTSTR lpString, //键值,LPCTSTR或CString型的
LPCTSTR lpFileName //完整的INI文件名
);
读取,前者为读字符串,后者为读整型:
DWORD GetPrivateProfileString(
LPCTSTR lpAppName, //INI文件中的一个字段名
LPCTSTR lpKeyName, //lpAppName下的一个键名
LPCTSTR lpDefault, //如果没有前两个参数指定的字段名或者键名,则用此值赋给变量
LPCTSTR lpReturnedString, //接受INI文件重点的值的CString对象,即目的缓冲器
DWORD nSize, //目的缓冲器大小
LPCTSTR lpFileName //完整的INI文件名
);
UINT GetPrivateProfileInt(
LPCTSTR lpAppName, //INI文件中的一个字段名
LPCTSTR lpKeyName, //lpAppName下的一个键名
INT nDefault, //如果没有前两个参数指定的字段名或者键名,则用此值赋给变量
LPCTSTR lpFileName //完整的INI文件名
);
这三个函数在这里要比fstream方便的多了(因为本真人还不会vc++里那个读写文件的函数……)
下面让我们想想,树,应该怎么保存?一棵树是由一个根节点,一堆支干节点和一堆叶子节点组成的。而构造一个树的节点,就要给他:
一个lParam的序号,一个pszText的名字,一个父节点的指针。这些是我们构造一个节点的充分必要条件,而只要我们把对应的所有节点构造出
来,那么我们就相当于构造出了一棵一样的树。下面,我来说说我的想法:
递归遍历树的每一个节点,顺序保存他的pszText和他所在的层数(根算0层,第一层枝算一层,类推)。
打开时,动态产生lParam的序号给每个节点,通过每个节点所在层数来确定其父节点是谁。
好象很简单的样子,一句话就完了,事实上——确实很简单:下面是代码。
写:
void CRaWtreeDlg::OnWrite() //按下保存按钮
{
m_tn=1; //节点总数,包括根节点——我是从1开始记的
CString ct;
::WritePrivateProfileString("Count","Ceng","0",".\\root.ini");//当前层数,0
HTREEITEM hRoot;
hRoot=m_tw.GetChildItem(TVI_ROOT); //获得根节点
LB(hRoot,0); //递归调用,最开始把根节点放入,并指定当前层数为0
ct.Format("%d",m_tn-1); //记录完毕,记录总节点数
::WritePrivateProfileString("Count","Count",ct,".\\root.ini");
}
void CRaWtreeDlg::LB(HTREEITEM hroot,int Ceng) //遍历
{
HTREEITEM hRoot,hCur;
CString ct;
CString st;
adddat *addp;
hRoot=hroot;
if (m_tw.ItemHasChildren(hRoot)) //如果有子节点,即不是叶子
{
ct.Format("%d",Ceng);
st.Format("Ceng[%d]",m_tn);
::WritePrivateProfileString("Root",st,ct,".\\root.ini"); //保存当前层数
ct=m_tw.GetItemText(hRoot);
st.Format("Name[%d]",m_tn);
::WritePrivateProfileString("Root",st,ct,".\\root.ini"); //保存当前名字
m_tn++;
//记录最大层数
int nc=::GetPrivateProfileInt("Count","Ceng",0,".\\root.ini");
if (nc<Ceng)
{
st.Format("%d",Ceng);
::WritePrivateProfileString("Count","Ceng",st,".\\root.ini");
}
hCur=m_tw.GetChildItem(hRoot); //获得子节点
while (hCur!=NULL) //如果子节点不空
{
LB(hCur,Ceng+1); //递归调用,层数+1
hCur=m_tw.GetNextItem(hCur,TVGN_NEXT); //获得下一个子节点
}
}
else //下面代码含义同上
{
ct.Format("%d",Ceng);
st.Format("Ceng[%d]",m_tn);
::WritePrivateProfileString("Root",st,ct,".\\root.ini");
ct=m_tw.GetItemText(hRoot);
st.Format("Name[%d]",m_tn);
::WritePrivateProfileString("Root",st,ct,".\\root.ini");
addp=m_cul.GetItem(m_tw.GetItemData(hRoot));
ct=addp->m_urladd;
st.Format("Address[%d]",m_tn);
::WritePrivateProfileString("Root",st,ct,".\\root.ini");
m_tn++;
int nc=::GetPrivateProfileInt("Count","Ceng",0,".\\root.ini");
if (nc<Ceng)
{
st.Format("%d",Ceng);
::WritePrivateProfileString("Count","Ceng",st,".\\root.ini");
}
}
}
本问来自tczzya.spaces.msn.com
读:
void CRaWtreeDlg::OnRead()
{
int iCount;
int nid=10;
char schar[50];
iCount=::GetPrivateProfileInt("Count","Count",0,".\\root.ini"); //如果打开错误
if (iCount==0)
{
MessageBox("未找到配置文件");
return;
}
HTREEITEM hRoot;
//读入根节点并储存
TV_INSERTSTRUCT TCItem;
TCItem.hParent=TVI_ROOT;
TCItem.hInsertAfter=TVI_LAST;
TCItem.item.mask=TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
::GetPrivateProfileString("Root","Name[1]",NULL,schar,50,".\\root.ini");
TCItem.item.pszText=schar;
TCItem.item.lParam=nid; //序号
TCItem.item.iImage=1;
TCItem.item.iSelectedImage=0;
hRoot=m_tr.InsertItem(&TCItem);
nid++;
//获得最大层数
int nc=::GetPrivateProfileInt("Count","Ceng",0,".\\root.ini");
HTREEITEM *hR;
hR=new HTREEITEM[nc]; //创建nc个节点的指针,用来指向每层当前的父节点
hR[0]=hRoot; //第0层为根节点
int ceng; //读入层数
CString st;
for(int i = 2; i <= iCount; i++)//读入剩下的iCount-1个节点
{
st.Format("Ceng[%d]",i);
ceng=::GetPrivateProfileInt("Root",st,0,".\\root.ini");
st.Format("Name[%d]",i);
::GetPrivateProfileString("Root",st,NULL,schar,50,".\\root.ini");
TCItem.item.pszText=schar;
TCItem.item.lParam=nid++;
TCItem.hParent=hR[ceng-1]; //当前读入节点的父节点为父节点层的当前节点
hR[ceng]=m_tr.InsertItem(&TCItem);//读入节点层的当前节点为读入节点
}
}
不是很详细,有不明白的地方就说吧~
欢迎转载,请标明出处,谢谢
联系客服