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); file://设置图象列表
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()
{ file://增加子项功能函数
htreeitem hsel=m_treectrl.getselecteditem();//取得选择项句柄
if(hsel==null) return;//无任何选项则返回
static int naddno=100;//编号大于100为新增数据
tv_insertstruct tcitem;//定义插入项数据结构
tcitem.hparent=hsel; file://设置父项句柄
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()
{ file://删除子项功能函数
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()
{ file://排序子项功能函数
htreeitem hsel=m_treectrl.getselecteditem();//取得选项句柄;
if(hsel==null) return;//无任何选项则返回
m_treectrl.sortchildren(hsel);
}
(6)查看功能的实现
查看功能用来查看选中树项的有关信息,函数中中显示了树项的文本名称和标识号,可以将这两个信息作为查找关键字,来查看其它更详细的信息。
void cvctreedlg::onview()
{ file://查看选中项功能函数
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 编号:d",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()),而其余数据结构和代码均不需要修改,实现起来比较容易。笔者实现的树控制和视程序的运行结果如下:
树控制的演练示例结果
树视演练结果示例
第5章 演练ctab
5.1 标5.2 签控制的主要功能
标签控制(tab control)是用来在一个窗口如对话框等中的同一用户区域控制多组显示信息或控制信息,由顶部的一组标签来控制不同的信息提示,标签即可以是文本说明也可以是一个代表文本含义的图标,或是两者的组合。针对不同的选择标签,都会有一组提示信息或控制信息与之相对应,供用户进行交互操作,这在windows98的属性表中最常见。另外还存在一种特殊风格的标签,即tbs_buttons风格的标签,这种标签外观类似按钮,通过鼠标点击改变状态,一般用来执行一些功能而不是用来显示或控制信息。
提到标签,最快想到的应该是属性表对话(property sheet),这两者的配合应用更是随处可见。属性表对话框有时也称为多页对话框(multiple-page dialog)或是标签对话框(table dialog),最多可设置24个属性页(property page),通过顶部的标签来选择不同的属性页。另外还有一种特殊的属性表对话框,就象vc++5.0中的类向导appwizard一样,其不存在供用户选择的标签,而是按照顺序依次控制属性页的显示,并且还有一般属性页中不存在的“确认”、“上一步”、“下一步”、“完成”和“帮助”等按钮。
标签控制在mfc中只存在一种封装形式,即控制类ctabctrl。在使用标签时即可以在对话框中直接添加,也可以在窗口中作为子窗口来使用,只不过这样应用时需要选创建标签。
5.3 标5.4 签控制的对象结构
5.4.1 标5.4.2 签控制的建立方法
ctabctrl&tabctrl 建立标签控制对象结构
create 建立标签控制并绑定对象
标签控制ctabctrl::create的调用格式如下:
bool create( dword dwstyle, const rect& rect, cwnd* pparentwnd, uint nid );
其中参数dwstyle用来确定标签控制的风格;rect用来控制标签的大小和位置;pparentwnd用来确定标签控制的父窗口句柄;nid用来确定标签控制的标识符。
标签控制的风格可以是如下值的组合:
tcs_buttons 表示将标签外观定义成类似按钮
tcs_fixedwidth 使所有标签具有相同的宽度
tcs_focusnever 使特定标签永远不接收输入焦点
tcs_focusonbuttondown 当标签被鼠标点击时接收输入焦点,其仅与tcs_buttons合用
tcs_forceiconleft 强制图标在左面,剩余部分使标签居中
tcs_forcelabelleft 使图标和标签均左对齐
tcs_multiline 允许标签控制显示多行标签
tcs_ownerdrawfixed 允许父窗口自绘标签
tcs_rightjustify 使标签右对齐
tcs_shareimagelists 当控制被撤消时标签控制的图像不被撤消
tcs_tooltips 允许标签控制存在工具提示控制
tcs_tabs 标签正常显示,为默认状态
tcs_singleline 将标签只显示在一行上,默认状态
tcs_raggedright 不使标签自动填满控制区域,默认状态
同样,标签控制还可以使用窗口的一些控制风格:
ws_child 为标签控制创建子窗口,只能与ws_popup风格一起使用
ws_visible 建立一个初始可视的标签控制
ws_disabled 建立一个初始不可视的标签控制
ws_group 建立标签控制群组的第一个控制
ws_tabstop 建立可用tab键移动的标签控制
5.4.3 标5.4.4 签控制的属性类
标签控制的属性类包括取得与标签控制相关联的图像列表getimagelist、设置标签控制的图像列表setimagelist、取得标签控制中标签的总数getitemcount、取得标签控制中特定标答的相关信息getitem、设置标签的部分或全部属性setitem、检测当前被选中的标签getcursel、将一个标签设置为选中状态setcursel和取得具有当前输入焦点的标签setcursel等。
5.4.5 标5.4.6 签控制的操作方法
标签控制的操作方法包括在标签控制中插入一个标签insertitem、删除一个标签 deleteitem、从标签控制中删除所有项目deleteallitems、从标签控制中删除一个图像列表removeimage和绘制标签控制中的特定一项drawitem等。
5.5 标5.6 签控制的数据结构
在使用标签控制时,必须使用的函数就是在标签控制中插入标签。函数insertitem的原形如下:
bool insertitem(int nitem,tc_item * ptabctrlitem);
该函数中的tc_item为添加标签时所使用信息的数据结构,其数据成员的定义方法及含义如下:
typedef struct _tc_item {
uint mask; // 确定结构成员的屏蔽或设置位
uint lpreserved1; // 保留未用
uint lpreserved2; // 保留未用
lpstr psztext; // 标签名称字符串
int cchtextmax; // 标签名称字符串缓冲区大小
int iimage; // 标签控制的图像索引号
lparam lparam; // 应用程序定义的相关32位数据
} tc_item;
当鼠标点击标签控制中的标签时,标签控制就会向其父窗口发送相关的通知消息,通过处理这些通知消息,程序可以实现各种功能。
5.7 属性表和属性页的基本用法
在标签控制过程中,属性表对话框和属性页是必不可少的。在mfc类库中,属性表对话框类cpropertysheet是由cwnd类派生而来的,而属性页类cpropertypage是由cdialog类派生而来的,它们的用法基本相同:
1、创建所有的属性页。创建属性页的方法与创建一般对话框资源的方法一样,利用对话框编辑器可以为每个属性页创建一个对话框模板,其区别在于,当利用类向导classwizard为属性页生成类时应选择属性页类cpropertypage作为基类,而不是将一般的对话框类cdialog作为基类;
2、创建属性表对话框,并将事先创建好的各属性页添加进去,两者的创建顺序可以互换,但在创建完之后将属性页添加到属性表对话框中去这一步是必须要做的;
3、显示属性表对话框。虽然属性表对话框类cpropertysheet不是由对话框类cdialog派生而来的,但两者的操作非常类似,调用domodal()函数就会显示一个模态属性表对话框,而调用create()操作就会显示一个非模态的属性表对话框;
4、对数据交换的处理。和对话框类似,属性表对话框与对象之间的数据交换也是通过数据成员2来实现的,只是属性表本身不带数据成员,而实际进行数据交换的是属性页中的数据成员;
5、对向导对话框的处理。如果要显示一个向导对话框,在显示之前应首先调用setwizardmode()函数对向导对话框进行特殊处理,对于存在按钮的向导对话框,还应调用setwizardbuttons()来对向导对话框的按钮功能进行定制,在用户操作结束时还应调用setfinishtext()函数将“完成”按钮设置为有效状态。
5.8 标5.9 签控制的应用技巧示例程序
本文给出一个基于文档的标签应用实例。实例程序中通过简单设置菜单、标签和属性表来演示标签控制的实际应用技巧,程序通过选择菜单选项弹出设置正文颜色、字体和修饰等属性表对话框来和用户进行简单交互。其实现步骤如下:
1、利用应用程序向导appwizard创建一个基于文档的工程tab,在选择工程类型时应选择单文档;
2、利用资源中的菜单生成器,删除无用菜单,并增加如下菜单结构
菜单名 标识符
设置(s) (弹出菜单名)
背景设置(b) idm_bkgrnd
前景设置(f) idm_frgrnd
3、利用对话框设计器设置属性表对话框所需要的四个属性页,注意在选择基类时应将属性 页类cpropertypage作为基类,并将对话框及菜单等控件的所有属均改为中文。四个属性页及其包括的控件内容分别为:(1)文字属性对话框包括一个输入文字的文本输入框,用于输入和修改在窗口上显示的文字;(2)字体属性对话框包括三个选中框,用来确定显示的字体修饰;(3)字间距属性对话框包括一个用于显示提示信息的标签和用于输入字间距大小的文本输入框;(4)颜色属性对话框包括一个成组框和三个单选圆钮;(5)窗口中设置一个用于显示输入文字的标签。
以上控制的设置参数如下:
控制名称 标题名称 标识符串
标签控制 idc_tabctrl
表态文本 字间距(10-100) idc_static1
编辑框 idc_list
成组框 颜色 idc_static2
单选按钮 黑色 idc_black
红色 idc_red
蓝色 idc_blue
文本框(编辑框) idc_text
设置字体(复选按钮)粗体 idc_bold
斜体 idc_italic
下划线 idc_underline
按 钮 确认 idok
取消 idcancel
利用类向导classwizard在属性表对话框ctabdlg、属性页对话框ctextpage和cstylepage中分别加入如下数据成员:
标识符串 类型 数据成员
idc_tabctrl ctabctrl m_tabctrl
idc_dist int m_ndist
idc_black int m_ncolor
idc_text cstring m_ctext
idc_bold bool m_bbold
idc_italic bool m_bitalic
idc_underline bool m_bunderline
以上数据成员也可以在tabdlg.h、stylepage.h和textpage.h中利用手工方法增加。
4、将要显示的数据成员加入到视类中去,来和对话框之间进行数据交换,并且将其在初始化函数中进行数据初始化。
(1)在tabview.h中增加如下代码:
#include "tabdlg.h"
#include "textpage.h"
#include "stylepage.h"
class ctabview : public cview
{public:
int ndist;//数值
int ncolor;//颜色
cstring ctext;//中文字符串
bool bbold,bitalic,bunderline;//字体属性
}
(2)在tabview.cpp中对数据成员进行如下初始化。
ctabview::ctabview()
{ ndist=20;
ncolor=1;
ctext=cstring("标签控制演示实例");
bbold=bitalic=bunderline=false;
}
(3)在tabdlg.cpp中向控制中增加标签,来实现背景设置功能。
bool ctabdlg::oninitdialog()
{ cdialog::oninitdialog();
tc_item tcitem;//添加标签
tcitem.mask=tcif_text;
tcitem.psztext="字 间 距";
m_tabctrl.insertitem(0,&tcitem);
tcitem.psztext="颜色设置";
m_tabctrl.insertitem(1,&tcitem);
m_tabctrl.setcursel(1);
return true;
}
当标签切换时,标签控制会自动向对话框窗口发送tcn_selchange通知消息,这时需要根据所选择的标签索引号对属性页的显示和隐藏进行切换控制,应完善onselchangetabctrl()函数:
void ctabdlg::onselchangetabctrl(nmhdr* pnmhdr, lresult* presult)
{ int ipage=m_tabctrl.getcursel();//所选标签号
switch(ipage){
case 0://字间距
getdlgitem(idc_static2)->showwindow(sw_hide);//隐藏选择按钮
getdlgitem(idc_black)->showwindow(sw_hide);//隐藏选择按钮
getdlgitem(idc_red)->showwindow(sw_hide);//隐藏选择按钮
getdlgitem(idc_blue)->showwindow(sw_hide);//隐藏选择按钮
getdlgitem(idc_static1)->showwindow(sw_show);//显示输入项数
getdlgitem(idc_dist)->showwindow(sw_show);//显示输入项数
break;
case 1://颜色设置
getdlgitem(idc_static1)->showwindow(sw_hide);//隐藏项数输入
getdlgitem(idc_dist)->showwindow(sw_hide);//隐藏项数输入
getdlgitem(idc_static2)->showwindow(sw_show);//显示选项选择
getdlgitem(idc_black)->showwindow(sw_show);//显示选项选择
getdlgitem(idc_red)->showwindow(sw_show);//显示选项选择
getdlgitem(idc_blue)->showwindow(sw_show);//显示选项选择
break;
}
*presult = 0;
}
(4)菜单功能的完善。在执行相应的菜单功能时,必须对类向导增加的相应功能函数进行代码完善,这就要处理tabview.cpp文件,背景设置功能函数如下:
void ctabview::onbkgrnd()
{ ctabdlg ctd;
ctd.m_ndist=ndist;
ctd.m_ncolor=ncolor;
if(ctd.domodal()==idcancel) return;
ndist=ctd.m_ndist;
ncolor=ctd.m_ncolor;
invalidate();//重新绘制窗口
}
同样,也要对前景设置功能函数进行完善:
void ctabview::onfrgrnd()
{ cpropertysheet cps("前景设置");//创建属性表对象
ctextpage ctp; file://显示文字属性页
cstylepage csp;//显示字体属性页
ctp.m_ctext=ctext;
csp.m_bbold=bbold;
csp.m_bitalic=bitalic;
csp.m_bunderline=bunderline;
cps.addpage(&ctp);//添加属性页
cps.addpage(&csp);
if(cps.domodal()==idcancel) return;
ctext=ctp.m_ctext;
bbold=csp.m_bbold;
bitalic=csp.m_bitalic;
bunderline=csp.m_bunderline;
invalidate();//重新绘制窗口
}
(5)为了充分演示标签控制与各属性页之间的数据交换功能,应该实现标签控制各属性页与用户之间数据交换结束后的窗口显示功能,笔者实现的功能函数显示了由属性页中输入的字体及背景网格功能,tabview.cpp中的对应函数代码如下:
void ctabview::ondraw(cdc* pdc)
{ ctabdoc* pdoc = getdocument();
assert_valid(pdoc);
// todo: add draw code for native data here
rect rc;
getclientrect(&rc);
int i,j,k;
cpen pen,*poldpen;
colorref color;
switch (ncolor){
case 0:color=rgb(0,0,0); file://设置黑色
break;
case 1:color=rgb(0xff,0,0);//设置红色
break;
case 2:color=rgb(0,0,0xff);//设置蓝色
break;
}
pen.createpen(ps_solid,1,color);
poldpen=pdc->selectobject(&pen);//绘制背景网格
j=rc.right/ndist+1;
k=rc.bottom/ndist+1;
for(i=0;i<j+k;i++){
pdc->moveto(i*ndist,0);
pdc->lineto(0,i*ndist);
if(i<j){
pdc->moveto(i*ndist,0);
pdc->lineto(rc.right,(j-i)*ndist);
} else {
pdc->moveto(0,(i-j)*ndist);
pdc->lineto(rc.right,i*ndist);
}
}
pdc->selectobject(&poldpen);
cfont font,*poldfont;
font.createfont(50,0,0,0,bbold?1000:200,
bitalic,bunderline,0,ansi_charset,
out_default_precis,clip_default_precis,
default_quality,default_pitch,null);
poldfont=pdc->selectobject(&font);
pdc->textout(20,20,ctext);
pdc->selectobject(poldfont);
}
标签控制的整个实现过程虽然比较繁锁,但只要掌握其实现的本质,设计一个优秀的标签控制界面也并非很困难的事情。
笔者实现的标签控制的演练示例结果如下:
标签控制演练示例结果