以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 演练VC中的COMMON一族 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=37589) |
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:41:00 AM -- 演练VC中的COMMON一族 1.表头控制 Header Control 2.图像列表控制 Image List 3.列表控制和视 List Control&List View 4.树控制和视 Tree Control&Tree View 5.标签控制和视 Tab Control&Tab View 6.工具条控制 Toolbar Control 7.进度条控制 Progress Control 8.滑动条控制 Slider Control 9.旋转按钮控制 Spin Button Control 10.状态条控制 Status Bar Control 其中,绝大部分通用控制在MFC类库中都存在两种封装形式,即控制类和视类,控制类主要是供直接使用控制而提供的,而视类则是通过间接使用控制而提供的,视类可以更好地把控制类集成到MFC结构中,并且使用视类可以利用引用直接对嵌套在其中的控制进行各种操作。两者在使用方法上没有太大区别,就拿列表控制类和视类来说,当创建列表视后可通过CListCtrl& ctrlList = GetListCtrl()成员函数取得视类引用ctrlList之后,就可以利用列表视的视函数对视进行各项操作(ctrlList.Add等)。 第1章 演练CHeadCtrl 列表视演练示例结果
|
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:42:00 AM -- 第4章 演练CTree 4.1 树控制的主要功能 树控制和视(Tree Control&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、取得下一个可视树项GetNextVisible Item、取得前一个可视的树项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; file://结构成员有效性屏蔽位 HTREEITEM hItem; file://数据项控制句柄 UINT state; file://数据项状态 UINT stateMask; file://状态有效性屏蔽位 LPSTR pszText; file://数据项名称字符串 int cchTextMax; file://数据项名称的最大长度 int iImage; file://数据项图标索引号 int iSelectedImage;//选中数据项图标索引号 int cChildren; file://子项标识 LPARAM lParam; file://程序定义的32位数据 } TV_ITEM, FAR *LPTV_ITEM; 4.3.2 插入树项结构 typedef struct _TV_INSER TSTRUCT { HTREEITEM hParent; file://父项控制句柄 HTREEITEM hInsertAfter; file://插入树项的位置 TV_ITEM item; file://数据项的结构 } TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT; 其中插入的位置如果是TVI_FIRST 或TVI_LAST ,则分别插入到树控制的最前面或最后面,如果是TVI_SORT ,则插入的树项自动插入到合适的位置。 4.3.3 树控制通知消息结构 typedef struct _NM_TREEVIEW { NMHDR hdr; file://通知消息句柄 UINT action; file://通知消息标志 TV_ITEM itemOld; file://原来的数据结构 TV_ITEM itemNew; file://新的数据结构 POINT ptDrag; file://拖动指针 } NM_TREEVIEW; 4.3.4 取得或设置数据结构 typedef struct _TV_DISPINFO { tvdi NMHDR hdr; file://通知消息控制句柄 TV_ITEM item; file://数据项结构 } TV_DISPINFO; 4.3.5 指4.3.6 针测试数据结构 typedef struct _TVHITTESTINFO { tvhtst POINT pt; file://客户区域屏幕坐标指针 UINT flags; file://存放测试结果的变量 HTREEITEM hItem; file://测试的数据项结构 } 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->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); 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 编号:%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 签控制的主要功能 标签控制(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); } 标签控制的整个实现过程虽然比较繁锁,但只要掌握其实现的本质,设计一个优秀的标签控制界面也并非很困难的事情。 |
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:42:00 AM -- 第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; file://框架程序的状态条 CTestToolBar m_wndMainToolBar;//框架程序的工具条 CTestToolBar m_wndTestToolBar;//新增工具条 CTestToolBar m_wndDockToolBar;//浮动工具条 CTestToolBar m_wndDockNextBar;//浮动工具条 ......//其它代码 } 框架程序中工具条的控制类正常应为CToolBar,可以是自己设计的派生类CtestToolBar(为笔者扩充平面特性等功能后的新工具条控制类名)等,具体根据实际需要而定。利用CDialogBar类和CStyleBar 类还可以建立扩展类型的工具条,详见后面工具条中控制应用技巧,但在该文件头处必须 包含如下命令: #ifndef __AFXEXT_H__ #include <afxext.h>//直接存取CToolBar和CStatusBar #endif ②在MainFrm.cpp中完善窗口建立函数 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; WINDOWPLACEMENT wp;//保存主窗口及工具栏窗口位置状态 if (ReadWindowPlacement(&wp))//读取位置状态信息 SetWindowPlacement(&wp); file://设置位置状态信息 m_bToolTips=(AfxGetApp()->GetProfileInt(//读提示功能 _T("General"),_T("ToolTips"),1)!=0); file://默认值为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)) { file://CBRS_SIZE_DYNAMIC为锁定位置风格 TRACE0("主工具条MAINFRAME建立失败\n"); return -1;} // 建立失败处理 ......//建立其它工具条代码,基本相同 if (!m_wndStatusBar.Create(this)|| !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { file://建立状态条 TRACE0("Failed to create status bar\n"); return -1;} // fail to create m_wndMainToolBar.SetWindowText(_T("主工具栏"));//设置标题 m_wndMainToolBar.EnableDocking(CBRS_ALIGN_ANY);//停靠位置 file://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) { file://窗口位置状态读取函数,从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; file://结构指针 return TRUE; } static void PASCAL NEAR WriteWindowPlacement( LPWINDOWPLACEMENT pwp) { file://窗口位置状态写入函数,写到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); file://设置位置状态信息 (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() { file://保存工具条等的状态 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; file://如果窗口被放大,则保存为最大化状态 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(); file://新类构造函数 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 file://增加插入控制 virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: file://增加成员变量 UINT m_nColumns; file://工具栏按钮列数 UINT m_nFlags; file://鼠标按键标志 int m_nIndex; file://按下的按钮号 int m_nFlagl; file://左键按下标志 UINT m_nStated; file://工具栏状态 CRect rt; file://关闭按钮矩形区域 ......//其它代码 } (3)完善派生类实现文件TestToolBar.cpp内容 ......//其它代码 #define TOOLLEFT 18 #define LBUTTONDOWN 1 #define LBUTTONUP 2 ......//其它代码 CTestToolBar::CTestToolBar() { file://在构造函数中初始化变量 m_nColumns=0; file://工具栏按钮列数 m_cxLeftBorder=16; file://左边界 m_cxRightBorder=3; file://右边界 m_cyTopBorder=3; file://顶边界 m_cyBottomBorder=3;//底边界 m_nFlags=0; file://按键标志成员变量 m_nIndex=0xffff; file://按下的按钮号 m_nFlagl=0; file://左键按下标志 m_nStated=TRUE; file://工具栏状态 } ......//其它代码 #ifdef _DEBUG//插入代码完善 void CTestToolBar::AssertValid() const { CToolBar::AssertValid(); } void CTestToolBar::Dump(CDumpContext& dc) const { CToolBar::Dump(dc); } #endif file://_DEBUG ......//其它代码 虽然需要实现的函数比较多,但总起来说不过是取得客户区域或窗口所有区域的文本设备、建立画笔和绘图函数的集合,所以这里只给出了画按钮凸凹边线的函数,其它函数可仿造实现。 void CTestToolBar::OnDrawBorder(int index,CDC &dc,int flag) { file://画按钮边线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; file://按钮阴影颜色 } 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); file://画按钮边暗线 dc.MoveTo(rect.right,rect.top); dc.LineTo(rect.right,rect.bottom); dc.LineTo(rect.left,rect.bottom); file://dc.SelectStockObject(BLACK_PEN);//画按钮边黑线 file://dc.MoveTo(rect.right+1,rect.top); file://dc.LineTo(rect.right+1,rect.bottom+1); file://dc.LineTo(rect.left,rect.bottom+1); dc.SelectObject(oldpen); DeleteObject(pen1); DeleteObject(pen2); } void CTestToolBar::OnDrawBorders() { file://实现平面工具条 CRect rect; CPoint pt; GetCursorPos(&pt); file://取得鼠标指针 ScreenToClient(&pt);//变成窗口坐标 int index; int count=GetCount();//工具条按钮总数 CClientDC dc(this); file://窗口客户区域 TBBUTTON button; file://按钮数据结构 CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl(); OnDrawGrapper(); file://画把手 for(index=0;index<count;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() { file://完善重绘按钮功能 CToolBar::OnPaint(); OnDrawBorders();//处理所有按钮边界 } void CTestToolBar::OnLButtonDown(UINT nFlags, CPoint point) { file://完善鼠标左键按下功能 m_nFlags=LBUTTONDOWN;//设置鼠标按键标志 m_nFlagl=1; CToolBar::OnLButtonDown(nFlags,point);//调原函数 int index; int count=GetCount();//工具栏按钮总数 TBBUTTON button; CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl(); for(index=0;index<count;index++){ ToolBarCtrl.GetButton(index,&button);//取得按钮信息 if (button.fsState&TBSTATE_PRESSED){ file://记录按下按钮号 m_nIndex=index; } } } void CTestToolBar::OnLButtonUp(UINT nFlags, CPoint point) { file://完善鼠标释放功能 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) { file://完善鼠标移动功能 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<count;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() { file://背景重画函数 CToolBar::OnNcPaint(); OnDrawGrapper(); } void CTestToolBar::SetState(UINT nLeft,BOOL nStated) { file://状态设置函数 m_cxLeftBorder=nLeft;//左边界 m_nStated=nStated; file://工具栏状态 } (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(DWORD dwStyle)来确定的,其参数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(DWORD dwDockStyle)来实现的,其可选的参数除上述五种之外,还增加了CBRS_FLOAT_MULTI参数,这个参数主要是为设计浮动工具条而增加的,其用来确定一个框架窗口中允许存在多个浮动工具栏。同样利用应用程序向导AppWizard 生成的应用程序,其默认的停靠位置也是CBRS_ALIGN_ANY,即允许停靠在框架窗口的任何边,正常显示时为靠近框架窗口的顶部,即为EnableDocking(CBRS_ALIGN_ANY),详见上述的工具条建立函数ONCREATE()。 6.3.5.3 浮动工具条的停靠位置 当一个框架窗口中存在多个浮动工具条时,需要利用函数void DockControlBar(CControlBar *pBar,UINT nDockBarID=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) { file://设置工具条停靠在同一边窗口中 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() { file://定制工具条顶部位置控制函数 if (m_bDialogTop) m_wndDlgBar.SetWindowPos( &m_wndStatusBar,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); file://其它工具条停靠在最顶部,该工具条停靠其下 else m_wndDlgBar.SetWindowPos(&wndTop,0,0,0,0, SWP_NOSIZE|SWP_NOMOVE);//停靠在最顶部 RecalcLayout(); file://重新显示窗口 m_bDialogTop=!m_bDialogTop;//改变变量标志 } void CMainFrame::OnUpdateButtonDlgbartop(CCmdUI* pCmdUI) { file://设置菜单项检查状态 pCmdUI->SetCheck(m_bDialogTop); } 6.3.6 工具条按钮的排序方法 利用应用程序向导AppWizard 生成的应用程序工具条,其按钮均为单行水平排列的,这在实际程序开发时既不美观又不实用,很多大型应用程序等界面中的工具条都采用多行多列的排序方式,要在自己的应用程序中实现这种排列方式,应按下述方法在派生类中控制: (1)在TestToolBar.h中增加函数定义和成员变量控制 class CTestToolBar : public CToolBar {......//其它代码 public: CTestToolBar(); file://在构造函数后增加下一行 void SetColumns(UINT nColumns);//增加列控制 ......//其它代码 protected: file://增加成员变量 UINT m_nColumns; file://工具条列按钮数 ......//其它代码 } (2)在TestToolBar.cpp中增加变量初始化和函数 CTestToolBar::CTestToolBar() { file://在构造函数中初始化变量 m_nColumns=0; file://工具条按钮列数 ......//其它代码 } void CTestToolBar::SetColumns(UINT nColumns) { file://设置按钮排列格式 m_nColumns=nColumns;//列数 int nCount=GetToolBarCtrl().GetButtonCount();//按钮数 for(int i=0;i<nCount;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()中为相应的工具条增加列控制功能,并注意对保存和恢复工具条状态函数的列控制参数处理,请参阅工具条建立技巧和状态保存与恢复中的有关函数,重新编译并执行应用程序就可以看到多行多列的工具条。 |
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:45:00 AM -- 6.3.7 工具条的消息映射技巧 如果工具条上的按钮都存在对应的菜单命令,那么可以直接利用类向导ClassWizard 进行命令消息的映射,否则必须通过手工的方法来进行命令按钮的消息映射。由于同一工具栏的命令按钮都存在相类似的功能,所以只要注意将同一工具条中的命令按钮ID值设置成连续值,就可以利用范围命令处理机制统一对所有按钮命令进行管理,即所有命令按钮的消息可以在同一命令消息处理函数和更新命令消息处理函数中进行管理,这样不但结构紧凑而且维护特别方便。鉴于类向导ClassWizard 不支持范围命令处理功能,所以其消息映射机制必须通过手工方式来完成。按钮命令消息既可以在框架窗口中映射,也可以在文档和视图中完成映射,由于文档对象通常用来管理程序中的数据部分,并且在多文档应用程序(MDI) 中,一个文档可能有多个视图与之关联,因此把工具栏中的命令按钮消息映射在文档中比较合适些。其实现步骤如下: (1)在文档实现文件CToolDoc.h中增加函数定义和数据成员: class CCTOOLDoc : public CDocument {......//其它代码 protected: UINT iPosition; file://命令按钮消息位置 ......//其它代码 protected: file://{{AFX_MSG(CCTOOLDoc) afx_msg void OnTool(UINT nID);//命令消息映射 afx_msg void OnUpdateTool(CCmdUI* pCmdUI);//更新消息映射 file://}}AFX_MSG DECLARE_MESSAGE_MAP() ......//其它代码 } (2)在文档实现文件CToolDoc.cpp中进行消息映射 BEGIN_MESSAGE_MAP(CCTOOLDoc,CDocument) file://{{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); file://}}AFX_MSG_MAP END_MESSAGE_MAP() 函数中的参数ID_BUTTONLINE和ID_BUTTONSORT分别为工具栏中第一个按钮和最后一个按钮的消息,如果要增加或减少了按钮数,则必须使其ID值保持连续,并且需要修改函数中的最后一个消息值。对应函数的代码如下: void CtoolDoc::OnTool(UINT nID) { file://按钮消息处理函数 iPosition=nID-ID_BUTTON_LINE; switch(iPosition){ case 1: ......//调用按钮一的处理功能 case 2: ......//调用按钮二的处理功能 ...... case n: ......//调用按钮N的处理功能 break; } } void CToolDoc::OnUpdateTool(CCmdUI* pCmdUI) { file://更新按钮函数 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() { file://完善开始按钮函数功能 CToolBar *pToolBar=(CToolBar *)//取得工具栏对象指针 (GetParentFrame()->GetControlBar(IDR_DOCKTOOLBAR)); file://取得工具条控制指针 CToolBarCtrl *pToolBarCtrl=&(pToolBar->GetToolBarCtrl()); file://控制工具条按钮状态 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) { file://显示文本函数 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<j+k;i++){ dc.MoveTo(i*nDist,0); dc.LineTo(0,i*nDist); if(i<j){ 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() { file://完善停止按钮函数 m_bContinue=FALSE; CToolBar *pToolBar=(CToolBar *)//取得工具栏对象指针 (GetParentFrame()->GetControlBar(IDR_DOCKTOOLBAR)); file://取得工具条控制指针 CToolBarCtrl *pToolBarCtrl=&(pToolBar->GetToolBarCtrl()); file://控制工具条按钮状态 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) { file://完善鼠标按下函数 ......//其它代码 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) { file://按钮按下检查状态设置函数 pCmdUI->SetCheck((UINT)(ID_EDIT_TOGGLE+ m_bIndex) == pCmdUI->m_nID); } void CMainFrame::OnButton(UINT nID) { file://记录按下按钮的序号 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) { file://保持应用程序一个副本函数 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);//窗口副本处理函数 } 6.3.9 工具条中的通用控制使用 在工具条中通常不都是按钮,如存在输入框、下拉组合框和检查盒按钮等。这就需要利用VC中提供的特殊类型的工具条类控制或通过派生新类的方法来实现。 (1)在派生的新类中实现控制 如果利用派生新类的方法,很容易就可以实现文本输入框等通用控制功能。其实现步骤如下: ①利用类向导ClassWizard 建立新类CEDITBAR并生成实现文件; ②利用资源编辑器建立工具条的位图文件; ③在新类实现文件EDITBAR.h中增加如下函数定义和成员变量: class CEDITBAR : public CToolBar { public: CEDITBAR(); CSearchBox m_SearchBox;//文本查找控制 BOOL Init(CWnd* pParentWnd,BOOL bToolTips); BOOL SetColor(BOOL bColor); BOOL SetHorizontal(); BOOL SetVertical(); ......//其它代码 BOOL m_bVertical; file://virtual CSize CalcFixedLayout(BOOL bStretch,BOOL bHorz); virtual CSize CalcDynamicLayout(int nLength,DWORD dwMode); ......//其它代码 } ④在新类实现文件EDITBAR.cpp中增加如下函数: ......//其它代码 #define COMBOBOX_INDEX 5 file://定义组合框参数 #define COMBOBOX_WIDTH 150 #define COMBOBOX_HEIGHT 150 #define COMBOBOX_BITMAP 4 static UINT BASED_CODE EditButtons[] = { file://要求与工具栏位图顺序对应 ID_EDIT_TOGGLE, ID_EDIT_NEXT, ID_EDIT_PREV, ID_EDIT_CLEARALL, ID_SEPARATOR, ID_EDIT_FINDEDIT, ID_SEPARATOR, ID_EDIT_FINDINFILES, ID_SEPARATOR, ID_EDIT_INDENT, ID_EDIT_UNINDENT, ID_SEPARATOR, ID_WINDOW_NEW, ID_WINDOW_SPLIT, ID_WINDOW_CASCADE, ID_WINDOW_HORZ, ID_WINDOW_VERT, }; ......//其它代码 BOOL CEDITBAR::Init(CWnd* pParentWnd,BOOL bToolTips) { file://初始化输入框函数 m_bVertical=FALSE; DWORD dwStyle=WS_CHILD|WS_VISIBLE|CBRS_TOP|CBRS_SIZE_DYNAMIC; if(bToolTips)dwStyle|=(CBRS_TOOLTIPS|CBRS_FLYBY); if(!Create(pParentWnd,dwStyle,IDB_CEDIT)) return FALSE; if(!LoadBitmap(IDB_CEDIT)) return FALSE; if(!SetButtons(EditButtons,sizeof(EditButtons) /sizeof(UINT)))return FALSE; CRect rect(-COMBOBOX_WIDTH,-COMBOBOX_HEIGHT,0,0); // 非常重要:其一是可以从控制接收信息,如动态提示等 if(!m_SearchBox.Create(WS_CHILD|CBS_DROPDOWN|//建立文本查找 CBS_AUTOHSCROLL|WS_VSCROLL|CBS_HASSTRINGS,rect,this, IDC_EDITBAR_SEARCHBOX)) return FALSE; HFONT hFont=(HFONT)GetStockObject(DEFAULT_GUI_FONT); if(hFont==NULL)hFont=(HFONT)GetStockObject(ANSI_VAR_FONT); m_SearchBox.SendMessage(WM_SETFONT,(WPARAM)hFont); if(!SetHorizontal())return FALSE; return TRUE; } BOOL CEDITBAR::SetHorizontal() { file://设置按钮水平排列函数 m_bVertical=FALSE; SetBarStyle(GetBarStyle()|CBRS_ALIGN_TOP); SetButtonInfo(COMBOBOX_INDEX,IDC_EDITBAR_SEARCHBOX, TBBS_SEPARATOR, COMBOBOX_WIDTH); if(m_SearchBox.m_hWnd!=NULL){ CRect rect; GetItemRect(COMBOBOX_INDEX,rect); m_SearchBox.SetWindowPos(NULL,rect.left, rect.top,0,0,SWP_NOZORDER|SWP_NOACTIVATE| SWP_NOSIZE|SWP_NOCOPYBITS); m_SearchBox.ShowWindow(SW_SHOW); } return TRUE; } BOOL CEDITBAR::SetVertical() { file://设置按钮垂直排列函数 m_bVertical=TRUE; SetButtonInfo(COMBOBOX_INDEX,ID_EDIT_FIND, TBBS_BUTTON, COMBOBOX_BITMAP); if(m_SearchBox.m_hWnd != NULL) m_SearchBox.ShowWindow(SW_HIDE); return TRUE; } CSize CEDITBAR::CalcDynamicLayout(int nLength, DWORD dwMode) { file://设置固定大小工具条函数 if (dwMode&LM_COMMIT) { if (dwMode&LM_VERTDOCK) if(!m_bVertical) SetVertical(); else if(m_bVertical) SetHorizontal(); return CToolBar::CalcDynamicLayout(nLength,dwMode); } else { BOOL bOld=m_bVertical; BOOL bSwitch=(dwMode & LM_HORZ)?bOld:!bOld; if (bSwitch){ if (bOld) SetHorizontal(); else SetVertical();} CSize sizeResult=CToolBar::CalcDynamicLayout(nLength,dwMode); if (bSwitch){ if (bOld) SetHorizontal(); else SetVertical();} return sizeResult; } } /* CSize CEDITBAR::CalcFixedLayout(BOOL bStretch,BOOL bHorz) { file://设置可变大小工具条函数 if(!bHorz&&(m_dwStyle&CBRS_SIZE_DYNAMIC)&& !(m_dwStyle&CBRS_FLOATING)){ CSize size; size=CControlBar::CalcFixedLayout(bStretch,bHorz); CRect rect; rect.SetRectEmpty(); CalcInsideRect(rect, bHorz); size.cx=m_sizeVert.cx+(m_rectInsideVert.Width()-rect.Width()); size.cy=m_sizeVert.cy+(m_rectInsideVert.Height()-rect.Height()); return size; } else { if(m_bVertical) SetHorizontal(); return CToolBar::CalcFixedLayout(bStretch,bHorz); } } */ ⑤利用类向导ClassWizard建立新类CSearchBox并生成实现文件,同时为新类增加解释消息,即在新类实现文件SearchBox.h中增加如下函数定义: virtual BOOL PreTranslateMessage(MSG* pMsg); ⑥在其实现文件SearchBox.cpp文件中完善相应函数 BOOL CSearchBox::PreTranslateMessage(MSG* pMsg) { file://消息解释函数 if((pMsg->message!=WM_KEYDOWN)||(pMsg->wParam!=VK_RETURN)) return CComboBox::PreTranslateMessage(pMsg); // 在下拉列表中增加最后15次输入的数据 if((pMsg->lParam&0x40000000)==0){//不重复 CString strText; GetWindowText(strText); InsertString(0,strText); SetCurSel(0); if(GetCount()>15) DeleteString(GetCount()-1); } return TRUE; } ⑦在主程序实现文件MainFrm.h中增加工具条定义 #include "EDITBAR.H" CEDITBAR m_wndEditBar;//带输入框工具条 ⑧在主程序实现文件MainFrm.cpp中增加代码: file://在建立函数OnCreate中增加 if (!m_wndEditBar.Init(this,m_bToolTips)){ TRACE0("带编输入工具条建立失败\n"); return -1;} ...... DockControlBarLeftOf(&m_wndEditBar, &m_wndTestToolBar);//连接工具条 此外,还可以为工具条增加按钮检查状态等控制功能,详见工具条按钮的显示状态更新。 (2)利用工具条风格类实现控制 利用工具条风格类同样可以实现下拉列表等控制功能,其具体步骤如下: ①为工具栏建立位图按钮并定义按钮对应的消息,同前; ②在实现文件MainFrm.h中增加工具栏控制和函数定义; CStyleBar m_wndStyleBar; BOOL CreateStyleBar() ③在实现文件MainFrm.cpp中增加控制代码 在建立函数OnCreate中增加建立函数 if (!CreateStyleBar()) return -1; 增加建立相应工具条函数的代码 BOOL CMainFrame::CreateStyleBar() { file://建立具有下拉列表控制的工具条函数 const int nDropHeight = 100; if(!m_wndStyleBar.Create(this,WS_CHILD|WS_VISIBLE| CBRS_TOP|CBRS_TOOLTIPS|CBRS_FLYBY,IDW_STYLES)|| !m_wndStyleBar.LoadBitmap(IDB_STYLES)||!m_wndStyleBar. SetButtons(styles,sizeof(styles)/sizeof(UINT))){ TRACE0("特殊风格工具条建立失败\n"); return FALSE; } // 建立组合框 m_wndStyleBar.SetButtonInfo(0,IDW_COMBO,TBBS_SEPARATOR,100); // 设置组合框与按钮间距离 m_wndStyleBar.SetButtonInfo(1,ID_SEPARATOR,TBBS_SEPARATOR,12); CRect rect; m_wndStyleBar.GetItemRect(0,&rect); rect.top=3; rect.bottom=rect.top+nDropHeight; if (!m_wndStyleBar.m_comboBox.Create(//建立组合框 CBS_DROPDOWNLIST|WS_VISIBLE|WS_TABSTOP, rect, &m_wndStyleBar, IDW_COMBO)){ TRACE0("组合框建立失败\n"); return FALSE; } CString szStyle;//填充组合框 if (szStyle.LoadString(IDS_LEFT)) m_wndStyleBar.m_comboBox.AddString((LPCTSTR)szStyle); ......//同类的其它代码 LOGFONT logFont;//为组合框建立字体 memset(&logFont,0,sizeof(logFont)); if(!::GetSystemMetrics(SM_DBCSENABLED)){ logFont.lfHeight=-12; logFont.lfWeight=FW_BOLD; logFont.lfPitchAndFamily=VARIABLE_PITCH|FF_SWISS; CString strDefaultFont; strDefaultFont.LoadString(IDS_DEFAULT_FONT); lstrcpy(logFont.lfFaceName, strDefaultFont); if(!m_wndStyleBar.m_font.CreateFontIndirect(&logFont)) TRACE0("组合框建立字体失败\n"); else m_wndStyleBar.m_comboBox.SetFont(&m_wndStyleBar.m_font); } else { m_wndStyleBar.m_font.Attach(::GetStockObject(SYSTEM_FONT)); m_wndStyleBar.m_comboBox.SetFont(&m_wndStyleBar.m_font); } return TRUE; } 同样,也可以为这个特殊风格的工具条增加其它控制功能。 (3)利用对话框类建立多功能工具条 利用对话框工具条类可以建立功能更加强大的工具条,在对话框中实现的功能都可以增加到工具条中去。工具步骤如下: ①利用对话框资源增加一个对话框IDD_DLGTOOLBAR,并设计好对话框中的所有功能及功能对应的ID标识; ②在实现文件MainFrm.h中增加对话框工具条类控制和成员变量 CDialogBar m_wndDlgBar; file://对话框工具条控制 BOOL m_bDialogTop;//顶部标志 ③在实现文件MainFrm.cpp中增加控制代码 在建立函数OnCreate函数的状态条建立函数后增加: UINT nID, nStyle; int cxWidth; m_wndStatusBar.GetPaneInfo(0,nID,nStyle,cxWidth); m_wndStatusBar.SetPaneInfo(0,nID,SBPS_STRETCH|SBPS_NORMAL,cxWidth); if(!m_wndDlgBar.Create(this,IDD_TOOLBARCTRL, CBRS_LEFT|CBRS_TOOLTIPS|CBRS_FLYBY,IDD_TOOLBARCTRL)){ TRACE0("对话框工具条建立失败\n"); return -1; } 一定要注意EnableDocking(CBRS_ALIGN_ANY)函数在所有建立函数的前面,并且对话框工具条在状态条建立函数的后面。 ④为对话框资源中的所有控制消息手工增加映射,并完善相应的映射函数,可以是工具条的显示与隐藏等功能。示例函数如下: BOOL CMainFrame::OnDocktool(UINT nID) { file://控制工具条的显示与隐藏函数 BOOL bVisible = ((m_wndDockToolBar.GetStyle() & WS_VISIBLE) != 0); ShowControlBar(&m_wndDockToolBar, !bVisible, FALSE); RecalcLayout(); return TRUE; } void CMainFrame::OnUpdateDocktool(CCmdUI* pCmdUI) { file://工具条检查状态更新 BOOL bVisible = ((m_wndDockToolBar.GetStyle() & WS_VISIBLE) != 0); pCmdUI->SetCheck(bVisible); } ⑤在菜单中增加顶部控制菜单项,建立消息函数后按照工具条的停靠位置中的方法为其完善相应的顶部控制功能函数。 6.3.10 工具条的显示和隐藏 6.3.10.1 单一工具条的显示与隐藏 在大型应用程序中可能存在很多工具条,而用户实际使用时可能只需要其中的一部分,这就需要控制工具条的显示隐藏。对于单个工具条,其菜单项的一般控制方法为: 首先打开菜单编辑器,在查看菜单栏中增加工具条对应的子菜单项,如为TestToolBar 工具条增加的子菜单项的“测试”的ID为ID_BOTTON_TEST,其Caption 为“测试工具条”,依次增加其它工具条的控制子菜单项。利用类向导ClassWizard 为主框架窗口类CMainFrame增加各子菜单项的ONCOMMAND和ON_UPDATE_COMMAND_UI 消息映射功能函数,并完善对应的功能函数: void CMainFrame::OnButtonTest() { file://菜单命令消息函数 BOOL bVisible=((m_wndTestToolBar.GetStyle()& WS_VISIBLE)!=0);//取得工具条状态 ShowControlBar(&m_wndTestToolBar,!bVisible,FALSE); RecalcLayout();//更新工具条 } void CMainFrame::OnUpdateButtonTest(CCmdUI* pCmdUI) { file://菜单更新函数 BOOL bVisible=((m_wndTestToolBar.GetStyle()& WS_VISIBLE)!=0);//取得工具条状态 pCmdUI->SetCheck(bVisible);//设置检查标志 } |
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:46:00 AM -- 6.3.10.2 特殊工具条的显示与隐藏 当存在利用CStyleBar 类等建立的具有特殊控制功能的工具条时,工具条的显示与消隐必须利用下面的函数: BOOL CMainFrame::OnEdittool(UINT nID) { file://具有下拉组合框功能工具条显示与消隐函数 CWnd* pBar;//工具条窗口指针 if ((pBar=GetDlgItem(nID))==NULL)//取得鼠标点击项 return FALSE;//控制工具条窗口风格 pBar->ShowWindow((pBar->GetStyle()&WS_VISIBLE)==0); RecalcLayout();//重新显示窗口 return TRUE; } void CMainFrame::OnUpdateEdittool(CCmdUI* pCmdUI) { file://具有下拉组合框功能工具条状态更新 CWnd* pBar;//工具条窗口指针 if((pBar=GetDlgItem(pCmdUI->m_nID))==NULL){ pCmdUI->ContinueRouting();//继续判断 return; } file://设置检查状态 pCmdUI->SetCheck((pBar->GetStyle()&WS_VISIBLE)!=0); } 6.3.10.3 多工具条的显示与隐藏 如果存在很多工具条,使用菜单控制的方法并不方便,最好采用对话框设置的方法,如笔者在程序中通过对话框来控制工具条的显示与消隐、按钮排列数和动态提示功能的取舍。其步骤如下: ①首先打开资源列表中的对话框资源,利用"Insert->Resource->Dialog"插入对话框IDD_TOOLBAR,在对话框中建立四个工具条复选框按钮、四个排列数值的单选按钮和两个动态提示功能的单选按钮(请有关图形),并为各按钮设置ID标识和按钮名称,注意选中每组按钮中首按钮的Group属性; ②通过双击增加的对话框标题栏为对话框建立新类CToolDlg和两个实现文件ToolDlg.h和ToolDlg.cpp; ③打开对话框实现文件ToolDlg.h,增加如下成员控制变量: class CToolDlg : public CDialog { ......//其它代码 file://{{AFX_DATA(CToolDlg) enum { IDD = IDD_TOOLBAR }; BOOL m_bMainTool;//各工具条控制变量 BOOL m_bTestTool; BOOL m_bDockTool; BOOL m_bDockNext; int m_nToolTips;//动态提示控制变量 int m_nColumns; file://按钮排列数控制变量 ......//其它代码 } ④在实现文件ToolDlg.cpp中增加如下控制代码: CToolDlg::CToolDlg(CWnd* pParent /*=NULL*/) : CDialog(CToolDlg::IDD, pParent) { file://完善成员变量初始化 file://{{AFX_DATA_INIT(CToolDlg) m_bMainTool = FALSE; m_bTestTool = FALSE; m_bDockTool = FALSE; m_bDockNext = FALSE; m_nToolTips = -1; m_nColumns = -1; file://}}AFX_DATA_INIT } void CToolDlg::DoDataExchange(CDataExchange* pDX) { file://完善对话框按钮状态初始化 CDialog::DoDataExchange(pDX); file://{{AFX_DATA_MAP(CToolDlg) DDX_Check(pDX,IDC_MAINTOOL,m_bMainTool); DDX_Check(pDX,IDC_TESTTOOL,m_bTestTool); DDX_Check(pDX,IDC_DOCKTOOL,m_bDockTool); DDX_Check(pDX,IDC_DOCKNEXT,m_bDockNext); DDX_Radio(pDX,IDC_SHOW,m_nToolTips); DDX_Radio(pDX,IDC_TWO,m_nColumns); file://}}AFX_DATA_MAP } ⑤在主程序实现文件MainFrm.cpp 中增加ToolDlg.h 包含文件,并完善相应菜单选项功能。如笔者程序中为完善“测试”选项的功能,其控制代码如下: void CMainFrame::OnButtonTest() { file://完善相应菜单选项 CToolDlg ToolDlg;//调出对话框 UINT nColumns = m_wndDockToolBar.GetColumns(); nColumns -= 2;//取得按钮排列数 file://取得工具栏的可视风格 ToolDlg.m_bMainTool=((m_wndMainToolBar. GetStyle()&WS_VISIBLE)!=0); ToolDlg.m_bTestTool=((m_wndTestToolBar. GetStyle()&WS_VISIBLE)!=0); ToolDlg.m_bDockTool=((m_wndDockToolBar. GetStyle()&WS_VISIBLE)!=0); ToolDlg.m_bDockNext=((m_wndDockNextBar. GetStyle()&WS_VISIBLE)!=0); ToolDlg.m_nColumns=nColumns; ToolDlg.m_nToolTips=(m_bToolTips)?0:1; if (IDOK==ToolDlg.DoModal()) { file://控制工具条的显示与消隐 ShowControlBar(&m_wndMainToolBar, ToolDlg.m_bMainTool,FALSE); ......//其它工具条控制代码,基本相同 m_bToolTips=(ToolDlg.m_nToolTips==0); if (m_bToolTips){//控制动态提示功能 m_wndMainToolBar.SetBarStyle(m_wndMainToolBar. GetBarStyle()|CBRS_TOOLTIPS|CBRS_FLYBY); ......//其它工具条控制代码,基本相同 } else { m_wndMainToolBar.SetBarStyle(m_wndMainToolBar. GetBarStyle()&(CBRS_TOOLTIPS|CBRS_FLYBY)); ......//其它工具条控制代码,基本相同 } nColumns=ToolDlg.m_nColumns+2;//控制工具条按钮列数 m_wndDockToolBar.SetColumns(nColumns); m_wndDockNextBar.SetColumns(nColumns); m_wndMainToolBar.Invalidate();//控制各工具条显示更新 ......//其它工具条控制代码,基本相同 } } 6.3.11 工具条的属性控制 WIN95/98中的鼠标右键弹出属性表功能具有很强的功能特色,很多应用程序中都对各种功能进行了属性控制设置,给应用程序中的工具条增加属性控制功能更具有现实意义。在工具条的属性表中可以设置工具栏的消隐与显示、工具条按钮的排序和其它各种控制功能,这要根据应用程序的需要来确定。这里以控制工具条的显示与消隐为例,详细阐述其实现的具体步骤: (1)首先打开资源列表中的菜单资源,利用"Insert->Resource->Menu"增加菜单资源IDR_TOOLBAR,在菜单中建立四个工具条菜单选项,并为各菜单选项设置ID标识和菜单项名,注意将各菜单选项标识符设置为与工具条名称完全相同的控制符,如本例程序中主控工具条ID为IDR_wndMainToolBar,那么控制主控工具条的菜单选项ID标识符也必须是IDR_wndMainToolBar,这样设置后可以使用MFC 类库中的提供的功能来控制工具条的显示与消隐、菜单项检查状态与非检查状态。 (2)然后利用类向导ClassWizard 为应用程序主框架窗口增加解释消息PreTranslateMessage处理函数; (3)在应用程序实现文件MainFrm.cpp中完善解释消息函数: BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { if (pMsg->message==WM_RBUTTONDOWN){ CWnd* pWnd=CWnd::FromHandlePermanent(pMsg->hwnd); CControlBar* pBar=DYNAMIC_DOWNCAST(CControlBar,pWnd);//取得控制条句柄 if (pBar!=NULL){ CMenu Menu;//建立弹出式菜单 CPoint pt; file://鼠标指针位置 pt.x = LOWORD(pMsg->lParam); pt.y = HIWORD(pMsg->lParam); pBar->ClientToScreen(&pt);//转换成屏幕坐标 if (Menu.LoadMenu(IDR_TOOLBAR)){//调入菜单资源 CMenu* pSubMenu = Menu.GetSubMenu(0);//取得选项 if (pSubMenu!=NULL){//跟踪菜单以取得输入 pSubMenu->TrackPopupMenu(TPM_LEFTALIGN| TPM_RIGHTBUTTON,pt.x,pt.y,this); } } } } return CFrameWnd::PreTranslateMessage(pMsg); } (4)在消息映射函数后增加 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) file://{{AFX_MSG_MAP(CMainFrame) ......//其它消息映射函数 file://}}AFX_MSG_MAP // 增加扩展命令消息和更新消息映射,利用现成MFC函数更新菜单 ON_UPDATE_COMMAND_UI(IDR_MAINTOOLBAR,OnUpdateControlBarMenu) ON_COMMAND_EX(IDR_MAINTOOLBAR,OnBarCheck) ......//其它工具条映射函数基本相同,只有标识不同 END_MESSAGE_MAP() (5)重要的程序设计技巧步骤 上述步骤1和步骤4是实现工具条属性菜单有效控制的最关键步骤,如果不在步骤1中将菜单ID设置成工具条的ID,或者不在步骤4中设置相应的消息映射,都不可能实现工具条菜单与工具条状态的有效控制。工具条菜单中的其它属性功能可模仿实现。 |
-- 作者:卷积内核 -- 发布时间:9/5/2006 9:54:00 AM -- 第7章 演练CProgress 7.1 进度条的主要功能 进度条控制(Progress Control)主要用来进行数据读写、文件拷贝和磁盘格式等操作时的工作进度提示情况,如安装程序等,伴随工作进度的进展,进度条的矩形区域从左到右利用当前活动窗口标题条的颜色来不断填充。 进度条控制在MFC类库中的封装类为CProgressCtrl,通常仅作为输出类控制,所以其操作主要是设置进度条的范围和当前位置,并不断地更新当前位置。进度条的范围用来表示整个操作过程的时间长度,当前位置表示完成情况的当前时刻。SetRange()函数用来设置范围,初始范围为0-100,SetPos()函数用来设置当前位置,初始值为0,SetStep()函数用来设置步长,初始步长为10,StepIt()函数用来按照当前步长更新位置,OffsetPos()函数用来直接将当前位置移动一段距离。如果范围或位置发生变化,那么进度条将自动重绘进度区域来及时反映当前工作的进展情况。 进度条的对象结构 进度条控制的建立方法 CProgressCtrl &ProgressCtrl 建立进度条控制对象结构 Create 建立进度条控制对象并绑定对象 进度条控制类CprogressCtrl::Create的调用格式如下: BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 其中参数dwStyle用来确定进度条控制的控制风格;参数rect用来确定进度条控制的大小和位置;参数pParentWnd用来确定进度条父窗口指针;参数nID用来确定进度条控制的控制符ID值。 7.2 进度条控制的类属性 进度条控制的类属性包括设置进度条最大最小控制范围SetRange、设置进度条当前位置 SetPos、设置进度条当前位置偏移值OffsetPos和设置进度条控制增量值SetStep。 7.3 进度条控制的操作方法 进度条控制的操作方法主要是使进度条控制并重绘进度条的StepIt函数。 进度条控制的应用技巧示例 1、利用应用程序向导AppWizard生成基于对象框的应用程序CProgDlg; 2、在对话框中设置进度条和静态文本控制,其ID分别为IDC_PROG和IDCPERCENT; 在对话框初始代码中增加控制的范围和位置: 在ProgDlg.h中设置两个数据成员,用来表示进度条的最大值和步长: file://ProgDlg.h class CProgDlg:public Cdialog { ......//其它代码 public: int m_nMax,m_nStep; ...... file://其它代码 } (2)在ProgDlg.cpp中设置初始状态 BOOL CProgDlg::OnInitDialog() { Cdialog::OnInitDialog(); ......//其它代码 file://TODO:Add extra initialization here CProgressCtrl *pProgCtrl=(CProgressCtrl*)GetDlgItem(IDC_PROG); pProgCtrl->SetRange(0,200);//设置进度条范围 ......//其它代码 m_nMax=200; m_nStep=10; SetTimer(1,1000,NULL);//设置进度条更新时钟 return TRUE; } (3)完善WM_TIMER消息处理,使进度条按照当前步长进行更新,同时完成进度条的百分比显示: void CProgDlg::OnTimer(UINT nIDEvent) { file://TODO:Add your message handler CProgressCtrl *pProgCtrl=(CProgressCtrl*)GetDlgItem(IDC_PROG); int nPrePos=pProgCtrl->StepIt();//取得更新前位置 char test[10]; int nPercent=(int)(((nPrePos+m_nStep)/m_nMax*100+0.5); wsprintf(test,%d%%,nPercent); GetDlgItem(IDC_PERCENT)->SetWindowText(text); Cdialog::OnTimer(nIDEvent); } |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
390.625ms |