第1章 演练CHeadCtrl
表头控制(CHeaderCtrl)通常应用在窗口中的文本或数据的列表之上。一般为数据列的标题,可以包括多个部分,用户可以拖动每个部分并可以控制每列的宽度。表头控制类提供了普通表头控制的基本方法,只有在WINDOWS95以后版本系统中才提供,其方法包含在afxcmn.h文件中,一般与标签控制(CTabCtrl)和列表控制(CListCtrl)组合使用。
1.1 表头控制的对象结构
1.1.1 表头控制对象的建立方法
CHeaderCtrl &cheaderCtrl 建立表头控制对象
Create 建立表头并绑定对象
CHeaderCtrl::Create的格式如下:BOOL Create( DWORD dwStyle, const RECT&
rect, CWnd* pParentWnd, UINT nID );
其返回值非零时初始化成功,否则失败。
参数dwStyle用来确定表头控制类型;rect用来确定表头控制的大小和位置;ParentWnd用来确定表头控制的父窗口;nID用来表示表头控制的标志。
表头控制风格包括:
HDS_BUTTONS 表示表头控制外观类似按钮;
HDS_HORZ 表示表头控制为水平排列;
HDS_VERT 表示表头控制为垂直排列;
HDS_HIDDEN 表示表头控制为隐藏模式。
它也可以使用普通类控制风格,包括:
CCS_BOTTOM 设置控制位置在父窗口的底部并与父窗口同样宽度;
CCS_NODIVIDER 在控制顶部形成两个像素的高亮区;
CCS_NOHILITE 在控制顶部形成一个像素的高亮区;
CCS_NOMOVEY 在响应WM_SIZE消息时重置大小并水平排列;
CCS_NOPARENTALIGN 使控制自动靠近父窗口的顶部或底部;
CCS_NORESIZE 设置初始大小或新值时使控制使用默认宽度和高度;
CCS_TOP 设置在父窗口客户区域的顶部并与父窗口同样宽度;
同样表头控制也可以使用窗口控制风格,包括:
WS_CHILD 建立一个子窗口,不能用于WS_POPUP窗口类型;
WS_VISIBLE 建立一个初始时不可见的窗口;
WS_DISABLED 建立一个初始时无效的窗口;
WS_GROUP 确定可用光标移动的控制群组;
WS_TABSTOP 确定可用TAB控制移动站点;
表头控制一般分为两个步骤,首先确定表头控制的数据结构,然后建立表头控制并绑定对象。
1.1.2 表头控制的属性
表头控制的属性包括取得表头控制中项目的数量GetItemCount、取得表头控制中某一项目的内容GetItem和设置表头控制中某一项目的内容SetItem。
1.1.3 表头控制的操作方法
表头控制的操作方法包括向表头控制中插入一个新项目InsertItem、从表头控制中删除一个项目DeleteItem和绘制表头中给定的项目DrawItem等。
1.2 表头控制的数据结构
在使用表头控制时,首先必须建立一个数据结构HD_ITEM,其结构定义如下:
typedef struct _HD_ITEM
{ UINT mask; //结构成员有效控制位
int cxy; //表头项目的宽度
LPSTR pszText; //表头项目内容
HBITMAP hbm; //表头项目的位置句柄
int cchTextMax; //表头内容字符串长度
int fmt; //表头项目的格式
LPARAM lParam; //应用程序定义的32位数据
} HD_ITEM;
屏蔽控制位说明了数据结构成员中包含的有效数据,可以是下面标志的组合:
HDI_BITMAP hbm成员有效
HDI_FORMAT fmt 成员有效
HDI_LPARAM lParam成员有效
HDI_TEXT pszText 和cchTextMax 成员有效
HDI_WIDTH cxy 成员有效并确定项目宽度值
格式标志位fmt可以是以下标志的组合:
HDF_CENTER 表头项目居中
HDF_LEFT 表头项目左对齐
HDF_RIGHT 表头项目右对齐
HDF_BITMAP 表头显示一个位图
HDF_OWNERDRAW 由主窗口自绘表头项目
HDF_STRING 表头项目为一个字符串
1.3 表头控制的应用技巧
由于表头控制无法单独使用,其主要是配合列表控制和标签控制,并多以文字表头应用多见,InsertItem、SetItem和GetItem是常用的方法,如在列表控制时利用InsertColumn属性就可以增加一个表列的文本标题,具体用法和技巧见列表控制和标签控制。下面以在列表控制中的增加表列的方法来具体说明:
lvcol.pszText=品 名;//设置第一列名
lvcol.iSubItem=i; //表列宽
m_ListCtrl.InsertColumn(i++,&lvcol);//插入一列
lvcol.pszText=数 量;//第二列名
lvcol.iSubItem=i;
lvcol.cx=70;
m_ListCtrl.InsertColumn(i++,&lvcol);//插入一列
......
第2章 演练CImageList
图像列表控制(CImageList)是相同大小图像的一个集合,每个集合中均以0为图像的索引序号基数,图像列表通常由大图标或位图构成,其中包含透明位图模式。可以利用WINDOWS32位应用程序接口函数API来绘制、建立和删除图像,并能实现增加、删除、替换和拖动图像等操作。图像列表控制提供了控制图像列表的基本方法,这些方法在WINDOWS95及以后版本才能实现。
2.1 图像控制的对象结构
2.1.1 图像控制的数据成员
m_hImageList 连接图像对象的控制句柄
2.1.2 图像控制的建立方法
CimageList&imageList建立图像控制对象结构
Create 初始化图像列表并绑定对象
图像控制的建立方法如下:
BOOL Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow );
BOOL Create( UINT nBitmapID, int cx, int nGrow, COLORREF crMask );
BOOL Create( LPCTSTR lpszBitmapID, int cx, int nGrow, COLORREF crMask );
BOOL Create( CImageList& imagelist1, int nImage1, CImageList& imagelist2
,int nImage2,int dx, int dy );
其中各项参数的含义为:cx定义图像的宽度,单位为象素;cy定义图象的高度,单位为象素;nFlags确定建立图像列表的类型,可以是以下值的组合:ILC_COLOR、ILC_COLOR4、ILC_COLOR8、ILC_COLOR16、ILC_COLOR24、ILC_COLOR32、ILC_COLORDDB和ILC_MASK;nInitial用来确定图像列表包含的图像数量;nGrow用来确定图像列表可控制的图像数量。
NbitmapID 用来确定图像列表联系的位图标志值;crMask表示颜色屏蔽位;
LpszBitmapID 用来确定包含位图资源的标识串;
imagelist1 指向图像列表控制对象的一个指针;nImage1图像列表1中包含的图像数 量;imagelist2指向图像列表控制对象的一个指针;nImage2图像列表2中包含的图像数量;dx表示以象素为单位的图像宽度;dy表示以象素为单位的图像高度。
同样,图像控制的建立也包括两个步骤,首先建立图像列表结构,然后建立图像列表控制。
2.1.3 图像控制的属性类
图像控制的属性类包括返回m_hImageList.控制句柄GetSafeHandle、取得图像列表中的图像数量GetImageCount、设置图像列表的背景颜色SetBkColor、取得图像列表的背景颜色SetBkColor和取得图像的有关信息SetBkColor。
2.1.4 图像控制的操作方法
图像控制的操作方法包括将一个图像列表绑定到一个对象上Attach、将对象上的图像列表解除绑定并返回句柄Detach、删除一个图像列表DeleteImageList、将一个图像增加到图像列表中Add和将一个图像从图像列表中删除Remove等。
2.2 图像控制的应用技巧
对于图像控制,同样不能单独使用,必须与列表控制、树控制和标签控制相互结合应用,下面分别介绍其具体应用技巧。
2.2.1 图像控制在列表控制中的应用技巧
2.2.1.1 设置图像控制CListCtrl::SetImageList的调用格式如下:
CImageList* SetImageList( CImageList* pImageList, int nImageList );
其返回值是指向前一个图像列表控制的一个指针,如果不存在前一个图像列表则为NULL;其中参数pImageList是指向图像列表的标识,nImageList是图像列表的类型,可以是如下值:
LVSIL_NORMAL 用大图标方式进行图像列表;
LVSIL_SMALL 用小图标方式进行图像列表;
LVSIL_STATE 以图像状态进行图像列表;
2.2.1.2 取得图像控制CListCtrl::GetImageList的调用格式如下:
CImageList* GetImageList( int nImageList ) const;
其返回值为指向图像列表控制的指针,其中nImageList用来确定取得返回值的图像列表的 值,其取值与设置图像列表函数相同。
③图像控制在列表控制中的应用示例
CImageList Cil1,Cil2; //定义大小图标像列表
CVCLISTApp *pApp=(CVCLISTApp *)AfxGetApp();//取得列表控制程序
Cil1.Create(32,32,TRUE,2,2);//建立32位图像控制
Cil1.Add(pApp->LoadIcon(IDI_GJ));//增加选中状态图像
Cil1.Add(pApp->LoadIcon(IDI_XS));//增加非选中状态图像
Cil2.Create(16,16,TRUE,2,2); //建立16位图像控制
Cil2.Add(pApp->LoadIcon(IDI_GJ));//增加选中状态图像
Cil2.Add(pApp->LoadIcon(IDI_XS));//增加非选中状态图像
m_ListCtrl.SetImageList(&Cil1,LVSIL_NORMAL);//设置大图标控制
m_ListCtrl.SetImageList(&Cil2,LVSIL_SMALL);//设置小图标控制
2.2.2 图像控制在树控制中的应用技巧
2.2.2.1 设置图像控制CTreeCtrl::SetImageList的调用格式如下:
CImageList* SetImageList( CImageList * pImageList, int nImageListType );
其返回值为指向前前一个图像列表的指针,否则为NULL;参数pImageList为指向图像列表的标识,如果pImageList为NULL则所有的图像都将从树控制中被清除;nImageListType为图像列表设置的类型,可以是如下值之一:
TVSIL_NORMAL 设置正常图像列表,其中包括选中和非选中两种图标;
TVSIL_STATE 设置图像列表状态,指用户自定义状态;
2.2.2.2 取得图像控制CTreeCtrl::GetImageList的调用格式如下:
CImageList* GetImageList( UINT nImage );
如果调用成功则返回图像列表控制指针,否则为NULL;nImage为取得返回值的图像列表类型,其取值和取得图像列表控制完全相同。
2.2.2.3 图像控制在树控制中的应用示例
CImageList Cil1,Cil2;//定义大小图标像列表
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);//设置图像控制列表
然后在树控制的结构定义中进行如下设置:
TCItem.item.iImage=0; //设置未选中图像索引号
TCItem.item.iSelectedImage=1;//设置选中时图像引号
2.2.3 图像控制在标2.2.4 签控制中的应用技巧
2.2.4.1 设置图像控制CTabCtrl::SetImageList的调用格式
CImageList * SetImageList( CImageList * pImageList );
其返回值为指向前一个图像列表的指针,如果不存在前一个图像列表则为NULL;pImageList为标识TAB控制的图像列表指针。
2.2.4.2 取得图像控制CTabCtrl::GetImageList的调用格式
HIMAGELIST GetImageList() const;
其返回值为指向TAB控制的图像列表指针,如果调用不成功则为NULL。
其应用技巧较前两种更加简单,这里不再赘述。
第3章 演练CList
3.1 列表控制的主要功能
列表控制和视(ListControl&View)主要用来以各种方式显示一组数据记录供用户进行各种操作,Windows98/95中资源管理器中的“查看”标签下的“大图标|小图标|列表|详细资源”就是一个非常好的典型应用。列表中的记录可以包括多个数据项,也可以包括表示数据内容的大小图标,用来表示数据记录的各种属性。
列表控制提供了对Windows列表功能操作的基本方法,而使用列表视的视函数可以对列表视进行各种操作,通过调用视成员GetListCtrl获取嵌在列表视内列表控制的引用(GetListCtrl& ctrlList =GetListCtrl()),就可以和列表控制一样进行各种操作。操作一个列表控制和视的基本方法为:创建列表控制;创建列表控制所需要的图像列表;向列表控制添加表列和表项;对列表进行各种控制,主要包括查找、排序、删除、显示方式、排列方式以及各种消息处理功能等;最后撤消列表控制。
对于一个列表控制,其最典型最常用的显示控制方式为:大图标方式(LVS_ICON)、小图标方式(LVS_SMALLICON)、列表显示方式(LVS_LIST)和详细资料(即报告LVS_REPORT)显示方式。这可以通过设置其显示方式属性来实现。要控制列表所在窗口的风格,可通过功能函数GetWindowLong和SetWindowLong来实现,要控制列表图标的对齐方式,可通过设置列表窗口的风格LVS_ALIGNTOP或LVS_ALIGNLEFT来实现,
3.2 列表控制的对象结构
3.2.1 列表控制的建立方法
CListCtrl&listCtrl 定义列表对象的结构
Create 建立列表控制并绑定对象
列表控制CListCtrl::Create的调用格式如下:
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
其中参数dwStyle用来确定列表控制的风格;rect用来确定列表控制的大小和位置;pParentWnd用来确定列表控制的父窗口,通常是一个对话框;nID用来确定列表控制的标识。其中列表控制的风格可以是下列值的组合:
LVS_ALIGNLEFT 用来确定表项的大小图标以左对齐方式显示;
LVS_ALIGNTOP 用来确定表项的大小图标以顶对齐方式显示;
LVS_AUTOARRANGE 用来确定表项的大小图标以自动排列方式显示;
LVS_EDITLABELS 设置表项文本可以编辑,父窗口必须设有LVN_ENDLABELEDIT风格;
LVS_ICON 用来确定大图标的显示方式;
LVS_LIST 用来确定列表方式显示;
LVS_NOCOLUMNHEADER 用来确定在详细资料方式时不显示列表头;
LVS_NOLABELWRAP 用来确定以单行方式显示图标的文本项;
LVS_NOSCROLL 用来屏蔽滚动条;
LVS_NOSORTHEADER 用来确定列表头不能用作按钮功能;
LVS_OWNERDRAWFIXED 在详细列表方式时允许自绘窗口;
LVS_REPORT 用来确定以详细资料即报告方式显示;
LVS_SHAREIMAGELISTS用来确定共享图像列表方式;
LVS_SHOWSELALWAYS 用来确定一直显示被选中表项方式;
LVS_SINGLESEL 用来确定在某一时刻只能有一项被选中;
LVS_SMALLICON 用来确定小图标显示方式;
LVS_SORTASCENDING 用来确定表项排序时是基于表项文本的升序方式;
LVS_SORTDESCENDING 用来确定表项排序时是基于表项文本的降序方式;
3.2.2 列表控制的属性类
列表控制的属性类包括取得列表控制的背景色GetBkColor、设置列表控制的背景色SetBkColor、取得列表控制的图像列表GetImageList、设置列表控制的图像列表SetImageList、取得列表项数目GetItemCount、取得列表控制的属性GetItem、取得与表项相关的数据GetItemData、设置表项的属性SetItem、设置与表项相关的数值SetItemData、取得相关联的下一个表项GetNextItem、设置列表控制的文本颜色SetTextColor、取得列表控制的文本背景颜色GetTextBkColor、设置表项的最大数目SetItemCount和取得被选中表项的数目GetSelectedCount等。
3.2.3 列表控制的操作方法
列表控制的操作方法包括插入一个新的表项InsertItem、删除一个表项DeleteItem、排序表项SortItems、测试列表的位置HitTest、重绘表项RedrawItems、插入一个表列InsertColumn、删除一个表列DeleteColumn、编辑一个表项文本EditLabel和重绘一个表项DrawItem等。
3.3 列表控制的数据结构
列表控制中包含两个非常重要的数据结构LV_ITEM和LV_COLUMN。LV_ITEM用于定义列表控制的一个表项,LV_COLUMN用于定义列表控制的一个表列,其定义格式分别为:
typedef struct _LV_ITEM {
UINT mask; //结构成员屏蔽位
int iItem; //表项索引号
int iSubItem; //子表项索引号
UINT state; //表项状态
UINT stateMask; //状态有效性屏蔽位
LPTSTR pszText; //表项名文本
int cchTextMax; //表项名最大长度
int iImage; // 表项图标的索引号
LPARAM lParam; // 与表项相关的32位数
} LV_ITEM;
typedef struct _LV_COLUMN {
UINT mask; //结构成员有效性屏蔽位
int fmt; //表列对齐方式
int cx; //表列的象素宽度
LPTSTR pszText; //表列的表头名
int cchTextMax; //表列名的文本长度
int iSubItem; //与表列关联的子表项索引号
} LV_COLUMN;
其中fmt可以取如下值:
LVCFMT_CENTER 表列居中对齐
LVCFMT_LEFT 表列左对齐
3.4 列表控制的应用技巧示例
本文给出具体实例演示列表控制及前面的表头控制和图像列表的应用技巧。步骤如下:
1、通过“FILE->NEW->PROJECTS->MFCAppWizard(EXE)”建立名为VCLIST的工程,在建立过程中选择基于对话框(Dialogbased)的应用;将对话框中的默认控件删除,并将所有对话框属性中的Language域设置为Chinese(P.R.C.),以使应用程序支持中文;
建立两个图标IDI_GJ和IDI_XS,用来表示图标的选中和非选中状态,对于每个图标都应建立32X32和16X16两种大小,以保证程序的需要;
3、在对话框窗口中设计组合框(Group Box),组合框中设置四个无线按钮(Radio)“大图标|小图标|列表|资料”,同时设置排序、删除和关闭三个控制按钮(Button),并在对话框中设置大小合适的列表控制(List Ctrl),其对应标识分别如下:
控制名称 标题名称 标识符号
列表控制 IDC_LISTCTRL
组合框 方式 IDC_STATIC
无线按钮 大图标 IDC_STDICON
小图标 IDC_SMLICON
列 表 IDC_LIST
资 料 IDC_REPORT
按钮 排 序 IDC_SORT
删 除 IDC_DEL
关 闭 IDOK
4、在设置无线按钮时,需要注意的是只有大图标的Group属性为选中状态,而其它无线按钮的状态均为默认值。
5、选中列表控制控件,选择“VIEW->ClassWizard->Memory Variables”,并利用IDC_ LISTCTRL引入成员变量,其变量类型为:
变量名 种类 变量类型
m_ListCtrl Control ClistCtrl
同时利用“MESSAGES MAP”为各无线按钮和命令按钮增加控制功能。
6、然后在包含文件和代码文件中分别加入如下代码:
(1)在VCLISTDlg.h中增加数据结构和定义
typedef struct tagSPS { //定义结构
char szPm[10]; //品名
int Lx; //0-GJ1-XS
char szSl[10]; //数量
char szDj[10]; //单价
char szJe[10]; //金额
} SPS;
int CALLBACK CompareFunc(LPARAM lParam1,LPARAM lParam2,LPARAM lParamSort);
(2)在VCLISTDlg.CPP中的起始处增加初始化数据和程序定义
//在文件开始处增加数据结构初始化
SPS Sps[]={//信息
{红梅,0,1000,30,30000},
{黄梅,0,1000,29,29000},
{绿梅,0,1000,28,28000},
{青梅,0,1000,27,27000},
{白梅,0,1000,31,31000},
{红梅,1,1000,30,30000},
{黄梅,1,1000,29,29000},
{绿梅,1,1000,28,28000},
{青梅,1,1000,27,27000},
{白梅,1,1000,31,31000}};
CImageList Cil1,Cil2;//大小图像列表
(3)在程序初始化处增加表头、图像和列表控制建立代码
BOOL CVCLISTDlg::OnInitDialog()
{CDialog::OnInitDialog();
//其它代码
// TODO: Add extra initialization here此处增加代码
LV_ITEM lvitem;
LV_COLUMN lvcol;
int i,iPos,iItemNum;
CVCLISTApp *pApp=(CVCLISTApp *)AfxGetApp();//创建图象列表
Cil1.Create(32,32,TRUE,2,2);
Cil1.Add(pApp->LoadIcon(IDI_GJ));
Cil1.Add(pApp->LoadIcon(IDI_XS));
Cil2.Create(16,16,TRUE,2,2);
Cil2.Add(pApp->LoadIcon(IDI_GJ));
Cil2.Add(pApp->LoadIcon(IDI_XS));//设置图象列表
m_ListCtrl.SetImageList(&Cil1,LVSIL_NORMAL);
m_ListCtrl.SetImageList(&Cil2,LVSIL_SMALL);//向列表控制中添加表列
lvcol.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcol.fmt=LVCFMT_CENTER;//居中
i=0;
lvcol.pszText=品 名;
lvcol.iSubItem=i;
lvcol.cx=70;
m_ListCtrl.InsertColumn(i++,&lvcol);
lvcol.pszText=数 量;
lvcol.iSubItem=i;
lvcol.cx=70;
m_ListCtrl.InsertColumn(i++,&lvcol);
lvcol.pszText=单 价;
lvcol.iSubItem=i;
lvcol.cx=70;
m_ListCtrl.InsertColumn(i++,&lvcol);
lvcol.pszText=金 额;
lvcol.iSubItem=i;
lvcol.cx=70;
m_ListCtrl.InsertColumn(i++,&lvcol);
//向列表控制中添加表项
iItemNum=sizeof(Sps)/sizeof(SPS);
for(i=0;i
lvitem.mask=LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM;
lvitem.iItem=i;
lvitem.iSubItem=0;
lvitem.pszText=Sps[i].szPm;
lvitem.iImage=Sps[i].Lx;
lvitem.lParam=i;
iPos=m_ListCtrl.InsertItem(&lvitem);//返回表项插入后的索引号
lvitem.mask=LVIF_TEXT;
lvitem.iItem=iPos;
lvitem.iSubItem=1;
lvitem.pszText=Sps[i].szSl;
m_ListCtrl.SetItem(&lvitem);
lvitem.iSubItem=2;
lvitem.pszText=Sps[i].szDj;
m_ListCtrl.SetItem(&lvitem);
lvitem.iSubItem=3;
lvitem.pszText=Sps[i].szJe;
m_ListCtrl.SetItem(&lvitem);
}
CheckRadioButton(IDC_STDICON,IDC_REPORT,IDC_STDICON);
return TRUE; // return TRUE unless you set the focus to a control
}
(4)完善列表显示方式代码
在利用Classwizard类向导创建各功能按钮显示功能函数之后,必须依次完善这些功能函数的代码,这些功能函数如下:
void CVCLISTDlg::OnStdicon()//设置大图标显示方式
{ // TODO: Add your control notification handler code here
LONG lStyle;
lStyle=GetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE);//获取当前窗口类型
lStyle&=~LVS_TYPEMASK; //清除显示方式位
lStyle|=LVS_ICON; //设置显示方式
SetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE,lStyle);//设置窗口类型
}
void CVCLISTDlg::OnSmlicon() { // TODO: Add your control notification handler code here
LONG lStyle;
lStyle=GetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE);//获取当前窗口类型
lStyle&=~LVS_TYPEMASK; //清除显示方式位
lStyle|=LVS_SMALLICON; //设置显示方式
SetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE,lStyle);//设置窗口类型
}
void CVCLISTDlg::OnList() //设置列表显示方式
{ // TODO: Add your control notification handler code here
LONG lStyle;
lStyle=GetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE);//获取当前窗口类型
lStyle&=~LVS_TYPEMASK; //清除显示方式位
lStyle|=LVS_LIST; //设置显示方式
SetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE,lStyle);//设置窗口类型
}
void CVCLISTDlg::OnReport() //详细资料显示方式
{ // TODO: Add your control notification handler code here
LONG lStyle;
lStyle=GetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE);//获取当前窗口类型
lStyle&=~LVS_TYPEMASK; //清除显示方式位
lStyle|=LVS_REPORT; //设置显示方式
SetWindowLong(m_ListCtrl.m_hWnd,GWL_STYLE,lStyle);//设置窗口类型
}
(5)删除功能的实现
要实现删除功能,必须取得选中表项的数和表项总数,并且需要从后向前进行依次删除,其原因是每个表项被删除后,其后各表项的索引号均会发生递减变化,如果采取从前向后删除的方法,就会造成无法正常删除选中的表项,其功能代码如下:
void CVCLISTDlg::OnDel() //删除按钮功能
{ // TODO: Add your control notification handler code here
int i,iState;
int nItemSelected=m_ListCtrl.GetSelectedCount();//所选表项数
int nItemCount=m_ListCtrl.GetItemCount();//表项总数
if(nItemSelected<1) return;
for(i=nItemCount-1;i>=0;i--){
iState=m_ListCtrl.GetItemState(i,LVIS_SELECTED);
if(iState!=0) m_ListCtrl.DeleteItem(i);
}
}
(6)排序功能的实现
列表控制有一个特殊的功能,当以详细资料方式显示时,列表顶部的表头可以当作按钮来使用,这可以通过列表控制创建时的风格来控制。当鼠标点击列表头名称时,列表控制就会向其父窗口发送一个LNV_COLUMNCLICK消息,利用类导向中列表控制IDC_LISTCTRL对应的LNV_COLUMNCLICK消息加入相应处理函数,就可将表列按照特定顺序进行排列。其函数使用方法见程序,其中iSort为排序的表列索引号,(PFNLVCOMPARE)CompareFunc为进行具体排序的回调函数,也就是说,通过鼠标点击表头实现的排序过程是由第三方开发的专用排序函数来实现的,排序函数只是实现表项的具体比较操作,而整个排序过程是由SortItemS属性通过不断调用这个函数来实现的。正常的排序过程是升序方式,通过调换排序函数中的参数值,就可实现降序排列,即将PARAM1与PARAM2调换位置。这个回调函数的前两个参数为表列中表项的索引号,第三个参数为排序的表列索引号。
void CVCLISTDlg::OnColumnclickListctrl(NMHDR* pNMHDR, LRESULT* pResult)
{ //鼠标左键单击表头处理函数
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
static int iSorted=-1;//排列序号
if (pNMListView->iSubItem==iSorted) return;
iSorted=pNMListView->iSubItem;
m_ListCtrl.SortItems((PFNLVCOMPARE)CompareFunc,iSorted);
*pResult = 0;
}
//排序时比较表项的回调函数
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2,LPARAM lParamSort)
{ char *text1,*text2;
switch (lParamSort){
case 0L:text1=Sps[lParam1].szPm;
text2=Sps[lParam2].szPm;break;
case 1L:text1=Sps[lParam1].szSl;
text2=Sps[lParam2].szSl;break;
case 2L:text1=Sps[lParam1].szDj;
text2=Sps[lParam2].szDj;break;
case 3L:text1=Sps[lParam1].szJe;
text2=Sps[lParam2].szJe;break;
}
return (strcmp(text1,text2));//结果为>0 =0 <0
}
同样,也可以通过专用按钮来实现排序功能,如本文的排序按钮对应的功能代码如下:
void CVCLISTDlg::OnSort()
{ // TODO: Add your control notification handler code here
m_ListCtrl.SortItems((PFNLVCOMPARE)CompareFunc,0);}
7、列表视的演练技巧
在使用列表视时,其方法与列表控制基本相同,只不过列表视是在窗口中来实现的而列表控制是在对话框中实现,列表视的各种功能是通过菜单来实现的而列表控制是通过按钮等方式来实现的,列表控制需要在对话框中创建列表控制控件而列表视直接占据整个窗口,在设计过程中只要将按钮和列表控制设计过程变为菜单设计,并注意在功能增加是在类向导中是通过菜单命令来操作,同时在每个功能函数前面增加取得列表视引用的命令( CListCtrl&ListCtrl = GetListCtrl()),而其余数据结构和代码均不需要修改,实现起来比较容易。
笔者实现的列表控制和视程序的运行结果如下:
列表控制演练示例结果
列表视演练示例结果
第4章 演练CTree
4.1 树控制的主要功能
树控制和视(TreeControl&View)主要用来显示具有一定层次结构的数据项,如资源管理器中的磁盘目录等,以供用户在其中进行各种选择。树控制中的每个数据项包括数据项名称的文本字符串和用于表示该数据项的图像,每个数据项下面均可包含各种子项,整个结构就象目录树一样。对于包含各种子项的数据项,可通过鼠标双击来展开或合拢,这可以通过控制树的不同风格来实现树控制的不同显示形态。这些风格主要包括:
TVS_HASLINES表示用连线来连接父项和它下面的各个子项,这可以使树的显示层次结构更加清晰,但在无父项的各子项之间并没有连线;
TVS_LINESATROOT表示在无父项的各子项即根下面的各子项之间存在连线;
TVS_HASBUTTONS表示在带有子项的父项前面增加一个带“+”或“-”的按钮,这使得用户也可以通过单击这个小按钮来实现子项的展开和合拢,当存在子项时,按钮的初始状态为“+”,当子项被展开时,按小按钮由“+”变为“-”号,当子项合拢时,小按钮由“-”变为“+”号,这一风格同样对于根项无效,如果需要可通过组合TVS_LINESATROOT风格来实现;
TVS_EDITLABELS表示允许让用户单击具有输入焦点的数据项来修改其名称。
对于树控制,MFC中也以两种形式来封装,即树控制(CTREECTRL)和树视(CTREEVIEW),来满足用户的不同需求,对于一般要求的用户如在对话框中应用,使用树控制比较方便,而对于具有较高要求的用户,在使用树视时还具有视窗口的各种方便特性,可以更好地满足文档/视结构的要求。当在窗口中使用树视时,树视会占满两个窗口的客户区域并自动随窗口的框架结构的调整而调整,并能够很好地处理诸如菜单、加速键和工具条中的各种命令消息。在使用树视时只要利用其成员函数CtreeView取得其一个引用,就可以象树控制一样方便地应用:CtreeCtrl &treeCtrl =GetTreeCtrl()。
4.2 树控制的对象结构
4.2.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消息
4.2.2 树控制的属性类
树控制属性类包括取得树控制中项数GetCount、取得树控制中项相对于父项的偏移值GetIndent、取得树控制图像列表控制句柄GetImageList、设置树控制图像列表控制句柄SetImageList、取得匹配下一个树项GetNextItem、判断给定树项是否包含子项ItemHasChildren、取得树项子项GetChildItem、取得下一个同属树项GetNextSiblingItem、取得前一个同属树项GetPrevSiblingItem、取得父树项GetParentItem、取得第一个可视树项GetFirstVisibleItem、取得下一个可视树项GetNextVisibleItem、取得前一个可视的树项GetPrevVisibleItem、取得被选中的树项GetSelectedItem、取得根树项GetRootItem、取得树项的属性GetItem、设置树项的属性SetItem、取得树项的状态GetItemState、设置树项的状态SetItemState、取得与树项关联图像GetItemImage、设置与树项关联图像SetItemImage、取得树项文本GetItemText、设置树项文本SetItemText和取得树项编辑控制句柄GetEditControl等。
4.2.3 树控制的操作方法
树控制的操作方法包括插入一个树项InsertItem、删除一个树项DeleteItem、删除所有树项DeleteAllItems、展开或合拢树项的子项Expand、选中特定树项SelectItem、选择一个树项作为第一个可视树项SelectSetFirstVisible、编辑一个可视的树项EditLabel和排序给定父树项的子树项SortChildren等。
4.3 树控制的数据结构
在使用树控制时需要了解两个个非常重要的数据结构TV_ITEM和TV_INSERTSTRUCT,前一个数据结构是用来表示树控制的树项信息,后一个数据结构是用来定义将树项增加到数据控制中所需要的数据内容。另外,还需要NM_TREEVIEW、TV_DISPINFO和TV_HITTESTINFO三个数据结构,这几个数据结构的定义方法如下:
4.3.1 基本数据项结构
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;
4.3.2 插入树项结构
typedef struct _TV_INSER TSTRUCT {
HTREEITEM hParent; //父项控制句柄
HTREEITEM hInsertAfter; //插入树项的位置
TV_ITEM item; //数据项的结构
} TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;
其中插入的位置如果是TVI_FIRST 或TVI_LAST ,则分别插入到树控制的最前面或最后面,如果是TVI_SORT ,则插入的树项自动插入到合适的位置。
4.3.3 树控制通知消息结构
typedef struct _NM_TREEVIEW {
NMHDR hdr; //通知消息句柄
UINT action; //通知消息标志
TV_ITEM itemOld; //原来的数据结构
TV_ITEM itemNew; //新的数据结构
POINT ptDrag; //拖动指针
} NM_TREEVIEW;
4.3.4 取得或设置数据结构
typedef struct _TV_DISPINFO { tvdi
NMHDR hdr; //通知消息控制句柄
TV_ITEM item; //数据项结构
} TV_DISPINFO;
4.3.5 指4.3.6 针测试数据结构
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 在客户区域的右侧
4.4 树控制的应用技巧示例
这里仍以基于对话框演示实例来具体介绍树控制及其和图像列表相结构的应用技巧:
通过“FILE->NEW->PROJECTS->MFCAppWizard(EXE)”建立名为VCTREE的工程,在建立过程中选择基于对话框(Dialogbased)的应用;将对话框中的默认控件删除,并将所有对话框属性中的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()),而其余数据结构和代码均不需要修改,实现起来比较容易。笔者实现的树控制和视程序的运行结果如下:
树控制的演练示例结果
树视演练结果示例
第5章 演练CTab
5.1 标5.2 签控制的主要功能
标签控制(TabControl)是用来在一个窗口如对话框等中的同一用户区域控制多组显示信息或控制信息,由顶部的一组标签来控制不同的信息提示,标签即可以是文本说明也可以是一个代表文本含义的图标,或是两者的组合。针对不同的选择标签,都会有一组提示信息或控制信息与之相对应,供用户进行交互操作,这在WINDOWS98的属性表中最常见。另外还存在一种特殊风格的标签,即TBS_BUTTONS风格的标签,这种标签外观类似按钮,通过鼠标点击改变状态,一般用来执行一些功能而不是用来显示或控制信息。
提到标签,最快想到的应该是属性表对话(PropertySheet),这两者的配合应用更是随处可见。属性表对话框有时也称为多页对话框(Multiple-PageDialog)或是标签对话框(Table Dialog),最多可设置24个属性页(PropertyPage),通过顶部的标签来选择不同的属性页。另外还有一种特殊的属性表对话框,就象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; //显示文字属性页
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); //设置黑色
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
pDC->MoveTo(i*nDist,0);
pDC->LineTo(0,i*nDist);
if(i
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);
}
标签控制的整个实现过程虽然比较繁锁,但只要掌握其实现的本质,设计一个优秀的标签控制界面也并非很困难的事情。
笔者实现的标签控制的演练示例结果如下:
标签控制演练示例结果
第6章 演练CToolBar
6.1 工具条控制的主要功能
所谓工具条就是具有位图和分隔符组成的一组命令按钮,位图按钮部分可以是下推按钮、检查盒按钮、无线按钮等。工具条对象类派生于主窗口架框类CframeWnd或CMDIFrameWnd,其类控制CToolBar::GetToolBarCtrl是MFC类库中封装的一个成员函数,允许使用类库中提供的一般控制和附加功能,CtoolBar类控制成员控制提供了Windows一般控制的所有功能,然而,通过调用GetToolBarCtrl成员函数取得引用后,可以使工具条具有更强的特性。
工具条的创建具有四个步聚:首先是建立工具条资源;然后建立工具条对象结构;其次通过调用建立函数建立工具条对象并绑定;最后调用LoadToolBar调入工具条资源。
另外,还可以通过直接加载位图的方法来建立,步骤如下:首先建立工具条对象;然后通过调用建立函数建立工具条并绑定对象;其次调入包含按钮的位图;最后利用SetButtons 函数设置按钮的风格并与位图建立联系。
其中,所有按钮位图均存放在一个位图文件中,按钮位图的大小相同,默认为16点宽、15点高,位图必须从左至右存放。设置按钮函数具有指向一组控制标识符ID的指针和索引值,用来确定每个按钮的位置,如果存在分隔符ID_SEPARATOR,那么该图像就不存在索引值。正常情况下工具条中的按钮都是单排从左至右排列的,可以通过SetButtonInfo函数改变排序规则。工具条中最终形成的按钮大小相同,均为24 x 22象素,每个按钮只对象一幅图像。工具条中的按钮默认为下推按钮,通过设置TBBS_CHECKBOX风格可以实现检查盒按钮,通过调用SetRadio成员函数可以实现无线按钮。
6.2 工具条控制的对象结构
6.2.1 工具条的对象结构
6.2.1.1 工具条的建立方法
CToolBar &ToolBar 建立工具条对象结构
Create 建立工具条对象并绑定
工具条类CToolBar::Create 的调用格式如下:
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
UINT nID = AFX_IDW_TOOLBAR );
其中参数pParentWnd用来确定指向工具条父窗口的指针;参数dwStyle用来确定工具条的风格,其取值如下;参数nID用来确定工具条子窗口的标识符。
CBRS_TOP 表示工具条在框架窗口的顶部
CBRS_BOTTOM 表示工具条在框架窗口的底部
CBRS_NOALIGN 表示工具条在父窗口改变大小时不响应
CBRS_TOOLTIPS 表示工具条具有动态提示功能
CBRS_SIZE_DYNAMIC 表示工具条是静态的不能改变
CBRS_SIZE_FIXED 表示工具条是动态的可以改变
CBRS_FLOATING 表示工具条是浮动的
CBRS_FLYBY 表示状态条上显示工具条中按钮的信息
CBRS_HIDE_INPLACE 表示工具条隐藏
除以上函数外,还包括设置按钮和位图的大小SetSizes、设置工具条的高度SetHeight、调入工具条资源LoadToolBar、调入工具条按钮位图LoadBitmap、设置工具条按钮位图SetBitmap、设置工具条中位图按钮的风格和索引值SetButtons等控制函数。
6.2.1.2 工具条的类属性
工具条控制类的属性包括取得标识符ID对象按钮索引CommandToIndex、取得索引对应的命令标识符ID或分隔符GetItemID、取得索引对应的矩形区域GetItemRect、取得按钮风格GetButtonStyle、设置按钮风格SetButtonStyle、取得按钮的ID标识-风格-图象数GetButtonInfo、设置按钮ID标识-风格-图象数SetButtonInfo、取得按钮提示文本GetButtonText、设置按钮提示文本SetButtonText和取得工具条直接存取控制GetToolBarCtrl等。
6.2.2 工具条控制的对象结构
6.2.2.1 工具条控制的建立方法
CToolBarCtrl &ToolBarCtrl 建立工具条控制对象结构
Create 建立工具条控制对象并绑定
工具条控制类CToolBarCtrl::Create的调用格式如下:
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
其中参数dwStyle用来确定工具条控制的风格,必须存在WS_CHILD风格;参数rect用来确定工具条控制的大小和位置;参数pParentWnd用来确定工具条控制的父窗口指针,不能为NULL;参数nID用来确定工具条控制的标识符。
可以利用WS_CHILD、WS_VISIBLE和WS_DISABLED来设置工具条窗口的风格,但必须合理设置如下控制风格:
CCS_ADJUSTABLE 允许用户处理工具条窗口大小,如果存在工具条窗口必须处理相应信
CCS_BOTTOM 使控制处于父窗口客户区域底部并与窗口同样宽
CCS_NODIVIDER 禁止在控制的顶部绘制2个象素的高亮条
CCS_NOHILITE 禁止在控制的顶部绘制1个象素的高亮条
CCS_NOMOVEY 使控制改变大小和移动时自动水平对齐,垂直对齐必须处理WM_SIZE消息
如果CCS_NORESIZE风格有效,则该风格无效
CCS_NOPARENTALIGN禁止控制自动移到父窗口顶部或底部,如果CCS_TOP或 CCS_BOTTOM风格
有效,则高度调整为默认而宽度可以改变
CCS_NORESIZE 禁止设置新的大小或无效值时使用默认宽度和高度值,而使用建立值
CCS_TOP 使控制自动停靠在父窗口客户区域顶部并与父窗口同样宽度
最后,还必须利用下面的风格来控制工具条
TBSTYLE_TOOLTIPS 使工具条建立并管理动态提示控制
TBSTYLE_WRAPABLE 使工具条控制按钮具有多行排列格式
6.2.2.2 工具条控制中的数据结构
工具条控制中最常用的数据结构为TBBUTTON,其具体结构如下:
typedef struct _TBBUTTON {
int iBitmap; // 基于0的位图索引值
int idCommand; // 按钮按下时发送的命令值
BYTE fsState; // 按钮的状态
BYTE fsStyle; // 按钮的风格
DWORD dwData; // 应用程序定义的数据
int iString; // 基于0的按钮标签字符串索引值
} TBBUTTON;
其中按钮状态fsState的值如下:
TBSTATE_CHECKED 表示按钮具有TBSTYLE_CHECKED风格并且被按下
TBSTATE_ENABLED 表示按钮允许接受输入,否则变灰不接受任何输入
TBSTATE_HIDDEN 表示按钮不可见并且不接受任何输入
TBSTATE_INDETERMINATE 表示按钮是变灰的
TBSTATE_PRESSED 表示按钮正被按下
TBSTATE_WRAP 表示按钮具有换行特性,该按钮必须具有TBSTATE_ENABLED状态
按钮风格style可以是下列值的组合:
TBSTYLE_BUTTON 表示建立标准下推按钮
TBSTYLE_CHECK 表示建立检查状态按钮
TBSTYLE_CHECKGROUP表示建立检查按钮群
TBSTYLE_GROUP 表示建立按下状态按钮群
TBSTYLE_SEP 表示建立按钮分隔符
6.2.2.3 工具条控制的类属性
工具条控制的类属性必然的联系判断按钮使能状态IsButtonEnabled、判断按钮检查状态IsButtonChecked、判断按钮按下状态IsButtonPressed、判断按钮是否隐藏IsButtonHidden、判断按钮变灰状态IsButtonIndeterminate、设置按钮状态SetState、取得按钮状态GetState、取得按钮有关信息GetButton、取得按钮总数GetButtonCount、取得按钮矩形区域GetItemRect、设置按钮结构大小SetButtonStructSize、设置按钮大小SetButtonSize、设置按钮位图大小SetBitmapSize、取得按钮提示控制GetToolTips、设置按钮提示控制SetToolTips等。
6.2.2.4 工具条控制类的操作方法
工具条控制类的操作方法包括使能按钮EnableButton、检查按钮CheckButton、按下按钮PressButton、隐藏按钮HideButton、变灰按钮Indeterminate、增加按钮AddButtons、插入按钮InsertButton、删除按钮DeleteButton、取得控制符ID对应的索引CommandToIndex、恢复工具条状态RestoreState、保存工具条状态SaveState和重新确定工具条大小AutoSize等。
6.3 工具条控制的应用技巧
可以这样说,工具条和上述常用控制是应用程序中不可缺少的功能元素,它的优劣会直接影响程序的基本功能和操作特性。所以这里将对工具条的建立技巧、状态保存与恢复、平面特性、停靠位置、排序方法、消息映射、状态更新、控制使用和属性控制等方面,全面阐述工具条的使用技巧。
6.3.1 工具条的建立技巧
6.3.1.1 普通工具条的建立方法
如果应用程序在建立时就具有工具条,则只需对工具条中的按钮图标进行简单的增加、修改和删除等操作就可满足要求。如果未建立或者想增加其它工具条,则应按步骤追加建立。
首先打开已建立好的基于单文档的框架工程文件CTool并选择"Insert->Resource->ToolBar"选项,插入工具条资源并设置资源标识符;然后编辑工具栏中的按钮图标和相应的按钮标识符,并利用类向导ClassWizard为按钮消息增加COMMAND和UPDATE_COMMAND_UI两种处理函数;在资源文件中增加和修改工具条图标的动态提示等内容;打开MainFrm.h包含文件在"CToolBar m_wndMainToolBar"后增加"CToolBar m_wndTestToolBar"等来创建增加的工具条对象;在MainFrm.h中设置建立函数所需的成员变量,如颜色变量为m_bColor、动态提示功能变量为m_bToolTips等,注意成员变量名与其获取的参数应完全对应以便使用;最后在MainFrm.cpp中的OnCreate()建立函数中按下述示例规则增加控制代码,其实现具体步骤如下:
①在MainFrm.h中增加工具条对象控制和成员变量
#define TOOLLEFT 18
class CMainFrame:public CFrameWnd
......//其它代码
public:
BOOL m_bToolTips;//工具条提示功能
......//其它代码
protected://工具条控制成员变量
CStatusBar m_wndStatusBar; //框架程序的状态条
CTestToolBar m_wndMainToolBar;//框架程序的工具条
CTestToolBar m_wndTestToolBar;//新增工具条
CTestToolBar m_wndDockToolBar;//浮动工具条
CTestToolBar m_wndDockNextBar;//浮动工具条
......//其它代码
}
框架程序中工具条的控制类正常应为CToolBar,可以是自己设计的派生类CtestToolBar(为笔者扩充平面特性等功能后的新工具条控制类名)等,具体根据实际需要而定。利用CDialogBar类和CStyleBar类还可以建立扩展类型的工具条,详见后面工具条中控制应用技巧,但在该文件头处必须
包含如下命令:
#ifndef __AFXEXT_H__
#include //直接存取CToolBar和CStatusBar
#endif
②在MainFrm.cpp中完善窗口建立函数
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{ if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
WINDOWPLACEMENT wp;//保存主窗口及工具栏窗口位置状态
if (ReadWindowPlacement(&wp))//读取位置状态信息
SetWindowPlacement(&wp); //设置位置状态信息
m_bToolTips=(AfxGetApp()->GetProfileInt(//读提示功能
_T("General"),_T("ToolTips"),1)!=0); //默认值为1
m_wndMainToolBar.SetState(TOOLLEFT,TRUE);//设置初始状态
EnableDocking(CBRS_ALIGN_ANY);//停靠位置,必须提前位置
if (!m_wndMainToolBar.Create(this,WS_CHILD|WS_VISIBLE
|CBRS_SIZE_DYNAMIC|CBRS_TOP|((m_bToolTips)?
(CBRS_TOOLTIPS|CBRS_FLYBY):0),IDR_MAINFRAME)||
!m_wndMainToolBar.LoadToolBar(IDR_MAINFRAME))
{ //CBRS_SIZE_DYNAMIC为锁定位置风格
TRACE0("主工具条MAINFRAME建立失败n");
return -1;} // 建立失败处理
......//建立其它工具条代码,基本相同
if (!m_wndStatusBar.Create(this)||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{ //建立状态条
TRACE0("Failed to create status barn");
return -1;} // fail to create
m_wndMainToolBar.SetWindowText(_T("主工具栏"));//设置标题
m_wndMainToolBar.EnableDocking(CBRS_ALIGN_ANY);//停靠位置
//m_wndMainToolBar.ModifyStyle(0,TBSTYLE_FLAT);//平面特性
......//设置其它工具条位置代码,基本相同
DockControlBar(&m_wndMainToolBar,
AFX_IDW_DOCKBAR_TOP);//锁定位置
DockControlBarLeftOf(&m_wndTestToolBar,
&m_wndMainToolBar);//连接工具条
DockControlBar(&m_wndDockToolBar,AFX_IDW_DOCKBAR_RIGHT);
m_wndDockToolBar.SetColumns(AfxGetApp()->GetProfileInt(
_T("General"),_T("Columns"),3));//恢复列格式,默认为3
DockControlBarLeftOf(&m_wndDockNextBar,&m_wndDockToolBar);
m_wndDockNextBar.SetColumns(AfxGetApp()->GetProfileInt(
_T("General"),_T("Columns"),3));
LoadBarState(_T("General"));//恢复保存的状态和位置
return 0;
}
以上建立过程除工具条建立和资源调用函数外,还涉及到了窗口和工具条的状态保存和恢复函数、注册表参数读取函数、工具条停靠位置函数、工具条标题修改函数、工具条连接函数、工具条列格式控制函数和工具条风格修改函数,其中工具条建立函数中的风格设置很重要,如果建立的工具条需要重新设置多行多列的排序功能,除正确设置工具条停靠位置参数外,还必须设置CBRS_SIZE_FIXED风格,即允许程序改变工具条窗口的尺寸,如果工具条不需要重新排序,则必须设置为CBRS_SIZE_DYNAMIC风格,否则工具栏不但不能进行重新排序和正确停靠到理想的位置,而且也无法正确保存和恢复工具条的位置和状态,这一点应引起编程者高度重视。其余函数以后分别介绍。
6.3.1.2 浮动工具条的建立方法
如果要建立浮动工具条,必须使用如下工具条的控制方法:
Cpoint pt(GetSystemMetrics(SM_CXSCREEN)-100,GetSystemMetrics(SM_CYSCREEN)/3);
FloatControlBar(&m_wndPaletteBar,pt);//浮动工具条
6.3.1.3 多位图工具条的建立方法
如果工具条存在多幅按钮位图,如单色和彩色等,则必须将工具条按钮存在在位图资源文件中而不是工具条资源中,并如下建立:
if(!m_wndDockToolBar.Create(this,WS_CHILD|WS_VISIBLE|
CBRS_SIZE_FIXED|CBRS_TOP|CBRS_TOOLTIPS,ID_PALETTEBAR)||
!m_wndDockToolBar.LoadBitmap(IDR_DOCKTOOLBAR)||
!m_wndDockToolBar.SetButtons(DockTool,
sizeof(DockTool)/sizeof(UINT)))
其中DockTool为按钮IDs数据结构,其定义方法如下:
static UINT BASED_CODE DockTool[]=
{ ID_SEPARATOR,
ID_STYLE_LEFT,
ID_STYLE_CENTERED,
ID_STYLE_RIGHT,
ID_STYLE_JUSTIFIED,
};
上述建立过程中的EnableDocking 函数必须放在所有工具条建立函数之前,否则可能出现很难发现的错误,如特殊工具条初始位置控制等。工具条的所有特性均在上述建立函数中确定,所以其建立过程是实现理想工具条的关键环节。
6.3.2 工具条状态保存和恢复6.3.3
很多应用程序中都具有保存和恢复应用程序及其工具条等状态的功能,即下次启动应用程序后进入上次的运行状态,这种功能只需进行一次界面布局便可永久保存,极大方便用户。
要正确保存和恢复应用程序界面状态,必须对应用程序窗口和工具条窗口等均进行保存和恢复,这需要完善应用程序的建立和关闭过程。具体步骤如下:
(1)首先利用类向导ClassWizard为应用程序增加窗口关闭WM_CLOSE消息处理功能OnClose();
(2)在MainFrm.cpp中为应用程序状态设置成员变量
static TCHAR BASED_CODE szSection[]=_T("Settings");
static TCHAR BASED_CODE szWindowPos[]=_T("WindowPos");
static TCHAR szFormat[]=_T("%u,%u,%d,%d,%d,%d,%d,%d,%d,%d");
(3)编制窗口位置状态读取和写入函数
static BOOL PASCAL NEAR ReadWindowPlacement(LPWINDOWPLACEMENT pwp)
{ //窗口位置状态读取函数,从INI文件中
CString strBuffer=AfxGetApp()->GetProfileString(szSection,szWindowPos);
if (strBuffer.IsEmpty()) return FALSE;
WINDOWPLACEMENT wp;//窗口位置数据结构
int nRead=_stscanf(strBuffer,szFormat,
&wp.flags,&wp.showCmd,//为数据结构读取数值
&wp.ptMinPosition.x,&wp.ptMinPosition.y,
&wp.ptMaxPosition.x,&wp.ptMaxPosition.y,
&wp.rcNormalPosition.left,&wp.rcNormalPosition.top,
&wp.rcNormalPosition.right,&wp.rcNormalPosition.bottom);
if (nRead!=10) return FALSE;
wp.length=sizeof wp;//结构大小
*pwp=wp; //结构指针
return TRUE;
}
static void PASCAL NEAR WriteWindowPlacement(
LPWINDOWPLACEMENT pwp)
{ //窗口位置状态写入函数,写到INI文件
TCHAR szBuffer[sizeof("-32767")*8+sizeof("65535")*2];
wsprintf(szBuffer,szFormat,//将参数值转换为字符串
pwp->flags,pwp->showCmd,
pwp->ptMinPosition.x,pwp->ptMinPosition.y,
pwp->ptMaxPosition.x,pwp->ptMaxPosition.y,
pwp->rcNormalPosition.left,pwp->rcNormalPosition.top,
pwp->rcNormalPosition.right,pwp->rcNormalPosition.bottom);
AfxGetApp()->WriteProfileString(szSection,szWindowPos,szBuffer);
}
(4)在应用程序建立函数OnCreate()中增加状态读取和设置功能
WINDOWPLACEMENT wp;//保存主窗口及工具条窗口位置状态
if (ReadWindowPlacement(&wp))//读取位置状态信息
SetWindowPlacement(&wp); //设置位置状态信息
(5)在应用程序建立函数OnCreate()中增加工具条状态恢复功能
m_wndDockToolBar.SetColumns(AfxGetApp()->GetProfileInt(
_T("General"),_T("Columns"),3));//恢复列格式,默认为3
m_wndDockNextBar.SetColumns(AfxGetApp()->GetProfileInt(
_T("General"),_T("Columns"),3));
LoadBarState(_T("General"));//恢复保存的状态和位置
(6)在应用程序关闭函数OnClose()中完善状态保存功能
void CMainFrame::OnClose()
{ //保存工具条等的状态
SaveBarState(_T("General"));//保存工具条状态
AfxGetApp()->WriteProfileInt(_T("General"),//写入列数
_T("Columns"),m_wndDockToolBar.GetColumns());
AfxGetApp()->WriteProfileInt(_T("General"),
_T("ToolTips"),(m_bToolTips!=0));//写入提示功能
WINDOWPLACEMENT wp;
wp.length=sizeof wp;
if (GetWindowPlacement(&wp)){
wp.flags=0;
if (IsZoomed()) wp.flags|=WPF_RESTORETOMAXIMIZED;
//如果窗口被放大,则保存为最大化状态
WriteWindowPlacement(&wp);
}
CFrameWnd::OnClose();
}
虽然SaveBarState()和LoadBarState()函数保存和恢复了工具条的所有默认位置状态,但在实际自己实现的功能参数部分并不能被保存,所以应单独编写这些参数的保存代码,如工具栏的排列格式列参数值、颜色状态标志和是否存在动态提示功能标志等,在实际编程时一定要注意。
6.3.4 工具条的平面特性
工具条的平面特性给人耳目一新之感,很多大型应用程序中的工具条都采用这一特性,并取得了巨大成功。利用VC++5中的COMCTL32.DLL动态链接库可以实现平面式工具条,其主要解决问题包括:由于MFC使用风格控制位来控制工具条的外观,所以在建立工具条时不能直接设置这种风格,必须在建立后利用SetFlatLookStyle()函数来修改;工具条控制本身也不在各级按钮之间绘制分隔线,其另一个任务就是截取WM_PAINT消息,并在相应的位置处增加分隔线;工具条控制也不绘制左边的把手(gripper) ,最后的任务就是调整客户区域并绘制并绘制相应的gripper。
显然,实际工作中需要动态链接库COMCTL32.DLL支持的上述方法很不方便。尽管最简便的方法是利用VC++5中的未公开工具栏风格TBSTYLE_FLAT,可以得到工具条的平面特性,只需在工具条建立后简单地增加一条代码"m_WndMainToolBar.ModifyStyle(0,TBSTYLE_FLAT)",但笔者经试验发现这种方法存在两个严重错误:其一是所建立的平面工具条在移动时,不能自动清除移动前的按钮图标,使工具条画面杂乱无章;其二是当建立的平面工具条具有浮动特性时,只要鼠标指针移动到浮动工具条上,整个应用程序窗口就会自动消失。所以第二种方法根本不可行。实现平面工具条的最好方法是在派生类中自己来完成,虽然这一过程比较复杂普通用户很难做到,但如果存在一个完美的平面工具条控制类,在自己的应用程序中增加相应控制类就是一件很容易的事了。下面是笔者实现完美平面工具条派生类的步骤:
(1)首先利用类向导ClassWizard为工具条控制类派生一个新类CTESTTOOLBAR,并设置相应的派生类实现文件名。由于新类的基类无法直接选择CTOOLBAR,所以在选择新类的基类时先选择CTOOLBARCTRL为基类,当派生类生成后再将实现文件中的所有CTOOLBARCTRL类名修改为CTOOLBAR控制类,并利用ClassWizard为新类增加消息WM_PAINT、WM_NCPAINT、WM_MOUSEMOVE、WM_LBUTTONDOWN和WM_LBUTTONUP消息处理功能函数,以便实现新类中平面工具条的各种特性。同时,要在MainFrm.cpp中增加包含文件TestToolBar.h。
(2)完善派生类实现文件TestToolBar.h内容
class CTestToolBar : public CToolBar
{......//其它代码
public:
CTestToolBar(); //新类构造函数
UINT GetColumns() { return m_nColumns;};//取得列数
void SetState(UINT nLeft,BOOL nStated);//设置列数和状态
void OnDrawBorder(int index,CDC &dc,int flag);//画边框
void OnEraseBorder(int index,CDC &dc);//删除边框
void OnDrawBorders();//画平面特性
void OnDrawSep(int index,CDC &dc);//画分隔线
void OnDrawGrapper();//画把手
......//其它代码
#ifdef _DEBUG //增加插入控制
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected: //增加成员变量
UINT m_nColumns; //工具栏按钮列数
UINT m_nFlags; //鼠标按键标志
int m_nIndex; //按下的按钮号
int m_nFlagl; //左键按下标志
UINT m_nStated; //工具栏状态
CRect rt; //关闭按钮矩形区域
......//其它代码
}
(3)完善派生类实现文件TestToolBar.cpp内容
......//其它代码
#define TOOLLEFT 18
#define LBUTTONDOWN 1
#define LBUTTONUP 2
......//其它代码
CTestToolBar::CTestToolBar()
{ //在构造函数中初始化变量
m_nColumns=0; //工具栏按钮列数
m_cxLeftBorder=16; //左边界
m_cxRightBorder=3; //右边界
m_cyTopBorder=3; //顶边界
m_cyBottomBorder=3;//底边界
m_nFlags=0; //按键标志成员变量
m_nIndex=0xffff; //按下的按钮号
m_nFlagl=0; //左键按下标志
m_nStated=TRUE; //工具栏状态
}
......//其它代码
#ifdef _DEBUG//插入代码完善
void CTestToolBar::AssertValid() const
{ CToolBar::AssertValid(); }
void CTestToolBar::Dump(CDumpContext& dc) const
{ CToolBar::Dump(dc); }
#endif //_DEBUG
......//其它代码
虽然需要实现的函数比较多,但总起来说不过是取得客户区域或窗口所有区域的文本设备、建立画笔和绘图函数的集合,所以这里只给出了画按钮凸凹边线的函数,其它函数可仿造实现。
void CTestToolBar::OnDrawBorder(int index,CDC &dc,int flag)
{ //画按钮边线flag=0凸=1凹
CRect rect;
GetItemRect(index,&rect);//取得客户区域
rect.right--;rect.bottom--;
CPen *oldpen;
UINT color1,color2;
if (flag==0){//两种状态的颜色处理
color1=COLOR_BTNHILIGHT;//按钮高度颜色
color2=COLOR_BTNSHADOW; //按钮阴影颜色
} else {
color1=COLOR_BTNSHADOW;
color2=COLOR_BTNHILIGHT;
}
CPen pen1(PS_SOLID,1,::GetSysColor(color1));
CPen pen2(PS_SOLID,1,::GetSysColor(color2));
dc.SelectStockObject(NULL_BRUSH);
oldpen=dc.SelectObject(&pen1);
dc.MoveTo(rect.right,rect.top);//画按钮边亮线
dc.LineTo(rect.left,rect.top);
dc.LineTo(rect.left,rect.bottom);
dc.SelectObject(&pen2); //画按钮边暗线
dc.MoveTo(rect.right,rect.top);
dc.LineTo(rect.right,rect.bottom);
dc.LineTo(rect.left,rect.bottom);
dc.SelectStockObject(BLACK_PEN);//画按钮边黑线
dc.MoveTo(rect.right+1,rect.top);
dc.LineTo(rect.right+1,rect.bottom+1);
dc.LineTo(rect.left,rect.bottom+1);
dc.SelectObject(oldpen);
DeleteObject(pen1);
DeleteObject(pen2);
}
void CTestToolBar::OnDrawBorders()
{ //实现平面工具条
CRect rect;
CPoint pt;
GetCursorPos(&pt); //取得鼠标指针
ScreenToClient(&pt);//变成窗口坐标
int index;
int count=GetCount();//工具条按钮总数
CClientDC dc(this); //窗口客户区域
TBBUTTON button; //按钮数据结构
CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();
OnDrawGrapper(); //画把手
for(index=0;index
GetItemRect(index,&rect);//取得按钮矩形区域
rect.left++;rect.top++;
ToolBarCtrl.GetButton(index,&button);//取得按钮信息
if(button.fsState&(TBSTATE_CHECKED|TBSTATE_HIDDEN))
continue;
if(button.fsStyle&TBSTYLE_SEP){//画分隔线
if(m_nNew!=0) OnDrawSep(index,dc);
} else if ((m_nIndex==index)||
button.fsState&TBSTATE_PRESSED){//凹按钮
OnEraseBorder(index,dc);//删除按钮边界
if (rect.PtInRect(pt)) OnDrawBorder(index,dc,1);//绘下凹按钮
else OnDrawBorder(index,dc,0);//绘凸出按钮
} else if (!rect.PtInRect(pt)||m_nFlags==LBUTTONUP||
!(button.fsState&TBSTATE_ENABLED)){
OnEraseBorder(index,dc);//删除按钮边界
} else if (m_nFlags!=LBUTTONDOWN){//凸按钮
OnEraseBorder(index,dc);//删除按钮边界
if(m_nFlagl==0)//鼠标按下防止再次重新出现凸起
OnDrawBorder(index,dc,0);//绘按钮边界
}
m_nFlags=0;//按下后移动后不正常凸起
}
ReleaseDC(&dc);
}
void CTestToolBar::OnPaint()
{ //完善重绘按钮功能
CToolBar::OnPaint();
OnDrawBorders();//处理所有按钮边界
}
void CTestToolBar::OnLButtonDown(UINT nFlags, CPoint point)
{ //完善鼠标左键按下功能
m_nFlags=LBUTTONDOWN;//设置鼠标按键标志
m_nFlagl=1;
CToolBar::OnLButtonDown(nFlags,point);//调原函数
int index;
int count=GetCount();//工具栏按钮总数
TBBUTTON button;
CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();
for(index=0;index
ToolBarCtrl.GetButton(index,&button);//取得按钮信息
if (button.fsState&TBSTATE_PRESSED){ //记录按下按钮号
m_nIndex=index;
}
}
}
void CTestToolBar::OnLButtonUp(UINT nFlags, CPoint point)
{ //完善鼠标释放功能
m_nFlags=LBUTTONUP;//设置鼠标按键标志
m_nFlagl=0;
CToolBar::OnLButtonUp(nFlags, point);//调原函数
CRect rect;
CPoint pt;
GetCursorPos(&pt);//取得光标位置
ScreenToClient(&pt);//变成窗口坐标
CClientDC dc(this);//窗口客户区域
if (m_nIndex!=0xffff){//判断按下按钮执行功能时仍下凹
GetItemRect(m_nIndex,&rect);//取得矩形区域
rect.left++;rect.top++;
OnEraseBorder(m_nIndex,dc);//删除按钮边界
if (rect.PtInRect(pt)) OnDrawBorder(m_nIndex,dc,1);//绘下凹按钮
}
m_nIndex=0xffff;
}
void CTestToolBar::OnMouseMove(UINT nFlags, CPoint point)
{ //完善鼠标移动功能
CToolBar::OnMouseMove(nFlags, point);
int index;
int count=GetCount();//工具栏按钮总数
CRect rect;
if (nFlags&MK_LBUTTON) m_nFlagl=1;//防止再次重新出现凸起
else m_nFlagl=0;
OnDrawBorders();//绘制所有按钮
for(index=0;index GetItemRect(index,&rect);
rect.left++;rect.top++;
if (rect.PtInRect(point)&&//取得移动过程中输入焦点
!(GetButtonStyle(index)&TBBS_SEPARATOR)){
SetCapture();//设置鼠标输入焦点
return;
}
}
if (nFlags&MK_LBUTTON){//防止移出而失去输入焦点
SetCapture();//设置鼠标输入焦点
m_nFlagl=1;
return;
} else m_nFlagl=0;
ReleaseCapture();
return;
}
void CTestToolBar::OnNcPaint()
{ //背景重画函数
CToolBar::OnNcPaint();
OnDrawGrapper();
}
void CTestToolBar::SetState(UINT nLeft,BOOL nStated)
{ //状态设置函数
m_cxLeftBorder=nLeft;//左边界
m_nStated=nStated; //工具栏状态
}
(4)有关派生类函数几点说明
①画按钮凹凸边线函数OnDrawBorder()
正常工具条中的按钮具有黑色的边线,使按钮凹凸感更强烈,但在平面工具条中的这种按钮并不美观,所以应省略黑色边线部分,并且必须使用系统的API函数GetSysColor函数来取得边线颜色,以便系统改变颜色时按钮边线也随之改变,同时由于凹凸按钮边线画法完全相同,只是颜色相反,所以两者完全可由这个函数来实现;
②画分隔线函数OnDrawSep()
画分隔线时应遍历每个按钮,来取得分隔线的位置,并且利用客户区域文本描述表就可实现,只需画亮暗两条线就可实现;
③画把手函数OnDrawGripper()
画把手时应使用整个窗口的文本描述表,因为客户区域描述表不能在窗口的非客户区域画线,而且还必须判断按钮是否以多行多列方式排列,根据不同的排列方式画水平或垂直把手,同时还要实现画关闭按钮功能,以和VC++5等界面工具栏功能完全相同,另外还要判断工具栏是否为子窗口状态,以确定是否画把手和关闭按钮;
④删除按钮边线函数OnEraseBorder()
函数用于消除系统所绘按钮凹凸边线,使按钮具有平面效果,也必须利用系统的API函数GetSysColor函数来取得系统颜色,以保证系统改变颜色时能够正常消除按钮边线;
⑤实现平面工具栏所有功能函数OnDrawBorders()
在该函数中应特别注意对按钮分隔符判断、按钮凹凸状态判断、鼠标左键按下后按钮凹凸状态判断、删除系统所画按钮边线判断判断和按下鼠标左键并移动鼠标按钮的凹凸状态判断等,并需要利用工具条控制取得对工具栏的引用;
⑥工具条更新功能函数OnPaint()
在这个函数中应注意对原系统更新功能函数的调用,以实现动态提示和按钮图标的显示等功能;
⑦鼠标左键按下功能函数OnLButtonDown()
该函数中除需要调用原系统鼠标左键按下功能函数,以实现消息的发送等功能外,还需要设置鼠标左键按下标志并记录按下按钮的位置,以便程序正确判断按钮的凹凸状态;
⑧鼠标左键释放功能函数OnLButtonDown()
该函数中除需要调用原系统鼠标左键释放功能函数,以实现按钮执行消息的发送等功能外,还需要设置鼠标左键释放标志,以便程序正确判断按钮的凹凸状态,此外还应重绘按钮凹下状态以使按钮功能执行时按钮应处于凹下状态,来保证工具栏按钮与其它高级应用程序实现的功能完全相同;
⑨鼠标移动功能函数OnMouseMove()
该函数中应记录鼠标左键按下状态标志,并在鼠标移动到按钮上和鼠标左键按下时设置鼠标输入焦点,来保证平面工具条在鼠标移动过程中的正常凸起状态和鼠标点击按钮后对按钮状态的控制,如利用这一点可实现鼠标点击按钮后按钮下凹,不释放鼠标并移动到任何位置时按钮凸起,重新移动到按钮上按钮仍下凹,这些全是控制鼠标焦点的功能,并及时释放鼠标输入焦点;
⑩背景重绘等函数OnNcPaint()
背景重绘函数是用来防止一个工具条被切换显示状态后,另一个工具栏中的把手和关闭按钮等消失,这在鼠标反复双击排序后工具条非客户区域时,另一个排序后工具栏就会出现的现象;另外函数SetState()用来设置工具条左边界和状态,以便为画把手和关闭按钮调整客户区域并提供绘图状态。
此外,还有鼠标移动到把手上光标改变形状和关闭按钮功能,由于篇幅所限这里从略,有兴趣的读者完全可以自己实现。
6.3.5 工具条的停靠位置
6.3.5.1 标6.3.5.2 准工具条的停靠位置
工具条类CToolBar是控制条类CControlBar的派生类,其显示的初始停靠位置是通过调用继承的函数CControlBar::EnableDocking(DWORDdwStyle)来确定的,其参数dwStyle用来指定停靠具体位置,与本文有关的风格如下,其余请参阅VC5的联机帮助:
CBRS_ALIGN_TOP 工具条停靠在客户区域顶部
CBRS_ALIGN_BOTTOM 工具条停靠在客户区域底部
CBRS_ALIGN_LEFT 工具条停靠在客户区域左边
CBRS_ALIGN_RIGHT 工具条停靠在客户区域右边
CBRS_ALIGN_ANY 工具条停靠在客户区域任何位置
利用应用程序向导AppWizard生成的应用程序,其默认的停靠位置为CBRS_ALIGN_ANY,即允许停靠在客户区域的任何边,正常显示时为靠近客户区域的顶部:EnableDocking(CBRS_ALIGN_ANY) ,详见上述的工具栏建立函数ONCREATE()。
应用程序的单文档和多文档的窗口框架类均为CFrameWnd 的派生类,其指定工具条的停靠位置均是通过调用继承的函数CFrameWnd::EnableDocking(DWORDdwDockStyle)来实现的,其可选的参数除上述五种之外,还增加了CBRS_FLOAT_MULTI参数,这个参数主要是为设计浮动工具条而增加的,其用来确定一个框架窗口中允许存在多个浮动工具栏。同样利用应用程序向导AppWizard生成的应用程序,其默认的停靠位置也是CBRS_ALIGN_ANY,即允许停靠在框架窗口的任何边,正常显示时为靠近框架窗口的顶部,即为EnableDocking(CBRS_ALIGN_ANY),详见上述的工具条建立函数ONCREATE()。
6.3.5.3 浮动工具条的停靠位置
当一个框架窗口中存在多个浮动工具条时,需要利用函数void DockControlBar(CControlBar *pBar,UINTnDockBarID=0,LPCRECT lpRect=NULL)来确定要控制停靠位置的工具条,它也是CFrameWnd类的成员函数,其中参数pBar用来指向被控制停靠位置的工具条对象,参数nDockBarID用来确定工具条停靠在框架窗口的哪条边上,取值为:
AFX_IDW_DOCKBAR_TOP 工具条停靠在框架窗口的顶部
AFX_IDW_DOCKBAR_BOTTOM 工具条停靠在框架窗口的底部
AFX_IDW_DOCKBAR_LEFT 工具条停靠在框架窗口的左边
AFX_IDW_DOCKBAR_RIGHT 工具条停靠在框架窗口的右边
如果参数nDockBarID取值为0,则工具条可以停靠在框架窗口中的任何一个可停靠的边上,其默认位置为顶部。
6.3.5.4 工具条的连接停靠方法
在很多应用程序中都存在将多个工具条同时停靠在某窗口的某一条边上的同一工具条窗口中的情况,利用上述工具条控制函数DockControlBar的lpRect参数,通过控制工具条的停靠矩形区域来实现这个功能,如笔者实现的函数如下:
①在主程序实现文件MainFrm.h中增加函数定义
public:
void DockControlBarLeftOf(CToolBar* Bar,CToolBar* LeftOf);
②在主程序实现文件MainFrm.cpp中增加如下函数
void CMainFrame::DockControlBarLeftOf(
CToolBar* Bar,CToolBar* LeftOf)
{ //设置工具条停靠在同一边窗口中
CRect rect;
DWORD dw;
UINT n;
RecalcLayout();//重新显示
LeftOf->GetWindowRect(&rect);
rect.OffsetRect(1,0);//设置偏移值以停靠在同一窗口中
dw=LeftOf->GetBarStyle();
n=0;
n=(dw&CBRS_ALIGN_TOP)?AFX_IDW_DOCKBAR_TOP:n;
n=(dw&CBRS_ALIGN_BOTTOM&&n==0)?AFX_IDW_DOCKBAR_BOTTOM:n;
n=(dw&CBRS_ALIGN_LEFT&&n==0)?AFX_IDW_DOCKBAR_LEFT:n;
n=(dw&CBRS_ALIGN_RIGHT&&n==0)?AFX_IDW_DOCKBAR_RIGHT:n;
DockControlBar(Bar,n,&rect);
}
在这个函数中应注意对RecalcLayout()函数和OffsetRect()函数的调用,前一个函数用来重新显示被调整的客户区和工具条,后一个函数用来重新确定矩形区域,这相当于用鼠标将第二个工具条拖动到前一个工具条上。
③修改应用程序建立函数OnCreate()函数中的相应DockControlBar()函数为DoctControlBarOf()函数,并正确设置工具条指针,见工具条的建立技巧中的有关函数。
6.3.5.5 定制工具条的顶部停靠控制
另一种工具条的停靠位置是定制工具条的停靠位置,如具有通用控制功能工具条的停靠位置,这主要实现左侧定制工具条与顶部工具条之间的位置关系。其实现方法如下:
①打开菜单资源增加顶部位置控制菜单项IDD_DLGBARTOP;
②在实现文件MainFrm.h中增加成员控制变量m_bDialogTop;
BOOL m_bDialogTop;
并在构造函数中为其设置初始值;
③利用类向导函数为菜单项设置响应函数;
④在实现文件MainFrm.cpp中完善消息映射函数。
void CMainFrame::OnButtonDlgbartop()
{ //定制工具条顶部位置控制函数
if (m_bDialogTop) m_wndDlgBar.SetWindowPos(
&m_wndStatusBar,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
//其它工具条停靠在最顶部,该工具条停靠其下
else m_wndDlgBar.SetWindowPos(&wndTop,0,0,0,0,
SWP_NOSIZE|SWP_NOMOVE);//停靠在最顶部
RecalcLayout(); //重新显示窗口
m_bDialogTop=!m_bDialogTop;//改变变量标志
}
void CMainFrame::OnUpdateButtonDlgbartop(CCmdUI* pCmdUI)
{ //设置菜单项检查状态
pCmdUI->SetCheck(m_bDialogTop);
}
6.3.6 工具条按钮的排序方法
利用应用程序向导AppWizard 生成的应用程序工具条,其按钮均为单行水平排列的,这在实际程序开发时既不美观又不实用,很多大型应用程序等界面中的工具条都采用多行多列的排序方式,要在自己的应用程序中实现这种排列方式,应按下述方法在派生类中控制:
(1)在TestToolBar.h中增加函数定义和成员变量控制
class CTestToolBar : public CToolBar
{......//其它代码
public:
CTestToolBar(); //在构造函数后增加下一行
void SetColumns(UINT nColumns);//增加列控制
......//其它代码
protected: //增加成员变量
UINT m_nColumns; //工具条列按钮数
......//其它代码
}
(2)在TestToolBar.cpp中增加变量初始化和函数
CTestToolBar::CTestToolBar()
{ //在构造函数中初始化变量
m_nColumns=0; //工具条按钮列数
......//其它代码
}
void CTestToolBar::SetColumns(UINT nColumns)
{ //设置按钮排列格式
m_nColumns=nColumns;//列数
int nCount=GetToolBarCtrl().GetButtonCount();//按钮数
for(int i=0;i
UINT nStyle=GetButtonStyle(i);//按钮风格
BOOL bWrap=(((i+1)%nColumns)==0);
if(bWrap) nStyle|=TBBS_WRAPPED;//设置换行
else nStyle&=~TBBS_WRAPPED;//不换行
SetButtonStyle(i,nStyle);//设置风格
}
Invalidate();//窗口更新
GetParentFrame()->RecalcLayout();//工具栏状态更新
}
(3)在应用程序建立函数OnCreate()中为相应的工具条增加列控制功能,并注意对保存和恢复工具条状态函数的列控制参数处理,请参阅工具条建立技巧和状态保存与恢复中的有关函数,重新编译并执行应用程序就可以看到多行多列的工具条。
6.3.7 工具条的消息映射技巧
如果工具条上的按钮都存在对应的菜单命令,那么可以直接利用类向导ClassWizard进行命令消息的映射,否则必须通过手工的方法来进行命令按钮的消息映射。由于同一工具栏的命令按钮都存在相类似的功能,所以只要注意将同一工具条中的命令按钮ID值设置成连续值,就可以利用范围命令处理机制统一对所有按钮命令进行管理,即所有命令按钮的消息可以在同一命令消息处理函数和更新命令消息处理函数中进行管理,这样不但结构紧凑而且维护特别方便。鉴于类向导ClassWizard不支持范围命令处理功能,所以其消息映射机制必须通过手工方式来完成。按钮命令消息既可以在框架窗口中映射,也可以在文档和视图中完成映射,由于文档对象通常用来管理程序中的数据部分,并且在多文档应用程序(MDI)中,一个文档可能有多个视图与之关联,因此把工具栏中的命令按钮消息映射在文档中比较合适些。其实现步骤如下:
(1)在文档实现文件CToolDoc.h中增加函数定义和数据成员:
class CCTOOLDoc : public CDocument
{......//其它代码
protected:
UINT iPosition; //命令按钮消息位置
......//其它代码
protected:
//{{AFX_MSG(CCTOOLDoc)
afx_msg void OnTool(UINT nID);//命令消息映射
afx_msg void OnUpdateTool(CCmdUI* pCmdUI);//更新消息映射
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
......//其它代码
}
(2)在文档实现文件CToolDoc.cpp中进行消息映射
BEGIN_MESSAGE_MAP(CCTOOLDoc,CDocument)
//{{AFX_MSG_MAP(CCTOOLDoc)
ON_COMMAND_RANGE(ID_BUTTON_LINE,
ID_BUTTON_SORT,OnTool);
ON_UPDATE_COMMAND_UI_RANGE(ID_BUTTON_LINE,
ID_BUTTON_SORT,OnUpdateTool);
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
函数中的参数ID_BUTTONLINE和ID_BUTTONSORT分别为工具栏中第一个按钮和最后一个按钮的消息,如果要增加或减少了按钮数,则必须使其ID值保持连续,并且需要修改函数中的最后一个消息值。对应函数的代码如下:
void CtoolDoc::OnTool(UINT nID)
{ //按钮消息处理函数
iPosition=nID-ID_BUTTON_LINE;
switch(iPosition){
case 1:
......//调用按钮一的处理功能
case 2:
......//调用按钮二的处理功能
......
case n:
......//调用按钮N的处理功能
break;
}
}
void CToolDoc::OnUpdateTool(CCmdUI* pCmdUI)
{ //更新按钮函数
pCmdUI->SetCheck(iPosition==(pCmdUI->m_nID
-ID_BUTTON_LINE));
}
由于命令按钮比较多,其处理功能代码部分这里省略。
6.3.8 驾驭工具条按钮的状态显示更新
(1)互斥按钮的状态更新
很多应用程序中都存在相互排斥的工具条按钮,如开始和停止按钮,初始状态时停止按钮被禁止,当应用程序进行数据通讯、查找和打印等功能时,停止按钮有效而开始按钮禁止,当命令结束或按停止按钮后,开始按钮有效而停止按钮被禁止。这就是工具条中按钮的显示状态更新问题。
与菜单栏一样,工具条的构造、析构过程及其窗口的创建都是由应用程序的主框架窗口来管理的。当用户点击菜单条的菜单项时,Windows就会向应用程序发送WM_INITMENUPOPUP消息,以便应用程序在菜单显示之前对菜单项进行添加或修改,当利用MFC类库编程时只要针对某一具体的菜单项进行消息映射,加入相应的消息处理函数,MFC类库就会发送更新消息UPDATE_COMMAND_UI,使菜单在弹出时消息处理函数能够对菜单项进行禁止、变灰和其它处理。工具条的消息处理过程与菜单栏完全相同,它也存在工具条显示更新消息UPDATE_COMMAND_UI,只不是由于工具条总是处于显示状态,所以对其消息处理函数的调用是在应用程序的空闲状态时进行处理的,这点与菜单栏有所不同,当应用程序命令按钮所要执行的功能占用大量的处理器资源时,如进行通讯处理或大量数据计算时,应用程序的主框架窗口就无暇进入空闲状态,于是工具条中的命令按钮函数就不能被及时调用,按钮的显示状态就不能被及时更新,这就为工具条按钮的显示状态及时更新带来一定的困难。
基于文档的应用程序一般情况下都是在文档视中对工具条按钮进行处理,所以要实现按钮显示状态的及时更新,必须在命令按钮响应函数中利用CToolBarCtrl类的成员函数EnableButton()函数对按钮的显示状态进行必要的处理,其步骤如下:
①首先利用类向导ClassWizard 对CCTOOLView类进行开始按钮和停止按钮的COMMAND命令消息映射,增加对应消息的处理函数;
②在实现文件CTOOLView.h中增加成员变量:
public:
BOOL m_bContiune;//开始按钮标志
BOOL m_bSet;//结束按钮标志
③在实现文件CTOOLView.cpp中增加代码完善按钮状态更新:
......//其它代码
void DrawRectText(RECT &rc,CDC &dc);
......//其它代码
void CCTOOLView::OnButtonStart()
{ //完善开始按钮函数功能
CToolBar *pToolBar=(CToolBar *)//取得工具栏对象指针
(GetParentFrame()->GetControlBar(IDR_DOCKTOOLBAR));
//取得工具条控制指针
CToolBarCtrl *pToolBarCtrl=&(pToolBar->GetToolBarCtrl());
//控制工具条按钮状态
pToolBarCtrl->EnableButton(ID_BUTTON_START,FALSE);
pToolBarCtrl->EnableButton(ID_BUTTON_STOP,TRUE);
CClientDC dc(this);//取得设备文本
MSG msg;//定义消息
RECT rc;
GetClientRect(&rc);//以下画网格并显示
m_bContinue=TRUE;
m_bSet=FALSE;
for(int ii=0;ii<2;ii++){
if(m_bContinue==TRUE){
ii=0;//状态改变退出
DrawRectText(rc,dc);
GetMessage(&msg,NULL,0,0);//检查消息
TranslateMessage(&msg);//解释消息
DispatchMessage(&msg);//分配消息
}
}
ReleaseDC(&dc);
m_bSet=TRUE;
}
void DrawRectText(RECT &rc,CDC &dc)
{ //显示文本函数
int i,j,k,nDist=10;
CPen pen,*pOldPen;
pen.CreatePen(PS_SOLID,1,RGB(180,0,0));
pOldPen=dc.SelectObject(&pen);//绘制背景网格
j=rc.right/nDist+1;
k=rc.bottom/nDist+1;
for(i=0;i
dc.MoveTo(i*nDist,0);
dc.LineTo(0,i*nDist);
if(i
dc.MoveTo(i*nDist,0);
dc.LineTo(rc.right,(j-i)*nDist);
} else {
dc.MoveTo(0,(i-j)*nDist);
dc.LineTo(rc.right,i*nDist);
}
}
dc.SelectObject(pOldPen);
DeleteObject(&pen);
CFont font,*pOldFont;
font.CreateFont(50,50,0,0,1000,//创建字体
1,1,0,ANSI_CHARSET,
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,DEFAULT_PITCH,NULL);
pOldFont=dc.SelectObject(&font);
dc.TextOut(10,10,"BUTTON");
dc.SelectObject(pOldFont);
DeleteObject(&font);
}
void CCTOOLView::OnButtonStop()
{ //完善停止按钮函数
m_bContinue=FALSE;
CToolBar *pToolBar=(CToolBar *)//取得工具栏对象指针
(GetParentFrame()->GetControlBar(IDR_DOCKTOOLBAR));
//取得工具条控制指针
CToolBarCtrl *pToolBarCtrl=&(pToolBar->GetToolBarCtrl());
//控制工具条按钮状态
pToolBarCtrl->EnableButton(ID_BUTTON_START,TRUE);
pToolBarCtrl->EnableButton(ID_BUTTON_STOP,FALSE);
for(int jj=0;jj<2;jj++){
if(m_bSet==FALSE){
jj=0;
GetMessage(&msg1,NULL,0,0);//检查消息
TranslateMessage(&msg1);//解释消息
DispatchMessage(&msg1);//分配消息
}
}
}
在开始按钮函数中,当在屏幕上不断显示信息的同时必须利用消息处理函数检查消息队列,并解释和分配相应的消息,否则其它按钮就无法检查到应用程序的鼠标等消息,无法实现显示状态更新;在更新按钮显示状态前,必须先取得工具栏对象和相应的控制指针,然后才能实现对按钮状态的控制,并且要实现按钮状态的更新,其设置按钮的更新功能函数必须最后执行结束才能有效,即在停止按钮函数中必须判断标志并检测消息队列。
④在平面工具条派生类中的鼠标按下函数中增加代码,以实现鼠标按下开始和停止按钮后,保证按钮的正常按下和显示状态:
void CTestToolBar::OnLButtonDown(UINT nFlags, CPoint point)
{ //完善鼠标按下函数
......//其它代码
if(m_nIndex==ToolBarCtrl.CommandToIndex(ID_BUTTON_STOP)){
ToolBarCtrl.SetState(ID_BUTTON_STOP,ToolBarCtrl.GetState(
ID_BUTTON_STOP)&(~TBSTATE_ENABLED&~TBSTATE_PRESSED));
ToolBarCtrl.SetState(ID_BUTTON_START,ToolBarCtrl.
GetState(ID_BUTTON_START)|TBSTATE_ENABLED);
ToolBarCtrl.Invalidate();
AfxGetApp()->OnIdle(-1);
m_nIndex=0xffff;
}
if(m_nIndex==ToolBarCtrl.CommandToIndex(ID_BUTTON_START)){
ToolBarCtrl.SetState(ID_BUTTON_START,ToolBarCtrl.GetState(
ID_BUTTON_START)&(~TBSTATE_ENABLED & ~TBSTATE_PRESSED));
ToolBarCtrl.SetState(ID_BUTTON_STOP,ToolBarCtrl.GetState(
ID_BUTTON_STOP)&TBSTATE_ENABLED);
ToolBarCtrl.Invalidate();
AfxGetApp()->OnIdle(-1);
m_nIndex=0xffff;
}
}
(2)按钮按下状态的切换更新
另一种按钮状态的更新情况是按钮按下状态的切换更新,这在绘图工具条和屏幕字体效果工具条中最常见。其实现步骤如下:
①首先在MainFrm.cpp中手工增加消息范围映射函数
ON_COMMAND_RANGE(ID_EDIT_TOGGLE,ID_EDIT_UNINDENT,OnButton)
ON_UPDATE_COMMAND_UI_RANGE(ID_EDIT_TOGGLE,
ID_EDIT_UNINDENT,OnUpdateButton)
②然后在MainFrm.h中消息映射的最后增加成员函数定义和记录按下按钮序号的成员控制变量
int m_bIndex;//按下按钮序号
afx_msg void OnButton(UINT nID);
afx_msg void OnUpdateButton(CCmdUI* pCmdUI);
③最后分别增加实现消息函数的功能代码
void CMainFrame::OnUpdateButton(CCmdUI* pCmdUI)
{ //按钮按下检查状态设置函数
pCmdUI->SetCheck((UINT)(ID_EDIT_TOGGLE+
m_bIndex) == pCmdUI->m_nID);
}
void CMainFrame::OnButton(UINT nID)
{ //记录按下按钮的序号
m_bIndex=(int)(nID-ID_EDIT_TOGGLE);
}
如果涉及到其它控制的变化,可在这两个函数中分别增加。
(3)按钮的部分显示更新
最后一种更新工具条按钮显示状态的情况是部分按钮显示,这需要利用SetButtons()和SetButtonInfo()函数有效处理。步骤如下:
①首先在菜单栏中增加控制菜单项;
②在应用程序实现文件CTool.h中增加如下消息定义和函数定义
......//其它代码
#define WM_IDLEACTIVATE WM_USER+1
......//其它代码
class CCtrlbarsApp : public CWinApp
{
public:
......//其它代码
virtual BOOL OnIdle(LONG lCount);
......//其它代码
};
③在应用程序实现文件CTool. cpp中增加如下代码和副本函数功能,这个函数在工具条按钮的各种显示状态更新中都非常有用,是一个非常重要的程序设计技巧。
在初始化函数CCTOOLApp::InitInstance()最后增加代码:
m_pMainWnd = pMainFrame;
完善相应的功能函数:
BOOL CCTOOLApp::OnIdle(LONG lCount)
{ //保持应用程序一个副本函数
if(lCount == 0){
ASSERT(m_pMainWnd != NULL);
for(HWND hWnd=::GetWindow(m_pMainWnd->m_hWnd,
GW_HWNDFIRST);hWnd!=NULL;hWnd=::GetNextWindow(
hWnd,GW_HWNDNEXT)){//获取主程序子窗口
if(::GetParent(hWnd)==m_pMainWnd->m_hWnd){
if(GetActiveWindow()==hWnd&&(::GetCapture()==NULL))
m_pMainWnd->SetActiveWindow();//保持应用程序活动窗口
SendMessage(hWnd,WM_IDLEUPDATECMDUI,(WPARAM)TRUE,0L);
}//保持窗口的一个有效副本
}
}
return CWinApp::OnIdle(lCount);
}
④最后在菜单项处理函数中进行工具条按钮的重组处理,并在需要处理应用程序窗口副本的任何函数中增加副本处理函数。
void CMainFrame::OnViewShort()
{
m_wndMainToolBar.SetButtons(NULL,7);//共计7个
m_wndMainToolBar.SetButtonInfo(0,ID_BUTTON_LINE,TBBS_BUTTON,1);
m_wndMainToolBar.SetButtonInfo(1,ID_BUTTON_CURSE,TBBS_BUTTON,5);
......
m_wndMainToolBar.SetButtonInfo(6,ID_BUTTON_TEST,TBBS_BUTTON,7);
m_wndToolBar.Invalidate();//更新工具条
AfxGetApp()->OnIdle(-1);//窗口副本处理函数
}