VS2010/MFC完全版(全五十四章)从入门到精通


VS2010/MFC 编程入门教程之目录 第一部分:VS2010/MFC 开发环境 VS2010/MFC 编程入门之前言 VS2010/MFC 编程入门之一(VS2010 与 MSDN 安装过程图解) 第二部分:VS2010/MFC 应用程序框架 VS2010/MFC 编程入门之二(利用 MFC 向导生成单文档应用程序框架) VS2010/MFC 编程入门之三(VS2010 应用程序工程中文件的组成结构) VS2010/MFC 编程入门之四(MFC 应用程序框架分析) VS2010/MFC 编程入门之五(MFC 消息映射机制概述) 第三部分:对话框 VS2010/MFC 编程入门之六(对话框:创建对话框模板和修改对话框属性) VS2010/MFC 编程入门之七(对话框:为对话框添加控件) VS2010/MFC 编程入门之八(对话框:创建对话框类和添加控件变量) VS2010/MFC 编程入门之九(对话框:为控件添加消息处理函数) VS2010/MFC 编程入门之十(对话框:设置对话框控件的 Tab 顺序) VS2010/MFC 编程入门之十一(对话框:模态对话框及其弹出过程) VS2010/MFC 编程入门之十二(对话框:非模态对话框的创建及显示) VS2010/MFC 编程入门之十三(对话框:属性页对话框及相关类的介绍) VS2010/MFC 编程入门之十四(对话框:向导对话框的创建及显示) VS2010/MFC 编程入门之十五(对话框:一般属性页对话框的创建及显示) VS2010/MFC 编程入门之十六(对话框:消息对话框) VS2010/MFC 编程入门之十七(对话框:文件对话框) VS2010/MFC 编程入门之十八(对话框:字体对话框) VS2010/MFC 编程入门之十九(对话框:颜色对话框) 第四部分:常用控件 VS2010/MFC 编程入门之二十(常用控件:静态文本框) VS2010/MFC 编程入门之二十一(常用控件:编辑框 Edit Control) VS2010/MFC 编程入门之二十二(常用控件:按钮控件 Button、Radio Button 和 Check Box) VS2010/MFC 编程入门之二十三(常用控件:按钮控件的编程实例) VS2010/MFC 编程入门之二十四(常用控件:列表框控件 ListBox) VS2010/MFC 编程入门之二十五(常用控件:组合框控件 Combo Box) VS2010/MFC 编程入门之二十六(常用控件:滚动条控件 Scroll Bar) VS2010/MFC 编程入门之二十七(常用控件:图片控件 Picture Control) VS2010/MFC 编程入门之二十八(常用控件:列表视图控件 List Control 上) VS2010/MFC 编程入门之二十九(常用控件:列表视图控件 List Control 下) VS2010/MFC 编程入门之三十(常用控件:树形控件 Tree Control 上) VS2010/MFC 编程入门之三十一(常用控件:树形控件 Tree Control 下) VS2010/MFC 编程入门之三十二(常用控件:标签控件 Tab Control 上) VS2010/MFC 编程入门之三十三(常用控件:标签控件 Tab Control 下) 第五部分:菜单、工具栏与状态栏 VS2010/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解) VS2010/MFC 编程入门之三十五(菜单:菜单及 CMenu 类的使用) VS2010/MFC 编程入门之三十六(工具栏:工具栏资源及 CToolBar 类) VS2010/MFC 编程入门之三十七(工具栏:工具栏的创建、停靠与使用) VS2010/MFC 编程入门之三十八(状态栏的使用详解) 第六部分:文档、视图和框架 VS2010/MFC 编程入门之三十九(文档、视图和框架:概述) VS2010/MFC 编程入门之四十(文档、视图和框架:各对象之间的关系) VS2010/MFC 编程入门之四十一(文档、视图和框架:分割窗口) 第七部分:MFC 常用类 VS2010/MFC 编程入门之四十二(MFC 常用类:CString 类) VS2010/MFC 编程入门之四十三(MFC 常用类:CTime 类和 CTimeSpan 类) VS2010/MFC 编程入门之四十四(MFC 常用类:定时器 Timer) VS2010/MFC 编程入门之四十五(MFC 常用类:CFile 文件操作类) VS2010/MFC 编程入门之四十六(MFC 常用类:MFC 异常处理) 第八部分:字体和文本输出 VS2010/MFC 编程入门之四十七(字体和文本输出:CFont 字体类) VS2010/MFC 编程入门之四十八(字体和文本输出:文本输出) 第九部分:图形图像 VS2010/MFC 编程入门之四十九(图形图像:CDC 类及其屏幕绘图函数) VS2010/MFC 编程入门之五十(图形图像:GDI 对象之画笔 CPen) VS2010/MFC 编程入门之五十一(图形图像:GDI 对象之画刷 CBrush) 第十部分:Ribbon 界面开发 VS2010/MFC 编程入门之五十二(Ribbon 界面开发:创建 Ribbon 样式的应用程序框架) VS2010/MFC 编程入门之五十三(Ribbon 界面开发:为 Ribbon Bar 添加控件) VS2010/MFC 编程入门之五十四(Ribbon 界面开发:使用更多控件并为控件添加消息处理 函数) VS2010/MFC 编程入门之前言 鸡啄米的 C++编程入门系列给大家讲了 C++的编程入门知识,大家对 C++语言在语法 和设计思想上应该有了一定的了解了。但是教程中讲的例子只是一个个简单的例程,并没 有可视化窗口。鸡啄米在这套 VS2010/MFC 编程入门教程中将会给大家讲解怎样使用 VS 2010 进行可视化编程,也就是基于窗口的程序。 C++编程入门系列主要偏重于理论方面的知识,目的是让大家打好底子,练好内功, 在使用 VC++编程时不至于丈二和尚摸不着头脑。本套教程也会涉及到 VC++的原理性的 东西,同样更重视实用性,让大家学完本套教程以后,基本的界面程序都能很容易编写出 来。 VC++简介 VC++全称是 Visual C++,是由微软提供的 C++开发工具,它与 C++的根本区别就在 于,C++是语言,而 VC++是用 C++语言编写程序的工具平台。VC++不仅是一个编译器更 是一个集成开发环境,包括编辑器、调试器和编译器等,一般它包含在 Visual Studio 中。 Visual Studio 包含了 VB、VC++、C#等编译环境。当然我们在使用 VC++ 6.0 的时候为了 轻便,总是只单独安装 VC++ 6.0。但自微软 2002 年发布 Visual Studio.NET 以来,微软 建立了在.NET 框架上的代码托管机制,一个项目可以支持多种语言开发的组件,VC++同 样被扩展为支持代码托管机制的开发环境,所以.NET Framework 是必须的,也就不再有 VC++的独立安装程序,不过可以在安装 Visual Studio 时只选择 VC++进行安装。 VC++版本的选择:VS2010 因为 VC++ 6.0 以后的版本不再有独立的安装程序,所以鸡啄米在教程中将不会称 VC ++ 6.0 以后的版本为 VC++ 7.0 等等,而是用 VC++所属的 Visual Studio 的版本名称代替 ,比如 VS2003。 近些年 VC++主要的版本包括:VC++ 6.0、VS2003、VS2005、VS2008 和VS2010。 VC++ 6.0 占用的系统资源比较少,打开工程、编译运行都比较快,所以赢得很多软件 开发者的青睐。但因为它先于 C++标准推出,所以对 C++标准的支持不太好。举个例子: for(int i=0; i<5; i++) { a[i] = i; } for 语句中声明的变量 i,对于 VC++ 6.0 来说,出了 for 循环仍能使用。但很显然这与 C++标准对于变量生存期的规定不符合。 随着 VC++版本的更新,对 C++标准的支持越来越好,对各种技术的支持也越来越完 善。但同时新版本所需的资源也越来越多,对处理器和内存的要求越来越高。到 VS2010 ,光安装文件就 2G 多,安装后的文件占 3G 多空间,其运行也经常受处理器和内存等性 能的限制。但鸡啄米还是推荐大家使用 VS2010,毕竟它是最新版本,类库和开发技术都 是最完善的,本教程也将使用 VS2010 为大家做例程的演示。当然如果系统配置确实比较 低,可以选择 VS2005,VS2005 和 VS2010 相比还是要轻量级一些的。VC++ 6.0 已经过 时,奉劝大家尽量别用了。 VC++与 MFC 讲 VC++免不了要提 MFC,MFC 全称 Microsoft Foundation Classes,也就是微软基 础类库。它是 VC++的核心,是 C++与 Windows API 的结合,很彻底的用 C++封装了 Win dows SDK(Software Development Kit,软件开发工具包)中的结构和功能,还提供了一 个应用程序框架,此应用程序框架为软件开发者完成了一些例行化的工作,比如各种窗口 、工具栏、菜单的生成和管理等,不需要开发者再去解决那些很复杂很乏味的难题,比如 每个窗口都要使用 Windows API 注册、生成与管理。这样就大大减少了软件开发者的工作 量,提高了开发效率。 当然 VC++不是只能够创建 MFC 应用程序,同样也能够进行 Windows SDK 编程,但 是那样的话就舍弃了 VC++的核心,放弃了 VC++最强大的部分。MFC 也不是只能用于 V C++中,它同样也可以用在 Borland C++等编译器中,当然没有几个人这样做。 本节旨在让大家对 VC++、VS2010 和 MFC 有基本的概念上的认识,后面鸡啄米会带 大家进入 VS2010/MFC 的世界,让大家轻松的开发各种包含窗口、图形等的可视化程序。 VS2010/MFC 编程入门之一(VS2010 与 MSDN 安装过程图解) 上一讲中鸡啄米对 VC++和 MFC 做了一些简单介绍。在本套教程中鸡啄米将使用 VS 2010 为大家讲解如何使用 VC++和 MFC 进行编程,所以首先要安装 VS2010。 一.下载 VS2010 首先我们需要下载 VS2010,大家可以在网上下载 VS2010 破解正式版,建议选择英 文版,养成使用英文工具的习惯。鸡啄米使用 VS2010 旗舰试用版 VS2010UltimTrial.iso 为例介绍安装过程,旗舰试用版官方下载地址为:http://www.microsoft.com/download/en/ details.aspx?displaylang=en&id=12187。正式版的安装过程与试用版类似。 二.安装 VS2010 下载后进行安装。安装方法与一般的 iso 文件一样,可以使用虚拟光驱软件 Daemon Tools 安装,也可以将其解压后点击 setup.exe 进行安装。 鸡啄米为了让大家更直观的看到安装过程,我将在自己机子上再重新安装一次,并截 图为大家讲解。 这里使用 Daemon Tools 安装 VS2010。首先打开 Daemon Tools,屏幕右下角会出现 托盘图标,在图标上点右键,会弹出菜单,再把鼠标移到菜单项“虚拟设备”上,然后再移 到子菜单项“设备 0:[L:] 无媒体”上,最后点击下一级子菜单项“装载映像”,弹出对话框选 择 VS2010UltimTrial.iso 文件。 这样虚拟光驱就会打开此 iso 文件,弹出自动安装的提示,选择“运行 autorun.exe”就 可以了,如果没有弹出提示就通过资源管理器进入虚拟光驱,用 setup.exe 安装。接着会 弹出下面的对话框: 当然选择“Install Microsoft Visual Studio 2010”进入下一步,加载安装组件后如下显示 : 点“Next”后: 选择“I have read and accept the license terms”后点“Next”弹出对话框: 此处是让我们选择要安装的功能,有两种:Full(完全)和 Custom(自定义)。Full 选项表示安装所有编程语言和工具,Custom 选择表示可以自定义要安装的编程语言和工 具。右侧可以更改安装路径,鸡啄米建议不要安装到 C 盘,因为它占用的空间比较大。鸡 啄米安装到了 D 盘,使用 Full 完全安装。如果选择 Custom 安装,点“Next”则出现如下画 面: 大家可以根据自己的需要取消某些语言或工具的安装,比如不想安装 Visual C#,取消 选择它就可以了。如果觉得以后都有可能会用到,那就像鸡啄米一样选择完全安装吧。 Full 或 Custom 方式和安装路径设置好后,点“Install”进行安装: 可能正式版的安装文件在安装过程中会有重启过程。鸡啄米使用的试用版中间并没有 重启。安装完成: 如果要继续安装 MSDN,先不要卸载虚拟光驱映像。 三.安装 MSDN 我们使用 VS2010 进行软件开发同样离不开帮助文档,即 MSDN。在本地安装 MSD N 的方法如下: 在开始菜单的“所有程序”->“Microsoft Visual Studio 2010”->“Visual Studio Tools”下 选择“Manage Help Settings - ENU”: 弹出对话框: 可以将帮助库存在默认路径,也可以修改存放路径。鸡啄米使用默认路径,点“OK” 出现: 选择“Install Content From Disk”后弹出对话框选择帮助所在文件,这时需要在加载了 VS2010 的虚拟光驱中找,选择图中所示路径: 点 OK 后出现如下对话框,可以点“Add”选择要添加的帮助库,鸡啄米全部添加了。 点“Update”进行安装,等待其完成就可以了。 使用 MSDN 时点击开始菜单的“所有程序”->“Microsoft Visual Studio 2010”->“Micros oft Visual Studio 2010 Documentation”即可。 到此 VS2010 和 MSDN 的安装过程就结束了。以后就可以正式使用 VS2010 进行软 件开发了。至于VS2010 的使用方法在鸡啄米的 C++编程入门系列中已经介绍过,大家可 以看看。 VS2010/MFC 编程入门之二(利用 MFC 向导生成单文档应用程 序框架) 解决方案与工程 鸡啄米在VS2010 的使用介绍中已经讲了解决方案与工程的概念,这里再重提一下。 每个应用程序都作为一个工程来处理,它包含了头文件、源文件和资源文件等,这些文件 通过工程集中管理。在 VS2010 中,工程都是在解决方案管理之下的。一个解决方案可以 管理多个工程,可以把解决方案理解为多个有关系或者没有关系的工程的集合。VS2010 提供了一个 Solution Explorer 解决方案浏览器视图,可以显示当前解决方案的内容,当新 建一个工程时可以选择新建一个解决方案还是加入当前解决方案。 下图左侧面板中正在显示的视图就是 Solution Explorer,视图中有一个解决方案-Hello World,此解决方案下有一个同名的工程-HelloWorld。 在应用程序向导生成应用程序后,VS2010 会在用户设置的路径下,以解决方案名为名 称建立一个目录,里面存放自动生成的文件。 使用 VS2010 应用程序向导生成单文档应用程序框架 鸡啄米这里简略演示下怎样生成单文档应用程序框架,让大家先有个直观的了解,有 不理解的地方可以留着以后回来再看。下面按照操作步骤一步步讲解: 1.点菜单栏 File->New->Project,弹出 New Project 对话框,我们可以选择工程类型。 如果安装完 VS2010 以后第一启动时已经设置为 VC++,则 Installed Templates->Visu al C++项会默认展开,而如果没有设置 VC++,则可以展开到 Installed Templates->Other Languages->Visual C++项。因为我们要生成的是 MFC 程序,所以在“Visual C++”下选择“ MFC”,对话框中间区域会出现三个选项:MFC ActiveX Control、MFC Application 和 MF C DLL。MFC ActiveX Control 用来生成 MFC ActiveX 控件程序。MFC Application 用来生 成 MFC 应用程序。MFC DLL 用来生成 MFC 动态链接库程序。当然我们要选择 MFC App lication。 在对话框下部有 Name、Location 和 Solution name 三个设置项。意义如下:Name-- 工程名,Location--解决方案路径,Solution name--解决方案名称。这里 Name 我们设为“ HelloWorld”,Location 设置为“桌面”的路径,Solution name 默认和 Name 一样,当然可 以修改为其他名字,这里我们不作修改,也使用“HelloWorld”。点“OK”按钮。 2.这时会弹出“MFC Application Wizard”对话框,上部写有“Welcome to the MFC Appli cation Wizard”,下面显示了当前工程的默认设置。第一条“Tabbed multiple document inte rface (MDI)”是说此工程是多文档应用程序。如果这时直接点下面的“Finish”按钮,可生成 具有上面列出设置的多文档程序。但我们此例是要建立单文档应用程序,所以点“Next”按 钮再继续设置吧。 3.接下来弹出的对话框上部写有“Application Type”,当然是让选择应用程序类型,我 们看到有四种类型:Single document(单文档)、Multiple documents(多文档)、Dialo g based(基于对话框)和 Multiple top-level documents。我们选择 Single document 类型 ,以生成一个单文档应用程序框架。单文档应用程序运行时是一个单窗口界面。 此对话框的“Resource language”还提供语言的选择,这里默认选择英语。“Project styl e”可选择工程风格,我们选择默认的“Visual Studio”风格。“Use of MFC”有两个选项:Use MFC in a shared DLL(动态链接库方式使用 MFC)和 Use MFC in a static library(静态 库方式使用 MFC)。选择 Use MFC in a shared DLL 时 MFC 的类会以动态链接库的方式 访问,所以我们的应用程序本身就会小些,但是发布应用程序时必须同时添加必要的动态 链接库,以便在没有安装 VS2010 的机子上能够正常运行程序。选择 Use MFC in a static l ibrary 时 MFC 的类会编译到可执行文件中,所以应用程序的可执行文件要比上种方式大, 但可以单独发布,不需另加包含 MFC 类的库。这里我们使用默认的 Use MFC in a shared DLL。点“Next”按钮。 4.此时弹出上部写有“Compound Document Support”的对话框,可以通过它向应用程 序加入 OLE 支持,指定 OLE 选项的复合文档类型。本例不需要 OLE 特性,使用默认值“ None”。点“Next”按钮。 5.弹出的新对话框上部写有“Document Template Properties”。“File extension”可以设 置程序能处理的文件的扩展名。对话框其他选项还可以更改程序窗口的标题。我们都使用 默认设置,点“Next”按钮。 6.此时弹出的对话框主题是“Database Support”。用于设置数据库选项。此向导可以生 成数据库应用程序需要的代码。它有四个选项: None:忽略所有的数据库支持; Header files only:只包含定义了数据库类的头文件,但不生成对应特定表的数据库类 或视图类; Database view without file support:创建对应指定表的一个数据库类和一个视图类, 不附加标准文件支持; Database view with file support:创建对应指定表的一个数据库类和一个视图类,并附 加标准文件支持。 本例选择默认值“None”,不使用数据库特性。点“Next”按钮。 7.这时弹出的对话框是关于“User Interface Features”,即用户界面特性。我们可以设 置有无最大化按钮、最小化按钮、系统菜单和初始状态栏等。还可以选择使用菜单栏和工 具栏生成简单的应用程序还是使用 ribbon。这里我们都选择默认设置。点“Next”进入下一 步。 8.此时弹出“高级特性”对话框。可以设置的高级特性包括有无打印和打印预览等。在“N umber of files on recent file list”项可以设置在程序界面的文件菜单下面最近打开文件的个 数。我们仍使用默认值。点“Next”按钮。 9.弹出“生成类”对话框。在对话框上部的“生成类”列表框内,列出了将要生成的 4 个类 :一个视图类(CHelloWorldView)、一个应用类(CHelloWorldApp)、一个文档类(C HelloWorldDoc)和一个主框架窗口类(CMainFrame)。在对话框下面的几个编辑框中, 可以修改默认的类名、类的头文件名和源文件名。对于视图类,还可以修改其基类名称, 默认的基类是 CView,还有其他几个基类可以选择。这里我们还是使用默认设置。点“Fini sh”按钮。 应用程序向导最后为我们生成了应用程序框架,并在 Solution Explorer 中自动打开了 解决方案(见上面第一张图)。 编译运行生成的程序 点菜单中的 Build->Build HelloWorld 编译程序,然后点 Debug->Start Without Debug ging(快捷键 Ctrl+F5)运行程序,也可以直接点 Debug->Start Without Debugging,这时 会弹出对话框提示是否编译,选择“Yes”,VS2010 将自动编译链接运行 HelloWorld 程序 。结果页面如下所示: 终于看见界面了。鸡啄米在以后的教程中会继续讲解各种界面和控件的使用方法。欢 迎到鸡啄米博客交流,您的关注是我前进的动力。 VS2010/MFC 编程入门之三(VS2010 应用程序工程中文件的组 成结构) 鸡啄米在上一讲中为大家演示了如何利用应用程序向导创建单文档应用程序框架。这 一节将以上一讲中生成应用程序 HelloWorld 的文件结构为例,讲解VS2010应用程序工程 中文件的组成结构。 用应用程序向导生成框架程序后,我们可以在之前设置的 Location 下看到以解决方案 名命名的文件夹,此文件夹中包含了几个文件和一个以工程名命名的子文件夹,这个子文 件夹中又包含了若干个文件和一个 res 文件夹,创建工程时的选项不同,工程文件夹下的 文件可能也会有所不同。 如果已经以 Debug 方式编译链接过程序,则会在解决方案文件夹下和工程子文件夹下 各有一个名为“Debug”的文件夹,而如果是 Release 方式编译则会有名为“Release”的文件 夹。这两种编译方式将产生两种不同版本的可执行程序:Debug 版本和 Release 版本。D ebug 版本的可执行文件中包含了用于调试的信息和代码,而 Release 版本则没有调试信 息,不能进行调试,但可执行文件比较小。 鸡啄米将所有文件分为 6 个部分:解决方案相关文件、工程相关文件、应用程序头文 件和源文件、资源文件、预编译头文件和编译链接生成文件。 1.解决方案相关文件 解决方案相关文件包括解决方案文件夹下的.sdf 文件、.sln 文件、.suo 文件和 ipch 文 件夹。 .sdf 文件和 ipch 目录一般占用空间比较大,几十兆甚至上百兆,与智能提示、错误提 示、代码恢复和团队本地仓库等相关。如果你觉得不需要则可以设置不生成它们,方法是 点击菜单栏 Tools->Options,弹出 Options 对话框,选择左侧面板中 Text Editor->C/C++->A dvanced,右侧列表中第一项 Disable Database 由 False 改为 True 就可以了,最后关闭 VS2010 再删除.sdf 文件和 ipch 目录以后就不会再产生了。但关闭此选项以后也会有很多 不便,例如写程序时的智能提示没有了。 .sln 文件和.suo 文件为 MFC 自动生成的解决方案文件,它包含当前解决方案中的工 程信息,存储解决方案的设置。 2.工程相关文件 工程相关文件包括工程文件夹下的.vcxproj 文件和.vcxproj.filters 文件。 .vcxproj 文件是 MFC 生成的工程文件,它包含当前工程的设置和工程所包含的文件等 信息。.vcxproj.filters 文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录结 构信息。 3.应用程序头文件和源文件 应用程序向导会根据应用程序的类型(单文档、多文档或基于对话框的程序)自动生 成一些头文件和源文件,这些文件是工程的主体部分,用于实现主框架、文档、视图等。 鸡啄米下面分别简单介绍下各个文件: HelloWorld.h:应用程序的主头文件。主要包含由 CWinAppEx 类派生的 CHelloWorld App 类的声明,以及 CHelloWorldApp 类的全局对象 theApp 的声明。 HelloWorld.cpp:应用程序的主源文件。主要包含 CHelloWorldApp 类的实现,CHell oWorldApp 类的全局对象 theApp 的定义等。 MainFrm.h 和 MainFrm.cpp:通过这两个文件从 CFrameWndEx 类派生出 CMainFra me 类,用于创建主框架、菜单栏、工具栏和状态栏等。 HelloWorldDoc.h 和 HelloWorldDoc.cpp:这两个文件从 CDocument 类派生出文档类 CHelloWorldDoc,包含一些用来初始化文档、串行化(保存和装入)文档和调试的成员函 数。 HelloWorldView.h 和 HelloWorldView.cpp:它们从 CView 类派生出名为 CHelloWorld View 的视图类,用来显示和打印文档数据,包含了一些绘图和用于调试的成员函数。 ClassView.h 和 ClassView.cpp:由 CDockablePane 类派生出 CClassView 类,用于 实现应用程序界面左侧面板上的 Class View。 FileView.h 和 FileView.cpp:由 CDockablePane 类派生出 CFileView 类,用于实现应 用程序界面左侧面板上的 File View。 OutputWnd.h 和 OutputWnd.cpp:由 CDockablePane 类派生出 COutputWnd 类,用 于实现应用程序界面下侧面板 Output。 PropertiesWnd.h 和 PropertiesWnd.cpp:由 CDockablePane 类派生出 CProperties Wnd 类,用于实现应用程序界面右侧面板 Properties。 ViewTree.h 和 ViewTree.cpp:由 CTreeCtrl 类派生出 CViewTree 类,用于实现出现 在 ClassView 和 FileView 等中的树视图。 4.资源文件 一般我们使用 MFC 生成窗口程序都会有对话框、图标、菜单等资源,应用程序向导 会生成资源相关文件:res 目录、HelloWorld.rc 文件和 Resource.h 文件。 res 目录:工程文件夹下的 res 目录中含有应用程序默认图标、工具栏使用图标等图标 文件。 HelloWorld.rc:包含默认菜单定义、字符串表和加速键表,指定了默认的 About 对话 框和应用程序默认图标文件等。 Resource.h:含有各种资源的 ID 定义。 5.预编译头文件 几乎所有的 MFC 程序的文件都要包含 afxwin.h 等文件,如果每次都编译一次则会大 大减慢编译速度。所以把常用的 MFC 头文件都放到了 stdafx.h 文件中,然后由 stdafx.cpp 包含 stdafx.h 文件,编译器对 stdafx.cpp 只编译一次,并生成编译之后的预编译头 HelloW orld.pch,大大提高了编译效率。 6.编译链接生成文件 如果是 Debug 方式编译,则会在解决方案文件夹和工程文件夹下都生成 Debug 子文 件夹,而如果是 Release 方式编译则生成 Release 子文件夹。 工程文件夹下的 Debug 或 Release 子文件夹中包含了编译链接时产生的中间文件, 解决方案文件夹下的 Debug 或 Release 子文件夹中主要包含有应用程序的可执行文件。 关于应用程序工程文件的组成结构鸡啄米就先讲到这了。其中包含了很多专有名词, 以后大家会慢慢熟悉的。欢迎来鸡啄米博客交流。谢谢。 VS2010/MFC 编程入门之四(MFC 应用程序框架分析) 上一讲鸡啄米讲的是VS2010 应用程序工程中文件的组成结构,可能大家对工程的运 行原理还是很模糊,理不出头绪,毕竟跟 C++编程入门系列中的例程差别太大。这一节鸡 啄米就为大家分析下 MFC 应用程序框架的运行流程。 一.SDK 应用程序与 MFC 应用程序运行过程的对比 程序运行都要有入口函数,在之前的 C++教程中都是 main 函数,而 Windows 应用程 序的入口函数是 WinMain 函数,MFC 程序也是从 WinMain 函数开始的。下面鸡啄米就给 出用 Windows SDK 写的“HelloWorld”程序,与应用程序框架进行对比,这样能更好的了解 框架是怎样运行的。Windows SDK 开发程序就是不使用 MFC 类库,直接用 Windows API 函数进行软件开发。鸡啄米不是要讲解 SDK 开发,只是为了对比而简单介绍,至于 SDK 开发可以在大家学完 MFC 以后选择是否要研究,一般来说有简单了解就可以了。 SDK 应用程序 首先,给出 Windows SDK 应用程序“HelloWorld”的源码: C++代码 1. #include 2. 3. LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wPara m, LPARAM lParam); 4. 5. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) 6. { 7. const static TCHAR appName[] = TEXT("Hello world"); 8. WNDCLASSEX myWin; 9. myWin.cbSize = sizeof(myWin); 10. myWin.style = CS_HREDRAW | CS_VREDRAW; 11. myWin.lpfnWndProc = myWndProc; 12. myWin.cbClsExtra = 0; 13. myWin.cbWndExtra = 0; 14. myWin.hInstance = hInstance; 15. myWin.hIcon = 0; 16. myWin.hIconSm = 0; 17. myWin.hCursor = 0; 18. myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 19. myWin.lpszMenuName = 0; 20. myWin.lpszClassName = appName; 21. //Register 22. if (!RegisterClassEx(&myWin)) return 0; 23. const HWND hWindow = CreateWindow( 24. appName, 25. appName, 26. WS_OVERLAPPEDWINDOW, 27. CW_USEDEFAULT, 28. CW_USEDEFAULT, 29. CW_USEDEFAULT, 30. CW_USEDEFAULT, 31. 0, 32. 0, 33. hInstance, 34. 0); 35. ShowWindow(hWindow,iCmdShow); 36. UpdateWindow(hWindow); 37. { 38. MSG msg; 39. while(GetMessage(&msg,0,0,0)) 40. { 41. TranslateMessage(&msg); 42. DispatchMessage(&msg); 43. } 44. return (int)msg.wParam; 45. } 46. } 47. 48. LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wPara m, LPARAM lParam) 49. { 50. if (msg==WM_PAINT) 51. { 52. PAINTSTRUCT ps; 53. const HDC hDC = BeginPaint(hWindow,&ps); 54. RECT rect; 55. GetClientRect(hWindow,&rect); 56. DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); 57. EndPaint(hWindow,&ps); 58. return 0; 59. } 60. else if (msg==WM_DESTROY) 61. { 62. PostQuitMessage(0); 63. return 0; 64. } 65. return DefWindowProc(hWindow,msg,wParam,lParam); 66. } 上面的程序运行的流程是:进入 WinMain 函数->初始化 WNDCLASSEX,调用 Regis terClassEx 函数注册窗口类->调用 ShowWindow 和 UpdateWindow 函数显示并更新窗口->进 入消息循环。关于消息循环再简单说下,Windows 应用程序是消息驱动的,系统或用户让 应用程序进行某项操作或完成某个任务时会发送消息,进入程序的消息队列,然后消息循 环会将消息队列中的消息取出,交予相应的窗口过程处理,此程序的窗口过程函数就是 m yWndProc 函数,窗口过程函数处理完消息就完成了某项操作或任务。本例是要显示“HEL LO WORLD”字符串,UpdateWindow 函数会发送 WM_PAINT 消息,但是此消息不经过消 息队列而是直接送到窗口过程处理,在窗口过程函数中最终绘制了“HELLO WORLD”字符 串。 MFC 应用程序 下面是 MFC 应用程序的运行流程,通过 MFC 库中代码进行分析: 首先在 HelloWorld.cpp 中定义全局对象theApp:CHelloWorldApp theApp;。调用 CW inApp 和 CHelloWorldApp 的构造函数后,进入 WinMain 函数(位于 appmodul.cpp 中) 。 C++代码 1. extern "C" int WINAPI 2. _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 3. _In_ LPTSTR lpCmdLine, int nCmdShow) 4. #pragma warning(suppress: 4985) 5. { 6. // call shared/exported WinMain 7. return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmd Show); 8. } 在 TCHAR.h 中,有此定义:#define _tWinMain WinMain,所以这里的_tWinMain 就是 WinMain 函数。它调用了 AfxWinMain 函数(位于 WinMain.cpp 中)。 C++代码 1. int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInsta nce,LPTSTR lpCmdLine, int nCmdShow) 2. { 3. .............略 4. // App global initializations (rare) 5. if (pApp != NULL && !pApp->InitApplication()) 6. goto InitFailure; 7. 8. if (!pThread->InitInstance()) 9. { 10. .........略 11. } 12. 13. // Run 函数位于 THRDCORE.cpp 中,由此函数进入消息循环 14. nReturnCode = pThread->Run(); 15. 16. ..............略 17. 18. return nReturnCode; 19. } 上面 InitInstance 函数的代码如下: C++代码 1. BOOL CTestApp::InitInstance() 2. { 3. .............略 4. CSingleDocTemplate* pDocTemplate; 5. pDocTemplate = new CSingleDocTemplate( 6. IDR_MAINFRAME, 7. RUNTIME_CLASS(CTestDoc), 8. RUNTIME_CLASS(CMainFrame), // main SDI frame window 9. RUNTIME_CLASS(CTestView)); 10. if (!pDocTemplate) 11. return FALSE; 12. AddDocTemplate(pDocTemplate); 13. // Parse command line for standard shell commands, DDE, file open 14. 15. CCommandLineInfo cmdInfo; 16. ParseCommandLine(cmdInfo); 17. 18. //ProcessShellCommand 位于 AppUI2.cpp 中,注册并创建窗口 19. if (!ProcessShellCommand(cmdInfo)) 20. return FALSE; 21. 22. m_pMainWnd->ShowWindow(SW_SHOW); 23. m_pMainWnd->UpdateWindow(); 24. 25. return TRUE; 26. } InitInstance 中的 ProcessShellCommand 函数又调用了 CMainFrame 的 LoadFrame 函数注册并创建了窗口,执行完 ProcessShellCommand 函数以后,调用了 m_pMainWnd 的 ShowWindow 和 UpdateWindow 函数显示并更新框架窗口。这些是不是与上面的 SDK 程序十分类似? 接下来该是消息循环了,上面的 AfxWinMain 函数中调用了 pThread 的 Run 函数(位 于 THRDCORE.cpp 中),在 Run 中包含了消息循环。Run 函数的代码如下: C++代码 1. int CWinThread::Run() 2. { 3. .............略 4. // phase2: pump messages while available 5. do 6. { 7. // pump message, but quit on WM_QUIT 8. if (!PumpMessage()) 9. return ExitInstance(); 10. 11. // reset "no idle" state after pumping "normal" m essage 12. if (IsIdleMessage(&m_msgCur)) 13. { 14. bIdle = TRUE; 15. 16. lIdleCount = 0; 17. 18. } 19. } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_N OREMOVE)); 20. ..............略 21. } 22. 23. BOOL CWinThread::PumpMessage() 24. { 25. return AfxInternalPumpMessage(); 26. } 27. 28. BOOL AFXAPI AfxInternalPumpMessage() 29. { 30. _AFX_THREAD_STATE *pState = AfxGetThreadState(); 31. 32. if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) 33. { 34. .............略 35. } 36. ...............略 37. if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTr anslateMessage(&(pState->m_msgCur))) 38. { 39. ::TranslateMessage(&(pState->m_msgCur)); 40. ::DispatchMessage(&(pState->m_msgCur)); 41. } 42. 43. return TRUE; 44. } 我们看到 PumpMessage 中通过调用 GetMessage、TranslateMessage、DispatchMe ssage 等建立了消息循环并投递消息。 窗口过程函数 AfxWinProc 形式如下: C++代码 1. LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam) 2. { 3. …… 4. CWnd*pWnd=CWnd::FromHandlePermanent(hWnd); 5. ReturnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam); 6. } 两者运行过程对比 到此,通过对比可以发现,MFC 应用程序的运行流程与 SDK 程序是类似的,都是先 进行一些初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消 息都由窗口过程函数处理。现在大家是不是觉得有些头绪了?在运行流程上有基本的掌握 即可。 二.MFC 应用程序框架主要类之间的关系 在第二讲中,给大家演示了如何利用应用程序向导生成单文档应用程序框架,可以看 到程序的基本框架和必要的代码都自动生成了,上一讲又讲解了文件组成结构,实际上在 前面自动生成的框架中比较重要的类包括以下几个:CHelloWorldApp、CMainFrame、C HelloWorldDoc 和 CHelloWorldView,至于其他的类比如 CClassView、CFileView 等都是 在框架窗口(CMainFrame)上创建的面板等,不是必要的。 鸡啄米就四个主要类的关系简单讲下,CHelloWorldApp 类处理消息,将收到的消息 分发给相应的对象。CMainFrame 是视图 CHelloWorldView 的父窗口,视图 CHelloWorld View 就显示在 CMainFrame 的客户区中。视图类 CHelloWorldView 用来显示文档类 CHel loWorldDoc 中的数据,并根据对视图类的操作修改文档类的数据。一个视图类只能跟一个 文档类相联系,而一个文档类可以跟多个视图类相联系。关于视图类和文档类的关系后面 会详细讲解。 本节 VC++/MFC编程入门教程内容比较多,主要是让大家对 MFC 应用程序的运行原 理有大概的了解。对于以后的 MFC 开发有很多好处。如果有问题请在鸡啄米博客留言交 流。谢谢。 VS2010/MFC 编程入门之五(MFC 消息映射机制概述) 上一讲鸡啄米为大家简单分析了MFC 应用程序框架,这一讲是关于 MFC 消息映射机 制的内容。 前面已经说过,Windows 应用程序是消息驱动的。在 MFC软件开发中,界面操作或 者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作。比较典型的过程 是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出 响应。 什么是消息 窗口消息一般由三个部分组成:1.一个无符号整数,是消息值;(2)消息附带的 WPAR AM 类型的参数;(3)消息附带的 LPARAM 类型的参数。其实我们一般所说的消息是狭义 上的消息值,也就是一个无符号整数,经常被定义为宏。 什么是消息映射机制 MFC 使用一种消息映射机制来处理消息,在应用程序框架中的表现就是一个消息与消 息处理函数一一对应的消息映射表,以及消息处理函数的声明和实现等代码。当窗口接收 到消息时,会到消息映射表中查找该消息对应的消息处理函数,然后由消息处理函数进行 相应的处理。SDK 编程时需要在窗口过程中一一判断消息值进行相应的处理,相比之下 M FC 的消息映射机制要方便好用的多。 Windows 消息分类 先讲下 Windows 消息的分类。Windows 消息分为系统消息和用户自定义消息。Wind ows 系统消息有三种: 1.标准 Windows 消息。除 WM_COMMAND 外以 WM_开头的消息是标准消息。例如 ,WM_CREATE、WM_CLOSE。 2.命令消息。消息名为 WM_COMMAND,消息中附带了标识符 ID 来区分是来自哪个 菜单、工具栏按钮或加速键的消息。 3.通知消息。通知消息一般由列表框等子窗口发送给父窗口,消息名也是 WM_COMM AND,其中附带了控件通知码来区分控件。 CWnd 的派生类都可以接收到标准 Windows 消息、通知消息和命令消息。命令消息还 可以由文档类等接收。 用户自定义消息是实际上就是用户定义一个宏作为消息,此宏的值应该大于等于 WM_ USER,然后此宏就可以跟系统消息一样使用,窗口类中可以定义它的处理函数。 消息映射表 除了一些没有基类的类或 CObject 的直接派生类外,其他的类都可以自动生成消息映 射表。下面的讲解都以前面例程 HelloWorld 的 CMainFrame 为例。消息映射表如下: C++代码 1. BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) 2. ON_WM_CREATE() 3. ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize) 4. ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::On ToolbarCreateNew) 5. ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_ WINDOWS_7, &CMainFrame::OnApplicationLook) 6. ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIE W_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook) 7. ON_WM_SETTINGCHANGE() 8. END_MESSAGE_MAP() 在 BEGIN_MESSAG_MAP 和 END_MESSAGE_MAP 之间的内容成为消息映射入口 项。消息映射除了在 CMainFrame 的实现文件中添加消息映射表外,在类的定义文件 Mai nFrm.h 中还会添加一个宏调用: DECLARE_MESSAGE_MAP() 一般这个宏调用写在类定义的结尾处。 添加消息处理函数 如何添加消息处理函数呢?不管是自动还是手动添加都有三个步骤: 1.在类定义中加入消息处理函数的函数声明,注意要以 afx_msg 打头。例如 MainFrm. h 中 WM_CREATE 的消息处理函数的函数声明:afx_msg int OnCreate(LPCREATESTR UCT lpCreateStruct);。 2.在类的消息映射表中添加该消息的消息映射入口项。例如 WM_CREATE 的消息映 射入口项:ON_WM_CREATE()。 3.在类实现中添加消息处理函数的函数实现。例如,MainFrm.cpp 中 WM_CREATE 的消息处理函数的实现: int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ...... } 通过以上三个步骤以后,WM_CREATE 等消息就可以在窗口类中被消息处理函数处 理了。 各种 Windows 消息的消息处理函数 标准 Windows 消息的消息处理函数都与 WM_CREATE 消息类似。 命令消息的消息映射入口项形式如:ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMa inFrame::OnViewCustomize),消息为 ID_VIEW_CUSTOMIZE,消息处理函数为 OnView Customize。 如果想要使用某个处理函数批量处理某些命令消息,则可以像 CMainFrame 消息映射 表中的 ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOO K_WINDOWS_7, &CMainFrame::OnApplicationLook)一样添加消息映射入口项,这样值 在 ID_VIEW_APPLOOK_WIN_2000 到 ID_VIEW_APPLOOK_WINDOWS_7 之间的菜单 项等的命令消息都由 CMainFrame 的 OnApplicationLook 函数处理。函数原型为 afx_msg void OnApplicationLook(UINT id);,参数 id 为用户操作的菜单项等的 ID。 在操作列表框等控件时往往会给父窗口发送 WM_NOTIFY 通知消息。WM_NOTIFY 消息的 wParam 参数为发送通知消息的控件的 ID,lParam 参数指向一个结构体,可能是 NMHDR 结构体,也可能是第一个元素为 NMHDR 结构体变量的其他结构体。NMHDR 结 构体的定义如下(仅作了解): Typedef sturct tagNMHDR{ HWND hwndFrom; UINT idFrom; UINT code; } NMHDR; hwndFrom 为发送通知消息控件的句柄,idFrom 为控件 ID,code 为要处理的通知消 息的通知码,例如 NM_CLICK。 通知消息的消息映射入口项形式如: ON_NOTIFY(wNotifyCode,id,memberFxn) wNotifyCode 为要处理的通知消息通知码,例如:NM_CLICK。id 为控件标识 ID。M emberFxn 为此消息的处理函数。 通知消息的处理函数的原型为: afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result); 如果需要使用用户自定义消息,首先要定义消息宏,如:#define WM_UPDATE_WN D (WM_USER+1),再到消息映射表中添加消息映射入口项:ON_MESSAGE(WM_UPDA TE_WND, &CMainFrame::OnUpdateWnd),然后在 MainFrm.h 中添加消息处理函数的函 数声明:afx_msg LRESULT OnUpdateWnd(WPARAM wParam, LPARAM lParam);,最 后在 MainFrm.cpp 中实现此函数。 鸡啄米本节对 MFC 消息映射机制只是做了比较简单的讲解,让大家对它有一定的认 识,编程入门者不必强求完全掌握。在以后的教程中会经常涉及到消息的使用,大家会逐 渐熟悉 MFC 的消息映射机制。 VS2010/MFC 编程入门之六(对话框:创建对话框模板和修改对 话框属性) 鸡啄米在上一讲中介绍了MFC 的消息映射机制,属于原理方面的知识。对于VC++编 程入门学习者来说可能有些抽象,鸡啄米会把消息映射的知识渗透到后面的教程中。本节 开始为大家讲解偏应用的知识-创建对话框。 对话框,大家应该很熟悉了,在我们常用的软件中大多都有对话框界面,例如,360 安全卫士的主界面其实就是个对话框,只是它做了很多美工方面的工作,将其大大美化了 。 创建对话框主要分两大步,第一,创建对话框资源,主要包括创建新的对话框模板、 设置对话框属性和为对话框添加各种控件;第二,生成对话框类,主要包括新建对话框类 、添加控件变量和控件的消息处理函数等。鸡啄米在本节中先讲讲怎样创建对话框模板和 设置对话框属性。 创建基于对话框的应用程序框架 之前鸡啄米创建的 HelloWorld 程序是单文档应用程序,生成了多种窗口,如果用它来 将讲创建对话框的话可能有些复杂,对大家单纯理解对话框有点影响,所以这里鸡啄米就 再创建一个基于对话框的应用程序,用来实现加法运算的功能。创建步骤同单文档应用程 序大同小异,简单步骤如下: 1.选择菜单项 File->New->Project,弹出“New Project”对话框。 2.左侧面板中 Installed Templated 的 Visual C++下选择 MFC,中间窗口中选择 MFC Application,然后在下面的 Name 编辑框中键入工程名称,本例取名“Addition”,在 Locati on 编辑框中设置工程的保存路径。点“OK”。 3.点“Next”到“Application Type”对话框,在 Application type 下选择 Dialog based,其 他使用默认设置,点“Finish”。 我们可以在 Solution Explorer 视图中看到,此工程的文件要比单文档应用程序少的多 ,在 Class View 中主要有三个类:CAboutDlg、CAdditionApp 和 CAdditionDlg。CAbout Dlg 是应用程序的“关于”对话框类,CAdditionApp 是由 CWinApp 派生的类,CAdditionDlg 是主对话框类,主对话框也就是此应用程序运行后显示的主要界面。 注:如果在 VS2010 中找不到 Solution Explorer 或 Class View 等视图,可以在菜单 项 View 下找到对应视图选项选择即可。在VS2010 的使用介绍中已经有讲解。 在 Resource View 视图中可以看到工程 Addition 的资源树,展开 Addition.rc,下面有 四个子项:Dialog(对话框)、Icon(图标)、String Table(字符串表)和 Version(版 本)。然后展开 Dialog 项,下面有两个对话框模板,其 ID 分别为:IDD_ABOUTBOX 和 I DD_ADDITION_DIALOG,前者是“关于”对话框的模板,后者是主对话框的模板。ID 是资 源的唯一标识,本质上是一个无符号整数,一般 ID 代表的整数值由系统定义,我们无需干 涉。 对话框模板 可见对于主对话框来说,创建对话框第一步中的创建新的对话框模板已经由系统自动 完成了。而如果是再添加对话框需要创建新的对话框模板时,需要在 Resource View 的“Di alog”节点上点右键,在右键菜单中选择“Insert Dialog”,就会生成新的对话框模板,并且会 自动分配 ID。 在 Resource View 的资源树中双击某个 ID,可在中间区域内显示相应的资源界面。双 击 IDD_ADDITION_DIALOG 时,中间区域就会显示 Addition 对话框模板。如下图: 设置对话框属性 在 Addition 对话框模板上点右键,然后在右键菜单中选择 Properties,则在右侧面板 中会显示对话框的属性列表。如下图: 鸡啄米在这里对经常使用的几个属性作简单说明,并对 Addition 对话框进行属性设置 说明。 1.ID:对话框 ID,唯一标识对话框资源,可以修改。此处为 IDD_ADDITION_DIALO G,我们不修改它。 2.Caption:对话框标题。此处默认为 Addition,我们将其修改为“加法计算器”。 3.Border:边框类型。有四种类型:None、Thin、Resizing 和 Dialog Frame。我们使 用默认的 Dialog Frame。 4.Maximize:是否使用最大化按钮。我们使用默认的 False。 5.Minimize:是否使用最小化按钮。同样我们使用默认的 False。 6.Style:对话框类型。有三种类型:Overlapped(重叠窗口)、Popup(弹出式窗口 )和 Child(子窗口)。弹出式窗口比较常见。我们使用默认的 Popup 类型。 7.System Menu:是否带有标题栏左上角的系统菜单,包括移动、关闭等菜单项。我 们使用默认的 True。 8.Title Bar:是否带有标题栏。我们使用默认的 True。 9.Font(Size):字体类型和字体大小。如果将其修改为非系统字体,则 Use System 自 动改为 False。而如果 Use System 原来为 False,将其修改为 True,则 Font(Size)自动设 置为系统字体。这里我们使用默认的系统字体。 根据以上说明,其实我们只修改了标题属性。这时我们运行此程序后的界面如下: 这一讲就先讲到这里了,对于创建对话框第一步中的为对话框添加各种控件下一讲为 大家演示。欢迎来鸡啄米博客交流学习。 VS2010/MFC 编程入门之七(对话框:为对话框添加控件) 创建对话框资源需要创建对话框模板、修改对话框属性、为对话框添加各种控件等步 骤,前面一讲中鸡啄米已经讲了创建对话框模板和修改对话框属性,本节继续讲如何为对 话框添加控件。 上一讲中鸡啄米创建了一个名为“Addition”的工程,目的是生成一个实现加法运算的应 用程序。实现加法计算有几个必要的因素:被加数、加数、和。被加数和加数需要输入, 和需要输出显示。那么这几个因素都需要相应的控件来输入或显示,下面鸡啄米就一步步 讲解如何添加这些控件。 1.为对话框添加一个静态文本框(Static Text),用于显示字符串--“被加数”。 上一讲中生成的资源模板中自动添加了一个标题为“TODO:Place dialog controls here. ”的静态文本框,我们可以修改它的标题继续使用,也可以删掉它。这里为了从头讲解静态 文本框的添加过程,将它删掉,继续添加新的静态文本框。 删除控件时,可以使用鼠标左键点击选中它,选中后控件的周围会出现虚线框,然后 按 Delete 键就可以将其删除了。在“Addition”工程的 Resource View 中打开上一讲中创建 的对话框模板 IDD_ADDITION_DIALOG,自动添加的静态文本框就可以使用这种方法删 除。 在添加新的静态文本框以前,先看看 Toolbox 视图是否显示了,如果没有显示,在菜 单栏上点击 View->Toolbox 即可。Toolbox 视图如下图: Toolbox 中列出了一些常用控件,其中有一个是 Static Text,即是我们要添加的控件 。在 Toolbox 中的 Static Text 上点下鼠标左键不放开,并拖到 IDD_ADDITION_DIALOG 对话框模板上,模板上会出现一个虚线框,我们找到合适的位置松开鼠标左键放下它。 用鼠标左键选中控件后周围出现虚线框,然后鼠标移到虚线框上几个黑点的位置会变 成双向箭头的形状,此时就可以按下鼠标左键并拖动来改变控件大小了。我们可以这样改 变新添加的静态文本框控件的大小,以更好的显示标题。当然,整个对话框模板也可以用 这种方法改变大小。 接下来就该修改静态文本框的文字了。鼠标右键点击静态文本框,在右键菜单中选择“ Properties”,Properties 面板就会显示出来,在面板上修改 Caption 属性为“被加数”,ID 修改为 IDC_SUMMAND_STATIC。此时模板如下图: 2.为对话框添加一个编辑框(Edit Control),用来输入被加数。 添加编辑框的过程与静态文本框类似,在 Toolbox 中选中 Edit Control 控件拖到对话 框模板上,并使其与之前的静态文本框水平对齐(为了美观),然后调整其大小使之适合 被加数的输入。 在编辑框上点右键,仍然在右键菜单中选择“Properties”显示出属性(Properties)面 板,修改其 ID 为 IDC_SUMMAND_EDIT。此时模板如下图: 3.按照 1 的方法添加一个标题为“加数”的静态文本框,用于显示字符串--“加数”。并将 其 ID 改为 IDC_ADDEND_STATIC。 4.按照 2 的方法添加一个 ID 为 IDC_ADDEND_EDIT 的编辑框,用来输入加数。 5.按照 1 的方法添加一个标题为“和”的静态文本框,用于显示文字--“和”。并修改其 I D 为 IDC_SUM_STATIC。 6.按照 2 的方法添加一个 ID 为 IDC_SUM_EDIT 的编辑框,用来显示最终的加和。 7.类似的添加按钮(Button)控件到对话框模板,用于在被点击后触发加法计算。修 改其标题为“计算”,ID 为 IDC_ADD_BUTTON。 到此,对话框模板如图: 8.删除 OK 按钮。打开 Cancel 按钮的属性面板,将标题改为“退出”,并使其与“计算 ”按钮水平对齐。 9.根据控件的布局,适当调整整个对话框模板的大小,使其相对控件布局来说大小合 适,界面美观。 这样在对话框模板中就把我们在本例中需要用到的控件就添加完了。最终效果如下: 至此,我们的对话框资源就基本创建完了。应用程序运行后的界面效果已经很清楚了 。后面鸡啄米会讲如何在对话框类中实现加法计算功能,并能很好的和界面交互。欢迎继 续到鸡啄米博客交流。 VS2010/MFC 编程入门之八(对话框:创建对话框类和添加控件 变量) 前两讲中鸡啄米为大家讲解了如何创建对话框资源。创建好对话框资源后要做的就是 生成对话框类了。鸡啄米再声明下,生成对话框类主要包括新建对话框类、添加控件变量 和控件的消息处理函数等。 因为鸡啄米给大家的例程 Addition 是基于对话框的程序,所以程序自动创建了对话框 模板 IDD_ADDITION_DIALOG,并自动生成了对话框类 CAdditionDlg,它是从 CDialogE x 类派生的。大家用过VC++ 6.0 的可能记得,我们定义的对话框类都是从 CDialog 类派生 的,但在VS2010中,一般对话框类都是继承自 CDialogEx 类。 创建对话框类 如果是自己新添加的对话框模板,怎样为它创建对话框类呢? 1.首先鸡啄米就按第六讲:创建对话框模板和修改对话框属性中说的那样,在 Resour ce View 的“Dialog”节点上右键,然后在右键菜单中选择“Insert Dialog”创建一个新的对话框 模板,ID 就使用默认的 IDD_DIALOG1。 2.在中间区域会显示新建的对话框模板,然后选中此对话框模板,点右键,在右键菜 单中选择 Add Class。 3.选择“Add Class”后会弹出一个对话框,在对话框中“Class name”下的编辑框中写入 自定义的类名就可以了,例如 CMyDialog。 4.最后点“Finish”完成。 最终你就可以在 Class View 中看到新生成的对话框类 CMyDialog 了,并且在 Solutio n Explorer 中有相应的 MyDialog.h 头文件和 MyDialog.cpp 源文件生成。CMyDialog 类同 样派生于 CDialogEx 类。 注意,一般类名都以 C 打头,又比如,CTestDlg。 为对话框中的控件添加变量 在上一讲中为对话框添加了几个控件,包括三个静态文本框,三个编辑框,一个按钮 控件。程序自动生成的 Cancel 按钮保留,作为退出按钮,而 OK 按钮删除掉了。 静态文本框只是为了说明后面紧跟的编辑框中数据的意义,是被加数、加数还是和, 所以它们是不会变的,我们就不为它们添加变量了。按钮控件是用来操作的,这里也不为 它们添加变量。编辑框中的数据可能会经常变化,有必要为它们每个控件关联一个变量。 首先为被加数的编辑框 IDC_SUMMAND_EDIT 添加变量。 1.在编辑框上点右键,在右键菜单中选择“Add Variable”。弹出添加成员变量的向导对 话框。 2.我们想为其添加值变量而不是控件变量,所以对话框中“Category”下的组合框中选择 Value。 3.“Variable type”下的组合框此时默认选中的是“CString”,CString 是字符串类,显然 不能进行加法运算。我们可以选择 double、float、int 等。这里我们选择 double,即编辑 框关联一个 double 类型的变量。 4.在“Variable name”中写入自定义的变量名。鸡啄米为其取名 m_editSummand。 5.点“Finish”完成。 注意,类的成员变量名一般以 m_打头,以标识它是一个成员变量。 参照此方法,再分别为加数的编辑框 IDD_ADDEND_EDIT 添加 double 型变量 m_edit Addend、和的编辑框 IDD_SUM_EDIT 添加 double 型变量 m_editSum。 对话框类的数据交换和检验 在程序运行界面中,用户往往会改变控件的属性,例如,在编辑框中输入字符串,或 者改变组合框的选中项,又或者改变复选框的选中状态等。控件的属性改变后MFC会相应 修改控件关联变量的值。这种同步的改变是通过 MFC 为对话框类自动生成的成员函数 Do DataExchange()来实现的,这也叫做对话框的数据交换和检验机制。 我们为三个编辑框添加了变量以后,在 AdditionDlg.cpp 中 CAdditionDlg 的 DoDataE xchange()函数的函数体中多了三条 DDX_Text 调用语句。下面是函数体代码和鸡啄米添 加的注释。 C++代码 1. void CAdditionDlg::DoDataExchange(CDataExchange* pDX) 2. { 3. // 处理 MFC 默认的数据交换 4. CDialogEx::DoDataExchange(pDX); 5. // 处理控件 IDC_SUMMAND_EDIT 和变量 m_editSummand 之间的数据交换 6. DDX_Text(pDX, IDC_SUMMAND_EDIT, m_editSummand); 7. // 处理控件 IDC_ADDEND_EDIT 和变量 m_editAddend 之间的数据交换 8. DDX_Text(pDX, IDC_ADDEND_EDIT, m_editAddend); 9. // 处理控件 IDC_SUM_EDIT 和变量 m_editSum 之间的数据交换 10. DDX_Text(pDX, IDC_SUM_EDIT, m_editSum); 11. } 鸡啄米再以 Addition 程序为例简单说下数据交换机制。如果我们在程序运行界面中输 入被加数,则通过 CAddition 的 DoDataExchange()函数可以将输入的值保存到 m_editSu mmand 变量中,反之如果程序运行中修改了变量 m_editSummand 的值,则通过 CAdditi on 的 DoDataExchange()函数也可以将新的变量值显示到被加数的编辑框中。 但是这种数据交换机制中,DoDataExchange()并不是被自动调用的,而是需要我们在 程序中调用 CDialogEx::UpdateData()函数,由 UpdateData()函数再去自动调用 DoDataE xchange()的。 CDialogEx::UpdateData()函数的原型为: BOOL UpdateData(BOOL bSaveAndValidate = TRUE); 参数:bSaveAndValidate 用于指示数据传输的方向,TRUE 表示从控件传给变量,F ALSE 表示从变量传给数据。默认值是 TRUE,即从控件传给变量。 返回值:CDialogEx::UpdateData()函数的返回值表示操作是否成功,成功则返回 TRU E,否则返回 FALSE。 在下一讲中鸡啄米将具体演示 CDialogEx::UpdateData()函数如何使用。 鸡啄米本节主要讲的是新建对话框类和添加控件变量,控件的消息处理函数将在下一 讲详细介绍。依然欢迎大家常回鸡啄米博客学习和讨论。 VS2010/MFC 编程入门之九(对话框:为控件添加消息处理函数) 创建对话框类和添加控件变量在上一讲中已经讲过,这一讲的主要内容是如何为控件 添加消息处理函数。 MFC为对话框和控件等定义了诸多消息,我们对它们操作时会触发消息,这些消息最 终由消息处理函数处理。比如我们点击按钮时就会产生 BN_CLICKED 消息,修改编辑框 内容时会产生 EN_CHANGE 消息等。一般为了让某种操作达到效果,我们只需要实现某 个消息的消息处理函数。 一.添加消息处理函数 鸡啄米仍以前面的加法计算器的程序为例,说明怎样为“计算”按钮控件添加消息处理 函数。添加方法列出 4 种: 1.使用 Class Wizard 添加消息处理函数 用过的VC++ 6.0 的朋友应该对 Class Wizard 很熟悉了,添加类、消息处理函数等经 常会用到它,可以说是一个很核心的功能。但从 VS2002 开始就见不到 Class Wizard 了, 大部分功能都集成到对话框和控件等的属性中了,使用很方便。到VS2010,久违的 Class Wizard 又回来了。但鸡啄米已经习惯了使用属性中的功能了,对于从 VC++ 6.0 直接转 V S2010 的朋友可能觉得还是使用 Class Wizard 比较习惯。 大家应该记得,“计算”按钮的 ID 为 IDC_ADD_BUTTON,上图中 Commands 标签下 ,Oject IDs 列表中有此 ID,因为我们是想实现点击按钮后的消息处理函数,所以在 Mess ages 列表中选择 BN_CLICKED 消息,然后点右上方的 Add Handler 就可以添加 BN_CLI CKED 消息处理函数 OnClickedAddButton 了。当然你也可以改名,但一般用的默认的就 可以。 2.通过“Add Event Handler...”添加消息处理函数 在“计算”按钮上点右键,然后在右键菜单中选择菜单项“Add Event Handler...”,弹出“E vent Handler Wizard”对话框,如下图: 可见“Message type”中默认选中的就是 BN_CLICKED 消息,函数名和所在类都已经 自动给出,直接点“Add and Edit”就可以了。 3.在按钮的属性视图中添加消息处理函数 上面说过,从 VS2002 开始就主要从属性视图添加消息处理函数了。我们在“计算”按 钮上点右键,在右键菜单中选择“Properties”,右侧面板中会显示按钮的属性视图。 我们可以像上图中那样,点属性视图的“Control Events”按钮(类似闪电标志),下面 列出了“计算”按钮的所有消息。我们要处理的是 BN_CLICKED 消息,点其右侧空白列表项 ,会出现一个带下箭头的按钮,再点此按钮会出现“ OnBnClickedAddButton”选项, 最后选中这个选项就会自动添加 BN_CLICKED 处理函数了。 4.双击按钮添加消息处理函数 最直接最简单的方法就是,双击“计算”按钮,MFC 会自动为其在 CAdditionDlg 类中添 加 BN_CLICKED 消息的处理函数 OnBnClickedAddButton()。 二.在消息处理函数中添加自定义功能 在我们使用任意一种方法添加了消息处理函数以后,都只能得到一个空的 OnBnClicke dAddButton()函数的函数体,要实现我们想要的功能,还需要在函数体中加入自定义功能 代码。 在加法计算器程序中,我们想要“计算”按钮实现的功能是,获取被加数和加数的数值 ,然后计算它们的和并显示到和的编辑框里。那么,OnBnClickedAddButton()的函数体就 应修改为: C++代码 1. void CAdditionDlg::OnBnClickedAddButton() 2. { 3. // TODO: Add your control notification handler code here 4. // 将各控件中的数据保存到相应的变量 5. UpdateData(TRUE); 6. 7. // 将被加数和加数的加和赋值给 m_editSum 8. m_editSum = m_editSummand + m_editAddend; 9. 10. // 根据各变量的值更新相应的控件。和的编辑框会显示 m_editSum 的值 11. UpdateData(FALSE); 12. } 鸡啄米在上面的代码中已经添加注释,大家应该很容易理解了。对于 UpdateData()函 数的说明在上一讲中已经介绍过,如果忘了可以再回上一讲了解了解。 接下来我们运行下此应用程序。在运行结果界面中,输入被加数 5.1,加数 2.3,然后 点“计算”: 在上图中可以看到,点“计算”按钮后,和的编辑框中显示了正确结果:7.4。 鸡啄米简单分析下运行过程:输入被加数和加数,点“计算”按钮后产生点击消息,从 而调用 OnBnClickedAddButton()函数。进入此函数后,首先由 UpdateData(TRUE)函数将 被加数的值 5.1 和加数的值 2.3 分别保存到变量 m_editSummand 和 m_editAddend,然后 通过语句 m_editSum = m_editSummand + m_editAddend;计算出被加数和加数的和为 7.4 ,并把 7.4 赋值给 m_editSum。最后调用 UpdateData(FALSE)根据被加数、加数、和的 值更新三个编辑框的显示值,就得到了上图中的结果。 到此,一个具有简单的加法运算功能的加法计算器应用程序就基本完成了。如果大家 想实现其他功能,可以修改控件资源和消息处理函数来练习下。本节就讲到这里了,有问 题欢迎到鸡啄米博客或者我们的编程入门 qq 群讨论。 VS2010/MFC 编程入门之十(对话框:设置对话框控件的 Tab 顺序) 前面几节鸡啄米为大家演示了加法计算器程序完整的编写过程,本节主要讲对话框上 控件的 Tab 顺序如何调整。 上一讲为“计算”按钮添加了消息处理函数后,加法计算器已经能够进行浮点数的加法 运算。但是还有个遗留的小问题,就是对话框控件的 Tab 顺序问题。 运行加法计算器程序,显示对话框后不进行任何操作,直接按回车,可以看到对话框 退出了。这是因为“退出”按钮是 Tab 顺序为 1 的控件,也就是第一个接受用户输入的控件 。但是按照我们的输入习惯,应该是被加数的编辑框首先接受用户输入,然后是加数编辑 框,再接下来是“计算”按钮,最后才是“退出”按钮。 我们先来直观的看看各个控件的 Tab 顺序吧。打开“Resource View”视图,然后在资 源中找到对话框 IDD_ADDITION_DIALOG,双击 ID 后中间客户区域出现其模板视图。在 主菜单中选择“Format”->"Tab Order",或者按快捷键 Ctrl+D,对话框模板上就会显示各 个控件的 Tab 顺序数字。如下图: 上图中每个控件左上角都有一个数字,这就是它的 Tab 响应顺序。对话框刚打开时输 入焦点就在 Tab 顺序为 1 的“退出”按钮上,不做任何操作按下 Tab 键,输入焦点就会转移 到 Tab 顺序为 2 的“被加数”静态文本框上,但是因为静态文本框不接受任何输入,所以输 入焦点继续自动转移到 Tab 顺序为 3 的被加数编辑框,再按 Tab 键,输入焦点又会转移到 Tab 顺序为 4 的“加数”静态文本框上,同样由于它是静态文本框,输入焦点不停留继续转 移到加数编辑框,后面的控件同理。 我们认为这个顺序不合理,那怎么修改呢?很简单,从自己认为 Tab 顺序应该为 1 的 控件开始依次单击,随着单击的完成,各控件的 Tab 响应顺序也按我们的想法设置好了。 例如,此例中我们可以依次单击被加数编辑框、“被加数”静态文本框、加数编辑框、“ 加数”静态文本框、和编辑框、“和”静态文本框、“计算”按钮和“退出”按钮。设置完后如下图 : 最后按 ESC 键,确认设置并退出对话框模板的 Tab 顺序设置状态。 现在我们再运行程序,可以看到对话框打开后最初的输入焦点在被加数编辑框上,然 后我们按 Tab 键,输入焦点移到加数编辑框上,继续多次按 Tab 键时,输入焦点会按“和 编辑框--‘计算’按钮--‘退出’按钮--被加数编辑框--加数编辑框--和编辑框......”的顺序循环转移 。这样就达到了我们的目的。 VS2010/MFC 编程入门之十一(对话框:模态对话框及其弹出过 程) 加法计算器对话框程序大家照着做一遍后,相信对基于对话框的程序有些了解了,有 个好的开始对于以后的学习大有裨益。趁热打铁,鸡啄米这一节讲讲什么是模态对话框和 非模态对话框,以及模态对话框怎样弹出。 一.模态对话框和非模态对话框 Windows 对话框分为两类:模态对话框和非模态对话框。 模态对话框是这样的对话框,当它弹出后,本应用程序其他窗口将不再接受用户输入 ,只有该对话框响应用户输入,在对它进行相应操作退出后,其他窗口才能继续与用户交 互。 非模态对话框则是,它弹出后,本程序其他窗口仍能响应用户输入。非模态对话框一 般用来显示提示信息等。 大家对 Windows 系统很了解,相信这两种对话框应该都遇到过。之前的加法计算器对 话框其实就是模态对话框。 二.模态对话框是怎样弹出的 毕竟加法计算器程序大部分都是 MFC 自动生成的,对话框怎么弹出来的大家可能还 不是很清楚。鸡啄米下面简单说说它是在哪里弹出来的,再重新建一个新的对话框并弹出 它,这样大家实践以后就能更灵活的使用模态对话框了。 大家打开 Addition.cpp 文件,可以看到 CAdditionApp 类有个 InitInstance()函数,在M FC 应用程序框架分析中提到过此函数,不过那是单文档应用程序 App 类中的,函数体不 太相同,但都是进行 App 类实例的初始化工作。 InitInstance()函数的后半部分有一段代码就是定义对话框对象并弹出对话框的,鸡啄 米下面给出这段代码并加以注释: C++代码 1. CAdditionDlg dlg; // 定义对话框类 CAdditionDlg 的对象 dlg 2. m_pMainWnd = &dlg; // 将 dlg 设为主窗口 3. INT_PTR nResponse = dlg.DoModal(); // 弹出对话框 dlg,并将 DoModa l 函数的返回值(退出时点击按钮的 ID)赋值给 nResponse 4. if (nResponse == IDOK) // 判断返回值是否为 OK 按钮(其 ID 为 IDOK,鸡啄米已经将它删除) 5. { 6. // TODO: Place code here to handle when the dialog is 7. // dismissed with OK 8. } 9. else if (nResponse == IDCANCEL) // 判断返回值是否为 Cancel 按 钮(其 ID 为 IDCANCEL,鸡啄米将它的 Caption 改为了“退出”) 10. { 11. // TODO: Place code here to handle when the dialog is 12. // dismissed with Cancel 13. } 弹出对话框比较关键的一个函数,就是对话框类的 DoModal()函数。CDialog::DoMod al()函数的原型为: virtual INT_PTR DoModal();    返回值:整数值,指定了传递给 CDialog::EndDialog(该函数用于关闭对话框)的 nR esult 参数值。如果函数不能创建对话框,则返回-1;如果出现其它错误,则返回 IDABOR T。 调用了它对话框就会弹出,返回值是退出对话框时所点的按钮的 ID,比如,我们点了“ 退出”按钮,那么 DoModal 返回值为 IDCANCEL。 三.添加一个新对话框并弹出它 鸡啄米再为加法计算器程序添加一个对话框,以在计算之前询问用户是否确定要进行 计算。大家可以完整的看下对话框的添加和弹出过程。 1.根据“创建对话框模板和修改对话框属性”中所讲的方法,在 Resource View 中的“Dia log”上点右键选择“Insert Dialog”,创建一个新的对话框模板,修改其 ID 为 IDD_TIP_DIAL OG,Caption 改为“提示”,然后参考“为对话框添加控件”中所讲,在对话框模板上添加一 个静态文本框(static text),Caption 改为“您确定要进行加法计算吗?”,接下来修改 OK 按钮的 Caption 为“确定”,Cancel 按钮的 Caption 为“取消”,最后调整各个控件的位置和 对话框的大小。最终的对话框模板如下图: 2.根据“创建对话框类和添加控件变量”中创建对话框类的方法,在对话框模板上点右键 选择“Add Class...”,弹出添加类的对话框,设置“Class name”为 CTipDlg,点“OK”。在 So lution Explorer 中可以看到生成了 CTipDlg 类的头文件 TipDlg.h 和源文件 TipDlg.cpp。 3.我们要在点“计算”按钮之后弹出此提示对话框,那么就要在“计算”按钮的消息处理函 数 OnBnClickedAddButton()中访问提示对话框类,所以为了访问 CTipDlg 类,在 Addition Dlg.cpp 中包含 CTipDlg 的头文件:#include "TipDlg.h"。 4.修改 OnBnClickedAddButton()的函数体,在所有代码前,构造 CTipDlg 类的对象 ti pDlg,并通过语句 tipDlg.DoModal();弹出对话框,最后判断 DoModal()函数的返回值是 ID OK 还是 IDCANCEL 来确定是否继续进行计算。OnBnClickedAddButton()函数修改后如下 : C++代码 1. void CAdditionDlg::OnBnClickedAddButton() 2. { 3. // TODO: Add your control notification handler code here 4. INT_PTR nRes; // 用于保存 DoModal 函数的返回值 5. 6. CTipDlg tipDlg; // 构造对话框类 CTipDlg 的实例 7. nRes = tipDlg.DoModal(); // 弹出对话框 8. if (IDCANCEL == nRes) // 判断对话框退出后返回值是否为 IDCANC EL,如果是则 return,否则继续向下执行 9. return; 10. 11. // 将各控件中的数据保存到相应的变量 12. UpdateData(TRUE); 13. 14. // 将被加数和加数的加和赋值给 m_editSum 15. m_editSum = m_editSummand + m_editAddend; 16. 17. // 根据各变量的值更新相应的控件。和的编辑框会显示 m_editSum 的值 18. UpdateData(FALSE); 19. } 5.测试。编译运行程序后,在对话框上输入被加数和加数,点“计算”,弹出提示对话框 询问是否进行计算,如果选择“确定”,则提示对话框退出,并在主对话框上显示被加数和 加数的和,而如果选择“取消”,则提示对话框也会退出,但主对话框显示的和不变,即没 有进行加法计算。 VS2010/MFC 编程入门之十二(对话框:非模态对话框的创建及 显示) 上一节鸡啄米讲了模态对话框及其弹出过程,本节接着讲另一种对话框--非模态对话 框的创建及显示。 鸡啄米已经说过,非模态对话框显示后,程序其他窗口仍能正常运行,可以响应用户 输入,还可以相互切换。鸡啄米会将上一讲中创建的 Tip 模态对话框改为非模态对话框, 让大家看下效果。 非模态对话框的对话框资源和对话框类 实际上,模态对话框和非模态对话框在创建对话框资源和生成对话框类上是没有区别 的,所以上一讲中创建的 IDD_TIP_DIALOG 对话框资源和 CTipDlg 类都不需要修改。 创建及显示非模态对话框的步骤 需要修改的是,对话框类实例的创建和显示,也就是之前在 CAdditionDlg::OnBnClick edAddButton()函数体中添加的对话框显示代码。下面是具体步骤: 1.在 AdditionDlg.h 中包含 CTipDlg 头文件并定义 CTipDlg 类型的指针成员变量。详细 操作方法是,在 AdditionDlg.cpp 中删除之前添加的#include "TipDlg.h",而在 AdditionDlg.h 中添加#include "TipDlg.h",这是因为我们需要在 AdditionDlg.h 中定义 CTipDlg 类型的指 针变量,所以要先包含它的头文件;然后在 AdditionDlg.h 中为 CAdditionDlg 类添加 privat e 成员变量 CTipDlg *m_pTipDlg;。 2.在 CAdditionDlg 类的构造函数中初始化成员变量 m_pTipDlg。如果 cpp 文件中函数 太多,我们可以在 Class View 上半个视图中找到 CAdditionDlg 类,再在下半个视图中找 到其构造函数双击,中间客户区域即可马上切到构造函数的实现处。在构造函数体中添加 m_pTipDlg = NULL;,这是个好习惯,鸡啄米在 C++编程入门系列的指针的赋值和指针运 算中说到过,在任何指针变量使用前都初始化,可以避免因误访问重要内存地址而破坏此 地址的数据。 3.将上一讲中添加的模态对话框显示代码注释或删除掉,添加非模态对话框的创建和 显示代码。VC++中注释单行代码使用“//”,注释多行代码可以在需注释的代码开始处添加“/ *”,结束处添加“*/”。修改后的 CAdditionDlg::OnBnClickedAddButton()函数如下: C++代码 1. void CAdditionDlg::OnBnClickedAddButton() 2. { 3. // TODO: Add your control notification handler code here 4. /*INT_PTR nRes; // 用于保存 DoModal 函数的返回值 5. 6. CTipDlg tipDlg; // 构造对话框类 CTipDlg 的实例 7. nRes = tipDlg.DoModal(); // 弹出对话框 8. if (IDCANCEL == nRes) // 判断对话框退出后返回值是否为 IDCANC EL,如果是则 return,否则继续向下执行 9. return;*/ 10. 11. // 如果指针变量 m_pTipDlg 的值为 NULL,则对话框还未创建,需要动态创建 12. if (NULL == m_pTipDlg) 13. { 14. // 创建非模态对话框实例 15. m_pTipDlg = new CTipDlg(); 16. m_pTipDlg->Create(IDD_TIP_DIALOG, this); 17. } 18. // 显示非模态对话框 19. m_pTipDlg->ShowWindow(SW_SHOW); 20. 21. // 将各控件中的数据保存到相应的变量 22. UpdateData(TRUE); 23. 24. // 将被加数和加数的加和赋值给 m_editSum 25. m_editSum = m_editSummand + m_editAddend; 26. 27. // 根据各变量的值更新相应的控件。和的编辑框会显示 m_editSum 的值 28. UpdateData(FALSE); 29. } 4.因为此非模态对话框实例是动态创建的,所以需要手动删除此动态对象来销毁对话 框。我们在 CAdditionDlg 类的析构函数中添加删除代码,但是 MFC 并没有自动给出析构 函数,这时需要我们手动添加,在对话框对象析构时就会调用我们自定义的析构函数了。 在 AdditionDlg.h 文件中为 CAdditionDlg 添加析构函数声明:~CAdditionDlg();,然后在 A dditionDlg.cpp 文件中添加析构函数的实现,函数体如下: C++代码 1. CAdditionDlg::~CAdditionDlg() 2. { 3. // 如果非模态对话框已经创建则删除它 4. if (NULL != m_pTipDlg) 5. { 6. // 删除非模态对话框对象 7. delete m_pTipDlg; 8. } 9. } 这样,非模态对话框创建和显示的代码就添加修改完了。让我们运行下看看效果吧。 在加法计算器对话框上输入被加数和加数,然后点“计算”按钮,依然像上节一样弹出 了提示对话框,但是先不要关闭它,你可以拖动它后面的加法计算器对话框试试,我们发 现加法计算器对话框竟然可以拖动了,而且“和”编辑框里已经显示了运算结果,这表明提 示对话框显示以后还没有关闭,OnBnClickedAddButton() 就继续向下执行了,不仅如此, 加法计算器的每个编辑框还都可以响应输入。 这只是一个简单的例子,非模态对话框的用处有很多,以后大家在软件开发中会用到 。 VS2010/MFC 编程入门之十三(对话框:属性页对话框及相关类 的介绍) 前面讲了模态对话框和非模态对话框,本节开始鸡啄米讲一种特殊的对话框--属性页 对话框。另外,本套教程所讲大部分对 VC++各个版本均可适用或者稍作修改即可,但考 虑到终究还是基于VS2010版本的,所以将《VC++/MFC 编程入门》改为《VS2010/MFC 编程入门》。 属性页对话框的分类 属性页对话框想必大家并不陌生,XP 系统中桌面右键点属性,弹出的就是属性页对话 框,它通过标签切换各个页面。另外,我们在创建 MFC 工程时使用的向导对话框也属于 属性页对话框,它通过点击“Next”等按钮来切换页面。 属性页对话框就是包含一般属性页对话框和向导对话框两类。它将多个对话框集成于 一身,通过标签或按钮来切换页面。 属性页对话框相关类 我们使用属性页对话框时,用到的类主要有两个:CPropertyPage 类和 CPropertySh eet 类。 1.CPropertyPage 类 CPropertyPage 类继承自 CDialog 类,它被用于处理某单个的属性页,所以要为每个 属性页都创建一个继承自 CPropertyPage 的子类。大家可以在VS2010 的 MSDN中查找 C PropertyPage 类以及它的成员的详细说明。下面鸡啄米就为大家讲解 MSDN 中列出的 CP ropertyPage 类的部分主要成员函数。 (1)构造函数 这里讲三个 CProperty 类的构造函数,函数原型为: CPropertyPage( ); explicit CPropertyPage( UINT nIDTemplate, UINT nIDCaption = 0, DWORD dwSize = sizeof(PROPSHEETPAGE) ); explicit CPropertyPage( LPCTSTR lpszTemplateName, UINT nIDCaption = 0, DWORD dwSize = sizeof(PROPSHEETPAGE) ); 第一个是没有任何参数的构造函数。 第二个构造函数中,参数 nIDTemplate 是属性页的对话框资源ID,参数 nIDCaption 是属性页对话框选项卡的标题所用字符串资源的 ID,若设为 0,则选项卡标题就使用该属 性页的对话框资源的标题。 第三个构造函数中,参数 lpszTemplateName 为属性页的对话框资源的名称字符串, 不能为 NULL。参数 nIDCaption 同上。 (2)CancelToClose()函数 在模态属性页对话框的属性页进行了某不可恢复的操作后,使用 CancelToClose()函 数将“OK”按钮改为“Close”按钮,并禁用“Cancel”按钮。函数原型为: void CancelToClose( ); (3)SetModified()函数 调用此函数可激活或禁用“Apply”按钮,函数原型为: void SetModified(BOOL bChanged = TRUE); (4)可重载函数 CPropertyPage 类提供了一些消息处理函数,来响应属性页对话框的各种消息。我们 重载这些消息处理函数,就可以自定义对属性页对话框操作的处理。可重载的消息处理函 数包括: OnApply:处理属性页的“Apply”按钮被单击的消息 OnCancel:处理属性页的“Cancel”按钮被单击的消息 OnKillActive:处理属性页当前活动状态被切换的消息,常用于数据验证 OnOK:处理属性页的“OK”按钮、“Apply”按钮或者“Close”按钮被单击的消息 OnQueryCancel:处理属性页的“Cancel”按钮被单击前发出的消息 OnReset:处理属性页的“Reset”按钮被单击的消息 OnSetActive:处理属性页被切换为当前活动页的消息 OnWizardBack:处理属性页的“Next”按钮被单击的消息,仅在向导对话框中有效 OnWizardFinish:处理属性页的“Finish”按钮被单击的消息,仅在向导对话框中有效 OnWizardNext:处理属性页的“下一步”按钮被单击的消息,仅在向导对话框中有效 2.CPropertySheet 类 CPropertySheet 类继承自 CWnd 类,它是属性表类,负责加载、打开或删除属性页 ,并可以在属性页对话框中切换属性页。它跟对话框类似,也有模态和非模态两种。下面 鸡啄米就讲解 CPropertySheet 类的部分成员函数。 (1)构造函数 这里依然列出 CPropertySheet 类的三个构造函数: CPropertySheet( ); explicit CPropertySheet( UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0 ); explicit CPropertySheet( LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0 ); 参数 nIDCaption:标题的字符串资源的 ID。 参数 pParentWnd:属性页对话框的父窗口,若设为 NULL,则父窗口为应用程序的 主窗口。 参数 iSelectPage:初始状态时,活动属性页的索引,默认为第一个添加到属性表的属 性页。 参数 pszCaption:标题字符串。 (2)GetActiveIndex()函数 获取当前活动属性页的索引。函数原型为: int GetActiveIndex( ) const; 返回值:当前活动属性页的索引。 (3)GetActivePage()函数 获取当前活动属性页对象。函数原型为: CPropertyPage* GetActivePage( ) const; 返回值:当前活动属性页对象的指针。 (4)GetPage()函数 获取某个属性页对象。函数原型为: CPropertyPage* GetPage(int nPage) const; 参数 nPage:目标属性页的索引。 返回值:目标属性页对象的指针。 (5)GetPageCount()函数 获取属性页的数量。函数原型为: int GetPageCount( ) const; 返回值:属性页的数量。 (6)GetPageIndex()函数 获取某属性页在属性页对话框中的索引。函数原型为: int GetPageIndex(CPropertyPage* pPage); 参数 pPage:要获取索引的属性页对象的指针。 返回值:属性页对象在属性页对话框中的索引。 (7)SetActivePage()函数 设置某个属性页为活动属性页。函数原型为: BOOL SetActivePage( int nPage ); BOOL SetActivePage( CPropertyPage* pPage ); 参数 nPage:要设置为活动属性页的索引。 参数 pPage:要设置为活动属性页的对象指针。 (8)SetWizardButtons()函数 在向导对话框上启用或禁用 Back、Next 或 Finish 按钮,应在调用 DoModal 之前调用 此函数。函数原型为: void SetWizardButtons( DWORD dwFlags ); 参数 dwFlags:设置向导按钮的外观和功能属性。可以是以下值的组合: PSWIZB_BACK 启用“Back”按钮,如果不包含此值则禁用“Back”按钮。 PSWIZB_NEXT 启用“Next”按钮,如果不包含此值则禁用“Next”按钮。 PSWIZB_FINISH 启用“Finish”按钮。 PSWIZB_DISABLEDFINISH 显示禁用的“Finish”按钮。 (9)SetWizardMode()函数 设置属性页对话框为向导对话框模式,应在调用 DoModal 之前调用此函数。函数原型 为: void SetWizardMode( ); (10)SetTitle()函数 设置属性对话框的标题。函数原型为: void SetTitle( LPCTSTR lpszText, UINT nStyle = 0 ); 参数 lpszText:标题字符串。 参数 nStyle:指定属性表标题的风格。应当为 0 或 PSH_PROPTITLE。如果设为 PS H_PROPTITLE,则单词“Properties”会出现在指定标题之后。例如,SetTitle("Simple",PS H_PROPTITLE)这种调用会使得属性表标题为“Simple Properties”。 (11)AddPage()函数 为属性对话框添加新的属性页。函数原型为: void AddPage( CPropertyPage *pPage ); 参数 pPage:要添加的新的属性页的对象指针。 (12)PressButton()函数 模拟按下某指定的按钮。函数原型为: void PressButton( int nButton ); 参数 nButton:要模拟按下的按钮,它可以是下列值之一: PSBTN_BACK 选择“Back”按钮。 PSBTN_NEXT 选择“Next”按钮。 PSBTN_FINISH 选择“Finish”按钮。 PSBTN_OK 选择“OK”按钮。 PSBTN_APPLYNOW 选择“Apply”按钮。 PSBTN_CANCEL 选择“Cancel”按钮。 PSBTN_HELP 选择“帮助”按钮。 (13)RemovePage()函数 删除某属性页。函数原型为: void RemovePage( CPropertyPage *pPage ); void RemovePage( int nPage ); 参数 pPage:要删除的属性页的对象指针。 参数 nPage:要删除的属性页的索引。 VS2010/MFC 编程入门之十四(对话框:向导对话框的创建及显 示) 上一讲鸡啄米讲了属性页对话框和相关的两个类 CPropertyPage 类和 CPropertyShee t 类,对使用属性页对话框做准备。本节将为大家演示如何创建向导对话框。 仍然以前面的“加法计算器”的例子为基础,在其中加入向导对话框,我们可以用它来 说明加法计算器的使用方法,一步一步引导用户操作,这也是比较常见的用法。 加法计算器使用时大概可以分为三步:输入被加数、输入加数、点“计算”按钮。 鸡啄米就详细说明向导对话框的创建步骤: 1.创建属性页对话框资源 根据创建对话框模板和修改对话框属性中所讲方法,在“Resource View”的 Dialog”节 点上点右键,然后在右键菜单中选择“Insert Dialog”创建第一个对话框模板,对话框的 ID 属性设置为 IDD_SUMMAND_PAGE,Caption 属性改为“被加数页”,Style 属性在下拉列 表中选择“Child”,Border 属性在下拉列表中选择“Thin”。 删除“OK”和“Cancel”按钮,再按照为对话框添加控件中所讲方法,添加一个静态文本 框,并修改静态文本框的 Caption 属性为“请先输入 double 型被加数”。 按照上述步骤,继续添加第二个和第三个对话框资源。第二个对话框模板的 ID 设为 I DD_ADDEND_PAGE,Caption 属性改为“加数页”,也添加一个静态文本框,Caption 设 为“请继续输入 double 型加数”,其他属性同第一个对话框。第三个对话框模板的 ID 设为 I DD_ADD_PAGE,Caption 属性改为“计算页”,添加静态文本框的 Caption 属性改为“最后 请按下“计算”按钮”,其他属性也第一个对话框一样。 2.创建属性页类 按照创建对话框类和添加控件变量中的方法,在第一个对话框模板上点右键,在右键 菜单中选择“Add Class”,弹出类向导对话框,在“Class name”编辑框中输入类名“CSumm andPage”,与之前不同的是,因为属性页类都应继承于 CPropertyPage 类,所以要修改 下面“Base class”的选项,在下拉列表中选择“CPropertyPage”。 因为是第一个属性页,所以它应该有一个“下一步”按钮,在哪里添加呢?上一讲CProp ertyPage 类的可重载函数中提到,OnSetActive 函数用于处理属性页被切换为当前活动页 的消息,所以我们可以在 OnSetActive 函数中进行相关设置。 那怎样重载 OnSetActive 函数呢?我们可以在“Class View”中找到“CSummandPage” 节点,点右键弹出右键菜单,选择“Properties”,然后 VS2010 右侧面板上会显示对话框的 属性列表,属性列表的工具栏上有个 tip 信息为“Overrides”的按钮,按下它,下方列表中 就列出了重载函数,找到“OnSetActive”,点其右侧空白列表项出现向下箭头,再点箭头就 在下面出现了“OnSetActive”的选项,选择它就会自动在 CSummandPage 类中添加 函数 OnSetActive。 我们只需在 OnSetActive 函数体中添加相关代码就可以实现添加“下一步”按钮的效果 了。新的函数体如下: C++代码 1. BOOL CSummandPage::OnSetActive() 2. { 3. // TODO: Add your specialized code here and/or call the bas e class 4. 5. // 获得父窗口,即属性表 CPropertySheet 类 6. CPropertySheet* psheet = (CPropertySheet*) GetParent(); 7. // 设置属性表只有“下一步”按钮 8. psheet->SetWizardButtons(PSWIZB_NEXT); 9. 10. return CPropertyPage::OnSetActive(); 11. } 为第二个和第三个对话框也分别添加属性页类 CAddendPage 和 CAddPage。但第二 个对话框的属性页不需要重载 OnSetActive 函数。第三个对话框是最后一个对话框,所以 不需要“下一步”按钮,而应该换成“完成”按钮,所以也需要重载 OnSetActive 函数设置“完 成”按钮。重载后的 OnSetActive 如下: C++代码 1. BOOL CAddPage::OnSetActive() 2. { 3. // TODO: Add your specialized code here and/or call the bas e class 4. 5. // 获得父窗口,即属性表 CPropertySheet 类 6. CPropertySheet* psheet = (CPropertySheet*) GetParent(); 7. //设置属性表只有“完成”按钮 8. psheet->SetFinishText(_T("完成")); 9. 10. return CPropertyPage::OnSetActive(); 11. } 上面的代码段中,字符串“完成”前加了个_T,这是因为本工程创建的时候用的默认的 Unicode 字符集,而如果“完成”前不加_T 就是 ASCII 字符串。_T 实际上是一个宏,工程的 字符集选择为 Unicode 时字符串就转为 Unicode 字符串,选择为 Muli-Byte 时就转为 ASC II 字符串。我们可以在 Solution Explorer 的 Addition 根节点上点右键,在右键菜单上选择“ Properties”,弹出工程的属性对话框,Configuration Properties->General 右侧列表中的 C haracter Set 就显示选择的字符集。 那点了第三个属性页上的“完成”按钮我们想进行某些处理的话,就重载 OnWizardFinis h 函数,方法同 OnSetActive 函数。重载后的 OnWizardFinish 函数如下: C++代码 1. BOOL CAddPage::OnWizardFinish() 2. { 3. // TODO: Add your specialized code here and/or call the bas e class 4. 5. // 提示向导完成 6. MessageBox(_T("使用说明向导已阅读完!")); 7. 8. return CPropertyPage::OnWizardFinish(); 9. } 3.创建属性表类 属性页资源和属性页类创建完以后,还不能生成向导对话框,我们还需要一个属性表 类,来容纳这些属性页。 在 Solution Explorer 视图中的根节点“Addition”上点右键,在右键菜单中选择 Add->Cl ass,弹出“Add Class”对话框,然后在中间区域中选择“MFC Class”,点“Add”按钮,弹出 另一个类向导对话框,设置 Class name 为 CAddSheet,Base class 选择“CPropertyShee t”,点“Finish”按钮,这样就属性表类就建好了。 接下来,在新生成的 AddSheet.h 中包含三个属性页类的头文件: #include "SummandPage.h" #include "AddendPage.h" #include "AddPage.h" 之后在 AddSheet.h 中添加 private 变量: CSummandPage m_summandPage; CAddendPage m_addendPage; CAddPage m_addPage; 然后在 AddSheet.cpp 文件中修改 CAddSheet 的两个构造函数为: C++代码 1. CAddSheet::CAddSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iS electPage) 2. :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) 3. { 4. // 添加三个属性页到属性表 5. AddPage(&m_summandPage); 6. AddPage(&m_addendPage); 7. AddPage(&m_addPage); 8. } 9. 10. CAddSheet::CAddSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) 11. :CPropertySheet(pszCaption, pParentWnd, iSelectPage) 12. { 13. // 添加三个属性页到属性表 14. AddPage(&m_summandPage); 15. AddPage(&m_addendPage); 16. AddPage(&m_addPage); 17. } 4.显示向导对话框 我们在加法计算器对话框上添加一个按钮,点击它就打开向导对话框。此按钮的 ID 设为 IDC_INSTRUCT_BUTTON,Caption 属性设为“使用说明”。 按照为控件添加消息处理函数中所讲方法,为 IDC_INSTRUCT_BUTTON 按钮在 CA dditionDlg 类中添加点击消息的处理函数 OnBnClickedInstructButton。然后在 AdditionDlg.c pp 文件中包含 CAddSheet 的头文件:#include "AddSheet.h"。最后修改 OnBnClickedIns tructButton 函数如下: C++代码 1. void CAdditionDlg::OnBnClickedInstructButton() 2. { 3. // TODO: Add your control notification handler code here 4. 5. // 创建属性表对象 6. CAddSheet sheet(_T("")); 7. // 设置属性对话框为向导对话框 8. sheet.SetWizardMode(); 9. // 打开模态向导对话框 10. sheet.DoModal(); 11. } 到此,向导对话框就完整的创建完成了,并可以在加法计算器对话框上点“使用说明” 按钮显示出来。我们来看看效果吧: 上图只是被加数页的效果,点其上“下一步”按钮就可以继续显示后面的两个页面。 VS2010/MFC 编程入门之十五(对话框:一般属性页对话框的创 建及显示) 属性页对话框包括向导对话框和一般属性页对话框两类,上一节鸡啄米讲了如何创建 并显示向导对话框,本节将继续介绍一般属性页对话框的创建和显示。 实际上,一般属性页对话框的创建和显示过程和向导对话框是很类似的。鸡啄米将上 一节中的向导对话框进行少量修改,使其成为一般属性页对话框。 一般属性页对话框的创建步骤: 1.创建属性页对话框资源 属性页对话框资源的创建方法同向导对话框是一样的,上一讲中的对话框资源不需进 行任何修改。 2.创建属性页类 属性页类的创建和向导对话框的属性页类也基本一样,只是一般属性页对话框中不需 要“下一步”和“完成”等按钮,所以上一讲中属性页类的 OnSetActive 和 OnWizardFinish 等 重载函数可以去掉。即 CSummandPage 类中的 OnSetActive 函数、CAddPage 类中的 O nSetActive 函数和 OnWizardFinish 函数可以删除或注释掉。其他部分不需作任何修改。 3.创建属性表类 创建属性表类的过程同向导对话框属性表类也是一样的,所以上一讲中的 CAddSheet 类不需修改。 4.显示一般属性页对话框 上一讲向导对话框的显示是在 OnBnClickedInstructButton 函数中实现的,其中语句 s heet.SetWizardMode();旨在设置属性表为向导对话框模式,所以显示一般属性页对话框时 不需调用 SetWizardMode 成员函数。另外,我们可以将属性页对话框的标题设为“使用说 明”,在构造属性表对象时将此字符串作为构造函数的参数传入。OnBnClickedInstructButt on 函数修改如下: C++代码 1. void CAdditionDlg::OnBnClickedInstructButton() 2. { 3. // TODO: Add your control notification handler code here 4. 5. // 创建属性表对象 6. CAddSheet sheet(_T("使用说明")); 7. 8. // 打开模态一般属性页对话框 9. sheet.DoModal(); 10. } 这样一般属性页对话框的创建和显示就讲完了,我们运行下程序,在结果对话框上点“ 使用说明”按钮看看效果吧: 再总结下,一般属性页对话框和向导对话框的创建和显示的不同包括,是否需要 OnS etActive 和 OnWizardFinish 等重载函数,是否需要调用属性表类的 SetWizardMode 函 数设置为向导对话框模式。 VS2010/MFC 编程入门之十六(对话框:消息对话框) 前面几节鸡啄米讲了属性页对话框,我们可以根据所讲内容方便的建立自己的属性页 对话框。本节讲解 Windows 系统中最常用最简单的一类对话框--消息对话框。 我们在使用 Windows 系统的过程中经常会见到消息对话框,提示我们有异常发生或提 出询问等。因为在软件开发中经常用到消息对话框,所以 MFC 提供了两个函数可以直接 生成指定风格的消息对话框,而不需要我们在每次使用的时候都要去创建对话框资源和生 成对话框类等。这两个函数就是 CWnd 类的成员函数 MessageBox()和全局函数 AfxMess ageBox()。 一.CWnd::MessageBox()函数和 AfxMessageBox()函数的用法 下面鸡啄米就分别讲解两个函数的用法。 1.CWnd::MessageBox()函数 CWnd::MessageBox()的函数原型如下: int MessageBox( LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK ); 参数说明: lpszText:需要显示的消息字符串。 lpszCaption:消息对话框的标题字符串。默认值为 NULL。取值为 NULL 时使用默认 标题。 nType:消息对话框的风格和属性。默认为 MB_OK 风格,即只有“确定”按钮。 nType 的取值可以是下面两个表中任取一个值,也可以是各取一个值的任意组合。即 可以指定一个对话框类型,也可以指定一个对话框图标,还可以两者都设定。 nType 取值 参数说明 MB_ABORTRETRY 有“终止”、“重试”和“忽略”按钮 MB_OK 有“确定”按钮 MB_OKCANCEL 有“确定”和“取消”按钮 MB_RETRYCANCEL 有“重试”和“取消”按钮 MB_YESNO 有“是”和“否”按钮 MB_YESNOCANCEL 有“是”、“否”和“取消”按钮 对话框类型表 nType 取值 显示图标 MB_ICONEXCLAMTION MB_ICONWARNING MB_ICONASTERISK MB_ICONINFORMATION MB_ICONQUESTION MB_ICONHAND MB_ICONSTOP MB_ICONERROR 对话框图标表 如果想要设置 nType 的值为类型和图标的组合,可以像这样取值:MB_OKCANCEL | MB_ICONQUESTION。按位取或就可以了。 2.AfxMessageBox()函数 AfxMessageBox()的函数原型为: int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 ); 参数说明: lpszText:同 CWnd::MessageBox()函数 nType:CWnd::MessageBox()函数 nIDHelp:此消息的帮助的上下文 ID。默认值为 0,取 0 时表示要使用应用程序的默 认帮助上下文。 二.CWnd::MessageBox()和 AfxMessageBox()的返回值 我们在调用了上面两个函数后,都可以弹出模态消息对话框。消息对话框关闭后,我 们也都可以得到它们的返回值。两者的返回值就是用户在消息对话框上单击的按钮的 ID, 可以是以下值: IDABORT:单击“终止”按钮。 IDCANCEL:单击“取消”按钮。 IDIGNORE:单击“忽略”按钮。 IDNO:单击“否”按钮。 IDOK:单击“确定”按钮。 IDRETRY:单击“重试”按钮。 IDYES:单击“是”按钮。 三.应用举例 我们还是拿前面加法计算器的程序做例子。 大家是否记得,在模态对话框及其弹出过程中我们修改了 CAdditionDlg::OnBnClicked AddButton()函数,在点了“计算”按钮以后先弹出了一个模态对话框,询问用户是否确定要 进行加法计算,并通过模态对话框 DoModal 函数的返回值判断用户选择了“确定”还是“取消 ”。这些功能很明显消息对话框完全能够实现,鸡啄米就使用消息对话框来替代原来的模态 对话框。 在非模态对话框的创建及显示中,鸡啄米注释了模态对话框的相关代码,加入了非模 态对话框的创建和显示代码,我们在加入消息对话框之前将非模态对话框的代码也注释或 删除掉,确保此函数中不再生成原来的模态对话框或非模态对话框。 修改后的 CAdditionDlg::OnBnClickedAddButton()函数如下: C++代码 1. void CAdditionDlg::OnBnClickedAddButton() 2. { 3. // TODO: Add your control notification handler code here 4. 5. INT_PTR nRes; 6. 7. // 显示消息对话框 8. nRes = MessageBox(_T("您确定要进行加法计算吗?"), _T("加法计算器"), MB_OKCANCEL | MB_ICONQUESTION); 9. // 判断消息对话框返回值。如果为 IDCANCEL 就 return,否则继续向下执行 10. if (IDCANCEL == nRes) 11. return; 12. 13. // 将各控件中的数据保存到相应的变量 14. UpdateData(TRUE); 15. 16. // 将被加数和加数的加和赋值给 m_editSum 17. m_editSum = m_editSummand + m_editAddend; 18. 19. // 根据各变量的值更新相应的控件。和的编辑框会显示 m_editSum 的值 20. UpdateData(FALSE); 21. // 设置属性对话框为向导对话框 22. //sheet.SetWizardMode(); 23. } 编译运行,在运行结果对话框上点“计算”按钮弹出以下消息对话框: 大家也可以将 MessageBox 函数换为 AfxMessageBox()函数,同时参数进行相应修改 ,运行下看看效果。 VS2010/MFC 编程入门之十七(对话框:文件对话框) 上一讲鸡啄米介绍的是消息对话框,本节讲解文件对话框。文件对话框也是很常用的 一类对话框。 文件对话框的分类 文件对话框分为打开文件对话框和保存文件对话框,相信大家在 Windows 系统中经 常见到这两种文件对话框。例如,很多编辑软件像记事本等都有“打开”选项,选择“打开”后 会弹出一个对话框,让我们选择要打开文件的路径,这个对话框就是打开文件对话框;除 了“打开”选项一般还会有“另存为”选项,选择“另存为”后往往也会有一个对话框弹出,让我 们选择保存路径,这就是保存文件对话框。 正如上面举例说明的,打开文件对话框用于选择要打开的文件的路径,保存文件对话 框用来选择要保存的文件的路径。 文件对话框类 CFileDialog MFC 使用文件对话框类 CFileDialog 封装了对文件对话框的操作。CFileDialog 类的 构造函数原型如下: explicit CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL, DWORD dwSize = 0, BOOL bVistaStyle = TRUE ); 参数说明: bOpenFileDialog:指定要创建的文件对话框的类型。设为 TRUE 将创建打开文件对 话框,否则将创建保存文件对话框。 lpszDefExt:默认的文件扩展名。如果用户在文件名编辑框中没有输入扩展名,则由 l pszDefExt 指定的扩展名将被自动添加到文件名后。默认为 NULL。 lpszFileName:文件名编辑框中显示的初始文件名。如果为 NULL,则不显示初始文 件名。 dwFlags:文件对话框的属性,可以是一个值也可以是多个值的组合。关于属性值的 定义,可以在 MSDN 中查找结构体 OPENFILENAME,元素 Flags 的说明中包含了所有属 性值。默认为 OFN_HIDEREADONLY 和 OFN_OVERWRITEPROMPT 的组合,OFN_HI DEREADONLY 表示隐藏文件对话框上的“Read Only”复选框,OFN_OVERWRITEPROM PT 表示在保存文件对话框中如果你选择的文件存在了,就弹出一个消息对话框,要求确定 是否要覆盖此文件。 lpszFilter:文件过滤器,它是由若干字符串对组成的一个字符串序列。如果指定了文 件过滤器,则文件对话框中只有符合过滤条件的文件显示在文件列表中待选择。给大家看 看 VS2010 MSDN 中给出的一个例子: static TCHAR BASED_CODE szFilter[] = _T("Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||"); 这样设置过滤器以后,文件对话框的扩展名组合框中将有四个选项:Chart Files (*.xlc)、 Worksheet Files (*.xls)、Data Files(*.xlc;*.xls)和 All Files (*.*),大家可以看到每种文件的 扩展名规定都是一个字符串对,例如 Chart Files 的过滤字符串是 Chart Files(*.xlc)和*.xlc 成对出现的。 pParentWnd:文件对话框的父窗口的指针。 dwSize:OPENFILENAME 结构体的大小。不同的操作系统对应不同的 dwSize 值。 MFC 通过此参数决定文件对话框的适当类型(例如,创建 Windows 2000 文件对话框还是 XP 文件对话框)。默认为 0,表示 MFC 将根据程序运行的操作系统版本来决定使用哪种 文件对话框。 bVistaStyle:指定文件对话框的风格,设为 TRUE 则使用 Vista 风格的文件对话框, 否则使用旧版本的文件对话框。此参数仅在 Windows Vista 中编译时适用。 文件对话框也是模态对话框,所以在打开时也需要调用 CFileDialog 类的 DoModal()成 员函数。在打开文件对话框中点了“打开”或者在保存文件对话框中点了“保存”以后,我们可 以使用 CFileDialog 类的成员函数 GetPathName()获取选择的文件路径。 下面列出几个 CFileDialog 类的成员函数,我们可以使用它们获得文件对话框中的各 种选择。 GetFileExt():获得选定文件的后缀名。 GetFileName():获得选定文件的名称,包括后缀名。 GetFileTitle():获得选定文件的标题,即不包括后缀名。 GetFolderPath():获得选定文件的目录。 GetNextPathName():获得下一个选定的文件的路径全名。 GetPathName():获得选定文件的路径全名。 GetReadOnlyPref():获得是否“以只读方式打开”。 GetStartPosition():获得文件名列表中的第一个元素的位置。 文件对话框实例 根据前面所讲内容,鸡啄米给大家做个文件对话框实例。 1.创建一个基于对话框的 MFC 应用程序工程,名称设为“Example17”。 2.修改主对话框 IDD_EXAMPLE17_DIALOG 的模板,删除自动生成的“TODO: Place dialog controls here.”静态文本框,添加两个编辑框,ID 分别为 IDC_OPEN_EDIT 和 IDC _SAVE_EDIT,再添加两个按钮,ID 分别设为 IDC_OPEN_BUTTON 和 IDC_SAVE_BUT TON,Caption 分别设为“打开”和“保存”。按钮 IDC_OPEN_BUTTON 用于显示打开文件对 话框,编辑框 IDC_OPEN_EDIT 显示在打开文件对话框中选择的文件路径。按钮 IDC_SA VE_BUTTON 用于显示保存文件对话框,编辑框 IDC_SAVE_BUTTON 显示在保存文件对 话框中选择的文件路径。 3.分别为按钮 IDC_OPEN_BUTTON 和 IDC_SAVE_BUTTON 添加点击消息的消息处 理函数CExample17Dlg::OnBnClickedOpenButton()和 CExample17Dlg::OnBnClickedSav eButton()。 4.修改两个消息处理函数如下: C++代码 1. void CExample17Dlg::OnBnClickedOpenButton() 2. { 3. // TODO: Add your control notification handler code here 4. // 设置过滤器 5. TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|所有文件(*.*)|*.* ||"); 6. // 构造打开文件对话框 7. CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, thi s); 8. CString strFilePath; 9. 10. // 显示打开文件对话框 11. if (IDOK == fileDlg.DoModal()) 12. { 13. // 如果点击了文件对话框上的“打开”按钮,则将选择的文件路径显示到编 辑框里 14. strFilePath = fileDlg.GetPathName(); 15. SetDlgItemText(IDC_OPEN_EDIT, strFilePath); 16. } 17. } 18. 19. 20. void CExample17Dlg::OnBnClickedSaveButton() 21. { 22. // TODO: Add your control notification handler code here 23. // 设置过滤器 24. TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|Word 文件(*.doc) |*.doc|所有文件(*.*)|*.*||"); 25. // 构造保存文件对话框 26. CFileDialog fileDlg(FALSE, _T("doc"), _T("my"), OFN_HIDEREA DONLY | OFN_OVERWRITEPROMPT, szFilter, this); 27. CString strFilePath; 28. 29. // 显示保存文件对话框 30. if (IDOK == fileDlg.DoModal()) 31. { 32. // 如果点击了文件对话框上的“保存”按钮,则将选择的文件路径显示到编 辑框里 33. strFilePath = fileDlg.GetPathName(); 34. SetDlgItemText(IDC_SAVE_EDIT, strFilePath); 35. } 36. } 上面显示编辑框内容时,鸡啄米使用了 Windows API 函数 SetDlgItemText,当然也可 以先给编辑框关联变量,然后再使用鸡啄米在创建对话框类和添加控件变量中介绍的 CDialogEx::UpdateData()函数,但是鸡啄米比较习惯使用 SetDlgItemText 函数,感觉比 较灵活。 5.运行此程序,在结果对话框上点“打开”按钮,显示打开文件对话框如下: 点“保存”按钮后,显示保存文件对话框: 在打开文件对话框和保存文件对话框都选择了文件路径后,主对话框如下: 到此,文件对话框就讲完了,是不是依然很简单?如果忘记了文件对话框类构造函数 的参数意义,可以回到鸡啄米来看看或者在 MSDN 上查阅。 VS2010/MFC 编程入门之十八(对话框:字体对话框) 鸡啄米在上一节为大家讲解了文件对话框的使用,本节则主要介绍字体对话框如何应 用。 字体对话框的作用是用来选择字体。我们也经常能够见到。MFC 使用 CFontDialog 类 封装了字体对话框的所有操作。字体对话框也是一种模态对话框。 CFontDialog 类的构造函数 我们先来了解 CFontDialog 类。它的常用构造函数原型如下: CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL ); 参数说明: lplfInitial:指向 LOGFONT 结构体数据的指针,可以通过它设置字体的一些特征。 dwFlags:指定选择字体的一个或多个属性,详情可在 MSDN 中查阅。 pdcPrinter:指向一个打印设备上下文的指针。 pParentWnd:指向字体对话框父窗口的指针。 上面的构造函数中第一个参数为 LOGFONT 指针,LOGFONT 结构体中包含了字体的 大部分特征,包括字体高度、宽度、方向、名称等等。下面是此结构体的定义: typedef struct tagLOGFONT { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; TCHAR lfFaceName[LF_FACESIZE]; } LOGFONT; 获取字体对话框中所选字体 我们在字体对话框中选择了字体后,如何获取选定的字体呢?我们可以通过 CFontDia log 类的成员变量 m_cf 间接获得选定字体的 CFont 对象。m_cf 是 CHOOSEFONT 类型的 变量,CHOOSEFONT 结构体定义如下: typedef struct { DWORD lStructSize; HWND hwndOwner; HDC hDC; LPLOGFONT lpLogFont; INT iPointSize; DWORD Flags; COLORREF rgbColors; LPARAM lCustData; LPCFHOOKPROC lpfnHook; LPCTSTR lpTemplateName; HINSTANCE hInstance; LPTSTR lpszStyle; WORD nFontType; INT nSizeMin; INT nSizeMax; } CHOOSEFONT, *LPCHOOSEFONT; CHOOSEFON 结构体中有个成员 lpLogFont,它是指向 LOGFONT 结构体变量的指 针,就像上面所说,LOGFONT 中包含了字体特征,例如,我们可以通过 LOGFONT 的 lf FaceName 得知字体名。 我们最终要获得的是所选择字体的 CFont 对象,有了字体的 LOGFONT 怎样获得对应 的 CFont 对象呢?使用 CFont 类的成员函数 CreateFontIndirect 可以达到此目的。函数原 型如下: BOOL CreateFontIndirect(const LOGFONT* lpLogFont ); 参数是 LOGFONT 指针类型,我们可以传入 CFontDialog 类成员变量 m_cf 的 lpLogF ont 成员,就可以得到所选字体的 CFont 对象了。 字体对话框应用实例 鸡啄米给大家做一个字体对话框的实例。先介绍此实例要实现的功能,生成一个对话 框,对话框中放置一个“字体选择”按钮和一个编辑框。点击“字体选择”按钮将弹出字体对话 框。编辑框用于显示所选字体名,并以选定的字体来显示字体名字符串,例如,如果选择 了宋体,则在编辑框中以宋体显示字符串“宋体”。 以下是创建此实例的步骤: 1.创建一个基于对话框的 MFC 工程,名字为“Example18”。 2.在自动生成的主对话框 IDD_EXAMPLE18_DIALOG 的模板中,删除“TODO: Place dialog controls here.”静态文本框,添加一个按钮,ID 设为 IDC_FONT_BUTTON,Captio n 设为“字体选择”,用于显示字体对话框来选择字体,再添加一个编辑框,ID 设为 IDC_F ONT_EDIT,用来以所选字体显示字体名字符串。 3.在 Example18Dlg.h 中为 CExample18Dlg 类添加 private 成员变量:CFont m_font; ,用来保存编辑框中选择的字体。 4.为按钮 IDC_FONT_BUTTON 添加点击消息的消息处理函数 CExample18Dlg::OnBn ClickedFontButton()。 5.修改消息处理函数CExample18Dlg::OnBnClickedFontButton()如下: C++代码 1. void CExample18Dlg::OnBnClickedFontButton() 2. { 3. // TODO: Add your control notification handler code here 4. CString strFontName; // 字体名称 5. LOGFONT lf; // LOGFONT 变量 6. 7. // 将 lf 所有字节清零 8. memset(&lf, 0, sizeof(LOGFONT)); 9. 10. // 将 lf 中的元素字体名设为“宋体” 11. _tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("宋体")); 12. 13. // 构造字体对话框,初始选择字体名为“宋体” 14. CFontDialog fontDlg(&lf); 15. 16. if (IDOK == fontDlg.DoModal()) // 显示字体对话框 17. { 18. // 如果 m_font 已经关联了一个字体资源对象,则释放它 19. if (m_font.m_hObject) 20. { 21. m_font.DeleteObject(); 22. } 23. // 使用选定字体的 LOGFONT 创建新的字体 24. m_font.CreateFontIndirect(fontDlg.m_cf.lpLogFont); 25. // 获取编辑框 IDC_FONT_EDIT 的 CWnd 指针,并设置其字体 26. GetDlgItem(IDC_FONT_EDIT)->SetFont(&m_font); 27. 28. // 如果用户选择了字体对话框的 OK 按钮,则获取被选择字体的名称并显 示到编辑框里 29. strFontName = fontDlg.m_cf.lpLogFont->lfFaceName; 30. SetDlgItemText(IDC_FONT_EDIT, strFontName); 31. } 32. } 6.最后,编译运行程序。显示结果对话框,点击“字体选择”按钮,将弹出字体对话框, 默认选择为“宋体”,我们改而选择“华文彩云”字体点“确定”,编辑框中会像如下显示: 到此,我们又学会了字体对话框的使用,对于以后在界面开发中控制显示的字体很有 帮助。有问题欢迎在鸡啄米留言。 VS2010/MFC 编程入门之十九(对话框:颜色对话框) 鸡啄米在上一节中为大家讲解了字体对话框的使用方法,熟悉了字体对话框,本节继 续讲另一种通用对话框--颜色对话框。 颜色对话框大家肯定也不陌生,我们可以打开它选择需要的颜色,简单说,它的作用 就是用来选择颜色。MFC 中提供了 CColorDialog 类封装了颜色对话框的所有操作,我们 可以通过它显示颜色对话框,并获取颜色对话框中选择的颜色。颜色对话框跟字体对话框 一样,也是一种模态对话框。 CColorDialog 类的构造函数 CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL ); 参数说明: clrInit:默认选择颜色的颜色值,类型为 COLORREF,实际上就是 unsigned long 类 型。如果没有设置它的值,则默认为 RGB(0,0,0),即黑色。 注:RGB(r,g,b)是宏,可以计算颜色值。括号中的三个值分别为红、绿、蓝分量的值 。 dwFlags:自定义颜色对话框功能和外观的属性值。详情可在 MSDN 中查阅。 pParentWnd:颜色对话框的父窗口的指针。 获取颜色对话框中所选颜色值 我们使用颜色对话框的最终目的还是要获得在颜色对话框中选择的颜色值。为此 CCol orDialog 类的成员函数 GetColor()能够很好的实现我们的要求。GetColor()函数的原型为 : COLORREF GetColor( ) const; 它返回所选颜色的 COLORREF 值。 如果我们想获得 R、G、B 各分量的值呢?可以根据 GetColor 得到的 COLORREF 颜 色值,通过使用 GetRValue、GetGValue 和 GetBValue 三个宏获得。GetRValue 的语法 形式为: BYTE GetRValue(DWORD rgb); 参数 rgb 就是 COLORREF 颜色值,返回值即是 R 分量值。其他两个宏的形式与之类 似。例如,GetColor()函数返回的 COLORREF 为 10000,则 R 分量值就是 GetRValue(1 0000)。 颜色对话框应用实例 鸡啄米下面给大家做一个颜色对话框的小例子。此例要实现的功能简单介绍下:生成 一个对话框,对话框中放置一个“颜色选择”按钮,四个静态文本框和四个编辑框。四个静 态文本框分别显示 Color:、R:、G:、B:,每个静态文本框后面跟一个编辑框,分别 用来显示颜色对话框中选择的颜色值和所选颜色值的红色分量、绿色分量、蓝色分量。 以下是实例创建的步骤: 1.创建一个基于对话框的 MFC 工程,名字为“Example19”。 2.在自动生成的主对话框 IDD_EXAMPLE19_DIALOG 的模板中,删除“TODO: Place dialog controls here.”静态文本框,添加一个按钮,ID 设为 IDC_COLOR_BUTTON,Capt ion 设为“颜色选择”,用于显示颜色对话框来选择颜色。再添加四个静态文本框,ID 分别为 IDC_COLOR_STATIC、IDC_R_STATIC、IDC_G_STATIC、IDC_B_STATIC,Caption 分别设为“Color:”、“R:”、“G:”、“B:”,然后每个静态文本框后添加一个编辑框,四个 编辑框的 ID 分别为 IDC_COLOR_EDIT、IDC_R_EDIT、IDC_G_EDIT、IDC_B_EDIT, 分别用来显示颜色对话框中选择的颜色值和所选颜色值的红色分量、绿色分量、蓝色分量 。 3.为按钮 IDC_COLOR_BUTTON 添加点击消息的消息处理函数 CExample19Dlg::On BnClickedColorButton()。 4.修改消息处理函数CExample19Dlg::OnBnClickedColorButton()如下: C++代码 1. void CExample19Dlg::OnBnClickedColorButton() 2. { 3. // TODO: Add your control notification handler code here 4. COLORREF color = RGB(255, 0, 0); // 颜色对话框的初始颜色为 红色 5. CColorDialog colorDlg(color); // 构造颜色对话框,传入初 始颜色值 6. 7. if (IDOK == colorDlg.DoModal()) // 显示颜色对话框,并判断 是否点击了“确定” 8. { 9. color = colorDlg.GetColor(); // 获取颜色对话框中选择的 颜色值 10. SetDlgItemInt(IDC_COLOR_EDIT, color); // 在 Col or 编辑框中显示所选颜色值 11. SetDlgItemInt(IDC_R_EDIT, GetRValue(color)); // 在 R 编 辑框中显示所选颜色的 R 分量值 12. SetDlgItemInt(IDC_G_EDIT, GetGValue(color)); // 在 G 编 辑框中显示所选颜色的 G 分量值 13. SetDlgItemInt(IDC_B_EDIT, GetBValue(color)); // 在 B 编 辑框中显示所选颜色的 B 分量值 14. } 15. } 5.最后编译运行程序,在结果对话框中点击“颜色选择”按钮,弹出颜色对话框。初始状 态下,选择框在红色上,我们选另一种颜色,此时的颜色对话框如下: 点“确定”,主对话框上的四个编辑框中分别显示了选择的颜色值、R 分量、G 分量和 B 分量: 我们在实际开发中,可以用获取到的颜色值来设置其他对象的颜色,使用还是很方便 的。 VS2010/MFC 编程入门之二十(常用控件:静态文本框) 上一节鸡啄米讲了颜色对话框之后,关于对话框的使用和各种通用对话框的介绍就到 此为止了。从本节开始鸡啄米将讲解各种常用控件的用法。常用控件主要包括:静态文本 框、编辑框、单选按钮、复选框、分组框、列表框、组合框、图片控件、列表控件、树形 控件和进度条控件等等。本节教程先来讲解静态文本框的使用。 控件的通知消息 在将静态文本框的使用之前,先大概讲讲控件的通知消息。 当控件有事件发生时,它会向父窗口发送通知消息。最常发生的事件就是鼠标单击了 ,此时控件会向父窗口发送 BN_CLICKED 消息,实际上也就是给父窗口发送 WM_COMM AND 消息,在 wParam 参数中包含有通知消息码(鼠标单击时的通知消息码就是 BN_CLI CKED)和控件 ID,lParam 参数中包含了控件的句柄。在MFC 消息映射机制概述中,鸡 啄米讲过,消息就是由三个部分组成:消息值、wParam 参数和 lParam 参数。 为控件通知消息添加消息映射和消息处理函数的方法,之前不止一遍讲过了。现在再 来具体说明下,控件的消息映射宏的格式大致是: ON_通知消息码(nID, memberFun) nID 参数是控件的 ID,memberFun 参数是消息处理函数名。例如,ON_BN_CLICKE D(IDC_BUTTON1, &CDlg::OnBnClickedButton1)。此消息映射宏应添加到 BEGIN_MESS AGE_MAP 和 END_MESSAGE_MAP 之间。 消息处理函数声明的语法形式为: afx_msg void memberFun(); 静态文本框的使用 在前面鸡啄米的举例中,大家应该也清楚了静态文本框的一般作用,就是用于显示文 字说明。MFC提供了 CStatic 类,封装了对静态文本框的所有操作。 如果我们想在程序中动态创建静态文本框,而不是像前面那样直接从 Toolbox 中拖到 对话框模板上,那么就需要使用 CStatic 类的成员函数 Create。Create 函数的原型如下: virtual BOOL Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 参数说明: lpszText:指定要在控件中显示的文字。如果为 NULL 则不会显示任何文字。 dwStyle:指定静态控件的风格。静态文本框一般都是对话框或其他窗口的子窗口,而 且是可见的,所以应该包含 WS_CHILD 和 WS_VISIBLE 风格,另外,MSDN 中说明,还 可以为其设置“static control styles”中风格的任意组合。下面大概为大家说明几个风格: SS_BITMAP 一个位图将显示在静态控件中,Create 函数的 lpszText 参数字符串是资源文件中定义的位图名。此风格 忽略宽度和高度参数,静态控件自动调整它的尺寸来适应 位图 SS_BLACKFRAME 指定一个具有与窗口边界同色的框,默认为黑色 SS_BLACKRECT 指定一个具有与窗口边界同色的实矩形,默认为黑色 SS_CENTER 使显示的正文居中对齐,正文可以换行 SS_GRAYFRAME 指定一个具有与屏幕背景同色的边框 SS_GRAYRECT 指定一个具有与屏幕背景同色的实矩形 SS_ICON 使控件显示一个在资源中定义的图标,图标的名字由 Create 函数的 lpszText 参数指定,图标自动调整它的尺 寸 SS_LEFT 左对齐正文,正文能回绕 SS_LEFTNOWORDWRAP 左对齐正文,正文不能回绕 SS_NOTIFY 使控件能向父窗口发送鼠标事件消息 SS_RIGHT 右对齐正文,可以回绕 SS_SIMPLE 使静态正文在运行时不能被改变并使正文显示在单行中 SS_WHITEFRAME 指定一个具有与窗口背景同色的框,默认为白色 SS_WHITERECT 指定一个具有与窗口背景同色的实心矩形,默认为白色 我们在对话框模板添加静态文本框时,可以在静态文本框的属性页中设置它的风格, 很多都与上面的风格是对应的,例如,Simple 属性就相当于 SS_SIMPLE 风格。 rect:指定静态控件的位置和大小,它可以是 RECT 结构体类型,也可以是 CRect 类 的对象。 pParentWnd:指定静态控件的父窗口,通常是一个 CDialog 对象,不能是 NULL。 nID:指定静态控件的 ID。 CStatic 类的成员函数简介 简单介绍下 CStatic 类的主要成员函数,下面是成员函数列表。 GetBitmap 获取由 SetBitmap 函数设置的位图的句柄 GetCursor 获取由 SetCurSor 设置的光标的句柄 GetEnhMetaFile 获取由 SetEnhMetaFile 设置的增强图元文件的句 柄 GetIcon 获取由 SetIcon 设置的图标的句柄 SetBitmap 设置要在静态控件中显示的位图 SetCursor 设置要在静态控件中显示的光标图片 SetEnhMetaFile 设置要在静态控件中显示的增强图元文件 SetIcon 设置要在静态控件中显示的图标 除了上述成员函数外,由于 CStatic 是 CWnd 的派生类,CWnd 的很多成员函数也可 以使用,例如,GetWindowText、GetWindowRect、SetWindowText 等。 VS2010/MFC 编程入门之二十一(常用控件:编辑框 Edit Control) 鸡啄米上一节讲了静态文本框,本节要讲的编辑框(Edit Control)同样是一种很常用 的控件,我们可以在编辑框中输入并编辑文本。在前面加法计算器的例子中已经演示了编 辑框的基本应用。下面具体讲解编辑框的使用。 编辑框的通知消息 编辑框发生某些事件时会向父窗口发送通知消息。在对话框模板中的编辑框上点右键 ,选择“Add Event Handler”,为编辑框添加消息处理函数时,可以在“Message type”列表 中看到这些消息。下面简单介绍编辑框的部分通知消息。 EN_CHANGE:编辑框的内容被用户改变了,与 EN_UPDATE 不同,该消息是在编 辑框显示的正文被刷新后才发出的 EN_ERRSPACE: 编辑框控件无法申请足够的动态内存来满足需要 EN_HSCROLL: 用户在水平滚动条上单击鼠标 EN_KILLFOCUS: 编辑框失去输入焦点 EN_MAXTEXT:输入的字符超过了规定的最大字符数。在没有 ES_AUTOHSCROLL 或 ES_AUTOVSCROLL: 的编辑框中,当正文超出了编辑框的边框时也会发出该消息 EN_SETFOCUS: 编辑框获得输入焦点 EN_UPDATE: 在编辑框准备显示改变了的正文时发送该消息 EN_VSCROLL: 用户在垂直滚动条上单击鼠标 编辑框的创建 MFC为编辑框提供了 CEdit 类。编辑框的所有操作都封装到了 CEdit 类中。 与静态文本框的创建类似,除了可以在对话框模板上拖进一个编辑框,然后关联一个 变量或通过 API 函数使用,也可以在程序中动态创建编辑框,即调用 CEdit 类的成员函数 Create。Create 成员函数的原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 参数说明: dwStyle:指定编辑框的风格。可以是MSDN中“edit styles”包含风格的任意组合。下面 是“edit styles”的所有风格说明。 ES_AUTOHSCROLL:当用户在行尾键入一个字符时,正文将自动向右滚动 10 个字 符,当用户按回车键时,正文总是滚向左边 ES_AUTOVSCROLL: 当用户在最后一个可见行按回车键时,正文向上滚动一页 ES_CENTER: 在多行编辑框中使正文居中 ES_LEFT :左对齐正文 ES_LOWERCASE: 把用户输入的字母统统转换成小写字母 ES_MULTILINE:指定一个多行编辑器。若多行编辑器不指定 ES_AUTOHSCROLL 风格,则会自动换行,若不指定 ES_AUTOVSCROLL,则多行编辑器会在窗口中正文装 满时 发出警告声响 ES_NOHIDESEL:默认时,当编辑框失去输入焦点后会隐藏所选的正文,当获得输 入焦点时又显示出来。设置该风格可禁止这种默认行为 ES_NUMBER :编辑框中只允许输入数字 ES_OEMCONVERT:使编辑框中的正文可以在 ANSI 字符集和 OEM 字符集之间相 互转换。这在编辑框中包含文件名时是很有用的 ES_PASSWORD: 使所有键入的字符都用“*”来显示 ES_READONLY: 将编辑框设置成只读的 ES_RIGHT :右对齐正文 ES_UPPERCASE: 把用户输入的字母统统转换成大写字母 ES_WANTRETURN:使多行编辑器接收回车键输入并换行。如果不指定该风格,按 回车键会选择默认的命令按钮,这往往会导致对话框的关闭 除了上面的风格外,编辑款一般还会设置 WS_CHILD、WS_VISIBLE、WS_BORDE R 等窗口风格。另外,编辑框可以是多行的,也就是在编辑框中显示多行文字,这就需要 设置 ES_MULTILINE 风格,如果想要多行编辑框支持回车键,则还要设置 ES_WANTRE TURN。 对于在对话框模板中创建的编辑框,它的属性中包含了上述的风格,例如,Multiline 属性对应的就是 ES_MULTILINE 风格,Want Return 属性对应 ES_WANTRETURN 风格 。 其他三个参数与静态文本框的 Create 函数的参数类似,就不介绍了。 CEdit 类的主要成员函数 使用编辑框最重要的莫过于,获取和设置编辑框中的正文,它们对应的成员函数分别 是 GetWindowText 和 SetWindowText,这两个函数都是继承自 CWnd 类的成员函数,另 外,还可以使用 CWnd 类的 GetWindowTextLength 函数获取编辑框中正文的长度。 下面简单介绍 CEdit 类的其他几个主要的成员函数: int LineFromChar(int nIndex = –1) const; 返回多行编辑框中指定索引的字符所在行的行号(从零开始),只适用于多行编辑框 。nIndex 等于-1 则返回所选择正文的第一个字符所在行的索引。如果没有选择正文,则返 回当前行的行号。 int LineIndex(int nLine = –1) const; 返回由 nLine 指定行的起始字符在编辑框的整个字符串中的索引,只适用于多行编辑 框。如果指定行超过编辑框的最大行数,则返回-1,而如果 nLine 为-1,则返回当前插入 符所在行的起始字符的索引。 void GetSel(int& nStartChar,int& nEndChar) const; 获取选择正文的索引范围。nStartChar 返回被选择正文的起始索引,nEndChar 返回 被选择正文的终止索引(不包括在选择范围内)。如果没有选择正文,则两者均为当前插 入符的索引。 void SetSel(int nStartChar,int nEndChar,BOOL bNoScroll=FALSE); 选择编辑框中的正文。nStartChar 为选择开始处的索引,nEndChar 为选择结束处的 索引。如果 nStartChar 为 0 并且 nEndChar 为-1,则选择所有正文,而如果 nStartChar 为-1 则取消所有选择。bNoScroll 为 FALSE 时滚动插入符并使之可见,为 TRUE 时不滚 动。 void ReplaceSel(LPCTSTR lpszNewText,BOOL bCanUndo = FALSE); 用 lpszNewText 指向的字符串来替换选择的正文。如果 bCanUndo 为 TRUE 则替换 可以被撤销。 int GetLineCount() const; 获取正文的行数,只适用于多行编辑框。如果编辑框没有正文则返回 1。 int LineLength( int nLine = –1 ) const; 获取指定字符索引所在行的字节长度(行尾的回车和换行符不计算在内),参数 nLin e 说明了为字符索引。如果 nLine 的值为-1,则函数返回当前行的长度(假如没有正文被 选择),或选择正文占据的行的字符总数减去选择正文的字符数(假如有正文被选择)。 若用于单行编辑框,则函数返回整个正文的长度。 int GetLine( int nIndex, LPTSTR lpszBuffer ) const; int GetLine( int nIndex, LPTSTR lpszBuffer, int nMaxLength ) const; 用来获得指定行的正文(不包括行尾的回车和换行符),只适用于多行编辑框。参数 nIndex 是行号,lpszBuffer 指向存放正文的缓冲区,nMaxLength 规定了拷贝的最大字节 数。若指定的行号小于编辑框的实际行数,函数返回实际拷贝的字节数,若指定的行号大 于编辑框的实际行数,则函数返回 0。需要注意的是,GetLine 函数不会在缓冲区中字符 串的末尾添加字符串结束符(NULL)。 UINT GetLimitText( ) const; 获取编辑框能够接受的正文的最大字节数。 void LimitText(int nChars = 0); 设置用户在编辑框中可以输入的正文的最大长度(字节数)。如果 nChars 为 0,则最 大长度为 UINT_MAX 个字节。 CEdit 类应用实例 下面鸡啄米为大家写一个简单的例子,来说明 CEdit 类的几个成员函数的使用方法。 此例的功能是,首先在编辑框中显示一行正文,然后替换其中部分字符为另一个含有回车 符的字符串,最终显示为两行正文。下面是简单的步骤介绍: 1.创建基于对话框的 MFC 程序,名称为“Example21”。 2.在自动生成的对话框模板 IDD_EXAMPLE21_DIALOG 中,删除静态文本框“TODO: Place dialog controls here.”,添加一个编辑框,ID 设为 IDC_MULTI_LINE_EDIT,属性 Multiline 设置为 true。 3.为编辑框 IDC_MULTI_LINE_EDIT添加 CEdit 类型的控件变量m_editMultiLine。 4.修改 CExample21Dlg::OnInitDialog()函数为: C++代码 1. BOOL CExample21Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. m_editMultiLine.SetWindowText(_T("鸡啄米博客/software")); / / 设置编辑框正文为“鸡啄米博客.com” 32. m_editMultiLine.SetSel(3, 5); // 选择起始索引为 3,终止索引为 5(不包括在选择范围内)的正文,即“博客” 33. m_editMultiLine.ReplaceSel(_T("\r\nwww.jizhuomi.com")); // 将选择的“博客”替换为“\r\nwww.jizhuomi.com” 34. 35. return TRUE; // return TRUE unless you set the focus to a control 36. } 5.编译运行程序,结果对话框如下: 关于编辑框的介绍就到这里了。CEdit 类成员函数的更详细的讲解可以查阅 MSDN。 鸡啄米谢谢您的持续关注。 VS2010/MFC 编程入门之二十二(常用控件:按钮控件 Button、Radio Button 和 Check Box) 因为私人问题,鸡啄米暂停更新了几天,首先向关注鸡啄米动态的朋友说一声抱歉。 言归正传,鸡啄米上一节中讲了编辑框的用法,本节继续讲解常用控件--按钮控件的 使用。 按钮控件简介 按钮控件包括命令按钮(Button)、单选按钮(Radio Button)和复选框(Check Box )等。命令按钮就是我们前面多次提到的狭义的按钮控件,用来响应用户的鼠标单击操作 ,进行相应的处理,它可以显示文本也可以嵌入位图。单选按钮使用时,一般是多个组成 一组,组中每个单选按钮的选中状态具有互斥关系,即同组的单选按钮只能有一个被选中 。 命令按钮是我们最熟悉也是最常用的一种按钮控件,而单选按钮和复选框都是一种比 较特殊的按钮控件。单选按钮有选中和未选中两种状态,为选中状态时单选按钮中心会出 现一个蓝点,以标识选中状态。一般的复选框也是有选中和未选中两种状态,选中时复选 框内会增加一个“√”,而三态复选框(设置了 BS_3STATE 风格)有选中、未选中和不确定 三种状态,不确定状态时复选框内出现一个灰色“√”。 按钮控件会向父窗口发送通知消息,最常用的通知消息莫过于 BN_CLICKED 和 BN_ DOUBLECLICKED 了。用户在按钮上单击鼠标时会向父窗口发送 BN_CLICKED 消息,双 击鼠标时发送 BN_DOUBLECLICKED 消息。 按钮控件的创建 MFC提供了 CButton 类封装按钮控件的所有操作。 之前的教程中,我们是在对话框模板上直接添加的按钮控件资源,但某些特殊情况下 需要我们动态创建按钮控件,即通过 CButton 类的成员函数 Create 来创建按钮。下面是 Create 函数的原型: virtual BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 参数说明: lpszCaption:指定按钮控件显示的文本。 dwStyle:指定按钮控件的风格,可以设置为以下按钮风格的任意组合。 BS_AUTOCHECKBOX :同 BS_CHECKBOX,不过单击鼠标时按钮会自动反转 BS_AUTORADIOBUTTON: 同 BS_RADIOBUTTON,不过单击鼠标时按钮会自动 反转 BS_AUTO3STATE :同 BS_3STATE,不过单击按钮时会改变状态 BS_CHECKBOX:指定在矩形按钮右侧带有标题的选择框 BS_DEFPUSHBUTTON:指定默认的命令按钮,这种按钮的周围有一个黑框,用户 可以按回车键来快速选择该按钮 BS_GROUPBOX:指定一个组框 BS_LEFTTEXT:使控件的标题显示在按钮的左边 BS_OWNERDRAW:指定一个自绘式按钮 BS_PUSHBUTTON:指定一个命令按钮 BS_RADIOBUTTON:指定一个单选按钮,在圆按钮的右边显示正文 BS_3STATE:同 BS_CHECKBOX,不过控件有 3 种状态—选择、未选择和变灰 当然,除了以上列出的风格,一般还会为按钮设置 WS_CHILD、WS_VISIBLE 和 WS _TABSTOP 等风格,WS_TABSTOP 风格使按钮控件具有 tab 停止属性,即按 tab 键切换 焦点控件时能够将焦点停在按钮控件上。创建一组单选按钮时,第一个按钮的风格应设置 为 WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_GROUP|BS_AUTORADIOBUTTON, 其他单选按钮的风格应为 WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,不包含 WS_TABSTOP 和 WS_GROUP。 在对话框模板上直接添加按钮控件时,它的属性中包含了上述风格,例如,复选框的 Tri_state 属性实际上代表的就是 BS_3STATE 风格。 剩下的三个参数与静态文本框的 Create 函数中的相应参数类似,大家可以参考前面静 态文本框的讲解,也可以查阅MSDN。 CButton 类的主要成员函数 下面是 CButton 类的一些主要的成员函数,至于其他的函数大家可以在 MSDN 中查看 。 HBITMAP SetBitmap(HBITMAP hBitmap); 设置要在按钮中显示的位图。参数 hBitmap 为位图的句柄。返回值为按钮原来位图的 句柄。 HBITMAP GetBitmap( ) const; 获取之前由 SetBitmap 函数设置的按钮位图的句柄。 void SetButtonStyle(UINT nStyle,BOOL bRedraw = TRUE); 设置按钮的风格。参数 nStyle 指定按钮的风格,bRedraw 指定按钮是否重绘,为 TR UE 则重绘,否则不重绘,默认为重绘。 UINT GetButtonStyle( ) const; 获取按钮控件的风格。 void SetCheck(int nCheck); 设置按钮的选择状态。参数 nCheck 为 0 表示未选中状态,1 表示选中状态,2 表示不 确定状态(仅用于复选框)。 int GetCheck( ) const; 获取按钮的选择状态。返回值的意义同 SetCheck 函数的 nCheck 参数。 HCURSOR SetCursor(HCURSOR hCursor); 设置要显示到按钮上的光标图。参数 hCursor 指定了光标的句柄。返回值为按钮原来 光标的句柄。 HCURSOR GetCursor( ); 获取之前由 SetCursor 设置的光标的句柄。 HICON SetIcon(HICON hIcon); 设置要在按钮上显示的图标。参数 hIcon 指定了图标的句柄。返回值为按钮原来图标 的句柄。 HICON GetIcon( ) const; 获取之前由 SetIcon 设置的图标的句柄。 void SetState(BOOL bHighlight); 设置按钮的高亮状态。参数 bHighlight 指定按钮是否高亮显示,非 0 则高亮显示,否 则取消高亮显示状态。 UINT GetState( ) const; 获取按钮控件的选择状态、高亮状态和焦点状态。我们可以通过将返回值与各个掩码 相与来获得各种状态值,掩码与对应的相与结果说明如下: 掩码 0x0003:用来获取单选按钮或复选框的状态。相与结果为 0 表示未选中,1 表示 被选中,2 表示不确定状态(仅用于复选框)。 掩码 0x0004:用来判断按钮是否是高亮显示。相与结果为非 0 值表示按钮是高亮显示 的。当单击按钮并按住鼠标左键时,按钮会呈高亮显示。 掩码 0x0008:相与结果为非零值表示按钮拥有输入焦点。 下面再列出几个继承自 CWnd 类的成员函数,通过它们获取或设置按钮控件的状态非 常方便,只需要知道按钮的 ID。 void CheckDlgButton(int nIDButton,UINT nCheck); 用来设置按钮的选择状态。参数 nIDButton 指定了按钮的 ID。nCheck 的值为 0 表示 按钮未被选择,为 1 表示按钮被选择,为 2 表示按钮处于不确定状态(仅用于复选框)。 UINT IsDlgButtonChecked(int nIDButton) const; 返回复选框或单选按钮的选择状态。返回值为 0 表示按钮未被选择,为 1 表示按钮被 选择,为 2 表示按钮处于不确定状态(仅用于复选框)。 void CheckRadioButton(int nIDFirstButton,int nIDLastButton,int nIDCheckButton); 用来选择组中的一个单选按钮。参数 nIDFirstButton 指定了组中第一个按钮的 ID,nI DLastButton 指定了组中最后一个按钮的 ID,nIDCheckButton 指定了要选择的按钮的 ID 。 int GetCheckedRadioButton(int nIDFirstButton, int nIDLastButton); 用来获得一组单选按钮中被选中按钮的 ID。参数 nIDFirstButton 说明了组中第一个按 钮的 ID,nIDLastButton 说明了组中最后一个按钮的 ID。 另外,CWnd 类的成员函数 GetWindowText()、SetWindowText()等也可以用来获取 或设置按钮中显示的文本。 关于按钮控件 Button、Radio Button 和 Check Box 的使用基础就介绍到此,下一节中 鸡啄米将举实例为大家演示各种按钮控件的使用方法,希望大家能继续关注。 VS2010/MFC 编程入门之二十三(常用控件:按钮控件的编程实 例) 上一节 VS2010/MFC编程入门教程中鸡啄米讲了按钮控件 Button、Radio Button 和 C heck Box的基本用法,本节就继续讲按钮控件的内容,通过一个实例让大家更清楚按钮控 件在实际的软件开发中如何使用。 因为 Button 控件在前面的例子中涉及到了,比较简单,本文就不作深入分析了,而是 重点讲解单选按钮 Radio Button、复选框 Check Box 的使用。 按钮控件实例的功能 首先介绍此实例实现的功能。此实例用来根据网站类型选择网站,并将选择的网站的 名称显示到编辑框中。网站类型有“门户”、“论坛”和“博客”三种,为单选按钮。网站有六个 :鸡啄米、新浪、天涯论坛、韩寒博客、网易和凤凰网论坛,均为复选框。 当选中某种网站类型即点了某个单选按钮时,其对应的网站的复选框就激活,其他则 禁用,不允许选择,且为非选中状态。例如,如果选中了“门户”单选按钮,则“新浪”、“网 易”复选框激活,允许用户选择,而其他复选框则禁用。 按钮控件实例的实现 鸡啄米下面为大家详细阐述此实例的编写步骤。 1. 创建一个基于对话框的 MFC 工程,名称设为“Example23”。 2. 在自动生成的主对话框 IDD_EXAMPLE23_DIALOG 的模板中,删除“TODO: Place dialog controls here.”静态文本框,添加两个 Group Box,属性 Caption 分别改为“网站类 型”、“网站”。 3. 在 Group Box“网站类型”中加入三个 Radio Button,Caption 分别设为“门户”、“论坛 ”和“博客”,ID 分别设为 IDC_PORTAL_RADIO、IDC_FORUM_RADIO 和 IDC_BLOG_R ADIO。 4. 在 Group Box“网站”中加入六个 Check Box,Caption 分别设为“鸡啄米”、“新浪”、“ 天涯论坛”、“韩寒博客”、“网易”和“凤凰网论坛”,ID 分别设为 IDC_CHECK1、IDC_CHEC K2、IDC_CHECK3、IDC_CHECK4、IDC_CHECK5 和 IDC_CHECK6。然后为每个复选 框添加 CButton 类型的变量 m_check1、m_check2、m_check3、m_check4、m_check5 和 m_check6。 5. 在两个 Group Box 下面,添加一个静态文本框和一个编辑框。静态文本框的 Captio n 设为“选择的网站:”。编辑框的 ID 设为 IDC_WEBSITE_SEL_EDIT,属性 Read Only 改 为 True,使此编辑框为只读状态,不允许用户编辑。 6. 将“OK”按钮的 Caption 修改为“确定”,“Cancel”按钮的 Caption 修改为“退出”。到此 ,对话框模板就修改好了,如下图: 7. 为“门户”、“论坛”和“博客”三个单选按钮分别添加点击消息的消息处理函数CExampl e23Dlg::OnBnClickedPortalRadio()、CExample23Dlg::OnBnClickedForumRadio()和 CEx ample23Dlg::OnBnClickedBlogRadio()。 在某个单选按钮被点击之后,我们可以先将六个网站复选框都禁用且置为非选中状态 ,而后将选择的网站类型对应的网站复选框激活。为了代码复用,我们将置所有复选框为 禁用且非选中状态的操作写到一个函数里,此函数为 CExample23Dlg::InitAllCheckBoxSta tus(),然后就可以在三个单选按钮的消息处理函数中调用 InitAllCheckBoxStatus(),实现 复选框状态的初始化。 三个消息处理函数及 InitAllCheckBoxStatus()函数的实现如下: C++代码 1. void CExample23Dlg::OnBnClickedPortalRadio() 2. { 3. // TODO: Add your control notification handler code here 4. // 如果选择了“门户”单选按钮,则激活复选框“新浪”和“网易”,其他复选框禁 用并非选中 5. InitAllCheckBoxStatus(); 6. m_check2.EnableWindow(TRUE); 7. m_check5.EnableWindow(TRUE); 8. } 9. 10. 11. void CExample23Dlg::OnBnClickedForumRadio() 12. { 13. // TODO: Add your control notification handler code here 14. // 如果选择了“论坛”单选按钮,则激活复选框“天涯论坛”和“凤凰网论坛”,其 他复选框禁用并非选中 15. InitAllCheckBoxStatus(); 16. m_check3.EnableWindow(TRUE); 17. m_check6.EnableWindow(TRUE); 18. } 19. 20. 21. void CExample23Dlg::OnBnClickedBlogRadio() 22. { 23. // TODO: Add your control notification handler code here 24. // 如果选择了“博客”单选按钮,则激活复选框“鸡啄米”和“韩寒博客”,其他复 选框禁用并非选中 25. InitAllCheckBoxStatus(); 26. m_check1.EnableWindow(TRUE); 27. m_check4.EnableWindow(TRUE); 28. } 29. 30. // 初始化所有复选框的状态,即全部禁用,全部非选中 31. void CExample23Dlg::InitAllCheckBoxStatus() 32. { 33. // 全部禁用 34. m_check1.EnableWindow(FALSE); 35. m_check2.EnableWindow(FALSE); 36. m_check3.EnableWindow(FALSE); 37. m_check4.EnableWindow(FALSE); 38. m_check5.EnableWindow(FALSE); 39. m_check6.EnableWindow(FALSE); 40. 41. // 全部非选中 42. m_check1.SetCheck(0); 43. m_check2.SetCheck(0); 44. m_check3.SetCheck(0); 45. m_check4.SetCheck(0); 46. m_check5.SetCheck(0); 47. m_check6.SetCheck(0); 48. } 8. 程序运行后,我们希望网站类型默认选择为“门户”,则修改对话框初始化函数 CExa mple23Dlg::OnInitDialog()为: C++代码 1. BOOL CExample23Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. // 默认选中“门户”单选按钮 32. CheckDlgButton(IDC_PORTAL_RADIO, 1); 33. OnBnClickedPortalRadio(); 34. 35. return TRUE; // return TRUE unless you set the focus to a control 36. } 9. 点击“确定”后,将选择的网站名字显示到编辑框中,那么需要修改“确定”按钮(原来 的 OK 按钮)的消息处理函数 CExample23Dlg::OnBnClickedOk()如下: C++代码 1. void CExample23Dlg::OnBnClickedOk() 2. { 3. // TODO: Add your control notification handler code here 4. CString strWebsiteSel; // 选择的网站 5. 6. // 若选中“鸡啄米”则将其加入结果字符串 7. if (1 == m_check1.GetCheck()) 8. { 9. strWebsiteSel += _T("鸡啄米 "); 10. } 11. // 若选中“新浪”则将其加入结果字符串 12. if (1 == m_check2.GetCheck()) 13. { 14. strWebsiteSel += _T("新浪 "); 15. } 16. // 若选中“天涯论坛”则将其加入结果字符串 17. if (1 == m_check3.GetCheck()) 18. { 19. strWebsiteSel += _T("天涯论坛 "); 20. } 21. // 若选中“韩寒博客”则将其加入结果字符串 22. if (1 == m_check4.GetCheck()) 23. { 24. strWebsiteSel += _T("韩寒博客 "); 25. } 26. // 若选中“网易”则将其加入结果字符串 27. if (1 == m_check5.GetCheck()) 28. { 29. strWebsiteSel += _T("网易 "); 30. } 31. // 若选中“凤凰网论坛”则将其加入结果字符串 32. if (1 == m_check6.GetCheck()) 33. { 34. strWebsiteSel += _T("凤凰网论坛 "); 35. } 36. 37. // 将结果字符串显示于“选择的网站”后的编辑框中 38. SetDlgItemText(IDC_WEBSITE_SEL_EDIT, strWebsiteSel); 39. 40. // 为了避免点“确定”后对话框退出,将 OnOk 注掉 41. //CDialogEx::OnOK(); 42. } 10. 到此程序编写完成。运行程序弹出结果对话框,选择网站后界面如下图: 按钮控件的内容就这些了。掌握了按钮控件的基本用法,又动手编写了这个实例后, 相信大家对按钮控件已经很熟悉了。 VS2010/MFC 编程入门之二十四(常用控件:列表框控件 ListBox) 前面两节讲了比较常用的按钮控件,并通过按钮控件实例说明了具体用法。本文要讲 的是列表框控件(ListBox)及其使用实例。 列表框控件简介 列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选中的项会高亮 显示。列表框可分为单选列表框和多选列表框,顾名思义,单选列表框中一次只能选择一 个列表项,而多选列表框可以同时选择多个列表项。 列表框也会向父窗口发送通知消息。这些通知消息及含义如下: LBN_DBLCLK :用户用鼠标双击了一列表项,只有具有 LBS_NOTIFY 的列表框才能 发送该消息 LBN_ERRSPACE :列表框不能申请足够的动态内存来满足需要 LBN_KILLFOCUS :列表框失去输入焦点 LBN_SELCANCEL: 当前的选择被取消,只有具有 LBS_NOTIFY 的列表框才能发送 该消息 LBN_SELCHANGE:单击鼠标选择了一列表项,只有具有 LBS_NOTIFY 的列表框才 能发送该消息 LBN_SETFOCUS:列表框获得输入焦点 WM_CHARTOITEM:当列表框收到 WM_CHAR 消息后, 向父窗口发送该消息, 只 有具有 LBS_WANTKEYBOARDINPUT 风格的列表框才会发送该消息 WM_VKEYTOITEM:当列表框收到 WM_KEYDOWN 消息后,向父窗口发送该消息 ,只有具有 LBS_WANTKEYBOARDINPUT 风格的列表框才会发送该消息 列表框控件的创建 MFC将列表框控件的所有操作都封装到了 CListBox 类中。 创建列表框控件时,可以在对话框模板中直接拖入列表框控件 Listbox,然后添加控 件变量使用。但如果需要动态创建列表框,就要用到 CListBox 类的 Create 成员函数了。 Create 成员函数的原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 参数 rect 指定了列表框的位置和尺寸,pParentWnd 为父窗口的指针,nID 用于指定 列表框控件的 ID。最后重点讲讲参数 dwStyle,它指定了列表框控件的风格,以下是各种 风格说明: LBS_EXTENDEDSEL:支持多重选择,在点击列表项时按住 Shift 键或 Ctrl 键即可选 择多个项 LBS_HASSTRINGS:指定一个含有字符串的自绘式列表框 LBS_MULTICOLUMN:指定一个水平滚动的多列列表框, 通过调用 CListBox::SetC olumnWidth 来设置每列的宽度 LBS_MULTIPLESEL:支持多重选择。列表项的选择状态随着用户对该项单击或双击 鼠标而翻转 LBS_NOINTEGRALHEIGHT:列表框的尺寸由应用程序而不是 Windows 指定。通常 ,Windows 指定尺寸会使列表项的某些部分隐藏起来 LBS_NOREDRAW:当选择发生变化时防止列表框被更新,可发送消息改变该风格 LBS_NOTIFY:当用户单击或双击鼠标时通知父窗口 LBS_OWNERDRAWFIXED:指定自绘式列表框,即由父窗口负责绘制列表框的内容 ,并且列表项有相同的高度 LBS_OWNERDRAWVARIABLE:指定自绘式列表框,并且列表项有不同的高度 LBS_SORT:使插入列表框中的项按升序排列 LBS_STANDARD:相当于指定了 WS_BORDER|WS_VSCROLL|LBS_SORT LBS_USETABSTOPS:使列表框在显示列表项时识别并扩展制表符(‘\t’),默认的制表 宽度是 32 个对话框单位 LBS_WANTKEYBOARDINPUT:允许列表框的父窗口接收 WM_VKEYTOITEM 和 W M_CHARTOITEM 消息,以响应键盘输入 LBS_DISABLENOSCROLL:使列表框在不需要滚动时显示一个禁止的垂直滚动条 dwStyle 可以是以上所列风格的组合。与其他控件一样,除了这些风格一般还要为列 表框控件设置 WS_CHILD、WS_VISIBLE、WS_TABSTOP、WS_BORDER、WS_VSC ROLL 等风格。一般创建单选列表框时,风格要设置为:WS_CHILD|WS_VISIBLE|WS_T ABSTOP|LBS_STANDARD,如果不希望列表框项排序显示则应去掉 LBS_STANDARD。 创建多选列表框时,只需要在单选列表框风格后添加 LBS_MULTIPLESEL 或 LBS_EXTE NDEDSEL 风格。 对于对话框模板中直接添加的列表框控件,其属性页中的属性包含了以上风格,例如 属性 Multicolumn 对应的就是 LBS_MULTICOLUMN 风格。 CListBox 类的主要成员函数 int GetCount( ) const; 返回值:返回列表框中列表项的数目,如果发生错误则返回 LB_ERR。 int GetSel(int nIndex) const; 参数:nIndex 指定某个列表项的索引。 返回值:返回 nIndex 指定列表项的状态。如果此列表项被选择了则返回一个正值,否 则返回 0,若发生错误则返回 LB_ERR。 int SetSel(int nIndex,BOOL bSelect = TRUE); 此函数只用于多选列表框,使用它可以选择或取消选择指定的列表项。 参数:nIndex 指定某个列表项的索引,若为-1 则相当于指定了所有列表项。bSelect 为 TRUE 时选择指定列表项,否则取消选择指定列表项。 返回值:如果发生错误则返回 LB_ERR。 int AddString(LPCTSTR lpszItem); 此函数用来向列表框中添加字符串。如果列表框指定了 LBS_SORT 风格,字符串就 被以排序顺序插入到列表框中,如果没有指定 LBS_SORT 风格,字符串就被添加到列表 框的结尾。 参数:lpszItem 指定了要添加的字符串。 返回值:返回字符串在列表框中添加的位置。如果发生错误则返回 LB_ERR,内存不够 则返回 LB_ERRSPACE。 int InsertString(int nIndex, LPCTSTR lpszItem); 该函数用来在列表框中的指定位置插入字符串。与 AddString 函数不同的是,InsertSt ring 函数不会导致 LBS_SORT 风格的列表框重新排序。不要在具有 LBS_SORT 风格的列 表框中使用 InsertString 函数,以免破坏列表项的次序。 参数:。参数 nIndex 给出了插入位置(索引),如果值为-1,则字符串将被添加到列 表的末尾。参数 lpszItem 指定了要插入的字符串。 返回值:返回实际的插入位置,若发生错误,会返回 LB_ERR 或 LB_ERRSPACE。 int DeleteString(UINT nIndex); 该函数用于删除指定的列表项。 参数:nIndex 指定了要删除项的索引。 返回值:函数的返回值为剩下的列表项数目,如果 nIndex 超过了实际的表项总数,则 返回 LB_ERR。 void ResetContent(); 该函数用于清除所有列表项。 int GetText(int nIndex,LPTSTR lpszBuffer) const; void GetText(int nIndex,CString& rString) const; 这两个成员函数用于获取指定列表项的字符串。参数 nIndex 指定了列表项的索引。参 数 lpszBuffer 指向一个接收字符串的缓冲区。引用参数 rString 则指定了接收字符串的 CSt ring 对象。第一个版本的函数会返回获得的字符串的长度,若出错,则返回 LB_ERR;第 二个版本的函数则不会。 int GetTextLen(int nIndex) const; 该函数返回指定列表项的字符串的字节长度。 参数:nIndex 指定了列表项的索引。 返回值:若出错则返回 LB_ERR。 int GetCurSel() const; 该函数仅适用于单选列表框,用来返回当前被选择项的索引,如果没有列表项被选择 或有错误发生,则函数返回 LB_ERR。 int SetCurSel(int nSelect); 该函数仅适用于单选列表框,用来选择指定的列表项。该函数会滚动列表框以使选择 项可见。参数 nIndex 指定了列表项的索引,若为-1,那么将清除列表框中的选择。若出错 函数返回 LB_ERR。 int GetSelCount() const; 该函数仅用于多重选择列表框,它返回选择项的数目,若出错函数返回 LB_ERR。 int FindString(int nStartAfter,LPCTSTR lpszItem) const; 该函数用于对列表项进行与大小写无关的搜索。参数 nStartAfter 指定了开始搜索的位 置,合理指定 nStartAfter 可以加快搜索速度,若 nStartAfter 为-1,则从头开始搜索整个列 表。参数 lpszItem 指定了要搜索的字符串。函数返回与 lpszItem 指定的字符串相匹配的列 表项的索引,若没有找到匹配项或发生了错误,则会返回 LB_ERR。FindString 函数先从 nStartAfter 指定的位置开始搜索,若没有找到匹配项,则会从头开始搜索列表。只有找到 匹配项,或对整个列表搜索完一遍后,搜索过程才会停止,所以不必担心会漏掉要搜索的 列表项。 int SelectString(int nStartAfter,LPCTSTR lpszItem); 该函数仅适用于单选列表框,用来选择与指定字符串相匹配的列表项。该函数会滚动 列表框以使选择项可见。参数的意义及搜索的方法与函数 FindString 类似。如果找到了匹 配的项,函数返回该项的索引,如果没有匹配的项,函数返回 LB_ERR 并且当前的选择不 被改变。 CListBox 类应用实例 最后鸡啄米给大家写一个简单的实例,说明 CListBox 的几个成员函数及通知消息等 的使用方法。此实例实现的功能:在单选列表框中显示一个网站列表,然后在用鼠标左键 选择某列表项时,将选中列表项的文本显示到编辑框中。下面是具体实现步骤: 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example24”。 2. 在自动生成的对话框模板 IDD_EXAMPLE24_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本控件、“OK”按钮和“Cancel”按钮。添加一个 Listbox 控件,ID 设置为 IDC_WEB_LIST,Sort 属性设为 False,以取消排序显示。再添加一个静态文本控 件和一个编辑框,静态文本控件的 Caption 属性设为“您选择的站点:”,编辑框的 ID 设为 IDC_SEL_WEB_EDIT,Read Only 属性设为 True。此时的对话框模板如下图: 3. 为列表框 IDC_WEB_LIST添加 CListBox 类型的控件变量m_listBox。 4. 在对话框初始化时,我们将站点名加入到列表框中,那么需要修改 CExample24Dlg:: OnInitDialog()函数为: C++代码 1. BOOL CExample24Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. m_listBox.AddString(_T("新浪")); // 在列表框结尾添加字符 串“新浪” 32. m_listBox.AddString(_T("鸡啄米")); // 在列表框结尾添加字符 串“鸡啄米” 33. m_listBox.AddString(_T("猫扑")); // 在列表框结尾添加字符 串“猫扑” 34. m_listBox.InsertString(2, _T("百度")); // 在列表框中索引为 2 的位置插入字符串“百度” 35. 36. return TRUE; // return TRUE unless you set the focus to a control 37. } 5. 我们希望在选中列表项改变时,将最新的选择项实时显示到编辑框中,那么这就要 用到 LBN_SELCHANGE 通知消息。为列表框 IDC_WEB_LIST 的通知消息 LBN_SELCH ANGE添加消息处理函数CExample24Dlg::OnLbnSelchangeWebList(),并修改如下: C++代码 1. void CExample24Dlg::OnLbnSelchangeWebList() 2. { 3. // TODO: Add your control notification handler code here 4. CString strText; 5. int nCurSel; 6. 7. nCurSel = m_listBox.GetCurSel(); // 获取当前选中列表 项 8. m_listBox.GetText(nCurSel, strText); // 获取选中列表项的 字符串 9. SetDlgItemText(IDC_SEL_WEB_EDIT, strText); // 将选中列表项的字 符串显示到编辑框中 10. } 6. 运行程序,弹出结果对话框,在对话框的列表框中用鼠标改变选中项时,编辑框中 的显示会相应改变。效果图如下: 关于列表框 ListBox 的讲解就到此为止了。大家如果想试验更多的列表框成员函数, 可以在上面的小例子中加入更多的功能来体会 VS2010/MFC 编程入门之二十五(常用控件:组合框控件 Combo Box) 上一节鸡啄米讲了列表框控件 ListBox的使用,本节主要讲解组合框控件 Combo Box 。组合框同样相当常见,例如,在 Windows 系统的控制面板上设置语言或位置时,有很 多选项,用来进行选择的控件就是组合框控件。它为我们的日常操作提供了很多方便。 组合框控件简介 组合框其实就是把一个编辑框和一个列表框组合到了一起,分为三种:简易(Simple )组合框、下拉式(Dropdown)组合框和下拉列表式(Drop List)组合框。下面讲讲它 们的区别。 简易组合框中的列表框是一直显示的,效果如下图: 下拉式组合框默认不显示列表框,只有在点击了编辑框右侧的下拉箭头才会弹出列表 框,列表框弹出后如下图: 下拉列表式组合框的编辑框是不能编辑的,只能由用户在下拉列表框中选择了某项后 ,在编辑框中显示其文本。下拉列表式组合框如下图: 经过上面的介绍,大家应该知道,最常用的当属下拉式组合框和下拉列表式组合框了 ,它们在很多时候能使程序看起来更专业,更简洁,让用户在进行选择操作时更方便。 组合框被操作时会向父窗口发送通知消息,这些通知消息及其含义如下: CBN_CLOSEUP:组合框的列表框组件被关闭,简易组合框不会发送该通知消息 CBN_DBLCLK:用户在某列表项上双击鼠标,只有简易组合框才会发送该通知消息 CBN_DROPDOWN:组合框的列表框组件下拉,简易式组合框不会发送该通知消息 CBN_EDITUPDATE:在编辑框准备显示改变了的正文时发送该消息,下拉列表式组 合框不会发送该消息 CBN_EDITCHANGE:编辑框的内容被用户改变了,与 CBN_EDITUPDATE 不同, 该消息是在编辑框显示的正文被刷新后才发出的,下拉列表式组合框不会发送该消息 CBN_ERRSPACE:组合框无法申请足够的内存来容纳列表项 CBN_SELENDCANCEL:表明用户的选择应该取消,当用户在列表框中选择了一项 ,然后又在组合框控件外单击鼠标时就会导致该消息的发送 CBN_SELENDOK:用户选择了一项,然后按了回车键或单击了下滚箭头,该消息表 明用户确认了自己所作的选择 CBN_KILLFOCUS:组合框失去了输入焦点 CBN_SELCHANGE:用户通过单击或移动箭头键改变了列表的选择 CBN_SETFOCUS:组合框获得了输入焦点 组合框控件的创建 MFC将组合框控件的所有操作都封装到了 CComboBox 类中。 我们在对话框中加入组合框时,可以往对话框模板中拖入 Combo Box 控件,而后添 加 CComboBox 类型的控件变量使用,但如果我们想在程序中动态创建的话,就要使用 C ComboBox 类的成员函数 Create 了。Create 函数的原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 大家可以看出,CComboBox 类的 Create 成员函数同前面几个控件类的 Create 成员 函数非常类似,dwStyle 指定组合框控件的风格,rect 为列表框弹出后组合框的位置和尺 寸,pParentWnd 是指向父窗口的指针,不能为 NULL,nID 指定组合框控件的 ID。最后 还是重点讲讲 dwStyle 参数。组合框控件的风格包括以下几种,并给出了相应说明: CBS_AUTOHSCROLL:使编辑框组件具有水平滚动的风格 CBS_DISABLENOSCROLL:使列表框在不需要滚动时显示一个禁止的垂直滚动条 CBS_DROPDOWN:指定一个下拉式组合框 CBS_DROPDOWNLIST:指定一个下拉列表式组合框 CBS_HASSTRINGS:指定一个含有字符串的自绘式组合框 CBS_LOWERCASE:将编辑框和列表框中的所有文本都自动转换为小写字符 CBS_NOINTEGRALHEIGHT:组合框的尺寸由应用程序而不是 Windows 指定,通常 ,由 Windows 指定尺寸会使列表项的某些部分隐藏起来 CBS_OEMCONVERT:使编辑框组件中的正文可以在 ANSI 字符集和 OEM 字符集之 间相互转换。这在编辑框中包含文件名时是很有用的 CBS_OWNERDRAWFIXED:指定自绘式组合框,即由父窗口负责绘制列表框的内容 ,并且列表项有相同的高度 CBS_OWNERDRAWVARIABLE:指定自绘式组合框,并且列表项有不同的高度 CBS_SIIMPLE:指定一个简易组合框 CBS_SORT:自动对列表框组件中的项进行排序 CBS_UPPERCASE:将编辑框和列表框中的所有文本都自动转换为大写字符 dwStyle 参数可以是以上风格的组合。跟其他控件一样,创建时一般也还要指定 WS_ CHILD、WS_VISIBLE、WS_TABSTOP 和 WS_VSCROLL 等风格。 在对话框模板中直接添加组合框控件时,其属性页中的属性包含了以上风格,例如属 性 Uppercase 设为 True 就相当于指定了 CBS_UPPERCASE 风格。 CComboBox 类的主要成员函数 因为组合框是由编辑框和列表框组合而成的,所以组合框的操作和编辑框与列表框的 操作有很多相似之处,同样的,CComboBox 类的成员函数也和 CEdit 类与 CListBox 类的 成员函数有很多相似之处,不但功能相似,甚至函数名和参数也很相似。鸡啄米下面大概 讲解下 CComboBox 类的主要成员函数,更详细的内容可以参见 MSDN。 int GetCount( ) const; 获取组合框控件的列表框中列表项的数量。 int GetCurSel( ) const; 获取组合框控件的列表框中选中项的索引,如果没有选中任何项,该函数返回 CB_ER R。 int SetCurSel(int nSelect); 在组合框控件的列表框中选择某项。nSelect 参数指定了要选择的列表项的索引,如果 为-1 则列表框中当前选择项被取消选中,编辑框也被清空。 DWORD GetEditSel( ) const; 获取组合框控件的编辑框中当前选择范围的起始和终止字符的位置。该函数返回一个 32 位数,低 16 位存放起始位置,高 16 位存放选择范围后第一个非选择字符的位置。如 果该函数用于下拉列表式组合框时,会返回 CB_ERR。 BOOL SetEditSel(int nStartChar,int nEndChar); 用于在组合框控件的编辑框中选择字符。nStartChar 参数指定起始位置,nEndChar 参数指定终止位置。 DWORD_PTR GetItemData(int nIndex) const; 获取组合框中指定项所关联的 32 位数据。nIndex 参数指定组合框控件的列表框某项 的索引(从 0 开始)。 int SetItemData(int nIndex,DWORD_PTR dwItemData); 为某个指定的组合框列表项设置一个关联的 32 位数。nIndex 参数指定要进行设置的 列表项索引。dwItemData 参数指定要关联的新值。 void GetLBText(int nIndex,CString& rString) const; 从组合框控件的列表框中获取某项的字符串。nIndex 参数指定要获取字符串的列表项 的索引,CString 参数用于接收取到的字符串。 int GetLBTextLen(int nIndex) const; 获取组合框控件的列表框中某项的字符串长度。nIndex 参数指定要获取字符串长度的 列表项的索引。 int GetTopIndex( ) const; 获取组合框控件的列表框中第一个可见项的索引。 int SetTopIndex(int nIndex); 将组合框控件的列表框中某个指定项设置为可见的。nIndex 参数指定了该列表项的索 引。该函数成功则返回 0,有错误发生则返回 CB_ERR。 BOOL LimitText(int nMaxChars); 用于限制用户在组合框控件的编辑框中能够输入的最大字节长度。nMaxChars 参数指 定了用户能够输入文字的最大字节长度,如果为 0 则长度被限制为 65535 个字节。 int AddString(LPCTSTR lpszString); 为组合框控件中的列表框添加新的列表项。lpszString 参数是指向要添加的字符串的 指针。该函数的返回值如果大于等于 0,那么它就是新列表项的索引,而如果有错误发生 则会返回 CB_ERR,如果没有足够的内存存放新字符串则返回 CB_ERRSPACE。 int DeleteString(UINT nIndex); 删除组合框中某指定位置的列表项。nIndex 参数指定了要删除的列表项的索引。该函 数的返回值如果大于等于 0,那么它就是组合框中剩余列表项的数量。如果 nIndex 指定的 索引超出了列表项的数量则返回 CB_ERR。 int FindString(int nStartAfter,LPCTSTR lpszString) const; 在组合框控件的列表框中查找但不选中第一个包含指定前缀的列表项。nStartAfter 参 数指定了第一个要查找的列表项之前的那个列表项的索引。lpszString 指向包含要查找的 前缀的字符串。该函数的返回值如果大于等于 0,那么它是匹配列表项的索引,如果查找 失败则返回 CB_ERR。 int InsertString(int nIndex,LPCTSTR lpszString); 向组合框控件的列表框中插入一个列表项。nIndex 参数指定了要插入列表项的位置,l pszString 参数则指定了要插入的字符串。该函数返回字符串被插入的位置,如果有错误发 生则会返回 CB_ERR,如果没有足够的内存存放新字符串则返回 CB_ERRSPACE。 int SelectString(int nStartAfter,LPCTSTR lpszString); 在组合框控件的列表框中查找一个字符串,如果查找到则选中它,并将其显示到编辑 框中。参数同 FindString。如果字符串被查找到则返回此列表项的索引,如果查找失败则 返回 CB_ERR,并且当前选择项不改变。 此外,CComboBox 类还继承了 CWnd 类的成员函数 GetWindowText、SetWindowT ext 等。 CComboBox 类应用实例 最后鸡啄米给大家写一个简单的实例,说明 CComboBox 的几个成员函数及通知消息 等的使用方法。此实例实现的功能:在组合框中包含一个网站列表,切换组合框控件的列 表框中选择的列表项时,将新选中的列表项的文本显示到编辑框中。下面是具体实现步骤 : 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example25”。 2. 在自动生成的对话框模板 IDD_EXAMPLE25_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本控件、“OK”按钮和“Cancel”按钮。添加一个 Combo Box 控件, ID 设置为 IDC_WEB_COMBO,Type 属性设为 Drop List,为下拉列表式组合框,编辑框 不允许用户输入,Sort 属性设为 False,以取消排序显示。再添加一个静态文本控件和一 个编辑框,静态文本控件的 Caption 属性设为“您选择的网站:”,编辑框的 ID 设为 IDC_S EL_WEB_EDIT,Read Only 属性设为 True。此时的对话框模板如下图: 3. 为组合框 IDC_WEB_COMBO添加 CComboBox 类型的控件变量m_comboWeb。 4. 在对话框初始化时,我们将站点名加入到组合框中,并默认选择第一项,那么需要 修改 CExample25Dlg::OnInitDialog()函数为: C++代码 1. BOOL CExample25Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. // 为组合框控件的列表框添加列表项“鸡啄米” 32. m_comboWeb.AddString(_T("鸡啄米")); 33. // 为组合框控件的列表框添加列表项“百度” 34. m_comboWeb.AddString(_T("百度")); 35. // 在组合框控件的列表框中索引为 1 的位置插入列表项“新浪” 36. m_comboWeb.InsertString(1, _T("新浪")); 37. 38. // 默认选择第一项 39. m_comboWeb.SetCurSel(0); 40. // 编辑框中默认显示第一项的文字“鸡啄米” 41. SetDlgItemText(IDC_SEL_WEB_EDIT, _T("鸡啄米")); 42. 43. return TRUE; // return TRUE unless you set the focus to a control 44. } 5. 我们希望在组合框中选中的列表项改变时,将最新的选择项实时显示到编辑框中, 那么这就要用到 CBN_SELCHANGE 通知消息。为列表框 IDC_WEB_COMBO 的通知消 息 CBN_SELCHANGE 添加消息处理函数CExample25Dlg::OnCbnSelchangeWebCombo (),并修改如下: C++代码 1. void CExample25Dlg::OnCbnSelchangeWebCombo() 2. { 3. // TODO: Add your control notification handler code here 4. CString strWeb; 5. int nSel; 6. 7. // 获取组合框控件的列表框中选中项的索引 8. nSel = m_comboWeb.GetCurSel(); 9. // 根据选中项索引获取该项字符串 10. m_comboWeb.GetLBText(nSel, strWeb); 11. // 将组合框中选中的字符串显示到 IDC_SEL_WEB_EDIT 编辑框中 12. SetDlgItemText(IDC_SEL_WEB_EDIT, strWeb); 13. } 6. 运行程序,弹出结果对话框,在对话框的组合框中改变选择项时,编辑框中的显示 会相应改变。效果图如下: 组合框的内容就是这些了。相对于 CComboBox 类数量不少的成员函数来说,本节的 实例只是用到了很少的几个,大家可以根据上面所讲试试其他的成员函数 VS2010/MFC 编程入门之二十六(常用控件:滚动条控件 Scroll Bar) 回顾上一节,鸡啄米讲的是组合框控件 Combo Box的使用。本节详解滚动条控件 Scr oll Bar 的相关内容。 滚动条控件简介 滚动条大家也很熟悉了,Windows 窗口中很多都有滚动条。前面讲的列表框和组合框 设置了相应属性后,如果列表项显示不下也会出现滚动条。滚动条分为水平滚动条(Horiz ontal Scroll Bar)和垂直滚动条(Vertical Scroll Bar)两种。滚动条中有一个滚动块,用 于标识滚动条当前滚动的位置。我们可以拖动滚动块,也可以用鼠标点击滚动条某一位置 使滚动块移动。 从滚动条的创建形式来分,有标准滚动条和滚动条控件两种。像列表框和组合框设置 了 WS_HSCROLL 或 WS_VSCROLL 风格以后出现的滚动条,不是一个独立的窗口,而 是这些窗口的一部分,这就是标准滚动条。而滚动条控件是一个独立的窗口,它可以获得 焦点,响应某些操作。 滚动条控件的创建 MFC也为滚动条控件的操作提供了类,即为 CScrollBar 类。 滚动条控件的创建依然有两种方式,一种是直接在 Toolbox 中将滚动条控件拖入对话 框模板,然后添加控件变量使用,另一种就是用 CScrollBar 类的 Create 成员函数动态创 建。这两种方式适用于不同的场合。 CScrollBar 类的成员函数 Create 的函数原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 此函数与其他控件类的 Create 函数原型基本相同。参数 dwStyle 指定滚动条控件的风 格,rect 指定滚动条控件的位置和尺寸,pParentWnd 为指向滚动条控件父窗口的指针,n ID 指定滚动条控件的 ID。下面鸡啄米简单介绍几个主要的滚动条控件风格,更加具体的可 以查阅 MSDN。 SBS_HORZ:指定滚动条为水平滚动条。如果没有指定 SBS_BOTTOMALIGN 或 SB S_TOPALIGN 风格,则滚动条的高度、宽度和位置由 Create 函数的 rect 参数给出。 SBS_VERT:指定滚动条为垂直滚动条。如果没有指定 SBS_RIGHTALIGN 或 SBS_ LEFTALIGN 风格,则滚动条的高度、宽度和位置由 Create 函数的 rect 参数给出。 SBS_TOPALIGN:与 SBS_HORZ 配合使用。滚动条的上边缘与 Create 函数的 rect 参数指定矩形的上边缘对齐。滚动条高度为系统滚动条的默认高度。 SBS_BOTTOMALIGN:与 SBS_HORZ 配合使用。滚动条的下边缘与 Create 函数的 rect 参数指定矩形的下边缘对齐。滚动条高度为系统滚动条的默认高度。 SBS_LEFTALIGN:与 SBS_VERT 配合使用。滚动条的左边缘与 Create 函数的 rect 参数指定矩形的左边缘对齐。滚动条宽度为系统滚动条的默认宽度。 SBS_RIGHTALIGN:与 SBS_VERT 配合使用。滚动条的右边缘与 Create 函数的 rec t 参数指定矩形的右边缘对齐。滚动条宽度为系统滚动条的默认宽度。 dwStyle 参数可以是以上风格中某几个的组合,另外一般也会用到 WS_CHILD、WS_ VISIBLE 风格。例如,创建一个水平滚动条控件,dwStyle 参数应该为 WS_CHILD|WS_V ISIBLE|SBS_HORZ,创建垂直滚动条控件时 dwStyle 参数应该为 WS_CHILD|WS_VISIB LE|SBS_VERT。 CScrollBar 类的主要成员函数 BOOL GetScrollInfo(LPSCROLLINFO lpScrollInfo, UINT nMask = SIF_ALL); 获取的滚动条的参数信息,该信息为 SCROLLINFO 结构体的形式。参数 lpScrollInfo 为指向 SCROLLINFO 结构体变量的指针。SCROLLINFO 结构体的定义如下: C++代码 1. typedef struct tagSCROLLINFO { 2. UINT cbSize; // 结构的尺寸(字节为单位) 3. UINT fMask; // 说明结构中的哪些参数是有效的,可以是屏蔽值 的组合,如 SIF_POS|SIF_PAGE,若为 SIF_ALL 则整个结构都有效 4. int nMin; // 滚动范围最小值,当 fMask 中包含 SIF_RANG E 时有效 5. int nMax; // 滚动范围最大值,当 fMask 中包含 SIF_RANG E 时有效 6. UINT nPage; // 页尺寸,用来确定比例滚动框的大小,当 fMask 中包含 SIF_PAGE 时有效 7. int nPos; // 滚动框的位置,当 fMask 中包含 SIF_POS 有 效 8. int nTrackPos; // 滚动时滚动框的位置,当 fMask 中包含 SIF_T RACKPOS 时有效,该参数只能查询,不能设置,最好不要用该参数来查询拖动时滚动 框的位置 9. } SCROLLINFO, *LPSCROLLINFO; 10. typedef SCROLLINFO CONST *LPCSCROLLINFO; 参数 nMask 的含义与 SCROLLINFO 结构体中的 fMask 一样。该函数在获取信息成功 则返回 TRUE,否则返回 FALSE。 BOOL SetScrollInfo(LPSCROLLINFO lpScrollInfo, BOOL bRedraw = TRUE); 用于设置滚动条的各种参数信息。参数 lpScrollInfo 为指向 SCROLLINFO 结构体变量 的指针,参数 bRedraw 表示是否需要重绘滚动条,如果为 TRUE,则重绘。该函数操作成 功则返回 TRUE,否则返回 FALSE。 int GetScrollPos( ) const; 获取滚动块的当前位置。如果失败则返回 0。 int SetScrollPos(int nPos, BOOL bRedraw = TRUE); 将滚动块移动到指定位置。参数 nPos 指定了滚动块的新位置,参数 bRedraw 表示是 否需要重绘滚动条,如果为 TRUE,则重绘。函数返回滚动框原来的位置,若操作失败则 返回 0。 void GetScrollRange(LPINT lpMinPos, LPINT lpMaxPos) const; 获取滚动条的滚动范围。参数 lpMinPos 指向滚动条滚动范围的最小值,参数 lpMaxP os 指向滚动条滚动范围的最大值。 void SetScrollRange(int nMinPos, int nMaxPos, BOOL bRedraw = TRUE); 用于指定滚动条的滚动范围。参数 nMinPos 和 nMaxPos 分别指定了滚动范围的最小 值和最大值,两者的差不得超过 32767。当两者都为 0 时,滚动条将被隐藏。参数 bRedr aw 表示是否需要重绘滚动条,如果为 TRUE,则重绘。 OnHScroll()与 OnVScroll()函数 无论是标准滚动条,还是滚动条控件,滚动条的通知消息都是用 WM_HSCROLL 和 WM_VSCROLL 消息发送出去的。对这两个消息的默认处理函数是 CWnd::OnHScroll 和 CWnd::OnVScroll,一般需要在派生类中对这两个函数进行重载,以实现滚动功能。也就 是说,假设在一个对话框中放入了一个水平滚动条,我们可以在对话框类中重载 OnHScro ll 函数,并在 OnHScroll 函数中实现滚动功能。 这两个函数的声明如下: afx_msg void OnHScroll(UINT nSBCode,UINT nPos,CScrollBar* pScrollBar); afx_msg void OnVScroll(UINT nSBCode,UINT nPos,CScrollBar* pScrollBar); 参数 nSBCode 是通知消息码,主要通知码及含义的介绍下面已列出。nPos 是滚动框 的位置,只有在 nSBCode 为 SB_THUMBPOSITION 或 SB_THUMBTRACK 时,该参数 才有意义。如果通知消息是滚动条控件发来的,那么 pScrollBar 是指向该控件的指针,如 果是标准滚动条发来的,则 pScrollBar 为 NULL。 SB_BOTTOM/SB_RIGHT:滚动到底端(右端) SB_TOP/SB_LEFT:滚动到顶端(左端) SB_LINEDOWN/SB_LINERIGHT:向下(向右)滚动一行(列) SB_LINEUP/SB_LINELEFT:向上(向左)滚动一行(列) SB_PAGEDOWN/SB_PAGERIGHT:向下(向右)滚动一页 SB_PAGEUP/SB_PAGELEFT:向上(向左)滚动一页 SB_THUMBPOSITION:滚动到指定位置 SB_THUMBTRACK:滚动框被拖动。可利用该消息来跟踪对滚动框的拖动 SB_ENDSCROLL:滚动结束 CScrollBar 类应用实例 讲完了基础知识,鸡啄米还是给大家一个简单的实例。例子非常简单,就是在一个对 话框中加入一个水平滚动条控件和一个编辑框控件,无论滚动条控件是在滚动还是静止, 编辑框中都显示滚动块的当前位置。以下是具体开发步骤: 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example26”。 2. 在自动生成的对话框模板 IDD_EXAMPLE26_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本控件、“OK”按钮和“Cancel”按钮。添加一个 Horizontal Scroll B ar 控件,ID 设置为 IDC_HORI_SCROLLBAR。再添加一个静态文本控件和一个编辑框, 静态文本控件的 Caption 属性设为“滚动块当前位置:”,编辑框的 ID 设为 IDC_HSCROLL _EDIT,Read Only 属性设为 True。此时的对话框模板如下图: 3. 为滚动条 IDC_HORI_SCROLLBAR 添加 CScrollBar 类型的控件变量 m_horiScroll bar。 4. 在对话框初始化时,我们需要设置滚动条的滚动范围和初始位置,并在编辑框中显 示初始位置,那么需要修改 CExample26Dlg::OnInitDialog()函数为: C++代码 1. BOOL CExample26Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. // 设置水平滚动条的滚动范围为 1 到 100 32. m_horiScrollbar.SetScrollRange(1, 100); 33. // 设置水平滚动条的初始位置为 20 34. m_horiScrollbar.SetScrollPos(20); 35. // 在编辑框中显示 20 36. SetDlgItemInt(IDC_HSCROLL_EDIT, 20); 37. 38. return TRUE; // return TRUE unless you set the focus to a control 39. } 5. 现在滚动条还不能正常滚动,并且编辑框中数字也不随滚动改变。根据上面所讲, 我们可以重载 CExample26Dlg 类的 OnHScroll 函数。具体操作为,在 CExample26Dlg 类 的属性页面的工具栏上点“Messages”按钮,找到 WM_HSCROLL 消息,添加响应函数就 可以了。OnHScroll 函数重写后如下: C++代码 1. void CExample26Dlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollB ar* pScrollBar) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. int pos = m_horiScrollbar.GetScrollPos(); // 获取水平滚动 条当前位置 5. 6. switch (nSBCode) 7. { 8. // 如果向左滚动一列,则 pos 减 1 9. case SB_LINEUP: 10. pos -= 1; 11. break; 12. // 如果向右滚动一列,则 pos 加 1 13. case SB_LINEDOWN: 14. pos += 1; 15. break; 16. // 如果向左滚动一页,则 pos 减 10 17. case SB_PAGEUP: 18. pos -= 10; 19. break; 20. // 如果向右滚动一页,则 pos 加 10 21. case SB_PAGEDOWN: 22. pos += 10; 23. break; 24. // 如果滚动到最左端,则 pos 为 1 25. case SB_TOP: 26. pos = 1; 27. break; 28. // 如果滚动到最右端,则 pos 为 100 29. case SB_BOTTOM: 30. pos = 100; 31. break; 32. // 如果拖动滚动块滚动到指定位置,则 pos 赋值为 nPos 的值 33. case SB_THUMBPOSITION: 34. pos = nPos; 35. break; 36. // 下面的 m_horiScrollbar.SetScrollPos(pos);执行时会第二次 进入此函数,最终确定滚动块位置,并且会直接到 default 分支,所以在此处设置编 辑框中显示数值 37. default: 38. SetDlgItemInt(IDC_HSCROLL_EDIT, pos); 39. return; 40. } 41. 42. // 设置滚动块位置 43. m_horiScrollbar.SetScrollPos(pos); 44. 45. CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar); 46. } 6. 编译运行程序,弹出结果对话框,可以自己拖动滚动块看是否能正常滚动,并且编 辑框中也显示了正确的数值。效果如下: 至于垂直滚动条,其实与水平滚动条类似,大家可以自己写写垂直滚动条的例子,鸡 啄米就不再举例了。 VS2010/MFC 编程入门之二十七(常用控件:图片控件 Picture Control) 上一节中鸡啄米讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件 Pictu re Control。我们可以在界面某个位置放入图片控件,显示图片以美化界面。 图片控件简介 图片控件和前面讲到的静态文本框都是静态文本控件,因此两者的使用方法有很多相 同之处,所属类都是 CStatic 类,有关成员函数已在前面介绍,这里就不重复了。 图片控件静态和动态加载图片 鸡啄米下面为大家演示如何为图片控件静态和动态加载位图图片。 1. 图片控件静态加载图片 1)创建一个基于对话框的MFC工程,名称设置为“Example27”。 2)准备一张 Bitmap 图片,名称设为“test.bmp”,放到工程的 res 文件夹中,res 文件 夹路径为...\Example27\Example27\res。鸡啄米在这里用的是一张鸡啄米网站的截图。 3)在 Resource View 中的“Example27.rc*”节点上点右键,选择“Add Resource...”, 弹出“Add Resource”对话框: 然后在左侧的“Resource Type”中选择“Bitmap”,点按钮“Import”,显示一个文件对话 框,我们选择 res 文件夹中的 test.bmp 图片文件,导入成功后会在 Resource View 的 Exa mple27.rc*节点下出现一个新的子节点“Bitmap”,而在“Bitmap”节点下可以看到刚添加的位 图资源 IDB_BITMAP1,这里的默认 ID 就不修改了。 4.)在自动生成的对话框模板 IDD_EXAMPLE27_DIALOG 中,删除“TODO: Place di alog controls here.”静态文本控件、“OK”按钮和“Cancel”按钮。添加一个 Picture Control 控件,在图片控件的属性页中有一个 Type 属性,Type 属性下拉列表中有 8 种类型,下面 分别介绍下: Frame:显示一个无填充的矩形框,边框颜色可以通过 Color 属性的下拉列表设定 Etched Horz:显示一条横分割线 Etched Vert:显示一条竖分割线 Rectangle:显示一个填充的矩形框,矩形颜色可通过 Color 属性的下拉列表设定 Icon:显示一个图标(Icon),图标通过 Image 下拉列表来设置图标资源 ID Bitmap:显示一个位图(Bitmap),位图通过 Image 下拉列表来设置位图资源 ID Enhanced Metafile:显示一个加强的元数据文件(Metafile) Owner Draw:自绘 因为我们要加载的是位图图片,所以 Type 属性选择 Bitmap。 5)在图片控件的 Image 属性的下拉列表中选择 3)中导入的位图 IDB_BITMAP1。 6)编译运行程序,弹出结果对话框,如下图所示: 2. 图片控件动态加载图片 以上讲的是静态加载图片的方法,下面接着讲动态加载图片的方法。程序依然沿用上 面的工程。步骤如下: 1)将上面添加的图片控件的 Image 属性 IDB_BITMAP1 清空,Type 属性不变。 2)修改图片控件的 ID 为 IDC_JIZHUOMI_STATIC,然后为其添加 CStatic 类型控件 变量 m_jzmPicture。(若不修改 ID 则无法为其添加控件变量) 3)在对话框下方添加一按钮控件,Caption 属性改为“加载图片”,ID 设为 IDC_LOA D_PIC_BUTTON。 4)为按钮 IDC_LOAD_PIC_BUTTON添加点击消息的处理函数CExample27Dlg::On BnClickedLoadPicButton(),然后修改此函数的函数实现如下: C++代码 1. void CExample27Dlg::OnBnClickedLoadPicButton() 2. { 3. // TODO: Add your control notification handler code here 4. CBitmap bitmap; // CBitmap 对象,用于加载位图 5. HBITMAP hBmp; // 保存 CBitmap 加载的位图的句柄 6. 7. bitmap.LoadBitmap(IDB_BITMAP1); // 将位图 IDB_BITMAP1 加载到 b itmap 8. hBmp = (HBITMAP)bitmap.GetSafeHandle(); // 获取 bitmap 加载位 图的句柄 9. m_jzmPicture.SetBitmap(hBmp); // 设置图片控件 m_jzmPicture 的位图图片为 IDB_BITMAP1 10. } 5)编译运行程序,弹出结果对话框,点击按钮“加载图片”,结果如下: 图片控件 Picture Control 的内容就讲到这里了。 VS2010/MFC 编程入门之二十八(常用控件:列表视图控件 List Control 上) 前面一节中,鸡啄米讲了图片控件 Picture Control,本节为大家详解列表视图控件 Lis t Control 的使用。 列表视图控件简介 列表视图控件 List Control 同样比较常见,它能够把任何字符串内容以列表的方式显示 出来,这种显示方式的特点是整洁、直观,在实际应用中能为用户带来方便。 列表视图控件是对前面讲到的列表框控件 List Box的改进和延伸。列表视图控件的列 表项一般有图标(Icon)和标签(Label)两部分。图标是对列表项的图形描述,标签是文 字描述。当然列表项可以只包含图标也可以只包含标签。 列表视图控件有 4 种风格:Icon、Small Icon、List 和 Report。下面简单说下 4 种风 格各自的特点: Icon 大图标风格:列表项的图标通常为 32×32 像素,在图标的下面显示标签。 Small Icon 小图标风格:列表项的图标通常为 16×16 像素,在图标的右面显示标签。 List 列表风格:与小图标风格类似,图标和文字的对齐方式不同。 Report 报表风格:列表视图控件可以包含一个列表头来描述各列的含义。每行显示一 个列表项,通常可以包含多个列表子项。最左边的列表子项的标签左边可以添加一个图标 ,而它右边的所有子项则只能显示文字。这种风格的列表视图控件很适合做各种报表。 列表视图控件的通知消息 鸡啄米在VS2010/MFC 编程入门之五(MFC 消息映射机制概述)中的“各种 Windows 消息的消息处理函数”部分,就曾以列表视图控件为例简单讲了 WM_NOTIFY 通知消息及 其消息映射入口和消息处理函数的形式。如果你忘记了可以回到第五节看一看,回忆一下 。 鸡啄米这里给出下一节中将要演示的列表视图控件实例中,通知码为 NM_CLICK 的通 知消息的消息映射入口: ON_NOTIFY(NM_CLICK, IDC_PROGRAM_LANG_LIST, &CExample29Dlg::OnNM ClickProgramLangList) 还有消息处理函数自动生成时的形式: C++代码 1. void CExample29Dlg::OnNMClickProgramLangList(NMHDR *pNMHDR, LRE SULT *pResult) 2. { 3. LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast(pNMHDR); 4. // TODO: Add your control notification handler code here 5. *pResult = 0; 6. } 我们看到,上面的代码中将 NMHDR 指针类型的 pNMHDR 强制转换为 LPNMITEMA CTIVATE 类型的 pNMItemActivate,那么我们就可以在函数中通过 pNMHDR 来访问 NM HDR 结构,也可以通过 pNMItemActive 指针变量来访问第一个元素为 NMHDR 结构体变 量的扩充结构。 当然列表视图控件还有一些自己特有的通知消息,下面就介绍几个其中比较常用的。 LVN_ITEMCHANGING 和 LVN_ITEMCHANGED:当列表视图的状态发生变化时, 会发送这两个通知消息。例如,当用户选择了新的列表项时,程序就会收到这两个消息。 消息会附带一个指向 NMLISTVIEW 结构的指针,消息处理函数可从该结构中获得状态信 息。两个消息的不同之处在于,前者的消息处理函数如果返回 TRUE,那么就阻 止选择的改变,如果返回 FALSE,则允许改变。 LVN_KEYDOWN:该消息表明了一个键盘事件。消息会附带一个指向 NMLVKEYDO WN 结构的指针,通过该结构程序可以获得按键的信息。 LVN_BEGINLABELEDIT 和 LVN_ENDLABELEDIT:分别在用户开始编辑和结束编辑 标题时发送。消息会附带一个指向 NMLVDISPINFO 结构的指针。在前者的消息处理函数 中,可以调用 GetEditControl 成员函数返回一个指向用于编辑标题的编辑框的指针,如果 处理函数返回 FALSE,则允许编辑,如果返回 TRUE,则禁止编辑。在后者的消息处理函 数中,NMLVDISPINFO 结构中的 item.pszText 指向编辑后的新标题,如果 pszText 为 N ULL,那么说明用户放弃了编辑,否则,程序应负责更新表项的标题,这可以由 SetItem 或 SetItemText 函数来完成。 列表视图控件的相关结构体 下面我们来介绍一下与列表视图控件有关的一些结构体。 1. NMHDR 结构体 C++代码 1. typedef struct tagNMHDR { 2. HWND hwndFrom; // 控件窗口的句柄 3. UINT_PTR idFrom; // 控件 ID 4. UINT code; // 控件的通知消息码 5. } NMHDR; 此结构体在很多情况下都是其他扩充结构体的第一个元素,比如上面的 NMITEMACT IVATE 结构体: C++代码 1. typedef struct tagNMITEMACTIVATE { 2. NMHDR hdr; 3. int iItem; 4. int iSubItem; 5. UINT uNewState; 6. UINT uOldState; 7. UINT uChanged; 8. POINT ptAction; 9. LPARAM lParam; 10. UINT uKeyFlags; 11. } NMITEMACTIVATE, *LPNMITEMACTIVATE; 2. LVITEM 结构体 该结构体包含了列表视图控件中列表项或列表子项的各种属性。 C++代码 1. typedef struct _LVITEM { 2. UINT mask; // 掩码位的组合(下面有对应掩码的元素都已在括 号中标出掩码),表明哪些元素是有效的 3. int iItem; // 列表项的索引 4. int iSubItem; // 列表子项的索引 5. UINT state; // 状态,下面会列出。(LVIF_STATE) 6. UINT stateMask; // 状态掩码,用来说明要获取或设置哪些状态。下 面会列出 7. LPTSTR pszText; // 指向列表项或列表子项的标签字符串。(LVIF_ TEXT) 8. int cchTextMax; // pszText 指向缓冲区的字符的个数,包括字符 串结束符。(LVIF_TEXT) 9. int iImage; // 图标的索引。(LVIF_IMAGE) 10. LPARAM lParam; // 32 位的附加数据。(LVIF_PARAM) 11. #if (_WIN32_IE >= 0x0300) 12. int iIndent; 13. #endif 14. #if (_WIN32_WINNT >= 0x501) 15. int iGroupId; 16. UINT cColumns; // tile view columns 17. PUINT puColumns; 18. #endif 19. #if (_WIN32_WINNT >= 0x0600) 20. int* piColFmt; 21. int iGroup; 22. #endif 23. } LVITEM, *LPLVITEM; 下面是 state 和 stateMask 的取值及含义: 状态 对应的状态掩码 含义 LVIS_CUT 同左 列表项或列表子项被选择用来进行剪切和粘 贴操作 LVIS_DROPHILITED 同左 列表项或列表子项成为拖动操作的目标 LVIS_FOCUSED 同左 列表项或列表子项具有输入焦点 LVIS_SELECTED 同左 列表项或列表子项被选中 3. LVCOLUMN 结构体 该结构体仅适用于 Report 报表式列表视图控件。在向列表控件中插入一列时需要用 到此结构体。它包含了列表控件某列的各种属性。 C++代码 1. typedef struct _LVCOLUMN { 2. UINT mask; // 掩码位的组合(下面有对应掩码的元素都已 在括号中标出掩码),表明哪些元素是有效的 3. int fmt; // 该列的表头和列表子项的标签正文显示 格式,可以是 LVCFMT_CENTER、LVCFMT_LEFT 或 LVCFMT_RIGHT。(LVCF_FMT ) 4. int cx; // 以像素为单位的列的宽度。(LVCF_F MT) 5. LPTSTR pszText; // 指向列表头标题正文的字符串。(LVCF_TEXT) 6. int cchTextMax; // pszText 指向缓冲区的字符的个数,包括字符串 结束符。(LVCF_TEXT) 7. int iSubItem; // 该列的索引。(LVCF_SUBITEM) 8. #if (_WIN32_IE >= 0x0300) 9. int iImage; 10. int iOrder; 11. #endif 12. #if (_WIN32_WINNT >= 0x0600) 13. int cxMin; 14. int cxDefault; 15. int cxIdeal; 16. #endif 17. } LVCOLUMN, *LPLVCOLUMN; 4. NMLISTVIEW 结构体 该结构体存放了列表视图控件通知消息的相关信息。列表视图控件的大部分通知消息 都会附带指向该结构体的指针。 C++代码 1. typedef struct tagNMLISTVIEW { 2. NMHDR hdr; // 标准的 NMHDR 结构 3. int iItem; // 列表项的索引 4. int iSubItem; // 列表子项的索引 5. UINT uNewState; // 列表项或列表子项的新状态 6. UINT uOldState; // 列表项或列表子项原来的状态 7. UINT uChanged; // 取值与 LVITEM 的 mask 成员相同,用来表明哪些状 态发生了变化 8. POINT ptAction; // 事件发生时鼠标的客户区坐标 9. LPARAM lParam; //32 位的附加数据 10. } NMLISTVIEW, *LPNMLISTVIEW; 有关列表视图控件的内容本节先讲这些,下节继续讲列表控件类 CListCtrl 的一些成员 函数和应用实例 VS2010/MFC 编程入门之二十九(常用控件:列表视图控件 List Control 下) 上一节是关于列表视图控件 List Control 的上半部分,简单介绍了列表视图控件,其通 知消息的处理和有关结构体的定义。本节继续讲解下半部分,包括列表视图控件的创建、 CListCtrl 类的主要成员函数和 CListCtrl 类应用实例。 列表视图控件的创建 MFC同样为列表视图控件的操作提供了 CListCtrl 类。 如果我们不想在对话框模板中直接拖入 List Control 来使用列表视图控件,而是希望动 态创建它,则要用到 CListCtrl 类的成员函数 Create 函数,原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 参数 rect 为列表视图控件的位置和尺寸,pParentWnd 为指向父窗口的指针,nID 指 定列表视图控件的 ID,最复杂的一个参数同样还是 dwStyle,它用于设定列表视图控件的 风格,可以是以下风格的组合: 风格 含义 LVS_ALIGNLEFT 显示格式是大图标或小图标时,标签放在图标的左边 LVS_ALIGNTOP 显示格式是大图标或小图标时,标题放在图标的上边 LVS_AUTOARRANGE 显示格式是大图标或小图标时,自动排列控件中的 列表项 LVS_EDITLABELS 用户可以修改标签文本 LVS_ICON 指定大图标显示格式 LVS_LIST 指定列表显示格式 LVS_NOCOLUMNHEADER 在报表格式中不显示列的表头 LVS_NOLABELWRAP 显示格式是大图标时,使标签文本单行显示。默认是 多行显示 LVS_NOSCROLL 列表视图控件无滚动条,此风格不能与 LVS_LIST 或 LVS_REPORT 组合使用 LVS_NOSORTHEADER 报表格式的列表视图控件的表头不能作为排序按钮 使用 LVS_OWNERDRAWFIXED 由控件的拥有者负责绘制表项 LVS_REPORT 指定报表显示格式 LVS_SHAREIMAGELISTS 使列表视图共享图像序列 LVS_SHOWSELALWAYS 即使控件失去输入焦点,仍显示出项的选择状态 LVS_SINGLESEL 指定只能有一个列表项被选中。默认时可以多项选择 LVS_SMALLICON 指定小图标显示格式 LVS_SORTASCENDING 按升序排列列表项 LVS_SORTDESCENDING 按降序排列列表项 与前面的控件一样,除了以上风格一般我们还要为列表视图控件设置 WS_CHILD 和 WS_VISIBLE 风格。对于直接在对话框模板中创建的列表视图控件,其属性页中的属性与 上述风格是对应的,例如,属性 Alignment 默认为 Left,也就等价于指定了 LVS_ALIGNL EFT 风格。 CListCtrl 类的主要成员函数 CListCtrl 类有很多成员函数,鸡啄米这里就为大家介绍几个常用的主要成员函数。 UINT GetSelectedCount( ) const; 该函数返回列表视图控件中被选择列表项的数量。 POSITION GetFirstSelectedItemPosition( ) const; 获取列表视图控件中第一个被选择项的位置。返回的 POSITION 值可以用来迭代来获 取其他选择项,可以当作参数传入下面的 GetNextSelectedItem 函数来获得选择项的索引 。如果没有被选择项则返回 NULL。 int GetNextSelectedItem(POSITION& pos) const; 该函数获取由 pos 指定的列表项的索引,然后将 pos 设置为下一个位置的 POSITION 值。参数 pos 为之前调用 GetNextSelectedItem 或 GetFirstSelectedItemPosition 得到的 P OSITION 值的引用。返回值就是 pos 指定列表项的索引。 int GetItemCount( ) const; 获取列表视图控件中列表项的数量。 int InsertColumn(int nCol,const LVCOLUMN* pColumn ); int InsertColumn(int nCol,LPCTSTR lpszColumnHeading,int nFormat = LVCFM T_LEFT,int nWidth = -1,int nSubItem = -1 ); 这两个函数用于在报表式列表视图控件中插入列。第一个函数中,nCol 参数为插入列 的索引,pColumn 参数指向 LVCOLUMN 结构,其中包含了插入列的属性。第二个函数中 ,nCol 参数也是插入列的索引,lpszColumnHeading 参数为列标题字符串,nFormat 参数 为列中文本的对齐方式,可以是 LVCFMT_LEFT、LVCFMT_RIGHT 或 LVCFMT_CENTE R,nWidth 参数为列宽,nSubItem 为插入列对应列表子项的索引。两个函数在成功时都 返回新列的索引,失败都返回-1。 BOOL DeleteColumn(int nCol); 该函数用于删除列表视图控件中的某列。参数 nCol 为删除列的索引。删除成功则返回 TRUE,失败返回 FALSE。 int InsertItem(int nItem,LPCTSTR lpszItem); 向列表视图控件中插入新的列表项。参数 nItem 为要插入项的索引,参数 lpszItem 为 要插入项的标签字符串。如果插入成功则返回新列表项的索引,否则返回-1。 BOOL DeleteItem(int nItem); 从列表视图控件中删除某个列表项。参数 nItem 指定了要删除的列表项的索引。删除 成功则返回 TRUE,否则返回 FALSE。 CString GetItemText(int nItem,int nSubItem) const; 获取指定列表项或列表子项的显示文本。参数 nItem 指定了列表项的索引,参数 nSub Item 指定了列表子项的索引。 BOOL SetItemText(int nItem,int nSubItem,LPCTSTR lpszText); 设置指定列表项或列表子项的显示文本。参数 nItem 和 nSubItem 同 GetItemText。参 数 lpszText 为要设置的显示文本字符串。如果设置成功则返回 TRUE,否则返回 FALSE 。 DWORD_PTR GetItemData(int nItem) const; 该函数用于获取指定列表项的附加 32 位数据。参数 nItem 为列表项的索引。返回值 就是由 nItem 指定列表项的附加 32 位数据。 BOOL SetItemData(int nItem,DWORD_PTR dwData); 该函数用于为指定列表项设置附加 32 位是数据。参数 nItem 为列表项的索引,参数 d wData 为列表项的附加 32 位数据。 CListCtrl 类应用实例 最后鸡啄米还是给大家写一个简单的实例,说明 CListCtrl 类的几个成员函数及通知消 息等的使用方法。因为在开发中最常用的要属报表风格的 List Control 了,所以鸡啄米给 大家写的是一个报表 List Control 的例子。 此实例实现的功能:在单选列表视图控件中显示一个简单的编程语言排行榜,然后在 用鼠标左键选择某列表项时,将选中列表项的文本显示到编辑框中。下面是具体实现步骤 : 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example29”。 2. 在自动生成的对话框模板 IDD_EXAMPLE29_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本控件、“OK”按钮和“Cancel”按钮。添加一个 List Control 控件,I D 设置为 IDC_PROGRAM_LANG_LIST,View 属性设为 Report,即为报表风格,Single Selection 属性设为 True。再添加一个静态文本控件和一个编辑框,静态文本控件的 Capti on 属性设为“选择的语言:”,编辑框的 ID 设为 IDC_LANG_SEL_EDIT,Read Only 属性 设为 True。此时的对话框模板如下图: 3. 为列表视图控件 IDC_PROGRAM_LANG_LIST添加 CListCtrl 类型的控件变量m_pr ogramLangList。 4. 在对话框初始化时,我们将编程语言排行榜加入到列表视图控件中,那么需要修改 CExample29Dlg::OnInitDialog()函数为: C++代码 1. BOOL CExample29Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. CRect rect; 32. 33. // 获取编程语言列表视图控件的位置和大小 34. m_programLangList.GetClientRect(&rect); 35. 36. // 为列表视图控件添加全行选中和栅格风格 37. m_programLangList.SetExtendedStyle(m_programLangList.GetExt endedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); 38. 39. // 为列表视图控件添加三列 40. m_programLangList.InsertColumn(0, _T("语言"), LVCFMT_CENTER, rect.Width()/3, 0); 41. m_programLangList.InsertColumn(1, _T("2012.02 排名"), LVCFMT _CENTER, rect.Width()/3, 1); 42. m_programLangList.InsertColumn(2, _T("2011.02 排名"), LVCFMT _CENTER, rect.Width()/3, 2); 43. 44. // 在列表视图控件中插入列表项,并设置列表子项文本 45. m_programLangList.InsertItem(0, _T("Java")); 46. m_programLangList.SetItemText(0, 1, _T("1")); 47. m_programLangList.SetItemText(0, 2, _T("1")); 48. m_programLangList.InsertItem(1, _T("C")); 49. m_programLangList.SetItemText(1, 1, _T("2")); 50. m_programLangList.SetItemText(1, 2, _T("2")); 51. m_programLangList.InsertItem(2, _T("C#")); 52. m_programLangList.SetItemText(2, 1, _T("3")); 53. m_programLangList.SetItemText(2, 2, _T("6")); 54. m_programLangList.InsertItem(3, _T("C++")); 55. m_programLangList.SetItemText(3, 1, _T("4")); 56. m_programLangList.SetItemText(3, 2, _T("3")); 57. 58. return TRUE; // return TRUE unless you set the focus to a control 59. } 5. 我们希望在选中列表项改变时,将最新的选择项实时显示到编辑框中,那么可以使 用 NM_CLICK 通知消息。为列表框 IDC_PROGRAM_LANG_LIST 的通知消息 NM_CLIC K添加消息处理函数CExample29Dlg::OnNMClickProgramLangList,并修改如下: C++代码 1. void CExample29Dlg::OnNMClickProgramLangList(NMHDR *pNMHDR, LRE SULT *pResult) 2. { 3. LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast(pNMHDR); 4. // TODO: Add your control notification handler code here 5. *pResult = 0; 6. 7. CString strLangName; // 选择语言的名称字符串 8. NMLISTVIEW *pNMListView = (NMLISTVIEW*)pNMHDR; 9. 10. if (-1 != pNMListView->iItem) // 如果 iItem 不是-1,就 说明有列表项被选择 11. { 12. // 获取被选择列表项第一个子项的文本 13. strLangName = m_programLangList.GetItemText(pNMListView ->iItem, 0); 14. // 将选择的语言显示与编辑框中 15. SetDlgItemText(IDC_LANG_SEL_EDIT, strLangName); 16. } 17. } 6. 运行程序,弹出结果对话框,在对话框的列表框中用鼠标改变选中项时,编辑框中 的显示会相应改变。效果图如下: 关于列表视图控件 List Control 的内容总算讲完了,内容不少,但实际上这些还只是一 部分,在实际开发中会遇到各种问题,需要大家去查阅 MSDN 或上网找资料等来解决。 VS2010/MFC 编程入门之三十(常用控件:树形控件 Tree Control 上) 前面两节为大家讲了列表视图控件 List Control,这一节开始介绍一种特殊的列表--树 形控件 Tree Control。 树形控件简介 树形控件在 Windows 系统中是很常见的,例如资源管理器左侧的窗口中就有用来显示 目录的树形视图。树形视图中以分层结构显示数据,每层的缩进不同,层次越低缩进越多 。树形控件的节点一般都由标签和图标两部分组成,图标用来抽象的描述数据,能够使树 形控件的层次关系更加清晰。 树形控件在插入新的树节点时会稍麻烦些,回顾之前的列表框,插入新列表项时调用 AddString 成员函数就可以了,而对于树形控件则需要指定新节点与已有节点的关系。另 外,树形控件与列表视图控件一样,可以在每一个节点的左边加入图标。这些都使得树形 控件给人一种复杂的感觉,但我们在使用它一两次后会发现其实树形控件用起来还是很方 便的。 树形控件的通知消息 下面列出树形控件特有的通知消息中比较常用的几个: TVN_SELCHANGING 和 TVN_SELCHANGED:在用户改变了对树节点的选择时, 控件会发送这两个消息。消息会附带一个指向 NMTREEVIEW 结构的指针,程序可从该结 构中获得必要的信息。两个消息都会在该结构的 itemOld 成员中包含原来的选择项信息, 在 itemNew 成员中包含新选择项的信息,在 action 成员中表明是用户的什么行为触发了 该通知消息(若是 TVC_BYKEYBOARD 则表明是键盘,若是 TVC_BYMOUSE 则表明是鼠 标,若是 TVC_UNKNOWN 则表示未知)。两个消息的不同之处在于,如果 TVN_SELCH ANGING 的消息处理函数返回 TRUE,那么就阻止选择的改变,如果返回 FALSE,则允 许改变。 TVN_KEYDOWN:该消息表明了一个键盘事件。消息会附带一个指向 NMTVKEYDO WN 结构的指针,通过该结构程序可以获得按键的信息。 TVN_BEGINLABELEDIT 和 TVN_ENDLABELEDIT:分别在用户开始编辑和结束编 辑节点的标签时发送。消息会附带一个指向 NMTVDISPINFO 结构的指针,程序可从该结 构中获得必要的信息。在前者的消息处理函数中,可以调用 GetEditControl()成员函数返回 一个指向用于编辑标题的编辑框的指针。如果处理函数返回 FALSE,则允许编辑,如果返 回 TRUE,则禁止编辑。在后者的消息处理函数中,NMTVDISPINFO 结构中的 item.pszT ext 指向编辑后的新标题,如果 pszText 为 NULL,那么说明用户放弃了编辑,否则,程序 应负责更新节点的标签,这可以由 SetItem()或 SetItemText()函数来完成。 树形控件的相关数据结构 1. HTREEITEM 句柄 树形控件中的每个节点都可以由一个 HTREEITEM 类型的句柄表示。我们通过 CTree Ctrl 类的成员函数对树进行访问和操作时,很多时候都要用到 HTREEITEM 句柄。 2. TVITEM 结构体 TVITEM 结构体描述了树形控件节点的属性,定义如下: C++代码 1. typedef struct tagTVITEM { 2. UINT mask; // 包含一些掩码位(下面的括号中列出)的组合,用来 表明结构的哪些成员是有效的 3. HTREEITEM hItem; // 树节点的句柄(TVIF_HANDLE) 4. UINT state; // 树节点的状态(TVIF_STATE) 5. UINT stateMask; // 状态的掩码组合(TVIF_STATE) 6. LPTSTR pszText; // 树节点的标签文本(TVIF_TEXT) 7. int cchTextMax; // 标签文本缓冲区的大小(TVIF_TEXT) 8. int iImage; // 树节点的图像索引(TVIF_IMAGE) 9. int iSelectedImage; // 选中项的图像索引(TVIF_SELECTEDIMAGE) 10. int cChildren; // 表明节点是否有子节点,为 1 则有,为 0 则没有(TV IF_CHILDREN) 11. LPARAM lParam; // 一个 32 位的附加数据(TVIF_PARAM) 12. } TVITEM, *LPTVITEM; 此结构体中多个元素涉及到了图像和状态等,有必要具体解释下。 树形控件节点需要显示图标时,就要为树形控件关联一个图像序列,上面的 iImage 成 员就代表了该结构体对应的树节点的图标在图像序列中的索引,iSelectedImage 则代表该 树节点被选中时显示的图标在图像序列中的索引。对于如何为树形控件关联图像序列,鸡 啄米将在后面的实例中讲到。 stateMask 用来说明要获取或设置树节点的哪些状态。下面是 state 和 stateMask 的一 些常用值及含义: state 对应的 stateMask 含义 TVIS_CUT TVIS_CUT 节点被选择用来进行剪切和粘贴 操作 TVIS_DROPHILITED TVIS_DROPHILITED 节点成为拖动操作的目标 TVIS_EXPANDED TVIS_EXPANDED 节点的子节点被展开 TVIS_EXPANDEDONCE TVIS_EXPANDEDONCE 节点的子节点曾经被展开过 TVIS_SELECTED TVIS_SELECTED 节点被选中 lParam 在实际开发中常用来存放与树节点有关的附加数据。 3. NMTREEVIEW 结构体 NMTREEVIEW 结构体中包含了树形控件通知消息的相关信息。树形控件的大多数通 知消息都会带有指向该结构体的指针。NMTREEVIEW 结构体的定义如下: C++代码 1. typedef struct tagNMTREEVIEW { 2. NMHDR hdr; // 标准的 NMHDR 结构 3. UINT action; // 表明是用户的什么行为触发了该通知消息 4. TVITEM itemOld; // 原节点的属性 5. TVITEM itemNew; // 新节点的属性 6. POINT ptDrag; // 事件发生时鼠标的客户区坐标 7. } NMTREEVIEW, *LPNMTREEVIEW; 4. TVINSERTSTRUCT 结构体 向树形控件中插入新节点时需要用到 TVINSERTSTRUCT 结构体,它常与 TVM_INS ERTITEM 消息一起使用。定义如下: C++代码 1. typedef struct tagTVINSERTSTRUCT { 2. HTREEITEM hParent; // 父节点的句柄 3. HTREEITEM hInsertAfter; // 指明插入到同层中哪一项的后面 4. #if (_WIN32_IE >= 0x0400) 5. union 6. { 7. TVITEMEX itemex; 8. TVITEM item; 9. } DUMMYUNIONNAME; 10. #else 11. TVITEM item; // 要添加的新节点的属性 12. #endif 13. } TVINSERTSTRUCT, *LPTVINSERTSTRUCT; 若 hParent 成员为 TVI_ROOT 或 NULL,那么新节点将被作为树的根节点插入。hIns ertAfter 除了可以是某个节点的句柄,还可以有四种取值:TVI_FIRST(插入到树形控件的 最前面)、TVI_LAST(插入到树形控件的最后面)、TVI_ROOT(作为根节点插入)和 T VI_SORT(按字母顺序插入)。 5. NMTVDISPINFO 结构体 NMTVDISPINFO 结构体中包含了与树节点的显示有关的信息。定义如下: C++代码 1. typedef struct tagNMTVDISPINFO { 2. NMHDR hdr; 3. TVITEM item; 4. } NMTVDISPINFO, *LPNMTVDISPINFO; 关于树形控件的使用本节先讲这么多,在下节将继续讲解 CTreeCtrl 类的相关知识和 实例 VS2010/MFC 编程入门之三十一(常用控件:树形控件 Tree Control 下) 前面一节讲了树形控件 Tree Control 的简介、通知消息以及相关数据结构,本节继续 讲下半部分,包括树形控件的创建、CTreeCtrl 类的主要成员函数和应用实例。 树形控件的创建 MFC为树形控件提供了 CTreeCtrl 类,它封装了树形控件的所有操作。 树形控件的创建也是有两种方式,一种是在对话框模板中直接拖入 Tree Control 控件 创建,另一种就是通过 CTreeCtrl 类的 Create 成员函数创建。下面主要讲后者。 CTreeCtrl 类的 Create 成员函数的原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 此函数的原型与前面讲到的所有控件类的 Create 函数都类似。dwStyle 指定树形控件 风格的组合,rect 指定树形控件窗口的位置和大小,pParentWnd 为指向树形控件父窗口 的指针,nID 指定树形控件的 ID。下面还是主要讲讲树形控件的主要风格以及含义。 TVS_DISABLEDRAGDROP:禁止树形控件发送 TVN_BEGINDRAG 通知消息,即不 支持拖动操作 TVS_EDITLABELS:用户可以编辑节点的标签文本 TVS_HASBUTTONS:显示带有"+"或"-"的小方框来表示某项能否被展开或已展 开 TVS_HASLINES:在父节点与子节点间连线以更清晰地显示树的结构 TVS_LINESATROOT:在根节点处连线 TVS_SHOWSELALWAYS:即使控件失去输入焦点,仍显示出项的选择状态 同样,动态创建树形控件时,除了能够指定上述风格的组合外,一般还要指定 WS_C HILD 和 WS_VISIBLE 风格。 在对话框模板中直接拖入 Tree Control 创建树形控件时,可以在树形控件的属性页中 设置其风格,与上面的风格是对应的,例如,属性 Has Lines 对应的就是 TVS_HASLINE S 风格。 CTreeCtrl 类的主要成员函数 CImageList* SetImageList(CImageList * pImageList,int nImageListType); 如果树节点需要显示图标时,则必须先创建一个 CImageList 类的对象,并为其添加多 个图像组成一个图像序列,然后调用 SetImageList 函数为树形控件设置图像序列,在用 In sertItem 插入节点时传入所需图像在图像序列中的索引即可。后面的例子中会演示。参数 pImageList 为指向图像序列类 CImageList 的对象的指针,若为 NULL 则删除树形控件的 所有图像。参数 nImageListType 指定图像序列的类型,可以是 TVSIL_NORMAL(普通图 像序列)或 TVSIL_STATE(状态图像序列,用图像表示节点的状态)。 UINT GetCount( ) const; 获取树形控件中节点的数量。 DWORD_PTR GetItemData(HTREEITEM hItem) const; 获取树形控件中某个指定节点的附加 32 位数据。参数 hItem 为指定的树节点的句柄 。 BOOL SetItemData(HTREEITEM hItem,DWORD_PTR dwData); 为树形控件中某个指定节点设置附加的 32 位数据。参数 hItem 同上,dwData 为要设 置的 32 位数据。 CString GetItemText(HTREEITEM hItem) const; 获取树形控件中某个指定节点的标签文本。参数 hItem 同上。返回值是包含标签文本 的字符串。 BOOL SetItemText(HTREEITEM hItem,LPCTSTR lpszItem); 为树形控件中某个指定节点设置标签文本。参数 hItem 同上,lpszItem 为包含标签文 本的字符串的指针。 HTREEITEM GetNextSiblingItem(HTREEITEM hItem) const; 获取树形控件中某个指定节点的下一个兄弟节点。参数 hItem 同上。返回值是下一个 兄弟节点的句柄。 HTREEITEM GetPrevSiblingItem(HTREEITEM hItem) const; 获取树形控件中某个指定节点的上一个兄弟节点。参数 hItem 同上。返回值是上一个 兄弟节点的句柄。 HTREEITEM GetParentItem(HTREEITEM hItem) const; 获取树形控件中某个指定节点的父节点。参数 hItem 同上。返回值是父节点的句柄。 HTREEITEM GetRootItem( ) const; 获取树形控件根节点的句柄。 HTREEITEM GetSelectedItem( ) const; 获取树形控件当前选中节点的句柄。 BOOL DeleteAllItems( ); 删除树形控件中的所有节点。删除成功则返回 TRUE,否则返回 FALSE。 BOOL DeleteItem(HTREEITEM hItem); 删除树形控件中的某个节点。参数 hItem 为要删除的节点的句柄。删除成功则返回 TR UE,否则返回 FALSE。 HTREEITEM InsertItem(LPCTSTR lpszItem,int nImage,int nSelectedImage,HTR EEITEM hParent = TVI_ROOT,HTREEITEM hInsertAfter = TVI_LAST); 在树形控件中插入一个新节点。参数 lpszItem 为新节点的标签文本字符串的指针,参 数 nImage 为新节点的图标在树形控件图像序列中的索引,参数 nSelectedImage 为新节 点被选中时的图标在图像序列中的索引,参数 hParent 为插入节点的父节点的句柄,参数 hInsertAfter 为新节点的前一个节点的句柄,即新节点将被插入到 hInsertAfter 节点之后。 BOOL SelectItem(HTREEITEM hItem); 选中指定的树节点。参数 hItem 为要选择的节点的句柄。若成功则返回 TRUE,否则 返回 FALSE。 树形控件的应用实例 最后鸡啄米还是给大家写一个简单的实例,说明 CListCtrl 类的几个成员函数及树形控 件通知消息等的使用方法。 此实例实现的功能:在一个树形控件中显示鸡啄米网站的简单结构分层,共有三层, 分别为鸡啄米网站、各个分类和文章。用鼠标左键单击改变选中节点后,将选中节点的文 本显示到编辑框中。另外,还要实现一个常见的效果,就是鼠标划过除根节点外的某个树 节点时,显示相应的 Tip 提示信息。下面是具体实现步骤: 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example31”。 2. 在自动生成的对话框模板 IDD_EXAMPLE31_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本框、“OK”按钮和“Cancel”按钮。添加一个 Tree Control 控件,I D 设置为 IDC_WEB_TREE,属性 Has Buttons、Has Lines 和 Lines At Root 都设为 True ,为了在鼠标划过某个节点时显示提示信息还需要将 Info Tip 属性设为 True。再添加一个 静态文本框和一个编辑框,静态文本框的 Caption 属性设为“您选择的节点:”,编辑框的 I D 设为 IDC_ITEM_SEL_EDIT,Read Only 属性设为 True。此时的对话框模板如下图: 3. 导入需要为树形控件的节点添加的图标。鸡啄米在这里找了三个 32x32 的 Icon 图 标,保存到工程的 res 目录下。然后在 Resource View 资源视图中,右键点击 Icon 节点, 在右键菜单中选择“Add Resource...”,弹出“Add Resource”对话框,再从左边“Resource ty pe”列表中选择“Icon”,点击右边的“Import...”按钮,就可以选择三个图标文件进行导入了。 导入成功后,分别修改它们 ID 为 IDI_WEB_ICON、IDI_CATALOG_ICON 和 IDI_ARTICL E_ICON。 4. 为树形控件 IDC_WEB_TREE 添加 CTreeCtrl 类型的控件变量 m_webTree。并在 Example31Dlg.h 文件中为 CExample31Dlg 类添加成员对象:CImageList m_imageList; 。 5. 在对话框初始化时,我们在树形控件中添加鸡啄米网站的树形结构,那么需要修改 CExample31Dlg::OnInitDialog()函数为: C++代码 1. BOOL CExample31Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. ......略 5. 6. // TODO: Add extra initialization here 7. HICON hIcon[3]; // 图标句柄数组 8. HTREEITEM hRoot; // 树的根节点的句柄 9. HTREEITEM hCataItem; // 可表示任一分类节点的句柄 10. HTREEITEM hArtItem; // 可表示任一文章节点的句柄 11. 12. // 加载三个图标,并将它们的句柄保存到数组 13. hIcon[0] = theApp.LoadIcon(IDI_WEB_ICON); 14. hIcon[1] = theApp.LoadIcon(IDI_CATALOG_ICON); 15. hIcon[2] = theApp.LoadIcon(IDI_ARTICLE_ICON); 16. 17. // 创建图像序列 CImageList 对象 18. m_imageList.Create(32, 32, ILC_COLOR32, 3, 3); 19. // 将三个图标添加到图像序列 20. for (int i=0; i<3; i++) 21. { 22. m_imageList.Add(hIcon[i]); 23. } 24. 25. // 为树形控件设置图像序列 26. m_webTree.SetImageList(&m_imageList, TVSIL_NORMAL); 27. 28. // 插入根节点 29. hRoot = m_webTree.InsertItem(_T("鸡啄米"), 0, 0); 30. // 在根节点下插入子节点 31. hCataItem = m_webTree.InsertItem(_T("IT 互联网"), 1, 1, hRoo t, TVI_LAST); 32. // 为“IT 互联网”节点添加附加的编号数据,在鼠标划过该节点时显示 33. m_webTree.SetItemData(hCataItem, 1); 34. // 在“IT 互联网”节点下插入子节点 35. hArtItem = m_webTree.InsertItem(_T("百度文章 1"), 2, 2, hCat aItem, TVI_LAST); 36. // 为“百度文章 1”节点添加附加的编号数据,在鼠标划过该节点时显示 37. m_webTree.SetItemData(hArtItem, 2); 38. // 在“IT 互联网”节点下插入另一子节点 39. hArtItem = m_webTree.InsertItem(_T("谷歌文章 2"), 2, 2, hCat aItem, TVI_LAST); 40. // 为“谷歌文章 2”节点添加附加的编号数据,在鼠标划过该节点时显示 41. m_webTree.SetItemData(hArtItem, 3); 42. // 在根节点下插入第二个子节点 43. hCataItem = m_webTree.InsertItem(_T("数码生活"), 1, 1, hRoot, TVI_LAST); 44. // 为“数码生活”节点添加附加的编号数据,在鼠标划过该节点时显示 45. m_webTree.SetItemData(hCataItem, 4); 46. // 在“数码生活”节点下插入子节点 47. hArtItem = m_webTree.InsertItem(_T("智能手机文章 1"), 2, 2, h CataItem, TVI_LAST); 48. // 为“智能手机文章 1”节点添加附加的编号数据,在鼠标划过该节点时显示 49. m_webTree.SetItemData(hArtItem, 5); 50. // 在“数码生活”节点下插入另一子节点 51. hArtItem = m_webTree.InsertItem(_T("平板电脑文章 2"), 2, 2, h CataItem, TVI_LAST); 52. // 为“平板电脑文章 2”节点添加附加的编号数据,在鼠标划过该节点时显示 53. m_webTree.SetItemData(hArtItem, 6); 54. // 在根节点下插入第三个子节点 55. hCataItem = m_webTree.InsertItem(_T("软件开发"), 1, 1, hRoot, TVI_LAST); 56. // 为“软件开发”节点添加附加的编号数据,在鼠标划过该节点时显示 57. m_webTree.SetItemData(hCataItem, 7); 58. // 在“软件开发”节点下插入子节点 59. hArtItem = m_webTree.InsertItem(_T("C++编程入门系列 1"), 2, 2, hCataItem, TVI_LAST); 60. // 为“C++编程入门系列 1”节点添加附加的编号数据,在鼠标划过该节点时显 示 61. m_webTree.SetItemData(hArtItem, 8); 62. // 在“软件开发”节点下插入另一子节点 63. hArtItem = m_webTree.InsertItem(_T("VS2010/MFC 编程入门 2"), 2, 2, hCataItem, TVI_LAST); 64. // 为“VS2010/MFC 编程入门 2”节点添加附加的编号数据,在鼠标划过该节点 时显示 65. m_webTree.SetItemData(hArtItem, 9); 66. // 在根节点下插入第四个子节点 67. hCataItem = m_webTree.InsertItem(_T("娱乐休闲"), 1, 1, hRoot, TVI_LAST); 68. // 为“娱乐休闲”节点添加附加的编号数据,在鼠标划过该节点时显示 69. m_webTree.SetItemData(hCataItem, 10); 70. // 在“娱乐休闲”节点下插入子节点 71. hArtItem = m_webTree.InsertItem(_T("玛雅文明文章 1"), 2, 2, h CataItem, TVI_LAST); 72. // 为“玛雅文明文章 1”节点添加附加的编号数据,在鼠标划过该节点时显示 73. m_webTree.SetItemData(hArtItem, 11); 74. // 在“娱乐休闲”节点下插入另一子节点 75. hArtItem = m_webTree.InsertItem(_T("IT 笑话 2"), 2, 2, hCata Item, TVI_LAST); 76. // 为“IT 笑话 2”节点添加附加的编号数据,在鼠标划过该节点时显示 77. m_webTree.SetItemData(hArtItem, 12); 78. 79. return TRUE; // return TRUE unless you set the focus to a control 80. } 6. 我们希望在选中节点改变时,将最新的选择项实时显示到编辑框中,那么可以响应 TVN_SELCHANGED 通知消息。为树形控件 IDC_WEB_TREE 的通知消息 TVN_SELCH ANGED添加消息处理函数CExample31Dlg::OnTvnSelchangedWebTree,并修改函数体 如下: C++代码 1. void CExample31Dlg::OnTvnSelchangedWebTree(NMHDR *pNMHDR, LRESU LT *pResult) 2. { 3. LPNMTREEVIEW pNMTreeView = reinterpret_cast(p NMHDR); 4. // TODO: Add your control notification handler code here 5. *pResult = 0; 6. 7. CString strText; // 树节点的标签文本字符串 8. 9. // 获取当前选中节点的句柄 10. HTREEITEM hItem = m_webTree.GetSelectedItem(); 11. // 获取选中节点的标签文本字符串 12. strText = m_webTree.GetItemText(hItem); 13. // 将字符串显示到编辑框中 14. SetDlgItemText(IDC_ITEM_SEL_EDIT, strText); 15. } 7. 还有一个功能需要实现,那就是鼠标划过除根节点外的某个树节点时,显示相应的 Tip 提示信息,本实例中提示信息为节点的编号。这需要响应 TVN_GETINFOTIP 通知消 息。为树形控件 IDC_WEB_TREE 的通知消息 TVN_GETINFOTIP 添加消息处理函数 CEx ample31Dlg::OnTvnGetInfoTipWebTree,并修改函数体如下: C++代码 1. void CExample31Dlg::OnTvnGetInfoTipWebTree(NMHDR *pNMHDR, LRESU LT *pResult) 2. { 3. LPNMTVGETINFOTIP pGetInfoTip = reinterpret_cast(pNMHDR); 4. // TODO: Add your control notification handler code here 5. *pResult = 0; 6. NMTVGETINFOTIP* pTVTipInfo = (NMTVGETINFOTIP*)pNMHDR; // 将传入的 pNMHDR 转换为 NMTVGETINFOTIP 指针类型 7. HTREEITEM hRoot = m_webTree.GetRootItem(); // 获取树的根 节点 8. CString strText; // 每个树节点的提示信息 9. 10. if (pTVTipInfo->hItem == hRoot) 11. { 12. // 如果鼠标划过的节点是根节点,则提示信息为空 13. strText = _T(""); 14. } 15. else 16. { 17. // 如果鼠标划过的节点不是根节点,则将该节点的附加 32 位数据格式化 为字符串 18. strText.Format(_T("%d"), pTVTipInfo->lParam); 19. } 20. 21. // 将 strText 字符串拷贝到 pTVTipInfo 结构体变量的 pszText 成员中, 这样就能显示内容为 strText 的提示信息 22. wcscpy(pTVTipInfo->pszText, strText); 23. } 8. 运行程序,弹出结果对话框。效果如下图: 树形控件的知识就讲到这里了,相比之前的控件可能稍有复杂。 VS2010/MFC 编程入门之三十二(常用控件:标签控件 Tab Control 上) 前面两节鸡啄米讲了树形控件 Tree Control,本节开始讲解标签控件 Tab Control,也 可以称为选项卡控件。 标签控件简介 标签控件也比较常见。它可以把多个页面集成到一个窗口中,每个页面对应一个标签 ,用户点击某个标签时,它对应的页面就会显示。下图是 Windows 系统配置中标签控件 的例子: 使用标签控件我们可以同时加载多个有关联的页面,用户只需点击标签即可实现页面 切换,方便灵活的进行操作。每个标签除了可以显示标签文本,还可以显示图标。 标签控件相当于是一个页面的容器,可以容纳多个对话框,而且一般也只容纳对话框 ,所以我们不能直接在标签控件上添加其他控件,必须先将其他控件放到对话框中,再将 对话框添加到标签控件中。最终我们点击标签切换页面时,切换的不是控件的组合,而是 对话框。 标签控件的通知消息 在对标签控件进行一些操作,比如点击标签时,标签控件也会向父窗口发送一些通知 消息。我们可以为这些通知消息添加处理函数,实现各种功能。标签控件的主要通知消息 及含义如下所示: TCN_SELCHANGE:通知父窗口控件的标签选择项已经改变 TCN_SELCHANGING 通知父窗口控件的标签选择项正在改变 TCN_KEYDOWN:通知父窗口在控件范围内键盘被按下 TCN_GETOBJECT:具有 TCS_EX_REGISTERDROP 扩展特性并且对象被拖动时的 通知消息 TCN_FOCUSCHANGE:通知父窗口控件的按钮聚焦已经改变 NM_CLICK:通知父窗口用户在控件区域范围内点击了鼠标左键 NM_RCLICK:通知父窗口用户在控件区域范围内点击了鼠标右键 NM_RELEASEDCAPTURE:通知父窗口在控件区域范围内释放鼠标捕获消息 标签控件的相关结构体 标签控件在使用中也有一些相关的结构体经常用到,主要以下几个: 1. TCITEMHEADER 结构体 该结构体用来指定或获取标签控件本身的属性。用在 TCM_INSERTITEM、TCM_GE TITEM 和 TCM_SETITEM 消息中。 C++代码 1. typedef struct tagTCITEMHEADER { 2. UINT mask; // 掩码,可以为 TCIF_IMAGE(iImage 成员有效)、TCIF _RTLREADING、TCIF_TEXT(pszText 成员有效) 3. UINT lpReserved1; // 预留 4. UINT lpReserved2; // 预留 5. LPTSTR pszText; // 标签文本字符串 6. int cchTextMax; 7. int iImage; // 图标在标签控件图像序列中的索引 8. } TCITEMHEADER, *LPTCITEMHEADER; 2. TCITEM 结构体 该结构体用来指定或获取标签页的属性。用在 TCM_INSERTITEM、TCM_GETITEM 和 TCM_SETITEM 消息中。 C++代码 1. typedef struct tagTCITEM { 2. UINT mask; // 掩码,可以是 TCIF_IMAGE(iImage 成员有效)、TCIF_ PARAM(lParam 成员有效)、TCIF_RTLREADING、TCIF_STATE、TCIF_TEXT(p szText 成员有效) 3. #if (_WIN32_IE >= 0x0300) 4. DWORD dwState; 5. DWORD dwStateMask; 6. #else 7. UINT lpReserved1; 8. UINT lpReserved2; 9. #endif 10. LPTSTR pszText; 11. int cchTextMax; 12. int iImage; 13. LPARAM lParam; // 与标签页关联的 32 位数据 14. } TCITEM, *LPTCITEM; 3. TCHITTESTINFO 结构体 该结构体包含了鼠标单击测试的信息。 C++代码 1. typedef struct tagTCHITTESTINFO { 2. POINT pt; // 鼠标点击测试的客户区坐标 3. UINT flags; // 接收点击测试的结果。有以下几种:TCHT_NOWHERE(坐标 点不在标签上)、TCHT_ONITEM(坐标点在标签上但不在标签文本或图标上)、TCHT _ONITEMICON(坐标点在标签图标上)、TCHT_ONITEMLABEL(坐标点在标签文本 上) 4. } TCHITTESTINFO, *LPTCHITTESTINFO; 4. NMTCKEYDOWN 结构体 该结构体包含了标签控件中键盘按下的相关信息。主要用在 TCN_KEYDOWN 通知消 息中。 C++代码 1. typedef struct tagNMTCKEYDOWN { 2. NMHDR hdr; 3. WORD wVKey; 4. UINT flags; 5. } NMTCKEYDOWN; 标签控件的上半部分就讲到这里了 VS2010/MFC 编程入门之三十三(常用控件:标签控件 Tab Control 下) 上一节中鸡啄米讲了标签控件知识的上半部分,本节继续讲下半部分。 标签控件的创建 MFC为标签控件的操作提供了 CTabCtrl 类。 与之前的控件类似,创建标签控件可以在对话框模板中直接拖入 Tab Control,也可以 使用 CTabCtrl 类的 Create 成员函数创建。Create 函数的原型如下: virtual BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 参数 dwStyle 为标签控件的风格,rect 为标签控件的位置和大小,pParentWnd 为指 向标签控件父窗口的指针,nID 指定标签控件的 ID。这里还是要具体说下 dwStyle,下面 列出了几种主要的控件风格: TCS_BUTTONS:标签(控件上部用来选择标签页的位置)外观为按钮风格,且整个 控件周围没有边框。 TCS_FIXEDWIDTH :所有标签具有相同的宽度。 TCS_MULTILINE:标签以多行显示,如果需要,可以显示所有标签。 TCS_SINGLELINE:只显示一行标签,用户可以滚动着看其他标签。 TCS_TABS:标签以普通标签样式显示,且整个控件周围有边框。 如果想了解标签控件的所有风格,可以查阅 MSDN。 CTabCtrl 类的主要成员函数 int GetCurSel( ) const; 获取标签控件中当前选择标签的索引。如果成功则返回选择标签的索引,否则返回-1 。 BOOL GetItem(int nItem,TCITEM* pTabCtrlItem) const; 获取标签控件中某个标签的信息。参数 nItem 为标签索引,pTabCtrlItem 为指向 TCIT EM 结构体的指针,用来接收标签信息。若获取成功返回 TRUE,否则返回 FALSE。 int GetItemCount( ) const; 获取标签控件中标签的数量。 int SetCurSel(int nItem); 在标签控件中选择某标签。参数 nItem 为要选择的标签的索引。如果成功则返回之前 选择标签的索引,否则返回-1。 BOOL SetItem(int nItem,TCITEM* pTabCtrlItem); 设置某标签的所有或部分属性。参数 nItem 为标签的索引,pTabCtrlItem 为指向 TCIT EM 结构体的指针,包含了新的标签属性。成功则返回 TRUE,否则返回 FALSE。 BOOL DeleteAllItems( ); 删除标签控件中所有标签。 BOOL DeleteItem(int nItem); 删除标签控件中的某个标签。参数 nItem 为要删除标签的索引。 LONG InsertItem(int nItem,LPCTSTR lpszItem); 在标签控件中插入新的标签。参数 nItem 为新标签的索引,lpszItem 为标签文本字符 串。如果插入成功则返回新标签的索引,否则返回-1。 标签控件的应用实例 最后鸡啄米依然是给大家写一个简单的实例,说明 CTabCtrl 类的几个成员函数及标签 控件通知消息等的使用方法。 此实例实现的功能:在一个标签控件中加入两个标签页,标签文本分别为“鸡啄米”和“ Android 开发网”,点击不同的标签显示不同的标签页。下面是具体实现步骤: 1. 创建一个基于对话框的 MFC 工程,名称设置为“Example33”。 2. 在自动生成的对话框模板 IDD_EXAMPLE33_DIALOG 中,删除“TODO: Place dial og controls here.”静态文本框、“OK”按钮和“Cancel”按钮。添加一个 Tab Control 控件,并 为其关联一个 CTabCtrl 类型的控件变量 m_tab。 3. 创建两个新的对话框,ID 分别设为 IDD_JIZHUOMI_DIALOG、IDD_ANDROID_DI ALOG,两者都将 Border 属性设为 None,Style 属性设为 Child。在对话框模板 IDD_JIZ HUOMI_DIALOG 中加入一个静态文本框,Caption 属性设为“鸡啄米 www.jizhuomi.com” ,并为其生成对话框类 CJzmDlg;在对话框模板 IDD_ANDROID_DIALOG 中也加入一个 静态文本框,Caption 属性设为“Android 开发网 www.jizhuomi.com/android”,并为其生成 对话框类 CAndroidDlg。 4. 在“Example33Dlg.h”文件中包含“JzmDlg.h”和“AndroidDlg.h”两个头文件,然后继续 在“Example33Dlg.h”文件中为 CExample33Dlg 类添加两个成员变量: CJzmDlg m_jzmDlg; CAndroidDlg m_androidDlg; 5. 在 CExample33Dlg 对话框初始化时,我们也初始化标签控件。修改 CExample33 Dlg::OnInitDialog()函数如下: C++代码 1. BOOL CExample33Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. CRect tabRect; // 标签控件客户区的位置和大小 32. 33. m_tab.InsertItem(0, _T("鸡啄米")); // 插入第一个标签“ 鸡啄米” 34. m_tab.InsertItem(1, _T("Android 开发网")); // 插入第二个标签“A ndroid 开发网” 35. m_jzmDlg.Create(IDD_JIZHUOMI_DIALOG, &m_tab); // 创建第一 个标签页 36. m_androidDlg.Create(IDD_ANDROID_DIALOG, &m_tab); // 创建第二 个标签页 37. 38. m_tab.GetClientRect(&tabRect); // 获取标签控件客户区 Rect 39. // 调整 tabRect,使其覆盖范围适合放置标签页 40. tabRect.left += 1; 41. tabRect.right -= 1; 42. tabRect.top += 25; 43. tabRect.bottom -= 1; 44. // 根据调整好的 tabRect 放置 m_jzmDlg 子对话框,并设置为显示 45. m_jzmDlg.SetWindowPos(NULL, tabRect.left, tabRect.top, tabR ect.Width(), tabRect.Height(), SWP_SHOWWINDOW); 46. // 根据调整好的 tabRect 放置 m_androidDlg 子对话框,并设置为隐藏 47. m_androidDlg.SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), SWP_HIDEWINDOW); 48. 49. return TRUE; // return TRUE unless you set the focus to a control 50. } 6. 运行程序,查看结果,这时我们发现切换标签时,标签页并不跟着切换,而总是显 示 CJzmDlg 对话框。 7. 我们要实现的是标签页的切换效果,所以还要为 m_tab 标签控件的通知消息 TCN_ SELCHANGE添加处理函数,并修改如下: C++代码 1. void CExample33Dlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT * pResult) 2. { 3. // TODO: Add your control notification handler code here 4. *pResult = 0; 5. CRect tabRect; // 标签控件客户区的 Rect 6. 7. // 获取标签控件客户区 Rect,并对其调整,以适合放置标签页 8. m_tab.GetClientRect(&tabRect); 9. tabRect.left += 1; 10. tabRect.right -= 1; 11. tabRect.top += 25; 12. tabRect.bottom -= 1; 13. 14. switch (m_tab.GetCurSel()) 15. { 16. // 如果标签控件当前选择标签为“鸡啄米”,则显示 m_jzmDlg 对话框,隐藏 m _androidDlg 对话框 17. case 0: 18. m_jzmDlg.SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), SWP_SHOWWINDOW); 19. m_androidDlg.SetWindowPos(NULL, tabRect.left, tabRect.t op, tabRect.Width(), tabRect.Height(), SWP_HIDEWINDOW); 20. break; 21. // 如果标签控件当前选择标签为“Android 开发网”,则隐藏 m_jzmDlg 对话 框,显示 m_androidDlg 对话框 22. case 1: 23. m_jzmDlg.SetWindowPos(NULL, tabRect.left, tabRect.top, tabRect.Width(), tabRect.Height(), SWP_HIDEWINDOW); 24. m_androidDlg.SetWindowPos(NULL, tabRect.left, tabRect.t op, tabRect.Width(), tabRect.Height(), SWP_SHOWWINDOW); 25. break; 26. default: 27. break; 28. } 29. } 8. 再运行程序,最终的标签页切换效果如下面两图: 经过两讲内容,终于把标签控件的主要知识讲完了。 VS2010/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解) 上一节讲了标签控件 Tab Control以后,常用控件的内容就全部讲完了,当然并没有包 括所有控件,主要是一些很常用很重要的控件。本节开始鸡啄米将为大家讲解菜单的概念 及使用。 菜单简介 菜单在界面设计中是经常使用的一种元素,包括 Windows 系统中的窗口、智能终端设 备的应用界面等都会经常见到菜单的身影。我们在对可视化窗口操作时,菜单确实提供了 很大方便。 菜单可以分为下拉式菜单和弹出式菜单。 下拉式菜单一般在窗口标题栏下面显示,大家还记得我们在VS2010/MFC 编程入门之 二(利用 MFC 向导生成单文档应用程序框架)中创建的 HelloWorld 单文档工程吗?它的 运行结果窗口的标题栏下就是下拉式菜单。下拉式菜单通常是由主菜单栏、子菜单及子菜 单中的菜单项和分隔条所组成的。 弹出式菜单一般可以通过单击鼠标右键等操作显示。它的主菜单不可见,只显示子菜 单。 VS2010 菜单资源详解 菜单也可以在 VS2010 的资源视图中直接创建编辑。我们先来创建一个新的MFC单文 档工程,具体看看菜单的组成结构及各种标记的意义。 按照 VS2010/MFC 编程入门之二中的步骤创建一个名为“Example34”的 MFC 单文档 工程。打开 Resource View 资源视图,展开 Example34->Example34.rc->Menu,我们可 以看到有一个 ID 为 IDR_MAINFRAME 菜单资源,双击打开,菜单资源显示如下图: 上边包含“File”的一栏是主菜单栏,点击“File”弹出子菜单,可以看到子菜单中有多个菜 单项和分隔条。菜单项中含有“...”则表示点击后会弹出对话框。 除了这些,我们还注意到,很多菜单项的标题文本中都有一个字母带下划线,带下划 线的字母为热键,例如,主菜单栏上的“File”中字母“F”带下划线,F 就是热键,程序运行并 显示窗口时,在键盘上点击 Alt+F 就等同于直接点菜单项 File,弹出 File 下的子菜单后, 点击“Open”的热键 O 就可以实现与直接点菜单项 Open 相同的功能。 那么热键是如何定义的呢?我们可以看下“File”菜单项的属性,Caption 为“&File”,很 明显,只要在要定义为热键的字母前加&就可以了。 有些菜单项的右侧还显示了一些字符串,例如,“New”的右侧显示有“Ctrl+N”,这些代 表的是快捷键,也就是“New”菜单项的快捷键是 Ctrl+N,“Open”菜单项的快捷键是 Ctrl+O ,用这些组合键就能实现与相应菜单项一样的功能。 快捷键如何定义?我们再来看看“Open”菜单项的 Caption 属性,为“&Open...\tCtrl+O” ,这里的\t 表示在显示前面的文本后跳格再显示快捷键 Ctrl+O,但这样设置其 Caption 属 性只是能显示出快捷键,要实现快捷键的功能还需要在 Accelerator 资源中设定。资源视 图中展开 Example34.rc->Accelerator,双击打开下面的 IDR_MAINFRAME,如下图: Accelerator 中有四列,分别为:ID、Modifier、Key 和 Type。ID 就是菜单项的 ID, Modifer 和 Key 就代表了组合键。例如,Open 菜单项的 ID 为 ID_FILE_OPEN,Modifer 为“Ctrl”,Key 为“O”。 VS2010 菜单资源编辑 我们试着在 Example34 的 IDR_MAINFRAME 菜单资源中添加菜单项。 在主菜单栏的“Help”菜单项上点右键,弹出右键菜单,选择“Insert New”,就在“Help” 菜单项前添加了一个空的菜单项,我们可以直接在其中输入标题,也可以在属性页中设置 Caption 属性,标题设为“&Tools”。 然后编辑 Tools 下子菜单的第一个菜单项,标题设为“&Draw\tCtrl+D”,即热键为 D, 快捷键为 Ctrl+D。其 ID 默认为 ID_TOOLS_DRAW。为了实现快捷键的功能,还需要编辑 Accelerator,打开 Accelerator,在最下面的空白行中,ID 选择为 ID_TOOLS_DRAW,M odifier 选择“Ctrl”,Key 输入“D”,这样就设置好了快捷键。 最终的菜单资源如下图: 本节内容就是这些了,主要是关于菜单的一些基础知识,比较好理解 VS2010/MFC 编程入门之三十五(菜单:菜单及 CMenu 类的使 用) 鸡啄米在上一节中讲的是VS2010 的菜单资源,本节主要讲菜单及 CMenu 类的使用。 CMenu 类的主要成员函数 MFC为菜单的操作提供了 CMenu 类,下面鸡啄米就常用的几个成员函数进行简单的 介绍。 BOOL LoadMenu(UINT nIDResource); 加载菜单资源,并将其附加到 CMenu 对象上。参数 nIDResource 指定了要加载的菜 单资源的 ID。如果菜单加载成功则返回 TRUE,否则返回 FALSE。 BOOL DeleteMenu(UINT nPosition,UINT nFlags); 在菜单中删除一个菜单项。参数 nPosition 指定要删除的菜单项。参数 nFlags 就用来 解释 nPosition 的意义,为 MF_BYCOMMAND 时说明 nPosition 表示菜单项的 ID,为 MF _BYPOSITION 时说明 nPosition 表示菜单项的位置,第一个菜单项的位置为 0。如果删除 菜单项成功则返回 TRUE,否则返回 FALSE。 BOOL TrackPopupMenu(UINT nFlags,int x,int y,CWnd* pWnd,LPCRECT lpRect = 0); 用来在指定位置显示一个浮动的弹出式菜单。参数 nFlags 指定屏幕坐标和鼠标位置的 标志,可以是以下取值: TPM_CENTERALIGN:菜单在水平方向上相对于参数 x 指定的坐标值居中显示 TPM_LEFTALIGN:菜单的左侧与参数 x 指定的坐标值对齐 TPM_RIGHTALIGN:菜单的右侧与参数 x 指定的坐标值对齐 TPM_BOTTOMALIGN:菜单的底部与参数 y 指定的坐标值对齐 TPM_TOPALIGN:菜单项的顶部与参数 y 指定的坐标值对齐 TPM_VCENTERALIGN:菜单在垂直方向上相对于参数 y 指定的坐标值居中显示 这里先介绍这几个比较常用的,其他可参见 MSDN。参数 x 指定弹出式菜单的水平方 向的屏幕坐标,参数 y 指定菜单顶部垂直方向上的屏幕坐标,参数 pWnd 指明哪个窗口拥 有此弹出式菜单,不能为 NULL,参数 lpRect 忽略。 UINT CheckMenuItem(UINT nIDCheckItem,UINT nCheck); 在弹出菜单中为菜单项增加选中标记或移除选中标记。参数 nIDCheckItem 指定要选 中或取消选中的菜单项。参数 nCheck 指定菜单项的选中状态和如何根据 nIDCheckItem 确定菜单项的位置,可以是 MF_CHECKED 或 MF_UNCHECKED 与 MF_BYPOSITION 或 MF_BYCOMMAND 的组合,这几个标志的含义如下: MF_BYCOMMAND:为默认值。说明参数 nIDCheckItem 表示菜单项的 ID MF_BYPOSITION:说明参数 nIDCheckItem 表示菜单项的位置,第一个菜单项的位 置是 0 MF_CHECKED:为菜单项添加选中标记 MF_UNCHECKED:为菜单项移除选中标记 该函数返回菜单项之前的状态:MF_CHECKED 或 MF_UNCHECKED, 如果菜单项不 存在则返回 0xFFFFFFFF。 UINT EnableMenuItem(UINT nIDEnableItem,UINT nEnable); 激活、禁用菜单项或使其变灰。参数 nIDEnableItem 指定要激活、禁用或变灰的菜单 项。参数 nEnable 指定操作的类型,可以是 MF_DISABLED、MF_ENABLED 或 MF_GR AYED 与 MF_BYCOMMAND 或 MF_BYPOSITION 的组合,这些值的含义如下: MF_BYCOMMAND:同 CheckMenuItem MF_BYPOSITION:同 CheckMenuItem MF_DISABLED:禁用菜单项,使其不能被选择但不变灰 MF_ENABLED:激活菜单项,使其能够被选择并由变灰状态恢复 MF_GRAYED:禁用菜单项,使其不能被选择并变灰 该函数返回菜单项之前的状态:MF_DISABLED、MF_ENABLED 或 MF_GRAYED CMenu* GetSubMenu(int nPos) const; 获取弹出菜单的 CMenu 对象。参数 nPos 指定弹出菜单在菜单中的位置,不能使用 I D。返回值是 CMenu 对象的指针,该 CMenu 对象的 m_hMenu 成员为由 nPos 指定的弹 出菜单的句柄,如果不存在这样的 CMenu 对象则返回 NULL,然后创建一个临时弹出菜单 。 CMenu 类的成员函数先讲这些,如果大家需要用其他的函数可以到 MSDN 中查看, 解释的很清楚。 菜单消息 菜单主要能发送两种消息:COMMAND 消息和 UPDATE_COMMAND_UI 消息。下面 分别讲解: COMMAND 消息:在菜单项被点击时发送该消息。 UPDATE_COMMAND_UI 消息:用来维护菜单项的各项状态,包括激活、禁用、变 灰、选中、未选中等。在下拉菜单每次打开的时候,所有菜单项的此消息都会被发送出去 。如果所属类中为菜单项的该消息添加了处理函数,则执行相应函数更新菜单状态,如果 菜单项没有此消息处理函数,也没有 COMMAND 消息的处理函数,那么它就会变灰。 菜单的应用实例 鸡啄米先讲一下本实例要实现的功能,此实例是在上一节创建的单文档工程 Example 34 的基础上完成的,上一节中为主菜单栏添加了“Tools”菜单项,并设置它的第一个子菜单 项为“Draw”,另外我们还要为主菜单栏添加“Settings”项,然后为其添加一个子菜单项“Dra w Enable”,我们通过“Draw Enable”菜单项的选中状态控制菜单项“Draw”的激活状态,如 果“Draw Enable”菜单项选中,则“Draw”菜单项激活,点击它弹出一个 MessageBox,否则 “Draw”菜单项禁用。程序中已经在 Example34View 类中自动生成了 OnRButtonUp(UINT / * nFlags */, CPoint point)函数,并在其中实现了弹出右键菜单的功能,这里鸡啄米用 CMe nu 类的 TrackPopupMenu 成员函数重新做一遍。 注意:Example34 的 CMainFrame 类中定义的菜单并没有使用常用的 CMenu 类,而 是用的 CMFCMenuBar 类(自 VS2008 起提供),但菜单的消息处理函数的添加是相同的 。 下面是具体步骤: 1. 打开 Example34 工程的 IDR_MAINFRAME 菜单资源,在“Help”菜单项前通过“Inse rt New”操作插入一个菜单项,Caption 设为“Settings”,在新菜单项的子菜单中再添加一个 菜单项,Caption 设为“Draw Enable”,ID 默认为 ID_SETTINGS_DRAWENABLE。 2. 因为此菜单为 CMainFrame 所拥有,所以我们在 CMainFrame 类中对菜单进行操 作。在“MainFrm.h”中为 CMainFrame 类添加成员变量 bool m_bDraw,以标识当前是否可 以点击 Tools->Draw 菜单项,并在 CMainFrame 类的构造函数中为 m_bDraw 初始化:m _bDraw = TRUE。 3. 为菜单项 Tools->Draw 的 COMMAND 消息和 UPDATE_COMMAND_UI 消息分别 添加处理函数 CMainFrame::OnToolsDraw()和 OnUpdateToolsDraw(CCmdUI *pCmdUI) ,这里要注意,添加处理函数时 class list 中应选择 CMainFrame,修改两个函数的实现为 : C++代码 1. void CMainFrame::OnToolsDraw() 2. { 3. // TODO: Add your command handler code here 4. // 弹出提示框 5. MessageBox(_T("Draw")); 6. } 7. 8. void CMainFrame::OnUpdateToolsDraw(CCmdUI *pCmdUI) 9. { 10. // TODO: Add your command update UI handler code here 11. // 根据 m_bDraw 的值设置是否激活 12. pCmdUI->Enable(m_bDraw); 13. } 4. 为菜单项 Settings->Draw Enable 的 COMMAND 消息和 UPDATE_COMMAND_UI 消息分别添加处理函数 CMainFrame::OnSettingsDrawenable()和 OnUpdateSettingsDraw enable(CCmdUI *pCmdUI),并将它们的实现修改为: C++代码 1. void CMainFrame::OnSettingsDrawenable() 2. { 3. // TODO: Add your command handler code here 4. // 绘图使能标识取反 5. m_bDraw = !m_bDraw; 6. } 7. 8. 9. void CMainFrame::OnUpdateSettingsDrawenable(CCmdUI *pCmdUI) 10. { 11. // TODO: Add your command update UI handler code here 12. // 根据 m_bDraw 的值设置是否选中 13. pCmdUI->SetCheck(m_bDraw); 14. } 5. 运行程序,效果图如下: 6. 接下来我们要重新实现右键菜单。大家以后可以仿照VS2010自动生成的代码实现 右键菜单,也可以用鸡啄米下面讲到的方法。首先将 CExample34View::OnRButtonUp(UI NT /* nFlags */, CPoint point)函数内的代码都注释掉,保证原来的弹出方法失效。 7. 自动生成代码是在鼠标弹起时实现的右键菜单,我们这里改为在鼠标按下时就弹出 右键菜单。在 class view 类视图中点击 CExample34View,然后在属性页的 messages 列 表中找到 WM_RBUTTONDOWN,添加其消息响应函数 CExample34View::OnRButtonDo wn(UINT nFlags,CPoint point),修改其实现为: C++代码 1. void CExample34View::OnRButtonDown(UINT nFlags, CPoint point) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. CMenu menu; // 菜单(包含主菜单栏和子菜单) 5. CMenu *pSubMenu; // 右键菜单 6. 7. // 加载菜单资源到 menu 对象 8. menu.LoadMenu(IDR_POPUP_EDIT); 9. // 因为右键菜单是弹出式菜单,不包含主菜单栏,所以取子菜单 10. pSubMenu = menu.GetSubMenu(0); 11. // 将坐标值由客户坐标转换为屏幕坐标 12. ClientToScreen(&point); 13. // 弹出右键菜单,菜单左侧与 point.x 坐标值对齐 14. pSubMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, t his); 15. 16. CView::OnRButtonDown(nFlags, point); 17. } 8. 最终的右键菜单效果: 本节内容不少,大家可以慢慢消化 VS2010/MFC 编程入门之三十六(工具栏:工具栏资源及 CToolBar 类) 上一节中鸡啄米讲了菜单及 CMenu 类的使用,这一节讲与菜单有密切联系的工具栏 。 工具栏简介 工具栏一般位于主框架窗口的上部,菜单栏的下方,由一些带图片的按钮组成。当用 户用鼠标单击工具栏上某个按钮时,程序会执行相应的操作,如果鼠标没有点击,只是停 留在某个按钮上一会后,会弹出一个小窗口显示提示信息。 一般工具栏中的按钮在菜单栏中都有对应的菜单项中,即点击工具栏按钮与点击菜单 项的效果相同。但工具栏中的按钮都显式的排列出来,操作很方便,而且按钮上的图片描 述功能更直观,所以工具栏作为用户操作接口来说比菜单更加便捷。 VS2010 工具栏资源详解 鸡啄米仍然以VS2010/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解)中创 建的单文档工程 Example34 为基础,讲解工具栏资源。 在 Example34 工程中,打开 Resource View 资源视图,展开 Example->Example34.r c->Toolbar,我们可以看到有一个 ID 为 IDR_MAINFRAME 的工具栏资源,双击打开,工 具栏资源显示如下: 以 IDR_MAINFRAME 工具栏的第一个按钮为例说明工具栏按钮的各项属性。用鼠标 单击工具栏资源上的第一个按钮,属性页中就会显示其属性。下面分别讲解各项属性。 ID 属性:ID_FILE_NEW。不知大家是否还记得,菜单 IDR_MAINFRAME 的菜单项 F ile->New 的 ID 也是 ID_FILE_NEW,两者 ID 相同,正是如此才使得工具栏第一个按钮与 菜单项 File->New 能实现相同的功能。所以大家一定要记住,如果想让工具栏某个按钮与 菜单栏某个菜单项点击后执行的操作相同,就要为两者设置相同的 ID。 Prompt 属性:Create a new document\nNew。此属性为工具栏按钮的提示文本。在 鼠标指向此按钮时,状态栏中会显示“Create a new document”,当弹出提示信息窗口时会 显示包含“New”的提示信息。“\n”是两者的分隔转义符。 Height 属性:15。此属性为工具栏按钮的像素高度。 Width 属性:16。此属性为工具栏按钮的像素宽度。 工具栏资源的最右边总是会有一个待编辑的按钮,我们对其进行编辑后,工具栏资源 会自动增加一个新的空白按钮,这也实现了按钮的添加操作。如果我们想要删除某个按钮 ,就可以用鼠标左键点住它,拖出工具栏资源的范围即可。 另外,我们看到,第三个按钮(保存按钮)和第四个按钮(剪切按钮)之间有一些间 隙,在运行程序后会出现一个竖的分隔线,所以想要在两个按钮之间添加分隔线的话,可 以用鼠标左键拖住右边的按钮往右稍移动一些就可以了。 CToolBar 类的主要成员函数 MFC为工具栏的操作提供了 CToolBar 类。下面介绍 CToolBar 类的主要成员函数。 virtual BOOL CreateEx( CWnd* pParentWnd, DWORD dwCtrlStyle = TBSTYLE_FLAT, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP, CRect rcBorders = CRect(0, 0, 0, 0), UINT nID = AFX_IDW_TOOLBAR ); 创建工具栏对象。参数 pParentWnd 为工具栏父窗口的指针。参数 dwCtrlStyle 为工 具栏按钮的风格,默认为 TBSTYLE_FLAT,即“平面的”。参数 dwStyle 为工具栏的风格, 默认取值 WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP,由于是主框架窗口的子窗口 ,所以要有 WS_CHILD 和 WS_VISIBLE 风格,CBRS_ALIGN_TOP 风格表示工具栏位于 父窗口的顶部, 各种风格可以参见 MSDN 的 Toolbar Control and Button Styles 中的定义 。参数 rcBorders 为工具栏边框各个方向的宽度,默认为 CRect(0, 0, 0, 0),即没有边框。 参数 nID 为工具栏子窗口的 ID,默认为 AFX_IDW_TOOLBAR。 BOOL LoadBitmap(UINT nIDResource); 为工具栏加载位图。参数 nIDResource 为位图资源的 ID。成功则返回 TRUE,否则返 回 FALSE。注意,这里的位图资源应当为每个工具栏按钮都提供位图,如果图片不是标准 大小(16 像素宽,15 像素高),则需要调用 SetSizes 成员函数调整按钮大小和图片大小 。 BOOL LoadToolBar(UINT nIDResource); 加载由 nIDResource 指定的工具栏。参数 nIDResource 为要加载的工具栏的资源 ID 。成功则返回 TRUE,否则返回 FALSE。 void SetSizes(SIZE sizeButton,SIZE sizeImage); 设置工具栏按钮的大小和图片的大小。参数 sizeButton 为工具栏按钮的像素大小。参 数 sizeImage 为图片的像素大小。 void SetButtonStyle(int nIndex,UINT nStyle); 设置工具栏按钮或分隔线的风格,或者为按钮分组。参数 nIndex 为将要进行设置的按 钮或分隔线的索引。参数 nStyle 为按钮风格,可以是以下取值: TBBS_BUTTON 标准按钮(默认) TBBS_SEPARATOR 分隔条 TBBS_CHECKBOX 复选框 TBBS_GROUP 标记一组按钮的开始 TBBS_CHECKGROUP 标记一组复选框的开始 TBBS_DROPDOWN 创建下拉列表按钮 TBBS_AUTOSIZE 按钮的宽度根据按钮文本计算,而不基于图片大小 TBBS_NOPREFIX 按钮的文本没有快捷键前缀 UINT GetButtonStyle(int nIndex) const; 获取工具栏按钮或分隔条的风格。风格可参考 SetButtonStyle。参数 nIndex 为按钮或 分隔条的索引。 BOOL SetButtonText(int nIndex,LPCTSTR lpszText); 设置工具栏按钮的文本。参数 nIndex 为工具栏按钮的索引。参数 lpszText 为指向要 设置的文本字符串的指针。设置成功则返回 TRUE,否则返回 FALSE。 CString GetButtonText(int nIndex) const; 获取工具栏按钮上显示的文本。参数 nIndex 为工具栏按钮的索引。 VS2010/MFC 编程入门之三十七(工具栏:工具栏的创建、停靠 与使用) 鸡啄米在上一节教程中讲了工具栏资源及 CToolBar 类,本节继续讲解工具栏的相关 知识,主要内容包括工具栏的创建、停靠与使用。 工具栏的使用 上一节中鸡啄米提到过,一般情况下工具栏中的按钮在菜单栏中都有对应的菜单项, 两者实现的功能相同,要想实现这种效果,只需要将工具栏按钮的 ID 与对应的菜单栏中菜 单项的 ID 设置为相同值即可。 在实际使用工具栏时,除了前面讲的资源编辑外,其他使用与菜单类似。例如,对 C OMMAND 消息和 UPDATE_COMMAND_UI 消息,可以像VS2010/MFC 编程入门之三十 五(菜单:菜单及 CMenu 类的使用)中的菜单应用实例那样为工具栏按钮添加消息处理 函数。 如果工具栏按钮对应的菜单项已经添加了消息处理函数,那么就不必再为它添加了, 因为它的 ID 与菜单项相同,所以会调用同样的消息处理函数。这样点击工具栏按钮与点击 相应菜单项执行相同的功能,在菜单项为选中、激活或禁用等状态时,工具栏按钮会有一 样的状态。 工具栏的创建 大家在第三十四讲创建的 Example34 工程的 CMainFrame 类中看到,它创建工具栏 所使用的类并不是常用的 CToolBar 类,而是 CMFCToolBar 类。CMFCToolBar 类是自 V S2008 以来 MFC 提供的类,它与 CToolBar 类有些类似,但功能更丰富。这里要注意,C MFCToolBar 类与 CToolBar 类并没有任何派生关系。 鸡啄米这里就以 CMFCToolBar 类来讲讲工具栏的创建步骤: 1. 创建工具栏资源。 2. 构造 CMFCToolBar 类的对象。 3. 调用 CMFCToolBar 类的 Create 或 CreateEx 成员函数创建工具栏。 4. 调用 LoadToolBar 成员函数加载工具栏资源。 大家可以对应着看看 Example34 的 CMainFrame 类自动生成的代码中创建工具栏的 过程。 工具栏 IDR_MAINFRAME 的资源已经自动创建好。在 MainFrm.h 文件对 CMainFram e 类的声明中,定义了 CMFCToolBar 类的对象作为成员对象:CMFCToolBar m_wndTo olBar;。然后在 CMainFrame::OnCreate 函数的实现中可以看到工具栏的创建以及加载工 具栏资源的代码,如下: C++代码 1. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 2. { 3. if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) 4. return -1; 5. ......略 6. 7. // 调用 CreateEx 函数创建工具栏,并调用 LoadToolBar 函数加载工具栏资 源 8. if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | W S_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYB Y | CBRS_SIZE_DYNAMIC) || 9. !m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_ MAINFRAME_256 : IDR_MAINFRAME)) 10. { 11. TRACE0("Failed to create toolbar\n"); 12. return -1; // fail to create 13. } 14. 15. ......略 16. 17. return 0; 18. } 因为创建框架窗口时需要调 OnCreate 函数,所以工具栏的创建也是在 OnCreate 中 完成的。 工具栏的停靠 在创建好工具栏后,如果想要停靠工具栏,也需要添加相应的停靠代码。工具栏停靠 的步骤及需要调用的函数如下(前两个步骤可以颠倒顺序): 1. 在框架窗口中启用停靠。 若要将工具栏停靠到某个框架窗口,则必须启用该框架窗口(或目标)以允许停靠 。可以在 CFrameWndEx 类中调用下面的成员函数来实现: BOOL EnableDocking(DWORD dwDockStyle); 该函数采用一个 DWORD 参数,用来指定框架窗口的哪个边可以接受停靠,可以有 四种取值:CBRS_ALIGN_TOP(顶部)、CBRS_ALIGN_BOTTOM(底部)、CBRS_A LIGN_LEFT(左侧)、CBRS_ALIGN_RIGHT(右侧)。如果希望能够将控制条停靠在任 意位置,将 CBRS_ALIGN_ANY 作为参数传递给 EnableDocking。 2. 工具栏启用停靠。 框架窗口启用停靠准备好后,必须以相似的方式准备工具栏。为想要停靠的每一个 工具栏 CMFCToolBar 对象调用下面的函数: virtual void EnableDocking(DWORD dwAlignment); 允许工具栏停靠到框架窗口,并指定工具栏应停靠的目标边。此函数指定的目标边 必须与框架窗口中启用停靠的边匹配,否则工具栏无法停靠,为浮动状态。 3. 停靠工具栏。 当用户试图将工具栏放置在允许停靠的框架窗口某一边时,需要框架 CFrameWnd Ex 类调用以下函数: void DockPane(CBasePane* pBar,UINT nDockBarID=0,LPCRECT lpRect=NULL); 参数 pBar 为要停靠的控制条的指针,参数 nDockBarID 为要停靠的框架窗口某条 边的 ID,可以是以下四种取值:AFX_IDW_DOCKBAR_TOP、AFX_IDW_DOCKBAR_B OTTOM、AFX_IDW_DOCKBAR_LEFT、AFX_IDW_DOCKBAR_RIGHT。 下面我们接着看 Example34 的 CMainFrame 类的 OnCreate 函数实现中,工具栏的 停靠过程: C++代码 1. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 2. { 3. if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) 4. return -1; 5. 6. ......略 7. 8. // 调用 CreateEx 函数创建工具栏,并调用 LoadToolBar 函数加载工具栏资 源 9. if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | W S_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYB Y | CBRS_SIZE_DYNAMIC) || 10. !m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_ MAINFRAME_256 : IDR_MAINFRAME)) 11. { 12. TRACE0("Failed to create toolbar\n"); 13. return -1; // fail to create 14. } 15. 16. ......略 17. 18. // TODO: Delete these five lines if you don't want the tool bar and menubar to be dockable 19. m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY); 20. // 为 m_wndToolBar 启用停靠 21. m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); 22. // 为框架窗口启用停靠 23. EnableDocking(CBRS_ALIGN_ANY); 24. DockPane(&m_wndMenuBar); 25. // 停靠工具栏 26. DockPane(&m_wndToolBar); 27. 28. ......略 29. 30. return 0; 31. } VS2010/MFC 编程入门之三十八(状态栏的使用详解) 上一节中鸡啄米讲了工具栏的创建、停靠与使用,本节来讲解状态栏的知识。 状态栏简介 状态栏相信大家在很多窗口中都能见到,它总是用来显示各种状态。状态栏实际上也 是一个窗口,一般分为几个窗格,每个窗格分别用来显示不同的信息和状态等,如菜单项 和工具栏按钮的提示信息。 用 MFC 向导生成的单文档或多文档程序都会自动创建状态栏,大家可以运行下VS20 10/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解)中创建的 Example34 程序, 在结果界面中可以看到窗口底部有个状态栏,该状态栏被分为了几个窗格,分别用来显示 菜单项和工具栏按钮的提示信息及 Caps Lock、Num Lock、Scroll Lock 键的状态。 当然,我们可以自定义状态栏,加入新的提示信息或指示器。 CStatusBar 类 MFC为状态栏提供了 CStatusBar 类,封装了状态栏的属性和操作。 下面是 CStatusBar 类几个主要的成员函数: virtual BOOL Create(CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_ VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR); 创建一个状态栏。参数 pParentWnd 为状态栏父窗口的指针,参数 dwStyle 为状态栏 的风格,除了标准的 Windows 风格外,它还支持: CBRS_TOP:位于框架窗口的顶部。 CBRS_BOTTOM:位于框架窗口的底部。 CBRS_NOALIGN:父窗口大小改变时状态栏不会被重新定位。 参数 nID 指定状态栏的 ID。 BOOL SetIndicators(const UINT* lpIDArray, int nIDCount); 为每个指示器设置显示文本,具体来说,就是用 lpIDArray 数组中的对应元素为每个 指示器设置一个 ID,然后加载每个 ID 代表的字符串,设置为这些指示器的显示文本。参 数 lpIDArray 为指向一个 ID 数组的指针,参数 nIDCount 为 lpIDArray 数组的元素个数。 UINT GetItemID(int nIndex) const; 获取由 nIndex 指定的指示器的 ID。参数 nIndex 为要获取 ID 的指示器索引。 CString GetPaneText(int nIndex) const; 获取状态栏窗格中显示的文本。参数 nIndex 为要获取文本的窗格的索引。返回值为包 含窗格文本的 CString 对象。 BOOL SetPaneText(int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE); 设置状态栏窗格的显示文本。参数 nIndex 为要设置文本的窗格的索引,参数 lpszNew Text 为指向新的窗格文本的指针,参数 bUpdate 表示是否设置后立即更新显示。如果设置 成功则返回 TRUE,否则返回 FALSE。 状态栏的创建 在 Example34 程序中,我们在 CMainFrame 类中看到,创建状态栏时使用的是 CMF CStatusBar 类对象。CMFCStatusBar 类是自 VS2008 以来提供的状态栏类,用法与 CSta tusBar 类相似,甚至很多成员函数也类似,但它的功能更加丰富。关于 CMFCStatusBar 类的成员函数可以查阅 MSDN 了解。 鸡啄米下面就以 Example34 程序的 CMFCStatusBar 类对象为例,来讲讲状态栏的创 建步骤: 1. 构造一个 CMFCStatusBar 类的对象。 在 MainFrm.h 文件中,为 CMainFrame 类定义了一个成员对象:CMFCStatusBar m_wndStatusBar;。 2. 调用 CMFCStatusBar::Create 函数来创建状态栏窗口。 在 CMainFrame::OnCreate 函数的实现中,我们可以找到 CMFCStatusBar::Create 函 数的调用: C++代码 1. if (!m_wndStatusBar.Create(this)) 2. { 3. TRACE0("Failed to create status bar\n"); 4. return -1; // fail to create 5. } 3. 调用 CMFCStatusBar::SetIndicators 函数为状态栏划分窗格,并为每个指示器设置 显示文本。 CMFCStatusBar::SetIndicators 函数需要一个 ID 数组的参数,在 MainFrm.cpp 中, 如下定义了一个窗格 ID 的数组: C++代码 1. static UINT indicators[] = 2. { 3. ID_SEPARATOR, // status line indicator 4. ID_INDICATOR_CAPS, 5. ID_INDICATOR_NUM, 6. ID_INDICATOR_SCRL, 7. }; indicators 数组定义了状态栏窗格的划分信息。第一个元素一般为 ID_SEPARATOR, 对应的窗格用来显示命令提示信息,上面数组中的后三项为指示器文本的字符串 ID,可以 根据这些 ID 在 String Table 字符串资源中找到相应的字符串,查找方法是,在 Resource View 资源视图中,打开 String Table 字符串资源,可以看到有 ID、Value 和 Caption 三列 ,在 ID 列中找到需要的 ID,对应的 Caption 列文本就是要查找的字符串。ID_INDICATO R_CAPS、ID_INDICATOR_NUM 和 ID_INDICATOR_SCRL 对应的字符串分别是 CAP、 NUM、SCRL,对应的三个窗格分别为 Caps Lock 指示器、Num Lock 指示器和 Scroll Lo ck 指示器。 定义了指示器数组就可以使用 CMFCStatusBar::SetIndicators 函数为状态栏划分窗格 了,依然是在 CMainFrame::OnCreate 函数中调用: C++代码 1. m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/siz eof(UINT)); 这样状态栏就创建完成了,之后我们可以通过 CMFCStatusBar::SetPaneText 设置窗 格的文本。 状态栏应用实例 鸡啄米看到网上有很多人在问,怎样在状态栏添加一个时间窗格,用来显示系统时间 ,本节就给出这样一个实例。此实例依然是在 Example34 的基础上进行修改的。步骤如下 : 1. 在 Resource View 资源视图中打开 String Table 字符串资源,然后在最后一行的下 一个空白行中,或者任意处点右键选择“New String”,添加一个新的字符串资源,ID 为 ID _INDICATOR_TIME,Value 设为一个不与任何其他字符串资源重复的整数值,Caption 设 为"00:00:00",这是为了给时间的显示预留空间,因为状态栏会根据字符串的长度为相应 的窗格确定缺省宽度。 2. 在 indicators 数组的第一个元素 ID_INDICATOR_SCRL 后插入 ID_INDICATOR_TI ME。 C++代码 1. static UINT indicators[] = 2. { 3. ID_SEPARATOR, // status line indicator 4. ID_INDICATOR_CAPS, 5. ID_INDICATOR_NUM, 6. ID_INDICATOR_SCRL, 7. ID_INDICATOR_TIME 8. }; 3. 要实时显示系统时间,就需要使用一个定时器,每秒钟更新一次时间显示。在 CMa inFrame::OnCreate 函数中开启定时器,代码如下: C++代码 1. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 2. { 3. if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) 4. return -1; 5. 6. ......略 7. 8. // 启动定时器,定时器 ID 为 1,定时时间为 1000ms,即 1s 9. SetTimer(1, 1000, NULL); 10. 11. return 0; 12. } 4. 在 Class View 类视图中找到 CMainFrame 类,右键选择“Properties”,然后在显示 出来的属性页中,点工具栏上的 Messages 按钮,即显示出消息列表,找到 WM_TIMER 消息,添加其消息处理函数 void CMainFrame::OnTimer(UINT_PTR nIDEvent),并修改此 函数实现如下: C++代码 1. void CMainFrame::OnTimer(UINT_PTR nIDEvent) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. CString strTime; 5. // 获取系统当前时间,并保存到 curTime 6. CTime curTime = CTime::GetCurrentTime(); 7. 8. // 格式化 curTime,将字符串保存到 strTime 9. strTime = curTime.Format(_T("%H:%M:%S")); 10. // 在状态栏的时间窗格中显示系统时间字符串 11. m_wndStatusBar.SetPaneText(4, strTime); 12. 13. CFrameWndEx::OnTimer(nIDEvent); 14. } 5. 运行程序,我们看到状态栏的最后一个窗格中能够实时显示系统时间,如下图: VS2010/MFC 编程入门之三十九(文档、视图和框架:概述) 前面几节讲了菜单、工具栏和状态栏的使用,鸡啄米本节开始将为大家讲解文档、视 图和框架的知识。 文档、视图和框架简介 在VS2010/MFC 编程入门之三十四(菜单:VS2010 菜单资源详解)创建的单文档工 程 Example34 中,我们可以看到MFC向导自动为我们生成了 CExample34Doc 类、CExa mple34View 类和 CMainFrame 类,它们就分别是文档类、视图类和框架窗口类。 文档/视图结构是 MFC 提供的一种不错的设计,它将数据的处理和显示分开来,这样 更便于我们对程序的维护和扩展。下面分别介绍这种结构中涉及到的几个重要概念。 文档 文档对象用于管理和维护数据,包括保存数据、取出数据以及修改数据等操作,在数 据被修改以后,文档可以通知其对应的所有视图更新显示。 视图 视图对象将文档中的数据可视化,负责从文档对象中取出数据显示给用户,并接受用 户的输入和编辑,将数据的改变反映给文档对象。视图充当了文档和用户之间媒介的角色 。 框架 一个文档可能有多个视图界面,这就需要有框架来管理了。框架就是用来管理文档和 视图的。框架窗口是应用程序的主窗口,应用程序执行时会先创建一个最顶层的框架窗口 。视图窗口是没有菜单和边界的子窗口,它必须包含在框架窗口中,即置于框架窗口的客 户区内。 文档模板 文档模板中存放了与文档、视图和框架相关的信息。应用程序通过文档模板创建文档 对象、框架窗口对象和视图对象。另外,文档、视图和框架之间的关系也是由文档模板来 管理的。 我们来看看 Example34 单文档程序中,CExample34App 应用程序类的成员函数 CEx ample34App::InitInstance()创建并注册文档模板的部分: C++代码 1. BOOL CExample34App::InitInstance() 2. { 3. ......略 4. // Register the application's document templates. Document templates 5. // serve as the connection between documents, frame window s and views 6. CSingleDocTemplate* pDocTemplate; 7. pDocTemplate = new CSingleDocTemplate( 8. IDR_MAINFRAME, 9. RUNTIME_CLASS(CExample34Doc), 10. RUNTIME_CLASS(CMainFrame), // main SDI frame wind ow 11. RUNTIME_CLASS(CExample34View)); 12. if (!pDocTemplate) 13. return FALSE; 14. AddDocTemplate(pDocTemplate); 15. 16. ......略 17. 18. return TRUE; 19. } 在构造文档模板类 CSingleDocTemplate 的对象时,第一个参数是资源 ID IDR_MAIN FRAME,它包括框架窗口图标等,后面的三个参数都是 RUNTIME_CLASS 宏的调用,R UNTIME_CLASS 用于获取类的运行时信息,文档模板可以根据这些动态创建信息来创建 相应类的对象,即文档对象、框架窗口对象和视图对象。AddDocTemplate 函数用来注册 文档模板对象。 框架类、文档类和视图类 在VS2010自动生成的代码中,框架类继承于 CFrameWndEx 类,文档类继承于 CDo cument 类,视图类继承于 CView 类。 CFrameWndEx 类又继承于 CFrameWnd 类,CFrameWnd 类中用于管理文档和视 图的成员函数包括: virtual CDocument* GetActiveDocument( ); 获得当前活动视图对应文档对象的指针,如果不存在则返回 NULL。 CView* GetActiveView( ) const; 获得当前活动视图对象的指针,如果不存在则返回 NULL。 void SetActiveView(CView* pViewNew, BOOL bNotify = TRUE); 设置活动视图。参数 pViewNew 为要激活的视图对象的指针,参数 bNotify 指定视图 是否接收激活通知。 CDocument 类的主要成员函数: virtual BOOL OnNewDocument( ); 创建新文档。可以重载使用。 virtual BOOL OnOpenDocument(LPCTSTR lpszPathName); 打开文档。参数 lpszPathName 为要打开的文档的路径。可以重载使用。 virtual BOOL OnSaveDocument(LPCTSTR lpszPathName); 保存文档。参数 lpszPathName 指定文档保存到的全路径。可以重载使用。 CDocTemplate* GetDocTemplate( ) const; 获取此文档类型对应的文档模板对象的指针。如果此文档没有被文档模板管理则返回 NULL。 virtual POSITION GetFirstViewPosition( ) const; 获取文档中视图列表的第一个视图的位置。 virtual CView* GetNextView(POSITION& rPosition) const; 利用此函数可以迭代处理文档的所有视图。参数 rPosition 为上一次调用 GetFirstView Position 或 GetNextView 成员函数返回的 POSITION 值的引用。 void AddView(CView* pView); 为文档增加一个视图。参数 pView 为要增加的视图对象的指针。 void RemoveView(CView* pView); 移除某个视图与文档的关联。参数 pView 为要移除的视图对象的指针。 void UpdateAllViews(CView* pSender, LPARAM lHint = 0L, CObject* pHint = N ULL); 在文档被更改后调用此函数更新视图。参数 pSender 指向修改文档的视图,实际应用 时常用来指定哪个视图不需要更新,如果更新所有视图则设为 NULL,参数 lHint 包含了文 档修改的信息,参数 pHint 指向存储文档修改信息的对象。 CView 类中与文档/视图结构相关的成员函数包括: CDocument* GetDocument( ) const; 获取视图关联的文档对象的指针。如果视图没有关联到文档上则返回 NULL。 VS2010/MFC 编程入门之四十(文档、视图和框架:各对象之间 的关系) 前面一节中鸡啄米进行了文档、视图和框架的概述,本节主要讲解文档、视图、框架 结构中各对象之间的关系。 各个对象之间的关系 文档、视图、框架结构中涉及到的对象主要有:应用程序对象、文档模板对象、文档 对象、视图对象和框架窗口对象等。根据上一节的概述,大家对它们的概念已经有所了解 了,下面就对它们之间的关系进行总结和概括,并对各个关系中用到的类的成员函数进行 介绍。 1. 应用程序对象保存了一个文档模板的列表。在任何对象中调用全局函数 AfxGetApp 都可以获得应用程序对象的指针。通过调用 CWinAppEx::GetFirstDocTemplatePosition、 CWinAppEx::GetNextDocTemplate 函数可以遍历所有的文档模板。 2. 文档模板对象用于维护文档、视图和框架窗口的映射关系,它包含有一个已打开文 档的列表。我们可以通过调用 CDocTemplate::GetFirstDocPosition、CDocTemplate::Get NextDoc 来遍历该文档模板对应的所有文档。 3. 框架窗口对象中包含有指向当前活动视图对象的指针。AfxGetApp()->m_pMainWn d 即为主框架窗口对象的指针。我们可以通过调用 CFrameWndEx::GetActiveView 来获取 当前活动视图对象的指针,并且使用 CFrameWndEx::GetActiveDocument 函数可以获得 当前活动视图对应的文档。 4. 文档对象中维护着该文档的视图列表,以及创建该文档的文档模板对象的指针。我 们可以通过调用 CDocument::GetFirstViewPosition,CDocument::GetNextView 来遍历该 文档关联的所有视图,调用 CDocument::GetDocTemplate 获取创建该文档的文档模板对 象的指针。 5. 视图是框架窗口的子窗口,它保存有指向对应的文档对象的指针。我们可以通过调 用 CView::GetParentFrame 获取其所属的框架窗口对象的指针,调用 CView::GetDocume nt 获取该视图对应的文档对象的指针。 另外,在 MDI 多文档程序中,调用 CMDIFrameWnd::MDIGetActive 可以获取当前活 动的 MDI 子窗口。 文档和视图的关系 应用程序可以是单文档程序也可以是多文档程序。单文档程序中主框架窗口和文档框 架窗口重合,而多文档程序的主框架窗口中有客户窗口,客户窗口中又包含了多个文档框 架窗口。 文档和视图是一对多的关系。一个文档可以对应多个视图,例如在 Word 中一个文档 有普通视图、大纲视图、Web 版式视图、阅读版式视图等多种视图。而一个视图只能属于 一个文档。最简单的应用程序是单文档单视图程序,除此之外还有单文档多视图程序、多 文档程序等。 每个文档对象都保存着一个视图列表,可以通过 CDocument::AddView 函数添加视图 ,通过 CDocument::RemoveView 函数删除视图,在数据发生变化时调用 CDocument::U pdateAllViews 函数更新所有视图。 在MFC中文档可以有三种视图模式: 1. 文档有多个视图对象,它们是同一个视图类的对象,每个视图对象位于一个独立的 文档框架窗口中。 2. 文档的基于同一个视图类的多个视图对象,位于同一个文档框架窗口中。Word 的 子窗口就是这种视图模式。   3.文档的视图对象属于不同的视图类,但所有的视图对象位于同一文档框架窗口中。 鸡啄米在网上找到了一张分别对应三种视图模式的图如下: 有关文档、视图和框架等对象之间的关系就讲到这里了 VS2010/MFC 编程入门之四十一(文档、视图和框架:分割窗口) 上一节中鸡啄米讲了文档、视图和框架结构中各对象之间的关系,本节主要讲讲在MF C中如何分割窗口。 分割窗口概述 分割窗口,顾名思义,就是将一个窗口分割成多个窗格,在每个窗格中都包含有视图 ,或者是同一类型的视图,或者是不同类型的视图。 MFC 分割窗口的方式有两种,动态分割和静态分割。 动态分割窗口通常用于创建同一个文档对应的多个视图,而且这些视图一般都是同一 类型的视图,能够在用户编辑文档的不同部分时提供方便。 大家看下 Word 里的动态分割窗口就很明白了,以 Word 2007 文档为例,在菜单中点 击“视图”->“拆分”,就可以看到一条随鼠标移动的分隔条,当我们在文档中某个位置按下鼠 标左键时,分割条就固定了下来,生成了上下两个分割窗格,通过滚动每个窗格中的垂直 滚动条可以看到,两个窗格中的内容相同,这就是所说的对应同一个文档的同一类视图。 动态分割窗口最多可以有两行两列。 静态分割窗口比较常见。我们经常能看到某个软件打开后,界面窗口默认被分割成了 几个窗格,这就是静态分割窗口。 静态分割窗口指在窗口创建时,分割的窗格就已经生成了,而且用户不能改变窗格的 数量和顺序。静态分割窗口最多支持 16 行 16 列。通常静态分割窗口的每个窗格中包含不 同类的视图,当然也可以是同一类的视图。 CSplitterWnd 类 MFC 中的分割窗口类-CSplitterWnd 类提供了分割窗口的功能。CSplitterWnd 类中包 含一个分割器窗口,该分割器窗口就是一个包含多个窗格的窗口。我们分割窗口时就是直 接在此分割器窗口中分割的。 鸡啄米下面介绍三个最常用的成员函数: C++代码 1. virtual BOOL Create( 2. CWnd* pParentWnd, 3. int nMaxRows, 4. int nMaxCols, 5. SIZE sizeMin, 6. CCreateContext* pContext, 7. DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCR OLL | SPLS_DYNAMIC_SPLIT, 8. UINT nID = AFX_IDW_PANE_FIRST 9. ); 创建动态分割窗口。参数 pParentWnd 为分割器窗口的父框架窗口;参数 nMaxRows 为分割器窗口的最大行数,不能超过 2;参数 nMaxCols 为分割器窗口的最大列数,也不 能超过 2;参数 sizeMin 为窗格能显示的最小尺寸,如果窗格尺寸小于 sizeMin 则不显示 ;参数 pContext 为指向 CCreateContext 结构的指针,大多数情况下可以赋值为父框架窗 口的 pContext;参数 dwStyle 指定窗口风格;参数 nID 为分割窗口的 ID,除非分割器窗 口嵌入到另一个分割器窗口中,否则可以取值 AFX_IDW_PANE_FIRST。 C++代码 1. virtual BOOL CreateStatic( 2. CWnd* pParentWnd, 3. int nRows, 4. int nCols, 5. DWORD dwStyle = WS_CHILD | WS_VISIBLE, 6. UINT nID = AFX_IDW_PANE_FIRST 7. ); 创建静态分割窗口。参数 pParentWnd、dwStyle 和 nID 同上;参数 nRows 为行数, 不能超过 16;参数 nCols 为列数,同样不能超过 16。 C++代码 1. virtual BOOL CreateView( 2. int row, 3. int col, 4. CRuntimeClass* pViewClass, 5. SIZE sizeInit, 6. CCreateContext* pContext 7. ); 为静态分割窗口创建窗格视图。参数 row 指定分割器窗口中放置新视图的行;参数 co l 指定放置新视图的列;参数 pViewClass 指定新视图的 CRuntimeClass 对象;参数 sizeIn it 指定新视图的初始大小;参数 pContext 为指向 CCreateContext 结构的指针,通常可以 赋值为传递给父框架窗口的重载函数 CFrameWnd::OnCreateClient 的 pContext 参数值。 动态分割窗口 创建动态分割窗口的步骤为: 1. 在父框架类中定义一个 CSplitterWnd 类型的成员对象。 2. 重载父框架类的 CFrameWnd::OnCreateClient 成员函数。 3. 在重载的 CFrameWnd::OnCreateClient 函数中调用 CSplitterWnd 成员对象的 Crea te 函数。 下面鸡啄米给大家一个实例。同样以VS2010/MFC 编程入门之三十四(菜单:VS201 0 菜单资源详解)中创建 Example34 工程为例,我们要实现在主框架窗口的客户区中创建 两行两列的动态分割窗口。以下是创建动态分割窗口的具体步骤: 1. 在 MainFrm.h 文件中为 CMainFrame 类添加成员对象:CSplitterWnd m_wndSplitt er;。 2. 在 Class View 类视图中找到 CMainFrame 类,右键点击,在右键菜单中选择 Prop erties,就会显示属性页,然后在属性页的工具栏上点击 Tip 为 Overrides 的按钮,下面的 列表中就列出了能够重载的函数,找到 OnCreateClient 生成重载函数。 3. 在 MainFrm.cpp 文件中找到刚重载的 OnCreateClient 函数修改如下: C++代码 1. BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateCon text* pContext) 2. { 3. // TODO: Add your specialized code here and/or call the base c lass 4. // 创建动态分割窗口,两行两列 5. return m_wndSplitter.Create(this,2, 2, CSize(10, 10), pContext); 6. 7. //return CFrameWndEx::OnCreateClient(lpcs, pContext); 8. } 4. 在 Resource View 资源视图中,打开 Menu 下的 IDR_MAINFRAME 菜单,在 View 下添加一个菜单项,Caption 设为 Splitter Window,ID 设为(一定要设为)ID_WINDOW _SPLIT。这样在运行结果界面中点击此菜单项时 MFC 会执行一些操作显示动态分割窗口 。 5. 运行程序,点击菜单中的 View->Splitter Window 菜单项,创建动态分割窗口后效 果如下: 静态分割窗口 创建静态分割窗口的步骤为: 1. 在父框架类中定义一个 CSplitterWnd 类型的成员对象。 2. 重载父框架类的 CFrameWnd::OnCreateClient 成员函数。 3. 在重载的 CFrameWnd::OnCreateClient 函数中调用 CSplitterWnd 成员对象的 Crea teStatic 成员函数,然后可以调用 CSplitterWnd 成员对象的 CreateView 成员函数为每个 窗格创建视图。 鸡啄米仍通过 Example34 工程给大家一个实例,目的是在主框架窗口中的客户区创建 一个两行一列的静态分割窗口。如果已经试验过动态分割窗口的创建,那么麻烦撤销那些 修改吧。创建静态分割窗口的具体步骤如下: 1. 在 MainFrm.h 文件中为 CMainFrame 类添加成员对象:CSplitterWnd m_wndSplitt er;。 2. 在 Class View 类视图中找到 CMainFrame 类,右键点击,在右键菜单中选择 Prop erties,就会显示属性页,然后在属性页的工具栏上点击 Tip 为 Overrides 的按钮,下面的 列表中就列出了能够重载的函数,找到 OnCreateClient 生成重载函数。 3. 在 MainFrm.cpp 文件中找到刚重载的 OnCreateClient 函数进行修改。因为鸡啄米 没有新建其他视图类,所以上下两个窗格的视图都是 CExample34View。为了能识别 CEx ample34View 类,还需在 MainFrm.cpp 文件中添加#include "Example34View.h",在 Exa mple34View.h 文件中添加#include "Example34Doc.h"。最终 OnCreateClient 函数修改如 下: C++代码 1. BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateCon text* pContext) 2. { 3. // TODO: Add your specialized code here and/or call the bas e class 4. CRect rc; 5. 6. // 获取框架窗口客户区的 CRect 对象 7. GetClientRect(&rc); 8. 9. // 创建静态分割窗口,两行一列 10. if (!m_wndSplitter.CreateStatic(this, 2, 1)) 11. return FALSE; 12. 13. // 创建上面窗格中的视图 14. if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CExample3 4View), CSize(rc.Width(), rc.Height()/2), pContext)) 15. return FALSE; 16. 17. // 创建下面窗格中的视图 18. if (!m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(CExample3 4View), CSize(rc.Width(), rc.Height()/2), pContext)) 19. return FALSE; 20. 21. return TRUE; 22. 23. //return CFrameWndEx::OnCreateClient(lpcs, pContext); 24. } 4. 运行程序,在结果界面中关掉其他面板后效果如下: 如果大家想创建在其中某个窗格中再嵌套分割窗口,那么就需要再定义一个 CSplitter Wnd 对象,以父窗格所在的 CSplitterWnd 对象为父框架窗口创建分割窗口即可。 VS2010/MFC 编程入门之四十二(MFC 常用类:CString 类) 上一节鸡啄米讲了分割窗口的有关知识,本节开始讲解MFC的一些常用类,先来说说 CString 类。 CString 类简介 CString 类作为 MFC 的常用类,当之无愧。可以这样说,只要是从事 MFC 开发,基 本都会遇到使用 CString 类的场合。因为字符串的使用比较普遍,而 CString 类又提供了 对字符串的便捷操作,所以它给 MFC 开发人员带来了高的开发效率,受到了开发者的欢 迎。 大家使用VS2010的话,可能会见到 CStringT,实际上它是一个操作可变长度字符串 的模板类。CStringT 模板类有三个实例:CString、CStringA 和 CStringW,它们分别提供 对 TCHAR、char 和 wchar_t 字符类型的字符串的操作。char 类型定义的是 Ansi 字符,w char_t 类型定义的是 Unicode 字符,而 TCHAR 取决于 MFC 工程的属性对话框中的 Confi guration Properties->General->Character Set 属性,如果此属性为 Use Multi-Byte Charac ter Set,则 TCHAR 类型定义的是 Ansi 字符,而如果为 Use Unicode Character Set,则 TCHAR 类型定义的是 Unicode 字符。 三个字符串类的操作是一样的,只是处理的字符类型不同。鸡啄米以 CString 类为讲 解对象。 CString 类的字符串操作 1. CString 类的构造函数 CString 类有很多构造函数,这里只介绍几个比较常用的: CString(const CString& stringSrc); 将一个已经存在的 CString 对象 stringSrc 的内容拷贝到该 CString 对象。例如: C++代码 1. CString str1(_T("www.jizhuomi.com")); // 将常量字符串拷贝到 str1 2. CString str2(str1); // 将 str1 的内容拷贝到 str2 CString(LPCTSTR lpch, int nLength); 将字符串 lpch 中的前 nLength 个字符拷贝到该 CString 对象。例如: C++代码 1. CString str(_T("www.jizhuomi.com"),3); // 构造的字符串对象内容为"ww w" CString(TCHAR ch, int nLength = 1); 使用此函数构造的 CString 对象中将含有 nLength 个重复的 ch 字符。例如: C++代码 1. CString str(_T('w'),3); // str 为"www" 2. CString 类的大小写转换及顺序转换函数 CString& MakeLower(); 将字符串中的所有大写字符转换为小写字符。 CString& MakeUpper(); 将字符串中的所有小写字符转换为大写字符。 CString& MakeReverse(); 将字符串中所有字符的顺序颠倒。 例如: C++代码 1. CString str(_T("JiZhuoMi")); 2. str.MakeLower(); // str 为"jizhuomi" 3. str.MakeUpper(); // str 为"JIZHUOMI" 4. str.MakeReverse(); // str 为"IMOUHZIJ" 3. CString 对象的连接 多个 CString 对象的连接可以通过重载运算符+、+=实现。例如: C++代码 1. CString str(_T("jizhuomi")); // str 内容为"jizhuomi" 2. str = _T("www.") + str + _T("."); // str 为"www.jizhuomi." 3. str += _T("com"); // str 为"www.jizhuomi.com" 4. CString 对象的比较 CString 对象的比较可以通过==、!=、<、>、<=、>=等重载运算符实现,也可以使用 Compare 和 CompareNoCase 成员函数实现。 int Compare(PCXSTR psz) const; 将该 CString 对象与 psz 字符串比较,如果相等则返回 0,如果小于 psz 则返回值小 于 0,如果大于 psz 则返回值大于 0。 int CompareNoCase(PCXSTR psz) const throw(); 此函数与 Compare 功能类似,只是不区分大小写。 例如: C++代码 1. CString str1 = _T("JiZhuoMi"); 2. CString str2 = _T("jizhuomi"); 3. if (str1 == str2) 4. { 5. // 因为 str1、str2 不相等,所以不执行下面的代码 6. ... 7. } 8. if (0 == str1.CompareNoCase(str2)) 9. { 10. // 因为不区分大小写比较时,CompareNoCase 函数返回 0,所以执行下面的 代码 11. ... 12. } 5. CString 对象字符串的提取操作 CString Left(int nCount) const; 提取该字符串左边 nCount 个字符的子字符串,并返回一个包含这个子字符串的拷贝 的 CString 对象。 CString Right(int nCount) const; 提取该字符串右边 nCount 个字符的子字符串,并返回一个包含这个子字符串的拷贝 的 CString 对象。 CString Mid(int iFirst,int nCount) const; 提取该字符串中以索引 iFirst 位置开始的 nCount 个字符组成的子字符串,并返回一个 包含这个子字符串的拷贝的 CString 对象。 CString Mid(int iFirst) const; 提取该字符串中以索引 iFirst 位置开始直至字符串结尾的子字符串,并返回一个包含 这个子字符串的拷贝的 CString 对象。 例如: C++代码 1. CString str1 = _T("jizhuomi"); 2. CString str2 = str1.Left(3); // str2 为"jiz" 3. str2 = str1.Right(2); // str2 为"mi" 4. str2 = str1.Mid(1,3); // str2 为"izh" 5. str2 = str1.Mid(5); // str2 为"omi" 6. CString 对象字符串的查找操作 int Find(PCXSTR pszSub,int iStart=0) const throw( ); int Find(XCHAR ch,int iStart=0) const throw( ); 在 CString 对象字符串的 iStart 索引位置开始,查找子字符串 pszSub 或字符 ch 第一 次出现的位置,如果没有找到则返回-1。 int FindOneOf(PCXSTR pszCharSet) const throw( ); 查找 pszCharSet 字符串中的任意字符,返回第一次出现的位置,找不到则返回-1。 int ReverseFind(XCHAR ch) const throw(); 从字符串末尾开始查找指定的字符 ch,返回其位置,找不到则返回-1。这里要注意, 尽管是从后向前查找,但是位置的索引还是要从开始算起。 C++代码 1. CString str = _T("jizhuomi"); 2. int nIndex1 = str.Find(_T("zh")); // nIndex1 的值为 2 3. int nIndex2 = str.FindOneOf(_T("mui")); // nIndex2 的值为 1 4. int nIndex3 = str.ReverseFind(_T('i')); // nIndex3 的值为 7 7. CString 类对象字符串的替换与删除 int Replace(PCXSTR pszOld,PCXSTR pszNew); 用字符串 pszNew 替换 CString 对象中的子字符串 pszOld,返回替换的字符个数。 int Replace(XCHAR chOld,XCHAR chNew); 用字符 chNew 替换 CString 对象中的字符 chOld,返回替换的字符个数。 int Delete(int iIndex,int nCount = 1); 从字符串中删除 iIndex 位置开始的 nCount 个字符,返回删除操作后的字符串的长度 。 int Remove(XCHAR chRemove); 删除字符串中的所有由 chRemove 指定的字符,返回删除的字符个数。 例如: C++代码 1. CString str = _T("jizhuomi"); 2. int n1 = str.Replace(_T('i'), _T('j')); // str 为"jjzhuomj",n1 为 2 3. int n2 = str.Delete(1,2); // str 为"jhuomj",n2 为 6 4. int n3 = str.Remove(_T('j')); // str 为"huom",n3 为 2 8. CString 类的格式化字符串方法 使用 CString 类的 Format 成员函数可以将 int、short、long、float、double 等数据类 型格式化为字符串对象。 void __cdecl Format(PCXSTR pszFormat,[, argument]...); 参数 pszFormat 为格式控制字符串;参数 argument 可选,为要格式化的数据,一般 每个 argument 在 pszFormat 中都有对应的表示其类型的子字符串,int 型的 argument 对 应的应该是"%d",float 型的应对应"%f",等等。 例如: C++代码 1. CString str; 2. int a = 1; 3. float b = 2.3f; 4. str.Format(_T("a=%d,b=%f"), a, b); // str 为"a=1,b=2.300000" 好了,关于 CString 类的内容就讲到这里了,用法不少,但仍不全,大家可以查看 MSDN 深入学习。 VS2010/MFC 编程入门之四十三(MFC 常用类:CTime 类和 CTimeSpan 类) 上一节中鸡啄米讲了MFC 常用类 CString 类的用法,本节继续讲另外两个MFC常用类 -日期和时间类 CTime 类和 CTimeSpan 类。 日期和时间类简介 CTime 类的对象表示的时间是基于格林威治标准时间(GMT)的。CTimeSpan 类的 对象表示的是时间间隔。 CTime 类和 CTimeSpan 类一般不会被继承使用。两者对象的大小都是 8 个字节。 CTime 表示的日期上限是 3000 年 12 月 31 日,下限是 1970 年 1 月 1 日 12:00:00 A M GMT。 CTime 类的主要成员函数 下面列出 CTime 类的主要成员函数,并加以讲解。 CTime(); 构造一个未经初始化的 CTime 对象。此构造函数使我们可以定义一个 CTime 对象的 数组,在使用数组前需要以有效的时间值为其初始化。 CTime(__time64_t time); 以一个__time64_t(注意:最前面的下划线有两条)类型的数据来构造一个 CTime 对 象。参数 time 是一个__time64_t 类型的值,表示自 GMT 时间 1970 年 1 月 1 日零点以来 的秒数,这里要注意的是,参数 time 代表的时间会转换为本地时间保存到构造的 CTime 对象中。例如,我们传递参数 0 构造一个 CTime 对象,然后调用 CTime 对象的 GetHour 成员函数将返回 8,因为参数 0 代表的 GMT 时间转换为北京时间后为 1970 年 1 月 1 日 8: 00:00。 CTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec, int nDST = -1 ); 以本地时间的年、月、日、小时、分钟、秒等几个时间分量构造 CTime 对象。参数 n Year、nMonth、nDay、nHour、nMin、nSec 分别表示年、月、日、小时、分钟、秒,取 值范围如下: 时间分量 取值范围 nYear 1970-3000 nMonth 1-12 nDay 1-31 nHour 0-23 nMin 0-59 nSec 0-59 参数 nDST 指定是否实行夏令时,为 0 时表示实行标准时间,为正数时表示实行夏令 时,为负数时由系统自动计算实行的是标准时间还是夏令时。 CTime(const SYSTEMTIME& st,int nDST = - 1) ; 以一个 SYSTEMTIME 结构体变量来构造 CTime 对象。SYSTEMTIME 结构体也是我 们对日期时间的常用表示方式。参数 st 为以本地时间表示的 SYSTEMTIME 对象,参数 n DST 同上。 static CTime WINAPI GetCurrentTime( ); 获取系统当前日期和时间。返回表示当前日期和时间的 CTime 对象。 int GetYear( ) const; 获取 CTime 对象表示时间的年份。范围从 1970 年 1 月 1 日到 2038 年(包括 2038 年)1 月 18 日。 int GetMonth( ) const; 获取 CTime 对象表示时间的月份。范围为 1 到 12。 int GetDay( ) const; 获取 CTime 对象表示时间的日期。范围为 1 到 31。 int GetHour( ) const; 获取 CTime 对象表示时间的小时。范围为 0 到 23。 int GetMinute( ) const; 获取 CTime 对象表示时间的分钟。范围为 0 到 59。 int GetSecond( ) const; 获取 CTime 对象表示时间的秒。范围为 0 到 59。 int GetDayOfWeek( ) const; 此函数的返回值表示 CTime 对象代表的是星期几,1 表示是周日,2 表示是周一,以 此类推。 CString Format(LPCTSTR pszFormat) const; 将 CTime 对象中的时间信息格式化为字符串。参数 pszFormat 是格式化字符串,与 p rintf 中的格式化字符串类似,格式化字符串中带有%前缀的格式码将会被相应的 CTime 时 间分量代替,而其他字符会原封不动的拷贝到返回字符串中。格式码及含义如下: %a:周的英文缩写形式。 %A:周的英文全名形式。 %b: 月的英文缩写形式。 %B:月的英文全名形式。 %c: 完整的日期和时间。 %d:十进制形式的日期(01-31)。 %H:24 小时制的小时(00-23)。 %I: 12 小时制的小时(00-11)。 %j: 十进制表示的一年中的第几天(001-366)。 %m: 月的十进制表示(01-12)。 %M:十进制表示的分钟(00-59)。 %p: 12 小时制的上下午标示(AM/PM)。 %S: 十进制表示的秒(00-59)。 %U: 一年中的第几个星期(00-51),星期日是一周的第一天。 %W: 一年中的第几个星期(00-51),星期一是一周的第一天。 %w: 十进制表示的星期几(0-6)。 %Y: 十进制表示的年。 CTime operator +(CTimeSpan timeSpan) const; 将 CTime 对象和 CTimeSpan 对象相加,返回一个 CTime 对象。实际意义就是在一 个时间的基础上推后一个时间间隔,得到一个新的时间。 CTime operator -(CTimeSpan timeSpan) const; 将 CTime 对象和一个 CTimeSpan 相减,返回一个 CTime 对象。实际意义就是在一 个时间的基础上提前一个时间间隔,得到一个新的时间。 CTimeSpan operator -(CTime time) const; 将该 CTime 对象和另一个 CTime 对象相减,返回一个 CTimeSpan 对象。实际意义 就是计算两个时间点的间隔,得到一个 CTimeSpan 对象。 CTime& operator +=(CTimeSpan span); 为该 CTime 对象增加一个 span 表示的时间间隔。 CTime& operator -=(CTimeSpan span); 为该 CTime 对象减去一个 span 表示的时间间隔。 CTime& operator =(__time64_t time); 为该 CTime 对象赋予一个新的时间值。 简单说下剩下的几个重载 i 运算符: operator == : 比较两个绝对时间是否相等。 operator != : 比较两个绝对时间是否不相等。 operator > : 比较两个绝对时间,是否前一个大于后一个。 operator < : 比较两个绝对时间,是否前一个小于后一个。 operator >= : 比较两个绝对时间,是否前一个大于等于后一个。 operator <= : 比较两个绝对时间,是否前一个小于等于后一个。 CTimeSpan 类的主要成员函数 前面介绍了 CTime 类的成员函数,再来看 CTimeSpan 类的成员函数就比较容易了, 这里只做简单介绍。 CTimeSpan( ); 构造一个未经初始化的 CTimeSpan 对象。 CTimeSpan(__time64_t time); 以一个__time64_t 类型的数据来构造 CTimeSpan 对象,参数 time 的含义上面 CTime (__time64_t time)的讲解。 CTimeSpan( LONG lDays, int nHours, int nMins, int nSecs ); 以天、小时、分钟、秒等时间分量来构造 CTimeSpan 对象。每个时间分量的取值范 围如下: 时间分量 取值范围 lDays 0-25000(大约) nHours 0-23 nMins 0-59 nSecs 0-59 GetDays():获得 CTimeSpan 类对象中包含的完整的天数。 GetHours():获得当天的小时数,取值范围为-23 到 23。 GetTotalHours():获得 CTimeSpan 类对象中包含的完整的小时数。 GetMinutes():获得当前小时包含的分数,取值范围为-59 到 59。 GetTotalMinutes():获得 CTimeSpan 类对象中包含的完整的分数。 GetSeconds():获得当前分钟包含的秒数,取值范围为-59 到 59。 GetTotalSeconds():获得 CTimeSpan 类对象中包含的完整的秒数。 CString Format(LPCTSTR pszFormat) const; 将一个 CTimeSpan 对象格式化为字符串。使用方式与 CTime::Format 类似,格式码 及含义如下: %D:CTimeSpan 对象中的总天数; %H:不足整天的小时数; %M:不足 1 小时的分钟数; %S:不足 1 分钟的秒数; %%:百分号。 另外,CTimeSpan 类也重载了运算符“=”,“+”,“-”,“+=”,“-=”,“==”,“!=”,“<”,“>” ,“<=”,“>=”,用于 CTimeSpan 对象的赋值、加减运算及两个 CTimeSpan 对象的比较。 CTime 类和 CTimeSpan 类的应用实例 鸡啄米在下面将为大家演示如何得到当前时间、计算两个时间的时间差以及 CTime 对 象怎样格式化为字符串等。具体步骤如下: 1. 创建一个 Win32 Console Application 工程,Name 设为“Example43”。 2. 因为要使用到 CTime 类、CTimeSpan 类和 cout 输出流,所以在 Example43.cpp 文件中包含相应的头文件: C++代码 1. #include "atltime.h" 2. #include 3. using namespace std; 3. 修改 main 函数如下: C++代码 1. int _tmain(int argc, _TCHAR* argv[]) 2. { 3. CString strTime; // 用于将 CTime 对象格式化为字符串 4. // 获取当前时间并保存到 curTime 5. CTime curTime = CTime::GetCurrentTime(); 6. 7. int nYear = curTime.GetYear(); // 获取当前年份 8. int nMonth = curTime.GetMonth(); // 获取当前月份 9. int nDay = curTime.GetDay(); // 获取当前日期 10. int nHour = curTime.GetHour(); // 获取当前小时时间 11. int nMin = curTime.GetMinute(); // 获取当前分钟时间 12. int nSec = curTime.GetSecond(); // 获取当前秒时间 13. 14. // 输出当前时间 15. cout<<"当前时间:"<LoadIcon(IDR_MAINFRAME); 5. // 两个数据初始化为 0 6. m_nData1 = 0; 7. m_nData2 = 0; 8. } 4、在对话框模板上双击 OK 按钮,添加点击消息的处理函数,并修改如下: C++代码 1. void CExample44Dlg::OnBnClickedOk() 2. { 3. // TODO: Add your control notification handler code here 4. // 启动 ID 为 1 的定时器,定时时间为 1 秒 5. SetTimer(1, 1000, NULL); 6. // 启动 ID 为 2 的定时器,定时时间为 2 秒 7. SetTimer(2, 2000, NULL); 8. 9. //CDialogEx::OnOK(); 10. } 这样,点击 OK 按钮时就不会退出,而是启动两个定时器。 5、根据上面 MFC 定时器讲解中为 WM_TIMER 消息添加处理函数的方法,添加 WM _TIMER 的消息处理函数 OnTimer,并修改其实现如下: C++代码 1. void CExample44Dlg::OnTimer(UINT_PTR nIDEvent) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. switch (nIDEvent) 5. { 6. case 1: 7. // 如果 m_nData1 已经达到 10,则销毁 ID 为 1 的定时器 8. if (10 == m_nData1) 9. { 10. KillTimer(1); 11. break; 12. } 13. // 刷新编辑框 IDC_EDIT1 的显示 14. SetDlgItemInt(IDC_EDIT1, ++m_nData1); 15. break; 16. case 2: 17. // 如果 m_nData1 已经达到 5,则销毁 ID 为 2 的定时器 18. if (5 == m_nData2) 19. { 20. KillTimer(2); 21. break; 22. } 23. // 刷新编辑框 IDC_EDIT2 的显示 24. SetDlgItemInt(IDC_EDIT2, ++m_nData2); 25. default: 26. break; 27. } 28. 29. CDialogEx::OnTimer(nIDEvent); 30. } 6、运行程序,点击 OK 按钮,查看效果。 关于定时器的内容就讲这些 VS2010/MFC 编程入门之四十五(MFC 常用类:CFile 文件操作 类) 上一节中鸡啄米讲了定时器 Timer的用法,本节介绍下文件操作类 CFile 类的使用。 CFile 类概述 如果你学过 C 语言,应该知道文件操作使用的是文件指针,通过文件指针实现对它指 向的文件的各种操作。这些文件操作函数中有的最终还是调用了操作系统的 API 函数或者 处理过程与之类似,例如在 Windows 系统中,fread 函数就调用了 API 函数 ReadFile。 Windows 系统的 API 函数除了 ReadFile,还有 CreateFile、WriteFile 等函数。而 MF C 基于面向对象的思想,将这些 Windows API 函数封装到了 CFile 类中,实现对文件的打 开、关闭、读、写、获取文件信息等操作。使用 CFile 类对文件进行操作非常便捷。 CFile 类的成员函数 CFile( ); CFile(HANDLE hFile); CFile(LPCTSTR lpszFileName,UINT nOpenFlags); 以上三个成员函数都是 CFile 的构造函数,用于构造 CFile 对象。参数 hFile 为要关联 到 CFile 对象的文件的句柄。参数 lpszFileName 为要关联到 CFile 对象的文件的相对路径 或者绝对路径;参数 nOpenFlags 为文件访问选项的组合,通过各选项的按位或运算实现 组合,下面的 5 个表列出了 nOpenFlags 参数可能取的选项: 下面的文件访问模式选项表中只能选择一个进行组合,默认取 CFile::modeRead。 取值 描述 CFile::modeRead 只读方式访问文件 CFile::modeWrite 写入方式访问文件 CFile::modeReadWrite 读写方式访问文件 下面的文件共享模式选项表中也只能选择一个进行组合,默认的共享模式是 CFile::sh areExclusive。 取值 描述 CFile::shareDenyNone 允许其他进程对文件进行读写 CFile::shareDenyRead 不允许其他进程读取文件 CFile::shareDenyWrite 不允许其他进程写文件 CFile::shareExclusive 禁止其他进程对文件的所有访问 下面的文件创建模式选项列表中可选择第一个或两者都选进行组合。 取值 描述 CFile::modeCreate 如果文件不存在则创建文件,而如果存在则 将它关联到此 CFile 对象并将长度截取为 0 CFile::modeNoTruncat e 如果文件不存在则创建文件,而如果存在则 将它关联到此 CFile 对象而不进行截取 注意,选择 CFile::modeNoTruncate 时需要与 CFile::modeCreate 一起使用,即 CFile:: modeCreate | CFile::modeNoTruncate。 另外,还有一个文件缓冲选项列表和一个文件安全选项。文件缓冲选项不太常用,鸡 啄米这里就不讲了,有兴趣的可以查阅 MSDN。文件安全选项是 CFile::modeNoInherit, 意为禁止子进程继承使用此文件。 当然,在实际使用时,以上各个表并不是都要用到,大家可以根据自己的需要选择用 哪个表,选择哪个选项。 virtual BOOL Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException* pError = NULL); 打开文件。它通常与默认构造函数 CFile::CFile()一起使用。参数 lpszFileName 和 nO penFlags 同构造函数。参数 pError 为指向文件异常对象的指针,默认为 NULL。 virtual void Close( ); 关闭文件。如果你没有在执行析构函数前调用此成员函数关闭文件,则析构函数会为 你关闭。 virtual UINT Read(void* lpBuf,UINT nCount); 读取文件数据到缓存。参数 lpBuf 是由用户提供的指向接收文件数据的缓存的指针; 参数 nCount 为读取的最大字节数。返回值是实际读取到缓存的字节数,如果到达文件尾 则返回值可能会小于 nCount,此时继续读取的话,会返回 0,所以通常我们都会判断返回 值是否小于 nCount 或者等于 0 来确定是否到达文件尾。 virtual void Write(const void* lpBuf,UINT nCount); 将缓存中的数据写入文件。参数 lpBuf 也是由用户提供,指向包含写入数据的缓存的 指针;参数 nCount 为缓存中要被写入文件的数据的字节数。 virtual ULONGLONG Seek(LONGLONG lOff,UINT nFrom); 在一个打开的文件中重定位文件指针。参数 lOff 为文件指针移动的字节个数,为正数 时表示向文件尾移动,为负数时表示向文件开头移动;参数 nFrom 为 lOff 的基准位置,即 由 nFrom 位置开始移动 lOff 个字节,它可以取下面几个值中的一个: CFile::begin 从文件开头开始移动 CFile::current 从文件指针的当前位置开始移动 CFile::end 从文件尾开始移动 文件打开时,文件指针被置于 0,即文件开头处。 如果此函数成功则返回文件指针的位置。 void SeekToBegin( ); 将文件指针移动到文件开头。它等价于 Seek( 0L, CFile::begin )。 ULONGLONG SeekToEnd( ); 将文件指针移动到文件末尾。返回值是文件的字节长度。它等价于 CFile::Seek( 0L, C File::end )。 virtual ULONGLONG GetLength( ) const; 获取文件的字节长度。 virtual void SetLength(ULONGLONG dwNewLen); 改变文件的长度。参数 dwNewLen 为文件的新长度,它可能比文件的当前长度值要大 或者小,文件会相应的被扩展或截取。 virtual CString GetFileName( ) const; 获取文件名称。 virtual CString GetFilePath( ) const; 获取文件的绝对路径。 virtual CString GetFileTitle( ) const; 获取文件的显示名称。举个例子,与 GetFileName 区分一下,如果你系统中的文件不 显示扩展名,则它获取到的文件名称就不包含扩展名,否则就显示扩展名。 virtual ULONGLONG GetPosition( ) const; 获取文件指针的当前位置。 static void PASCAL Remove(LPCTSTR lpszFileName,CAtlTransactionManager * pTM = NULL); 删除文件。参数 lpszFileName 为要删除的文件路径,可以是相对路径、绝对路径或者 网络路径;参数 pTM 指向一个 CAtlTransactionManager 对象。 static void PASCAL Rename(LPCTSTR lpszOldName,LPCTSTR lpszNewName, CAtlTransactionManager* pTM = NULL); 重命名文件。参数 lpszOldName 为老的文件路径;参数 lpszNewName 为新的文件路 径;参数 pTM 指向一个 CAtlTransactionManager 对象。实际上此函数的意义已经不只是 重命名文件,还可以移动文件到其他目录下,例如,lpszOldName 取"d:\\1.txt",lpszNew Name 取"e:\\2.txt",这样可以将 D 盘中的 1.txt 文件转移到 E 盘并重命名为 2.txt。 CFile 类应用实例 这里鸡啄米只给大家演示几个简单的代码片段,从这些代码片段中熟悉 CFile 类的文 件操作。 实例一:构造 CFile 对象时就打开文件,然后向文件中写入数据,最后以 Seek 函数 移动文件指针,读取文件内容。 C++代码 1. char writeBuffer[500]; // 要写入的数据的缓存 2. char readBuffer[500]; // 存放读取数据的缓存 3. LONGLONG lOff = 0; // 文件指针的偏移量,也是读取到的数据的总字 节数 4. // 构造 CFile 对象,同时以创建和读写的方式打开文件 E:\1.txt 5. CFile file(_T("e:\\1.txt"), CFile::modeCreate | CFile::modeRead Write); 6. 7. // 将写入数据的缓存中每个字节都赋值为字符 c 8. memset(writeBuffer, 'c', sizeof(writeBuffer)); 9. // 将数据写入到文件中 10. file.Write(writeBuffer, sizeof(writeBuffer)); 11. 12. while (true) 13. { 14. // 以文件开头为基准,移动文件指针到 lOff 的位置 15. file.Seek(lOff, CFile::begin); 16. // 读取 100 个字节的数据到存放读取数据的缓存的 readBuffer + lOff 位 置处 17. int nRet = file.Read(readBuffer + lOff, 100); 18. // 根据实际读取的字节数,增加文件指针的移动量 19. lOff += nRet; 20. // 如果读取数据时返回值小于指定的 100,说明已到文件尾,跳出循环 21. if (nRet < 100) 22. break; 23. } 24. 25. // 关闭文件 26. file.Close(); 实际上,在 Write 函数和 Read 函数执行后,文件指针会自动移动到最后操作的位置 ,所以其实上面的代码中无须使用 Seek 函数再去手动移动文件指针。这将在下面的实例 二中体现出来。 实例二:构造 CFile 对象,然后使用 Open 成员函数打开文件,再写入一个结构体数 组,最后读取出来。 先贴上结构体的定义: C++代码 1. struct student 2. { 3. int nNum; 4. float fScore; 5. }; 下面是文件操作的代码片段: C++代码 1. student s1[2]; // 存放要写入文件的数据 2. student s2[2]; // 存放从文件读取的数据 3. CFile file; // CFile 对象 4. int nReadBytes = 0; // 从文件中读取到的总字节数 5. 6. // 为 s1 数组各元素赋值 7. s1[0].nNum = 22; 8. s1[0].fScore = 91.5; 9. s1[1].nNum = 23; 10. s1[1].fScore = 85; 11. 12. // 以创建、读写方式打开文件 E:\1.txt 13. if (file.Open(_T("E:\\1.txt"), CFile::modeCreate | CFile::modeR eadWrite)) 14. { 15. // 写入数据 s1 结构体数组 16. file.Write(s1, sizeof(s1)); 17. // 因为上面调用 Write 以后文件指针在文件尾,所以需要将其移动到文件开头 18. file.SeekToBegin(); 19. 20. while (true) 21. { 22. // 读取数据到 s2 23. int nRet = file.Read((BYTE*)s2 + nReadBytes, sizeof(stu dent)); 24. // 计算已经读取到的总字节数 25. nReadBytes += nRet; 26. // 如果读取数据时返回值小于指定的 sizeof(student),则说明已到文 件尾,跳出循环 27. if (nRet < sizeof(student)) 28. break; 29. } 30. 31. // 关闭文件 32. file.Close(); 33. } 本节内容就到这里,如果有其他语言的文件操作的经验的话,应该还是比较简单的。 VS2010/MFC 编程入门之四十六(MFC 常用类:MFC 异常处理) 上一节中鸡啄米讲了CFile 文件操作类,本节主要来说说 MFC 异常处理。 在鸡啄米 C++编程入门系列的最后一节鸡啄米:C++编程入门系列之五十(异常处理 )中,鸡啄米讲了 C++标准异常的处理机制,如果你还没有学过这方面内容,可以到那节 教程中去学习下。 MFC 异常处理与 C++标准异常处理是类似的,只是它在抛出和捕获异常时使用了一些 宏,另外还将异常封装到了 CException 类及其派生类中。下面就分别讲解这些宏与异常 类。 MFC异常宏 MFC 提供的异常处理宏包括 TRY、CATCH、AND_CATCH、END_CATCH、THRO W、THROW_LAST 等,大家看着名称是不是与 C++标准异常处理的关键字相似?它们实 际上就是在 try、catch 和 throw 的基础上定义的。鸡啄米下面贴出 MFC 中这些宏的定义 : C++代码 1. /////////////////////////////////////////////////////////////// ////////////// 2. // Exception macros using try, catch and throw 3. // (for backward compatibility to previous versions of MFC) 4. 5. #define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try { 6. 7. #define CATCH(class, e) } catch (class* e) \ 8. { ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \ 9. _afxExceptionLink.m_pException = e; 10. 11. #define AND_CATCH(class, e) } catch (class* e) \ 12. { ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \ 13. _afxExceptionLink.m_pException = e; 14. 15. #define END_CATCH } } 16. 17. #define THROW(e) throw e 18. #define THROW_LAST() (AfxThrowLastCleanup(), throw) 19. 20. // Advanced macros for smaller code 21. #define CATCH_ALL(e) } catch (CException* e) \ 22. { { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \ 23. _afxExceptionLink.m_pException = e; 24. 25. #define AND_CATCH_ALL(e) } catch (CException* e) \ 26. { { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \ 27. _afxExceptionLink.m_pException = e; 28. 29. #define END_CATCH_ALL } } } 30. 31. #define END_TRY } catch (CException* e) \ 32. { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \ 33. _afxExceptionLink.m_pException = e; } } 可以看出这些宏的定义中都包含了相应的C++异常处理关键字,本质上还是要通过 try 、catch 和 throw 实现。 MFC 异常类 MFC 将对异常的处理封装到了异常类--CException 类及其子类中。其实即使我们不使 用 MFC 异常宏而是使用 C++标准异常处理的话,也是会用到 MFC 的 CException 类及其 子类的。MFC 异常类及其含义如下表: MFC 异常类 含义 CSimpleException 资源紧张异常的基类 CInvalidArgException 无效参数异常 CMemoryException 内存不足 CNotSupportedException 响应对不支持服务的请求 CArchiveException 存档/序列化异常 CFileException 文件异常 CResourceException Windows 资源分配异常 COleException OLE 异常 CDBException 数据库异常(ODBC 类) COleDispatchException 调度(自动化)异常 CUserException 用消息框警告用户然后引发一般 CException 的异常 CDaoException 数据库异常(DAO 类) CInternetException 网络异常 MFC 异常处理 MFC 异常处理的 TRY 块的形式如下: TRY { 复合语句 } CATCH (MFC 异常类名, 变量名) { 复合语句 } AND_CATCH (MFC 异常类名, 变量名) { 复合语句 } AND_CATCH (MFC 异常类名, 变量名) { 复合语句 } ...... END_CATCH 说明:TRY 后的一对大括号内包含了可能会抛出异常的代码块;用 CATCH 子句捕获 并处理异常,它捕获的是指向异常对象的指针,小括号中的“MFC 异常类名”就是 CExcepti on 类或其子类的名称,变量名代表的就是“MFC 异常类名”类型的指针变量;如果抛出的异 常类型与 CATCH 子句中的不一致,则对后面的所有 AND_CATCH 子句依次检查,若子 句的异常类型与抛出异常类型一致则由其捕获并处理此异常;最后用 END_CATCH 结束 整个 TRY 块。 注意:MFC 异常宏只能捕获处理 CException 及其子类类型的异常。 我们在使用 MFC 类时,有些会自动抛异常,当然我们可以在需要的情况下使用 AfxTh row******()自己抛异常,这里的******与上面 MFC 异常类列表中的各个异常类对应,例如 抛文件异常可以使用 AfxThrowFileException(),参数可以查阅 MSDN。 MFC 异常处理实例 鸡啄米给大家一个简单的 MFC 异常处理的代码段,了解下如何使用 MFC 异常处理即 可。 C++代码 1. TRY 2. { 3. CFile file(_T("C:\\1.txt"), CFile::modeRead); // 构造 CFil e 对象 file,并以只读模式打开一个文件,如果不存在则抛出 CFileException 异常 4. } 5. CATCH (CFileException, e) 6. { 7. if (e->m_cause == CFileException::fileNotFound) 8. { 9. // 如果捕获到 CFileException 异常且为文件未找到时,弹出提示对话 框 10. MessageBox(_T("file not found!")); 11. return; 12. } 13. } 14. END_CATCH 上面这段代码的意义很简单,就是打开一个文件 C:\1.txt,如果此文件不存在,则抛出 CFileException 异常,由 CATCH 子句捕获后判断是否是文件未找到,如果是则弹出提示 对话框并返回。 再将上面的代码稍微修改下,以演示 AfxThrow******()抛异常的用法: C++代码 1. TRY 2. { 3. AfxThrowFileException(CFileException::fileNotFound); // 手动抛出 CFileException 异常 4. } 5. CATCH (CFileException, e) 6. { 7. if (e->m_cause == CFileException::fileNotFound) 8. { 9. // 如果捕获到 CFileException 异常且为文件未找到时,弹出提示对话 框 10. MessageBox(_T("file not found!")); 11. return TRUE; 12. } 13. } 14. END_CATCH 上面这段代码执行时,在 CATCH 子句中会捕获到文件异常。 最后提醒大家一下,MFC 的建议是不再使用 MFC 异常宏,而是直接使用 C++标准异 常,它更加灵活。 VS2010/MFC 编程入门之四十七(字体和文本输出:CFont 字体 类) 上一节中鸡啄米讲了MFC 异常处理,本节的主要内容是字体 CFont 类。 字体简介 GDI(Graphics Device Interface),图形设备接口,是 Windows 提供的一些函数和结构 ,用于在显示器和打印机上显示图形。我们在MFC开发中经常会使用 GDI 来输出文本或图 形图像(当然现在也有了 GDI+,本教程主要讲解 GDI)。文本实际上就是一种特殊的图 形,它只不过是根据事先指定的“字体”绘制出来的图形。 字体通常用来为字符集中每一个字符,如字母、数字、标点符号等,指定其形状等外 表特征。窗口创建后,如果没有专门指定,一般会采用系统字体作为默认字体。我们可以 使用 API 函数 GetStockObject(SYSTEM_FONT)获得系统字体的句柄。 CFont 类 CFont 类封装了一个 Windows 图形设备接口(GDI)字体,并为操作字体提供了成员 函数。 为了使用 CFont 对象,首先构造一个 CFont 对象,再通过调用 CreateFont、CreateF ontIndirect、CreatePointFont 或 CreatePointFontIndirect 将一个 Windows 字体与此 CFon t 对象关联,然后使用此 CFont 对象的成员函数就可以操作字体了。 一般使用 CreatePointFont 或 CreatePointFontIndirect 比使用 CreateFont 或 CreateF ontIndirect 要更简便,因为前两者会自动的将字体高度的单位由点转换为逻辑单位。 注:“点”是传统计量字大小的单位,是从英文 Point 来的,一般用小写 p 表示,俗称“磅”。 其换算关系为:1p=0.35146mm≈0.35mm,1 英寸=72p。 先简单解释下下面用到的几个概念: 设备上下文是包含某个设备(如显示器、打印机)的绘制属性信息的 Windows 数据结 构,有了它就可以在 Windows 中进行与设备无关的绘图,而不用考虑此设备是显示器还 是打印机等。CDC 类就是设备上下文类。 在绘图时还经常会提到逻辑和物理,例如逻辑单位、物理单位,通俗点说,逻辑的就 是与具体设备无关的,甚至我们可以自定义,物理的就是由具体设备决定的了。 CFont 类为字体的操作提供了几个成员函数,下面是对其各个成员函数的详细介绍。 1、CFont( ); 构造一个 CFont 对象。此对象在使用之前应该先使用 CreateFont、CreateFontIndirec t、CreatePointFont 或 CreatePointFontIndirect 初始化。 2、 BOOL CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename ); 通过指定的一些特征初始化 CFont 对象。下面分别介绍每个参数: nHeight:指定字体高度(逻辑单位)。有三种取值:>0,字体映射器将高度值转换为 设备单位,并与可用字体的字符元高度进行匹配;=0,字体映射器使用默认的高度值;<0 ,字体映射器将高度值转换为设备单位,用其绝对值与可用字体的字符高度进行匹配。nH eight 转换后的绝对值不应超过 16384 个设备单位。 nWidth:指定字体中字符的平均宽度(逻辑单位)。 nEscapement:指定偏离垂线和显示界面 X 轴之间的角度,以十分之一度为单位。偏 离垂线是穿过一行文本中第一个字符和最后一个字符的直线。 nOrientation:指定每个字符的基线和设备 X 轴之间的角度,以十分之一度为单位。 nWeight:指定字体磅数(每 1000 点中墨点像素数)。可取 0 到 1000 之间的任意整 数值。 bItalic:指定字体是否为斜体。 bUnderline:指定字体是否带有下划线。 bStrikeOut:指定字体是否带有删除线。 nCharSet:指定字体的字符集。预定义的字符集: ANSI_CHARSET;BALTIC_CHARSET;CHINESEBIG5_CHARSET;DEFAULT_CHAR SET;EASTEUROPE_CHARSET; GB2312_CHARSET; GREEK_CHARSET;HANGUL_C HARSET; MAC_CHARSET; OEM_CHARSET; RUSSIAN_CHARSET; SHIFTJIS_CHAR SET;SYMBOL_CHARSET; TURKISH_CHARSET。韩国 Windows:JOHAB_CHARSET ;中东地区 Windows:HEBREW_CHARSSET,ARABIC_CHARSET;泰国 Windows: THAI_CHARSET。应用程序可以使用 DEFAULT_CHARSET 以允许字体名和大小完全指 定逻辑字体,如果指定的字体名不存在则可能会用任意字符集的字体来代替,所以为避免 不可预料的结果,应谨慎使用 DEFAULT_CHARSET。 nOutPrecision:指定输出精度。输出精度定义了输出与要求的字体高度、宽度、字符 方向、移位和间距等的接近程度。它的取值及含义如下(只能取其一):   OUT_CHARACTER_PRECIS;未用。    OUT_DEFAULT_PRECIS:指定缺省的字体映射器状态。    OUT_DEVICE_PRECIS:在当系统里有多种字体使用同一个名字时指示字体映射器 选择一种设备字体。    OUT_OUTLINE_PRCIS:在 Windows NT 中此值指示字体映射器从 TrueType 和其他 基于边框的字体中选择。    OUT_RASTER_PRECIS:在当系统里有多种字体使用同一个名字时指示字体映射器 选择一种光栅字体。    OUT_STRING_PRECIS:此值没有被字体映射器使用,但是当列举光栅字体时它会 被返回。    OUT_STROKE_PRECIS:没有被字体映射器使用,但是当列举 TrueType 字体、其 他基于边框的字体和向量字体时它会被返回。  OUT_TT_ONLY_PRECIS:指示字体映射器仅从 TrueType 字体中选择,如果系统中 没有安装 TrueType 字体,则字体映射返回缺省状态。   OUT_TT_PRECIS:在当系统里有多种同名的字体时指示字体映射器选择一种 TrueTy pe 字体。当操作系统含有多种与指定名字同名的字体时,应用程序可以使用 OUT_DEVIC E_PRECIS,OUT_RASTER_PRECIS 和 OUT_TT_PRECIS 值来控制字体映射器如何选 择一种字体,例如,如果操作系统含有名字 Symbol 的光栅和 TrueType 两种字体,指定 OUT_TT_PRECIS 使字体映射器选择 TrueType 方式(指定 OUT_TT_ONLY_PRECIS 强 制字体映射器选择一种 TrueType 字体,尽管这会给 TrueType 字体换一个名字)。 nClipPrecision:指定裁剪精度。裁剪精度定义了怎样裁剪部分超出裁剪区域的字符。 它的取值及含义如下(可取一个或多个值):   CLIP_DEFAULT_PRECIS:指定缺省裁剪状态。 CLIP_CHARACTER_PRECIS:未用。    CLIP_STROKE_PRECIS:未被字体映射器使用,但是当列举光栅字体、向量字体或 TrueType 字体时它会被返回。在 Windows 环境下,为保证兼容性,当列举字体时这个值 总被返回。    CLIP_MASK:未用。 CLIP_EMBEDDED:要使用嵌入式只读字体必须使用此标志。    CLIP_LH_ANGLES:当此值被使用时,所有字体的旋转依赖于坐标系统的定位是朝 左的还是朝右的。如果未使用此值,设备字体总是逆时针方向旋转,但其他字体的旋转依 赖于坐标系统的定向。    CLIP_TT_ALWAYS:未用。 nQuality:指定字体的输出质量。输出质量定义了 GDI 将逻辑字体属性匹配到实际物 理字体的细致程度。它的各个取值及含义如下(取其一): DEFAULT_QUALITY:字体的外观不重要。    DRAFT_QUALITY:字体外观的重要性次于使用 PROOF_QUALITY 时,对 GDI 光栅 字体,缩放比例是活动的,这意味着多种字体大小可供选择,但质量可能不高,如果有必 要,粗体、斜体、下划线、strikeout 字体可被综合起来使用。    PROOF_QUALITY:字符质量比精确匹配逻辑字体字体属性更重要。对 GDI 扫描字 体,缩放比例是活动的,并选择最接近的大小。尽管当使用 PROOF_QUALITY 时,选择 字体大小并不完全匹配,但字体的质量很高,并没有外观上的变形。如果有必要,粗体、 斜体、下划线、strikeout 字体可被综合起来使用。 nPitchAndFamily:指定字体间距和字体族。低 2 位用来指定字体的间距,可取下列值 中的一个:DEFAULT_PITCH,FIXED_PITCH,VARIABLE_PITCH。高 4 位指定字体族 ,取值及含义如下(取其一): FF_DECORATIVE:新奇的字体,如老式英语(Old English)。 FF_DONTCARE:不关心或不知道。    FF_MDERN:笔划宽度固定的字体,有或者无衬线。如 Pica、Elite 和 Courier New 。    FF_ROMAN:笔划宽度变动的字体,有衬线。如 MS Serif。    FF_SCRIPT:设计成看上去象手写体的字体。如 Script 和 Cursive。    FF_SWISS:笔划宽度变动的字体,无斜线。如 MS Sans Serif。    应用程序可以用运算符 OR 将字符间距和字体族组合起来给 nPitchAndFamily 赋值。    字体族描述一种字体的普通外观,当所有的精确字样都不能使用时,可用它们来指定 字体。 lpszFacename:指定字体的字样名的字符串。此字符串的长度不应超过 30 个字符。 Windows 函数 EnumFontFamilies 可以枚举出当前所有可用字体的字样名。如果 lpszFace name 为 NULL,则 GDI 使用一种与设备无关的字体。 返回值:此函数成功则返回 TRUE,否则返回 FALSE。 CreateFont 函数初始化 CFont 对象后,此字体就能够被选作任何设备上下文的字体了 。此函数并不会创建一个新的 Windows GDI 字体,只是从 GDI 的物理字体中选择了一个 最匹配的字体。在创建一个逻辑字体时,大部分参数可以使用默认值,但一般情况下都会 给出参数 nHeight 和 lpszFacename 的指定值,如果没有给 nHeight 和 lpszFacename 参 数设定取值,则创建的逻辑字体与设备相关。当使用 CreateFont 函数初始化一个 CFont 对象完成后,就能够使用 CDC::SelectObject 函数来为设备上下文选择字体了,并且还能 够在不再使用此 CFont 对象时删除它。 3、BOOL CreateFontIndirect(const LOGFONT* lpLogFont); 通过一个 LOGFONT 结构体变量给出的特征来初始化 CFont 对象。参数 lpLogFont 是 指向 LOGFONT 结构体变量的指针,此 LOGFONT 结构体变定义了逻辑字体的特征。LO GFONT 结构体的定义可以参见VS2010/MFC 编程入门之十八(对话框:字体对话框)。 4、BOOL CreatePointFont(int nPointSize,LPCTSTR lpszFaceName,CDC* pDC = NULL); 此函数提供了一种由指定字样和点数创建字体的简单方式。参数的意义如下: nPointSize:指定字体高度,以十分之一点为单位。例如,nPointSize 为 120 则表示 是 12 点的字体。 lpszFacename:指定字体的字样名的字符串。此字符串的长度不应超过 30 个字符。 Windows 函数 EnumFontFamilies 可以枚举出当前所有可用字体的字样名。如果 lpszFace name 为 NULL,则 GDI 使用一种与设备无关的字体。 pDC:指向 CDC 对象,用来将 nPointSize 指定的高度转换为逻辑单位,如果为 NUL L,则使用屏幕设备上下文进行转换。 5、BOOL CreatePointFontIndirect(const LOGFONT* lpLogFont,CDC* pDC = NU LL); 此函数是通过指定的字样和点数创建字体的间接方式。参数 lpLogFont 指向一个 LOG FONT 结构体变量,此 LOGFONT 变量定义了逻辑字体的特征,它的 lfHeight 成员以十分 之一点为单位,而不是逻辑单位。参数 pDC 指向 CDC 对象,用来将 lfHeight 表示的高度 转换为逻辑单位,如果为 NULL,则使用屏幕设备上下文进行转换。 此函数与 CreateFontIndirect 很相似,但区别是 LOGFONT 变量中 lfHeight 成员的单 位是十分之一点而不是逻辑单位。 6、static CFont* PASCAL FromHandle(HFONT hFont); 由 Windows GDI 字体的 HFONT 句柄获得相应的 CFont 对象指针。参数 hFont 是一 个 Windows 字体的 HFONT 句柄。成功则返回 CFont 对象的指针,否则返回 NULL。 7、int GetLogFont(LOGFONT * pLogFont); 获取 CFont 对象的 LOGFONT 结构体的拷贝。参数 pLogFont 指向用来接收字体信息 的 LOGFONT 结构体变量。成功则返回非零值,否则返回零。 VS2010/MFC 编程入门之四十八(字体和文本输出:文本输出) 鸡啄米在上一节中讲了CFont 字体类,本节主要讲解文本输出的方法和实例。 文本输出过程 在文本输出到设备以前,我们需要确定字体、字体颜色和输出的文本内容等信息。Wi ndows 窗口的客户区由应用程序管理,所以我们还要在应用程序中控制输出文本的格式, 例如后续字符的位置、换行等格式。 由此,文本的输出过程大致包括确定字体信息、格式化文本和执行输出操作三个步骤 。下面分别讲解。 1、确定字体信息 文本在输出以前应该先确定字体信息,或者是当前正在使用的字体,或者是自定义的 字体,之后就可以根据确定的字体来显示文本或者利用字体信息来设定文本的格式了,例 如,我们可以根据当前字体的字符高度来确定下一行字符在什么位置输出。 自定义字体可以通过 CFont 类的创建字体的几个成员函数完成。获取当前选择字体的 信息可以使用 API 函数 GetTextMetrics 实现,此函数的原型如下: BOOL GetTextMetrics(__in HDC hdc,__out LPTEXTMETRIC lptm); 参数 hdc 为设备上下文的句柄;参数 lptm 是指向 TEXTMETRIC 结构体变量的指针, 此结构体变量用于接收字体信息。TEXTMETRIC 结构体的定义如下: C++代码 1. typedef struct tagTEXTMETRIC { 2. LONG tmHeight; // 字符高度 3. LONG tmAscent; // 字符基线以上的高度 4. LONG tmDescent; // 字符基线以下的高度 5. LONG tmInternalLeading; // 由 tmHeight 成员指定的字符高度顶部的空 间 6. LONG tmExternalLeading; // 行间距 7. LONG tmAveCharWidth; // 字符的平均宽度 8. LONG tmMaxCharWidth; // 字符的最大宽度 9. LONG tmWeight; // 字符的粗度 10. LONG tmOverhang; // 合成字体间附加的宽度 11. LONG tmDigitizedAspectX; // 为输出设备设计的 x 轴尺寸 12. LONG tmDigitizedAspectY; // 为输出设备设计的 y 轴尺寸 13. TCHAR tmFirstChar; // 字体中第一个字符值 14. TCHAR tmLastChar; // 字体中最后一个字符值 15. TCHAR tmDefaultChar; // 替换字体中没有的字符 16. TCHAR tmBreakChar; // 作为分隔符的字符 17. BYTE tmItalic; // 非 0 则表示字体为斜体 18. BYTE tmUnderlined; // 非 0 则表示字体有下划线 19. BYTE tmStruckOut; // 非 0 则表示字符带有删除线 20. BYTE tmPitchAndFamily;// 字体间距和字体族 21. BYTE tmCharSet; // 字符集 22. } TEXTMETRIC, *PTEXTMETRIC; 2、格式化文本 格式化文本一般包括两种,一种是确定文本行中后续文本的位置,另一种是确定换行 时下一行文本的位置。 确定后续文本的位置 一般我们可以先获取当前字符串的宽度,根据此宽度确定文本行中后续文本的位置。 当前字符串的宽度可以通过 API 函数 GetTextExtentPoint32 获得。GetTextExtentPoint32 函数的原型如下: BOOL GetTextExtentPoint32(__in HDC hdc,__in LPCTSTR lpString,__in int c,__ out LPSIZE lpSize); 参数 hdc 为设备上下文的句柄;参数 lpString 为指向文本字符串缓存的指针,此字符 串不是必须以结束符结尾的,因为参数 c 指定了长度;参数 c 为 lpString 指向的字符串的 长度;参数 lpSize 为指向 SIZE 结构体变量的指针,此 SIZE 结构体变量用于接收字符串 的宽度和高度信息。SIZE 结构体定义如下: C++代码 1. typedef struct tagSIZE { 2. LONG cx; // 宽度 3. LONG cy; // 高度 4. } SIZE, *PSIZE; 已知本字符串的起始水平坐标和宽度,两者相加即是后续文本的起始坐标。 确定换行时下一行文本的位置 由 GetTextMetrics 函数获取了当前字体的信息并存入 TEXTMETRIC 结构体后,通过 计算当前文本行的垂直坐标、当前字体的高度和行间距之和,就可以得到换行时下一行的 垂直坐标。 3、执行文本输出操作 最后,通过 API 函数 TextOut 执行文本输出操作。TextOut 函数的原型如下: BOOL TextOut(__in HDC hdc,__in int nXStart,__in int nYStart,__in LPCTSTR lpSt ring,__in int cbString); 参数 hdc 为设备上下文的句柄;参数 nXStart 为起始点 x 坐标;参数 nYStart 为起始 点 y 坐标;参数 lpString 为要输出的文本字符串;参数 cbString 为字符串中要输出的字符 的数量。 当然也可以使用设备上下文类 CDC 的成员函数 TextOut 来输出,CDC::TextOut 函数 的两种重载形式如下: virtual BOOL TextOut(int x,int y,LPCTSTR lpszString,int nCount); BOOL TextOut(int x,int y,const CString& str); 参数 x 指定文本起始点的 x 坐标;参数 y 指定文本起始点的 y 坐标;参数 lpszString 为要输出的文本字符串;参数 nCount 指定字符串中的字节个数;参数 str 为包含要输出的 字符的 CString 对象。 字体和文本输出的应用实例 鸡啄米下面给大家演示一个简单的关于字体和文本输出的实例。功能就是实现两个字 符串分别在水平方向和垂直方向上定时滚动。实现步骤如下: 1、创建一个基于对话框的MFC工程,名字设置为“Example48”。 2、在自动生成的对话框模板 IDD_EXAMPLE48_DIALOG 中,删除“TODO: Place dia log controls here.”静态文本框。 3、在 Example48Dlg.h 文件中为 CExample48 类添加成员变量: C++代码 1. int m_nTextX; // 水平滚动文本的起始点的 x 坐标 2. int m_nTextY; // 垂直滚动文本的起始点的 y 坐标 3. CFont m_newFont; // 新字体 4. CFont *m_pOldFont; // 选择新字体之前的字体 4、在 CExample48Dlg 类的构造函数中,初始化新添加的成员变量: C++代码 1. CExample48Dlg::CExample48Dlg(CWnd* pParent /*=NULL*/) 2. : CDialogEx(CExample48Dlg::IDD, pParent) 3. { 4. m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); 5. 6. m_nTextX = 260; 7. m_nTextY = 10; 8. m_pOldFont = NULL; 9. } 5、在 CExample48Dlg 对话框初始化函数中,创建新的字体,并开启定时器: C++代码 1. BOOL CExample48Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. // 创建一种新的字体(18 点,隶书) 32. m_newFont.CreatePointFont(180, _T("隶书")); 33. 34. // 设置定时器,定时时间为 200ms 35. SetTimer(1,200,NULL); 36. 37. return TRUE; // return TRUE unless you set the focus to a control 38. } 6、修改 CExample48Dlg::OnPaint()函数,如果窗口没有最小化就在指定的位置输出 文本,即在 OnPaint 函数中 if(IsIconic())对应的 else 大括号内添加相应代码。CExample4 8Dlg::OnPaint()函数修改如下: C++代码 1. void CExample48Dlg::OnPaint() 2. { 3. if (IsIconic()) 4. { 5. CPaintDC dc(this); // device context for painting 6. 7. SendMessage(WM_ICONERASEBKGND, reinterpret_cast (dc.GetSafeHdc()), 0); 8. 9. // Center icon in client rectangle 10. int cxIcon = GetSystemMetrics(SM_CXICON); 11. int cyIcon = GetSystemMetrics(SM_CYICON); 12. CRect rect; 13. GetClientRect(&rect); 14. int x = (rect.Width() - cxIcon + 1) / 2; 15. int y = (rect.Height() - cyIcon + 1) / 2; 16. 17. // Draw the icon 18. dc.DrawIcon(x, y, m_hIcon); 19. } 20. else 21. { 22. CPaintDC dc(this); // device context for painting 23. // 设置 m_newFont 对象的字体为当前字体,并将之前的字体指针保存到 m_pOldFont 24. m_pOldFont = (CFont*)dc.SelectObject(&m_newFont); 25. // 设置 26. dc.SetBkMode(TRANSPARENT); //设置背景为透明! 27. // 设置文本颜色为红色 28. dc.SetTextColor(RGB(255,0,0)); 29. // 在指定位置输出文本 30. dc.TextOut(m_nTextX,10,_T("欢迎来到鸡啄米!")); 31. // 设置文本颜色为绿色 32. dc.SetTextColor(RGB(0,255,0)); 33. // 在指定位置输出文本 34. dc.TextOut(10,m_nTextY,_T("谢谢关注 www.jizhuomi.com")); 35. // 恢复以前的字体 36. dc.SelectObject(m_pOldFont); 37. 38. CDialogEx::OnPaint(); 39. } 40. } 7、在 Class View 类视图中找到 CExample48Dlg,右键点 Properties,显示出其属性 页,在属性页工具栏上点击 Messages 按钮,找到 WM_TIMER 消息,添加消息响应函数 CExample48Dlg::OnTimer(UINT_PTR nIDEvent),并在此函数中修改两个文本输出的坐 标位置。 C++代码 1. void CExample48Dlg::OnTimer(UINT_PTR nIDEvent) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. LOGFONT logFont; 5. // 获取 m_newFont 字体的 LOGFONT 结构 6. m_newFont.GetLogFont(&logFont); 7. 8. // 将 m_nTextX 的值减 5 9. m_nTextX -= 5; 10. // 如果 m_nTextX 小于 10,则文本“欢迎来到鸡啄米”回到起始位置 11. if (m_nTextX < 10) 12. m_nTextX = 260; 13. 14. // 将 m_nTextY 的值加一个字符高度 15. m_nTextY += abs(logFont.lfHeight); 16. // 如果 m_nTextY 大于 260,则文本“谢谢关注 www.jizhuomi.com”回到起 始位置 17. if (m_nTextY >260) 18. m_nTextY = 10; 19. 20. // 使窗口客户区无效,之后就会重绘 21. Invalidate(); 22. 23. CDialogEx::OnTimer(nIDEvent); 24. } 到这一步,两个文本就可以分别在水平和垂直方向滚动了。鸡啄米再简单解释下这个 过程:程序刚启动时,会调用 OnPaint 函数,在初始位置绘出两个文本,然后每次到了定 时器的定时时间后,会执行 OnTimer 函数,修改两个文本的坐标值,并通过 Invalidate 使 窗口重绘,又会重新调用 OnPaint 函数绘制两个文本。这样通过定时修改坐标值就实现了 两个文本的滚动效果。 8、运行程序,最终的效果如下图: VS2010/MFC 编程入门之四十九(图形图像:CDC 类及其屏幕 绘图函数) 上一节中鸡啄米讲了文本输出的知识,本节的主要内容是 CDC 类及其屏幕绘图函数 。 CDC 类简介 CDC 类是一个设备上下文类。 CDC 类提供了用来处理显示器或打印机等设备上下文的成员函数,还有处理与窗口客 户区关联的显示上下文的成员函数。使用 CDC 的成员函数可以进行所有的绘图操作,包 括处理绘图工具、GDI 对象的选择、颜色和调色板的处理、获取和设置绘图属性、映射、 窗口范围、坐标转换、剪切以及绘制直线、简单图形、椭圆和多边形等,另外它还为文本 输出、处理字体、使用打印机跳转和滚动等提供了成员函数。 如上所述,CDC 类几乎封装了所有的 Windows GDI 函数,另外,MFC中还有几个由 CDC 类派生的子类,包括 CWindowDC、CPaintDC、CClientDC、CMetaFileDC,它们 用来进行一些特定的绘图操作。 一般我们在使用完 CDC 对象后要记得删除它,否则会有内存泄露。很多情况下我们 可以调用 CWnd::GetDC()函数来获取设备上下文指针,即 CDC 指针,这个时候记得用完 后调用 CWnd::ReleaseDC()函数释放设备上下文。 CDC 类的屏幕绘图成员函数 CDC 类有很多成员函数,鸡啄米在这里只大概讲下比较常用的绘图函数,包括绘制点 、直线、矩形、椭圆、多边形、文本以及位图等的成员函数。 COLORREF SetPixel(int x,int y,COLORREF crColor); COLORREF SetPixel(POINT point,COLORREF crColor); 上面两个成员函数用来将指定坐标点的像素设置为指定的颜色,这样就实现了画点功 能。参数 x 为点的逻辑 x 坐标;参数 y 为点的逻辑 y 坐标;参数 crColor 为要为点设置的 颜色;参数 point 指定点的逻辑 x 坐标和逻辑 y 坐标,可以为其传入 POINT 结构体变量或 者 CPoint 对象。 CPoint MoveTo(int x,int y); CPoint MoveTo(POINT point); 将当前点移动到指定位置。参数 x 指定新位置的逻辑 x 坐标;参数 y 指定新位置的逻 辑 y 坐标;参数 point 指定新位置的逻辑 x 坐标和逻辑 y 坐标,可以为其传入 POINT 结构 体变量或者 CPoint 对象。 BOOL LineTo(int x,int y); BOOL LineTo(POINT point); 绘制一条从当前点到指定点(不包括指定点)的直线。参数 x 为指定点的逻辑 x 坐标 ;参数 y 为指定点的逻辑 y 坐标;参数 point 为指定点的逻辑 x 坐标和逻辑 y 坐标。一般 我们绘制直线时就可以先调用 MoveTo 函数移动当前点到某个位置,然后调用 LineTo 画 直线。 BOOL Rectangle(int x1,int y1,int x2,int y2); BOOL Rectangle(LPCRECT lpRect); 使用当前画笔绘制矩形。参数 x1 指定矩形左上角的 x 坐标;参数 y1 指定矩形左上角 的 y 坐标;参数 x2 指定矩形右下角的 x 坐标;参数 y2 指定矩形右下角的 y 坐标;以上坐 标均为逻辑单位。参数 lpRect 为矩形对象的指针,可以为其传入 CRect 对象或 RECT 结 构体变量的指针。 BOOL Ellipse(int x1,int y1,int x2,int y2); BOOL Ellipse(LPCRECT lpRect); 绘制椭圆。参数 x1 指定椭圆的包围矩形左上角的 x 坐标;参数 y1 指定椭圆的包围矩 形左上角的 y 坐标;参数 x2 指定椭圆的包围矩形右下角的 x 坐标;参数 y2 指定椭圆的包 围矩形右下角的 y 坐标;以上坐标均为逻辑单位。参数 lpRect 指定椭圆的包围矩形,可以 传入 CRect 对象或 RECT 结构体变量的指针。 BOOL Polyline(LPPOINT lpPoints,int nCount); 由指定的多边形顶点绘制多边形。参数 lpPoints 为指向一个 POINT 结构体变量数组 或 CPoint 对象数组的指针,其中的 POINT 结构体变量或 CPoint 对象代表了多边形顶点 的坐标;参数 nCount 为数组中点的个数,至少为 2。 virtual BOOL TextOut(int x,int y,LPCTSTR lpszString,int nCount); BOOL TextOut(int x,int y,const CString& str); 使用当前选择的字体在指定位置输出文本。 参数 x 指定文本起始点的 x 坐标;参数 y 指定文本起始点的 y 坐标;参数 lpszString 为要输出的文本字符串;参数 nCount 指定字 符串中的字节个数;参数 str 为包含要输出的字符的 CString 对象。这两个函数在上一节中 其实已经讲到了。 BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop ); 从源设备上下文拷贝一幅位图到当前设备上下文。参数 x 指定目标矩形区域左上角的 逻辑 x 坐标;参数 y 指定目标矩形区域左上角的逻辑 y 坐标;参数 nWidth 指定目标矩形 区域和源位图的宽度(逻辑单位);参数 nHeight 指定目标矩形区域和源位图的高度(逻 辑单位);参数 pSrcDC 为指向源设备上下文的 CDC 对象的指针,如果 dwRop 指定了一 个不包含源的光栅操作,那么 pSrcDC 可以为 NULL;参数 xSrc 指定源位图左上角的逻辑 x 坐标;参数 ySrc 指定源位图左上角的逻辑 y 坐标;参数 dwRop 指定要执行的光栅操作 ,光栅操作码定义了 GDI 如何将当前画刷颜色、源位图颜色和目标位图颜色组合形成新的 颜色,下面是一些常用的光栅操作码及含义: BLACKNESS:表示使用与物理调色板的索引 0 相关的色彩来填充目标矩形区域,( 对缺省的物理调色板而言,该颜色为黑色)。 DSTINVERT:表示使目标矩形区域颜色取反。 MERGECOPY:表示使用布尔型的 AND(与)操作符将源矩形区域的颜色与特定模 式组合一起。 MERGEPAINT:通过使用布尔型的 OR(或)操作符将反向的源矩形区域的颜色与目 标矩形区域的颜色合并。 NOTSRCCOPY:将源矩形区域颜色取反,于拷贝到目标矩形区域。 NOTSRCERASE:使用布尔类型的 OR(或)操作符组合源和目标矩形区域的颜色值 ,然后将合成的颜色取反。 PATCOPY:将特定的模式拷贝到目标位图上。 PATPAINT:通过使用布尔 OR(或)操作符将源矩形区域取反后的颜色值与特定模 式的颜色合并。然后使用 OR(或)操作符将该操作的结果与目标矩形区域内的颜色合并 。 PATINVERT:通过使用 XOR(异或)操作符将源和目标矩形区域内的颜色合并。 SRCAND:通过使用 AND(与)操作符来将源和目标矩形区域内的颜色合并。 SRCCOPY:将源矩形区域直接拷贝到目标矩形区域。 SRCERASE:通过使用 AND(与)操作符将目标矩形区域颜色取反后与源矩形区域 的颜色值合并。 SRCINVERT:通过使用布尔型的 XOR(异或)操作符将源和目标矩形区域的颜色合 并。 SRCPAINT:通过使用布尔型的 OR(或)操作符将源和目标矩形区域的颜色合并。 WHITENESS:使用与物理调色板中索引 1 有关的颜色填充目标矩形区域。(对于缺 省物理调色板来说,这个颜色就是白色)。 好了,本节就讲到这里了,主要就是大概讲了讲 CDC 类,又介绍了 CDC 类一些常用 的绘图函数。如果想了解更多的资料可以查阅 MSDN。 VS2010/MFC 编程入门之五十(图形图像:GDI 对象之画笔 CPen) 上一节中鸡啄米讲了CDC 类及其屏幕绘图函数,本节的主要内容是 GDI 对象之画笔 CPen。 GDI 对象 在MFC中,CGdiObject 类是 GDI 对象的基类,通过查阅 MSDN 我们可以看到,CGdi Object 类有六个直接的派生类,GDI 对象主要也是这六个,分别是:CBitmap、CBrush、 CFont、CPalette、CPen 和 CRgn。 在这六个 GDI 对象中,最常用的莫过于画笔和画刷了,即 CPen 类和 CBrush 类。本 文就主要讲解画笔的使用。 画笔的应用实例 鸡啄米在这里直接通过一个波形图的实例,来详细讲解画笔的使用方法。 首先介绍此实例要实现的功能:在对话框上有一个 Picture 控件,将此控件的背景填 充为黑色;启动一个定时器,每次定时器到时,所有波形数据都前移一个单位,并获取一 个 80 以内的随机数作为波形的最后一个数据,然后以绿色画笔在绘图控件上绘制波形。 这样就实现了波形的绘制及动态变化。 下面是具体实施步骤: 1、创建一个基于对话框的 MFC 工程,名字设为“Example50”。 2、在自动生成的对话框模板 IDD_EXAMPLE50_DIALOG 中,删除“TODO: Place dia log controls here.”静态文本框,添加一个Picture 控件,ID 设为 IDC_WAVE_DRAW。 3、为 Picture 控件 IDC_WAVE_DRAW 添加 CStatic 变量,名称设为 m_picDraw。 4、在文件 Example50Dlg.h 文件中 CExample50Dlg 类声明的上面添加宏定义: C++代码 1. #define POINT_COUNT 100 此符号常量的意义是波形的点数,这里用 define 将其定义为符号常量是为了方便以后 可能的修改,假如我们以后想将点数改为 200,则只改此宏定义就可以了:#define POINT _COUNT 200,而如果没有使用符号常量,在程序中直接使用了 100,那么就需要将所有 使用 100 的位置找出来,并替换为 200,这样不仅麻烦也很容易出错,所以最好是将其定 义为符号常量。 5、在 CExample50Dlg.h 文件中为 CExample50Dlg 类添加成员数组: C++代码 1. int m_nzValues[POINT_COUNT]; 此数组用于存放波形数据。 6、在 CExample50Dlg 类的构造函数中为数组 m_nzValues 的元素赋初值: C++代码 1. CExample50Dlg::CExample50Dlg(CWnd* pParent /*=NULL*/) 2. : CDialogEx(CExample50Dlg::IDD, pParent) 3. { 4. m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); 5. 6. // 将数组 m_nzValues 的元素都初始化为 0 7. memset(m_nzValues, 0, sizeof(int) * POINT_COUNT); 8. } 7、在 CExample50Dlg 对话框的初始化成员函数 CExample50Dlg::OnInitDialog()中, 构造随机数生成器,并启动定时器。CExample50Dlg::OnInitDialog()修改如下: C++代码 1. BOOL CExample50Dlg::OnInitDialog() 2. { 3. CDialogEx::OnInitDialog(); 4. 5. // Add "About..." menu item to system menu. 6. 7. // IDM_ABOUTBOX must be in the system command range. 8. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 9. ASSERT(IDM_ABOUTBOX < 0xF000); 10. 11. CMenu* pSysMenu = GetSystemMenu(FALSE); 12. if (pSysMenu != NULL) 13. { 14. BOOL bNameValid; 15. CString strAboutMenu; 16. bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 17. ASSERT(bNameValid); 18. if (!strAboutMenu.IsEmpty()) 19. { 20. pSysMenu->AppendMenu(MF_SEPARATOR); 21. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAb outMenu); 22. } 23. } 24. 25. // Set the icon for this dialog. The framework does this a utomatically 26. // when the application's main window is not a dialog 27. SetIcon(m_hIcon, TRUE); // Set big icon 28. SetIcon(m_hIcon, FALSE); // Set small icon 29. 30. // TODO: Add extra initialization here 31. 32. // 以时间为种子来构造随机数生成器 33. srand((unsigned)time(NULL)); 34. 35. // 启动定时器,ID 为 1,定时时间为 200ms 36. SetTimer(1, 200, NULL); 37. 38. return TRUE; // return TRUE unless you set the focus to a control 39. } 8、为 CExample50Dlg 类添加波形绘制的成员函数 CExample50Dlg::DrawWave(CD C *pDC, CRect &rectPicture),参数分别为设备上下文指针和绘图的矩形区域。 C++代码 1. void CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture) 2. { 3. float fDeltaX; // x 轴相邻两个绘图点的坐标距离 4. float fDeltaY; // y 轴每个逻辑单位对应的坐标值 5. int nX; // 在连线时用于存储绘图点的横坐标 6. int nY; // 在连线时用于存储绘图点的纵坐标 7. CPen newPen; // 用于创建新画笔 8. CPen *pOldPen; // 用于存放旧画笔 9. CBrush newBrush; // 用于创建新画刷 10. CBrush *pOldBrush; // 用于存放旧画刷 11. 12. // 计算 fDeltaX 和 fDeltaY 13. fDeltaX = (float)rectPicture.Width() / (POINT_COUNT - 1); 14. fDeltaY = (float)rectPicture.Height() / 80; 15. 16. // 创建黑色新画刷 17. newBrush.CreateSolidBrush(RGB(0,0,0)); 18. // 选择新画刷,并将旧画刷的指针保存到 pOldBrush 19. pOldBrush = pDC->SelectObject(&newBrush); 20. // 以黑色画刷为绘图控件填充黑色,形成黑色背景 21. pDC->Rectangle(rectPicture); 22. // 恢复旧画刷 23. pDC->SelectObject(pOldBrush); 24. // 删除新画刷 25. newBrush.DeleteObject(); 26. 27. // 创建实心画笔,粗度为 1,颜色为绿色 28. newPen.CreatePen(PS_SOLID, 1, RGB(0,255,0)); 29. // 选择新画笔,并将旧画笔的指针保存到 pOldPen 30. pOldPen = pDC->SelectObject(&newPen); 31. 32. // 将当前点移动到绘图控件窗口的左下角,以此为波形的起始点 33. pDC->MoveTo(rectPicture.left, rectPicture.bottom); 34. // 计算 m_nzValues 数组中每个点对应的坐标位置,并依次连接,最终形成曲 线 35. for (int i=0; iLineTo(nX, nY); 40. } 41. 42. // 恢复旧画笔 43. pDC->SelectObject(pOldPen); 44. // 删除新画笔 45. newPen.DeleteObject(); 46. } 9、有了定时器和绘图成员函数,我们就可以在 WM_TIMER 消息的响应函数中添加对 波形数据的定时处理和对波形的定时绘制了。定时器及 WM_TIMER 消息处理函数的添加 方法如果忘记了,可以再到VS2010/MFC 编程入门之四十四(MFC 常用类:定时器 Timer )温习下。 WM_TIMER 消息的处理函数修改如下: C++代码 1. void CExample50Dlg::OnTimer(UINT_PTR nIDEvent) 2. { 3. // TODO: Add your message handler code here and/or call def ault 4. CRect rectPicture; 5. 6. // 将数组中的所有元素前移一个单位,第一个元素丢弃 7. for (int i=0; iNew->Project,弹出标题为“New Project”的对 话框。在此对话框左侧面板中选择 Installed Templates->Visual C++->MFC,然后在中间 区域中选择“MFC Application”。 选择了工程类型后,可以看到对话框下边有三个设置项,分别是 Name--工程名、Loc ation--解决方案路径、Solution Name--解决方案名称。鸡啄米在这里将 Name 设为“Exam ple52”,Location 设为“桌面”的路径,Solution Name 默认与 Name 一样,我们这里不作修 改。如下图: 点“OK”按钮。 2、这时会弹出“MFC Application Wizard”对话框,上部写有“Welcome to the MFC Ap plication Wizard”,下面显示了当前工程的默认设置。第一条“Tabbed multiple document i nterface (MDI)”是说此工程是多文档应用程序。如果这时直接点下面的“Finish”按钮,可生 成具有上面列出设置的多文档程序。但我们此例是要建立 Ribbon 样式的单文档应用程序 ,所以点“Next”按钮再继续设置吧。 3、接下来弹出的对话框上部写有“Application Type”,当然是让选择应用程序类型, 我们选择“Single document”,说明要创建的是单文档应用程序框架。另外,在“Project Typ e”下选择“Office”,表示此应用程序为 Office 风格。如下图: 点“Next”按钮。 4、弹出上部写有“Compound Document Support”的对话框,可以通过它向应用程序 加入 OLE 支持,这里使用默认值“None”。点“Next”按钮。 5、弹出的新对话框上部写有“Document Template Properties”。这里都使用默认设置 ,点“Next”按钮。 6、此时弹出的对话框主题是“Database Support”。用于设置数据库选项。依然使用默 认值,点“Next”。 7、这时弹出的对话框是关于“User Interface Features”,即用户界面特性的。由于我 们要使用 Ribbon 界面,所以要确保“Command bars (menu/toolbar/ribbon)”下的单选按钮“ Use a ribbon”选中。其他设置使用默认值。如下图: 点“Next”按钮。 8、此时弹出“高级特性”对话框。可以设置的高级特性包括有无打印和打印预览等。保 持默认值不变,点“Next”。 9、弹出最后一个“Generated Classes”(生成类)向导对话框,列出了要生成的四个 类。这里不作修改,点“Finish”。 这样我们就完成了创建 Ribbon 样式的应用程序框架的全部设置。编译运行程序,得 到如下的的 Ribbon 界面(Office 2007(Blue Style)): 在 Ribbon 界面的右上角位置有个“Style”下拉菜单,我们可以选择不同的 Style,在 Of fice 2007 (Blue Style)、Office 2007 (Black Style)、Office 2007 (Silver Style)、Office 200 7 (Aqua Style)、Windows 7 等 5 中风格之间切换。 关于 Ribbon 样式的应用程序框架的创建鸡啄米就讲到这里了,有了以前的基础,这 些操作可以说都是小菜一碟。 VS2010/MFC 编程入门之五十三(Ribbon 界面开发:为 Ribbon Bar 添加控件) 前面一节中鸡啄米为大家简单介绍了如何创建 Ribbon 样式的应用程序框架,本节教 程就来初步讲讲怎样为 Ribbon Bar 添加 Ribbon 控件。 VS2010为 Ribbon 界面开发提供了 Ribbon Designer,通过它我们可以为 Ribbon Bar 添加各种 Ribbon 控件、设置控件属性和进行界面布局。 Ribbon 的界面元素可以分为类别、面板和基本控件(按钮、文本编辑框等),类别 由面板组成,面板又由按钮、文本编辑框等基本控件组成。 鸡啄米以实例的方式讲解 Ribbon 控件的添加方法和属性,这样比较直观。此实例还 是在上一节中创建的 Example52 工程的基础上进行修改。 1、打开 Example52 工程,在资源视图 Resource View 中,展开 Example52->Examp le52.rc->Ribbon,在 Ribbon 节点下,我们看到有一个系统自动生成的 IDR_RIBBON,双 击 IDR_RIBBON 节点,就可以在中间区域打开 Ribbon Designer,如下图: 上图中,Home 标签下的整个界面就是类别,Clipboard 和 View 对应的就是面板,每 个面板都有一些按钮、复选框等基本控件。 2、我们要向 Ribbon 界面中添加控件的话,也需要从 Toolbox 给出的 Ribbon 控件列 表中选择控件拖入 Ribbon bar。点击 View 菜单下的 Toolbox,就会显示出 Toolbox 视图 。下图就是 Ribbon Designer 的 Toolbox: 虽然与以前我们用的 Toolbox 有些不同,但还是很相似的。上图的 Toolbox 中的 Cate gory 就是类别,Panel 就是面板,其他的大部分都是基本控件。 3、在 Toolbox 中选择 Category 拖入 Ribbon bar,放到 Home 类别的后面,可以看到 它的默认名称为“Category1”,并且默认带了一个面板“Panel1”。这里我们选择“Category1” 标签,右键点击“Properties”,在显示出的属性页中,修改 Caption 属性为“Function”。然 后选择“Panel1”面板,以同样的方法修改其 Caption 属性为“Big Button”。 4、接下来我们再为“Function”类别添加一个新面板。在 Toolbox 中选择 Panel 拖到“F unction”类别下,放到“Big Button”面板后面,Caption 属性修改为“Small Button”。 5、再往“Big Button”面板中添加一个按钮控件。在 Toolbox 中选择 Button 拖入“Big Bu tton”面板中,Caption 修改为“Open”。以同样的方式往“Small Button”面板中添加两个按钮 控件,Caption 分别修改为“Click”和“Check”。此时的 Ribbon bar 如下图所示: 6、通过与 Home 下的按钮对比我们发现,新添加的几个按钮不太美观,因为没有加 图片。那么怎样为按钮加图片呢? 观察发现,Home 下的按钮的图标有两种:大图标和小图标。实际上大图标是像素为 32×32 的图标,小图标是像素为 16×16 的图标。鸡啄米为三个按钮制作了两套图像序列, 每个图像序列都由三个图标组成,第一个图像序列由三个大图标组成,第二个图像序列由 三个小图标组成。如下面的两个图: 大图标(newicons-32.bmp) 小图标(newicons-16.bmp) 要得到这样的图像序列,可以先找到每个图标,然后使用 IconWorkshop 等工具制作 成图像序列即可。 将两个图片都复制到目录...\Example52\Example52\res 下,然后在工程中 Resource View 资源视图的 Example52.rc->Bitmap 上点右键,选择“Add Resource”,弹出 Add Res ource 对话框,在 Add Resource 对话框左侧的树中选择“Bitmap”,然后点击“Import”按钮 ,选择 newicons-32.bmp 文件后就成功导入了大图标文件,ID 默认为 IDB_BITMAP1。以 同样的方式导入小图标文件 newicons-16.bmp,ID 默认为 IDB_BITMAP2。 在“Function”类别的属性页中可以看到有 Large Images 和 Small Images 两个属性, 它们就是用来设置本类别下控件所要使用的大图标序列和小图标序列的。这里我们将 Larg e Images 属性设为 IDB_BITMAP1,Small Images 属性设为 IDB_BITMAP2。 然后我们为 Open 按钮设置图片。在 Open 按钮的属性页中有 Image Index 和 Large I mage Index 两个属性,分别是其小图标在小图标序列中的索引和其大图标在大图标序列中 的索引,这里我们设置其图标为大图标,且为大图标序列中的第一个,那么直接设置 Larg e Image Index 属性为 0,也可以通过在选择 Large Image Index 属性的编辑框后出现的 浏览按钮上点击,弹出 Image Collection 对话框来选择图标。 再为 Click 按钮和 Check 按钮设置小图标。将 Click 按钮的 Image Index 属性设置为 1 ,Large Image Index 属性仍为-1,Check 按钮的 Image Index 属性设置为 2,Large Imag e Index 属性也保持为-1。 7、编译运行程序,最终界面的 Function 类别视图如下: 因为上述三个按钮都没有添加任何事件的响应函数,所以都是灰色的。 这一节就讲到这里了。大家可以试着添加其他控件看看效果 VS2010/MFC 编程入门之五十四(Ribbon 界面开发:使用更多 控件并为控件添加消息处理函数) 上一节中鸡啄米讲了为 Ribbon Bar 添加控件的方法。本节教程鸡啄米将继续完善前面 的实例,讲解一些稍复杂的控件的添加方法,及如何为它们添加消息处理函数。 一、为 Ribbon Bar 添加更多 Ribbon 控件 鸡啄米将在上一节实例的基础上,继续添加下拉菜单、Check Box、Combo Box 等 Ri bbon 控件。 1、首先把“Small Button”面板上的“Click”按钮改造成一个下拉菜单。“Click”按钮有一个 Behavior 属性 Menu Items,默认为 Empty,选中它右侧会出现一个浏览按钮,点击浏览 按钮会弹出“Items Editor”对话框,如下图: 我们可以在上图 Items 下的组合框中选择按钮、分割线等,点击组合框右侧的 Add 按 钮将其添加到下拉菜单中,添加按钮后在 Properties 分组中可以设置 Caption(标题)、I D、Image(图片)等属性。鸡啄米这里添加两个按钮,Caption 属性分别为 One Click、D ouble Click,ID 分别为 ID_ONE_CLICK、ID_DOUBLE_CLICK,Image 等属性就不设置 了。此时的 Ribbon Bar 如下图: “Click”右侧多了一个向下的箭头,运行程序后点击此箭头会显示包含 One Click 和 Do uble Click 按钮的下拉菜单。另外,上图中有一个按钮鸡啄米用红线指示了其提示信息-“Te st Ribbon”,点击了此按钮我们就可以不运行程序而直接查看 Ribbon 界面效果。 2、在 Small Button 面板的右侧再添加一个面板“More Controls”,然后在 Toolbox 工 具中找到 Check Box 和 Combo Box 控件拖入新面板,Check Box 的 Caption 属性设为“W ebsites Enable”,Combo Box 的属性设为“Websites”。效果图如下: 我们为 Websites 组合框(Combo Box)添加两个下拉选项,方法是,右键点击 Web sites 组合框,选择“Properties”,显示出其属性页,修改 Data 属性为“www.jizhuomi.com; www.jizhuomi.com/android”,这样就为此 Combo Box 添加两个网址选项。 二、为 Ribbon 控件添加消息处理函数 前面控件都添加好了,接下来我们就为控件添加消息处理函数。 1、首先为 Open 按钮添加单击事件的消息处理函数,其 ID 修改为 ID_OPEN_BUTTO N,然后右键点击 Open 按钮,选择“Add Event Handler”,弹出 Event Handler Wizard 对 话框,右侧的 Class list 中选择“CMainFrame”,左侧的 Message Type 中选择“COMMAN D”,最后点击“Add and Edit”按钮,CMainFrame 类中就添加了 void CMainFrame::OnOpe nButton()成员函数。 大家可能感觉到了,其实消息处理函数的添加过程与以前的普通控件是类似的。最后 修改 void CMainFrame::OnOpenButton()函数的函数实现如下: C++代码 1. void CMainFrame::OnOpenButton() 2. { 3. // TODO: Add your command handler code here 4. MessageBox(_T("Open Button!")); // 弹出对话框,提示“Open But ton!” 5. } 因为只是为了讲解按钮的消息处理函数的添加,所以没有写复杂的代码,只写了一个 弹出 MessageBox 的语句。运行程序,在结果界面的 Function 类别的 Big Button 面板中 ,点击 Open 按钮就会弹出一个对话框,并显示“Open Button!”。 2、我们再为 Websites 组合框添加消息处理函数(方法同上),同样也为其在 CMain Frame 类中添加 COMMAND 消息处理函数--void CMainFrame::OnWebsitesCombo(),修 改此函数实现如下: C++代码 1. void CMainFrame::OnWebsitesCombo() 2. { 3. // TODO: Add your command handler code here 4. // 获取 Combo Box 控件的指针 5. CMFCRibbonComboBox* pComboBox = DYNAMIC_DOWNCAST(CMFCRibbon ComboBox, m_wndRibbonBar.FindByID(ID_WEBSITES_COMBO)); 6. // 获取 Combo Box 控件当前选中项的索引 7. int nCurSel = pComboBox->GetCurSel(); 8. 9. if (nCurSel >= 0) 10. { 11. // 如果索引大于等于 0,则说明有选中项,弹出对话框并显示选中项的信 息 12. MessageBox(pComboBox->GetItem(nCurSel)); 13. } 14. else 15. { 16. // 如果索引小于 0,则说明没有选中项,弹出对话框提示用户进行选择 17. MessageBox(_T("Please select one item!")); 18. } 19. } 运行程序,在结果界面中,改变 WebSites 组合框的选中项,则会弹出对话框显示选 中项的信息。如下图: 3、然后我们为 Websites Enable 复选框添加消息处理函数。默认情况下 Check Box 控件不会因为用户的点击而改变状态,这就需要我们通过代码来实现正常的复选功能。 我们需要一个变量保存 Check Box 的当前选中状态,所以在 MainFrm.h 文件中为 CM ainFrame 类添加一个 BOOL 型的成员变量 m_bWebsitesEnable,并在 CMainFrame 类的 构造函数中为其初始化: C++代码 1. CMainFrame::CMainFrame() 2. { 3. // TODO: add member initialization code here 4. theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"), ID _VIEW_APPLOOK_OFF_2007_BLUE); 5. // 初始化为 TRUE,即复选框为选中状态 6. m_bWebsitesEnable = TRUE; 7. } 接下来仍然采用 1 中的方法为 Websites Enable 复选框在 CMainFrame 类中添加 CO MMAND 消息处理函数,并修改其函数体如下: C++代码 1. void CMainFrame::OnWebsitesCheck() 2. { 3. // TODO: Add your command handler code here 4. // 为 m_bWebsitesEnable 取反,即切换复选框的状态 5. m_bWebsitesEnable = !m_bWebsitesEnable; 6. } 但是现在复选框的状态只是保存到了变量中,我们还要根据变量值改变复选框的显示 状态,这就需要再为 Check Box 添加一个 UPDATE_COMMAND_UI 消息处理函数,方法 仍旧是右键点击 Check Box,选择“Add Event Handler”,在弹出的 Event Handler Wizard 对话框的 Class list 中选择“CMainFrame”,Message type 中则选择 UPDATE_COMMAN D_UI,最后点“Add and Edit”,这样就添加了 UPDATE_COMMAND_UI 消息处理函数, 修改此函数实现如下: C++代码 1. void CMainFrame::OnUpdateWebsitesCheck(CCmdUI *pCmdUI) 2. { 3. // TODO: Add your command update UI handler code here 4. // 根据当前变量值设置复选框状态 5. pCmdUI->SetCheck(m_bWebsitesEnable); 6. } 这时你可以试着运行下程序,点击 Website Enable 复选框,它已经能成功的改变状态 了。 4、除了以上功能,我们还要实现一个稍复杂的功能,就是如果选中 Websites Enable 复选框则激活 Websites 组合框,而如果取消选中则禁用 Websites 组合框。这就需要我们 为 Websites 组合框添加 UPDATE_COMMAND_UI 消息处理函数了,添加方法同上,鸡 啄米这里不再赘述。修改函数实现为: C++代码 1. void CMainFrame::OnUpdateWebsitesCombo(CCmdUI *pCmdUI) 2. { 3. // TODO: Add your command update UI handler code here 4. // 根据 Websites Enable 复选框的状态确实激活还是禁用 5. pCmdUI->Enable(m_bWebsitesEnable); 6. } 运行程序,试着改变 Websites Enable 复选框的状态,Websites 组合框的使能状态 也会跟着改变。 最后,鸡啄米再简单讲讲如何为 Ribbon Bar 左上角的圆形菜单按钮和快速访问工具 栏添加新项。 圆形菜单按钮的属性页中有一个 Buttons 属性,可以点击其右侧浏览按钮弹出 Items E ditor 对话框,使用该对话框可以在菜单按钮的弹出菜单窗口中添加右下角的按钮。还有一 个 Main Items 属性,点其右侧浏览按钮也会弹出 Items Editor 对话框,通过它可以为圆形 菜单按钮添加菜单项。 快速访问工具栏的属性页中有一个 QAT Items 属性,点击其右侧浏览按钮弹出 QAT It ems Editor 对话框,使用该对话框可以在快速访问工具栏中添加新项。
还剩305页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 8 人已下载

下载pdf

pdf贡献者

nbnw

贡献于2016-01-23

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf