以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- VC技术内幕笔记 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=18587) |
-- 作者:卷积内核 -- 发布时间:5/20/2005 5:21:00 PM -- VC技术内幕笔记 网上资源, 软件技术 卷积内核 发表于 2005-5-19 15:51:57 WINDOWS应用程序一定要有WinMain函数,该函数用来完成一些特殊的任务,象创建程序的主窗口,主窗口用来处理消息的代码。MFC将WinMain隐藏在框架中,不象写SDK程序时可以很容易的找到它。 WINDOWS采用的消息处理机制也交给了程序框架,我们不必担心如何使这些消息和代码联系起来。并且WINDOWS定义好了一些消息,当窗口被创建时系统就会发送WM_CREATE消息,当点击鼠标左键时便系统会发送WM_LBUTTONDOWN消息,当用户按下键盘时系统会发送WM_CHAR消息,当用户关闭窗口时系统会发送WM_CLOSE消息,当用户进行菜单项选择或单击按钮时系统回发送WM_COMMAND消息,什么都不做系统还会发送WM_TIMER消息。先不说别的,先搞清出这几个消息再说。从MSDN中可以很方便的找到关于这几个消息的帮助文档。 WINDOWS提供通用的图形设备接口(GUI),我们通过调用(GDI)函数和硬件打交道,不必理会设备环境,WINDOWS会自动将设备环境结构映射到相应的物理设备,这应该就是设备无关性吧。 用Developer Studio建立项目Developer Studio会创建很多中间文件,这些文件还是有必要说一下的。 APS //支持ResourceView 这些文件都有一定的作用,拿CLW来说,如果你有一个新类,需要加到ClassWizard中,除了将相应的.h 和.cpp加到DSP中还需要重新编译CLW。PLG文件记录着你的项目配置信息。 VC++的源程序浏览器能够使我们从类或函数的角度来了解或编辑程序,而不是直接从文件入手。在看别人的源代码时如果能熟练的使用源代码浏览器将会事倍功半。源程序浏览器主要的查看状态有以下几种: Definitions and References——选择任何函数、变量、类型、宏定义可以看到它在项目中的定义,并且在何处和什么地方用到它。 Call Graph/Caller Graph——对于所选择的函数,给出它的调用与被调用函数的图示。 Derived Class Graph/Base Class Graph——给出类层次关系的图形表示,可以看到所选择的类的派生类和基类以及成员。 File Outline——对于所选的文件,列出文件中的类、函数和数据成员,同时还显示它们定义的位置和使用位置。 可见Source Brower比起Class View来功能多了很多也更加好用,以前我就不知道,因为一般的VC++书没有讲或根本没有注意这块,看到讲菜单、操作界面、编辑器时总是跳过,心想“没吃过猪肉还没见过猪跑?这种东西不用学就会”。可实际上还是应该仔细的看看的。 对于本章学习雷神建议大家在VC++6中用AppWizard生成一个空的程序,然后试着看看都有那些文件,和他们的类层次、函数、宏、结构的定义,我就是这样干的,学编程不动手是不行的。 ◎MFC是C++的Microsoft Windows API,如果想要开发WINDOWS的应用程序当然VC/MFC是开发环境的首选。 这一章节主要介绍了MFC库的优点,其实我本人认为在某些特定环境下其实MFC不一定就象说的那样好。我想不会有人用纯VC做MIS系统吧,太累了。我是这样理解编程序的,如果把学编程看成学武的话,C/C++语言及编程思想(OOP)是内功,API是基本功(编程思想是内功、API是基本功这适用于任何WIN32编程,不论Visual C++、Delphi、C++Builder、VB......),VC/MFC应该不同武功其中的一种,不同的学习方法效果不一样,只要下工夫也都可以达到一定的境界。真正的高手是有着深厚的内功,扎实的基本功,至于武功招数无所谓了,随便一站不丁不八全无破绽,无招胜有招了。对不住扯远了。 C++可以通过类库来进行扩展,我们除了可以使用随编译器提供的类库外还可以很方便使用软件公司销售的类库产品,甚至可以自己开发。而应用程序框架是一种类库的超集,它定义了程序的结构。 下面给出两个示例程序(一个是书上的一个是我写的): 雷神建议:虽然现在很多书都附CD,CD上有书中所有示例的源代码,但还是应该亲自在VC6用手敲进去。这样可以加深印象以及感受一下编译除错后程序正确运行时的乐趣,因为是纯手工打造。自从我敲了近一百个代码示例后,由于笔误的BUG就很少了,打字速度也提高了。而且最好在原示例代码的基础上做些改动例如别千篇一律的显示HELLO WORLD!换点别的,这样做也可以加深对示例程序的理解。 1、打开VC++6从菜单选择NEW,给项目命名为”MyApp01“。 2、选择MFC AppWizard[exe] 选项,除STEP 1选择单文档外其他STEP缺省。 3、在Class View选择CMyApp01View类的OnDraw()成员函数双击会在C++编译器看到以下内容 void CMyApp01View::OnDraw(CDC* pDC) 完了,就这么简单。编译运行。看到了吗?这个程序具备WINDOWS程序的所有特性,例如有菜单、工具条、状态栏、最大化、关闭、甚至还有关于对话框、打印预览.....全了,这就是AppWizard通过MFC动态创建的一个应用程序。从这个小例子可以看出用VC/MFC设计WINDOWS程序多么方便。下面我们看看书上的例子,以便更进一步了解应用程序框架。 书上的例子: 1、先建立一个Win32 Application的应用程序。 2、选择Project->Add to project->Files,分别创建一个名为MyApp.h和一个名为MyApp.cpp的文件。 3、添加代码:(最好照敲以下代码到编译器,别用Ctrl+C/Ctrl+V) //*********************************************** class CMyApp:public CWinApp //见下② class CMyFrame:public CFrameWnd //***************************************************** #include "afxwin.h" BOOL CMyApp::InitInstance () BEGIN_MESSAGE_MAP(CMyFrame,CFrameWnd) CMyFrame::CMyFrame(){ void CMyFrame::OnPaint () 4、编译运行,报错。为什么呢?原来还没有添加MFC的支持,在Project Setting选项General属性页选择”Use MFC in a Static Library" 5、再Ctrl+F5,哈成功了。 让我们看看这个程序中的一些元素。 ①WinMain函数:并非不存在只是已经被隐藏在应用程序框架内部。 ②CMyApp类:CMyApp类的对象代表一个应用程序,CWinApp基类决定它的大部分行为。 ③应用程序的启动:当开始运行应用程序时WINDOWS会调用WinMain函数,WinMain会查找该应用程序的全局对象theApp。 ④CMyApp::InitInstance成员函数:发现theApp后自动调用重载的虚函数InitInstance来完成主窗口的构造和显示工作。记住这个函数。 ⑤CWinApp::Run成员函数:WinMain在调用InitInstance之后紧接着调用Run函数,它被隐藏在基类中负责传递应用程序的消息给相映的窗口。(我把它理解为好象SDK的窗口的过程函数不知对不对) ⑥CMyFrame类:此类的对象代表着应用程序的主窗口。它的构造函数调用基类CFrameWnd的Create函数创建具体的窗口结构。 ⑦CMyFrame::OnLButtonDown函数:演示消息处理机制,当鼠标坐键被按下这一事件被映射到CMyFrame的OnLButtonDown函数上,如果你选择F5进行编译运行的话可以在调试窗口看到TRACE宏显示的类似下面的信息 ⑧CMyFrame::OnPaint函数:应用程序每次重新绘制窗口都需要调用此函数,将显示"Hello World!"放在这里是因为每次窗口发生变化时保证"Hello World!"被显示,你可以试着将语句: 写在别出,例如写在 void CMyFrame::OnLButtonDown (UINT nFlags,CPoint point) 运行后当点击左键时显示"Hello World!",但当窗口最小化再最大化时"Hello World!"不见了。 ⑧关闭应用程序:用户关闭应用程序时会有一系列事件发生。首先CMyFrame对象被删除,然后退出Run,进而退出WinMain,最后删除CMyApp对象。 通过上面的示例我们看见程序的大部分功能包含在基类CWinApp和CFrameWnd中,我们只写了很少的函数,便可以完成很复杂的功能。所以应用程序框架不仅仅是一种类库,它还定义了应用程序的结构,除了基类外还包括WinMain函数,以及用来支持消息处理、诊断、DLL、等都包含在应用程序框架中。 第三篇:消息映射和视图类 在写了两篇笔记后有很多朋友给我发信,和我交流一些在学习过程中的问题。但由于我也是一个初学者,对一些问题自己也没有把握,所以实在不敢做答,但我会尽量尽我所能和大家一起研究,谢谢大家的鼓励和信任。 在开始先补充一下上一篇的内容“内容窗口的创建”,兼答天歌网友的问题(如果天歌兄弟能看到的话 )。为了简化我们看一个更简单代码,只有一个文件,创建一个空白的窗口,什么也不做。注意哦:真写程序时还是要分成.h和.cpp两个文件比较清晰。 //********************************** #include "afxwin.h" //afxwin.h会调用windows.h是MFC编程的途径,只要用到MFC就一定要包含它。 class CMyApp:public CWinApp //从CWinApp继承一个类 class CMyFrame:public CFrameWnd //从CFrameWnd继承一个类 BOOL CMyApp::InitInstance () 如果还不明白建议查看MSDN或看看侯大师的深入浅出。关于这个问题就说到这,最后对天歌朋友的学习态度表示敬意。 MFC是通过一些宏来将特定的消息影射到派生类相应的成员函数上,这种体制的好处是允许某些非窗口类(如文档类)来控制命令消息。且不需要C++作任何扩展。 记得上一篇的我的例子吗 1、打开VC++6从菜单选择NEW,给项目命名为”MyApp01“。 我们来看一下项目程序所在目录下的文件: myapp01.dsp //项目文件 仔细研究一下myapp01View.cpp和myapp01View.h文件,程序核心CMyAppView类在这两个文件中定义,就是我们今天要学的视图类。要想看到CMyAppView类的全貌,应该用Source Browser查看,选择CMyApp01View,按Alt+F12,选择Base Class and Members 选项。我们会看到CMyAppView类的层次关系(从哪来得),以及所有成员函数,包括从基类继承的(在Class View中不能显示父类的成员函数)。我们发现实际上你什么也不用做就拥有了一个有着强大功能的类。 virtual void OnDraw(CDC* pDC); // overridden to draw this view 参数是CDC类的指针,WINDOWS是通过和窗口相关联的设备环境(CDC类的对象就是设备环境)和显示硬件进行通讯。有了这个指针我们便可以调用CDC类的成员函数来完成各种绘制工作,如上一篇用到的textout()还有一些Ellipse()、Polygon()、BitBlt()等等在MSDN中有好长的一篇,它是直接从CObject派生的,好了我们开始在OnDraw()里添加一些绘图工作。 3、在Class View选择CMyApp01View类的OnDraw()成员函数双击会在C++编译器看到以下内容 void CMyApp01View::OnDraw(CDC* pDC) 编译运行,成功了吧。先到这吧。另外以后的文章将同时贴在写作区。 第四篇:资源和编译 资源文件(就是以应用程序名和扩展名是.rc的文件)很大程度上决定了应用程序的用户界面。在VC++中资源文件包括以下内容: Accelerator //模拟菜单和工具栏的选择内容 以上信息都在.rc文件中包含,同时.rc文件还包含了以下语句: #include "afxres.h" 它们的作用是把适合于所有应用程序的一些通用MFC库资源包含进来。 关于资源编辑器的使用就不多说了,因为它的操作很简单,需要注意的是虽然resource.h是一个ASCII码文件可以用文本编辑器进行编辑,单如果使用文本编辑器进行编辑的话,下次再使用资源编辑器时所做的修改有可能丢失,所以我们应该在尽量在资源编辑器中编辑应用程序的资源,新增的资源内容回自动的添加在我们的程序相应位置,例如resource.h而不用我们操心。这便是为什么称为Visual (可视)的原因之一。 编译在VC++中有两种模式,一种是Release Build另一种是Debug Build。它们之间的区别在于,Release Build不对源代码进行调试,不考虑MFC的诊断宏,使用的是MFC Release库,编译十对应用程序的速度进行优化,而Debug Build则正好相反,它允许对源代码进行调试,可以定义和使用MFC的诊断宏,采用MFC Debug库,对速度没有优化。所以我们应该在Debug模式下开发应用程序,然后在Release模式下发布应用程序。在我们的工程文件夹下会有一个Debug文件夹和一个Release文件夹分别存放输出文件和中间文件。 诊断宏是我们编译程序时检测程序状态的有利工具,例如上两篇用到的TRACE宏,可以在Debug窗口获得你需要的诊断信息,而不用设置对话框之类的方法,在发布时Release会自动滤掉此信息。 实际上对一个应用程序的调式是一件很具挑战的工作,我相信我们都有类似的经历,从网上或书本上找来了一段代码或源程序,当我们一点点将他们敲进 Deleloper Studio后进行编译时一下子出现了无数的错误和警告,(有些书的源代码就是错误的)这是需要的耐心和经验,有了VC++提供的调试工具如诊断宏、设断点、单步执行等等,会让我们省不少力气。至于编译的话题其实应该有很多可以说,但由于雷神本身的经验不足只能把书上所讲的作一个总结,大家应该熟练的掌握VC++为我们提供的的各种调试工具,象SPY之类的工具在MSDN中也有很详细的使用帮助说明,到现在我体会到了高手们所说的MSDN是最好的最全的也是最权威的。 第五篇:基本事件处理 我们已经知道MFC库应用程序框架调用CView视图类的虚函数OnDraw来完成屏幕显示。其实CView和CWnd类包含了几百个成员函数,在MSDN中可以看到这些成员函数,其中有许多On开头的,例如第二篇的例子就有一个OnLButtonDown,它们都是应用程序框架响应各种事件所需调用的函数。 当用户在视窗中按下鼠标左键时,Windows会自动发送WM_LBUTTONDOWN消息给该视窗,当然你可以什么都不做象我们第3篇的例子一样,如果你想要让程序对此消息做出反应就必须在视图类给出相应的函数,类似下面这样: void CMyView::OnLButtonDown(UINT nFlags,CPoint point) 还需要在类头文件包含相应的函数原型说明 afx_msg void OnLButtonDown(UINT nFlags,CPoint point); afx_msg只是说明该函数原型是针对消息映射函数。下一步在代码文件中还需要有一个消息映射宏,作用是把OnLButtonDown函数和应用程序框架联系在一起。 BEGIN_MESSAGE_MAP(CMyView,CView) 最后在类库的头文件还需包含: DECLARE_MESSAGE_MAP() 函数和Windows消息的对应关系可以从MSDN中找到在MSDN中索引输入(WM_ Messages)便会列出所有的Windows消息和消息控制函数原型。在实际的编程过程中我们不可能全部都用手工的添加或编制消息控制函数。除了一些特殊的,我们一般是借助Class Wizard来自动编制消息映射函数。这又是MFC应用程序框架比起SDK来的有一个便捷的地方。 第六篇:映射模式 在此篇之前我们已经学会了在窗口显示图形,更准确的说是在窗口指定位置显示图形或文字,我们使用的坐标单位是象素,称之为设备坐标。看下面语句: pDC->Rectangle(CRect(0,0,200,200)); 画一个高和宽均为200个象素的方块,因为采用的是默认的MM_TEXT映射模式,所以在设备环境不一样时,画的方块大小也不一样,在1024*768的显示器上看到的方块会比640*480的显示器上的小(在不同分辨率下的屏幕象素,在WINDOWS程序设计一书中有示例程序可以获得,或者可以用GetClientRect函数获得客户区的矩形大小。在这里就不说了,大家只要知道就行了),在输出到打印机时也会有类似的情况发生。如何做才能保证在不同设备上得到大小一致的方块或者图形、文字呢?就需要我们进行选择模式映射,来转换设备坐标和逻辑坐标。 Windows提供了以下几种映射模式: MM_TEXT 下面分别讲讲这几种映射模式: MM_TEXT: 默认的映射模式,把设备坐标被映射到象素。x值向右方向递增;y值向下方向递增。坐标原点是屏幕左上角(0,0)。但我们可以通过调用CDC的SetViewprotOrg和SetWindowOrg函数来改变坐标原点的位置看下面两个例子: //************************************************ //************************************************** 这两个例子显示出来的图形是一样的,都是从屏幕左上角开始的宽和高为200象素的方块,可以看出例子2将逻辑坐标(100,100)映射到了设备坐标(0,0)处,这样做有什么用?滚动窗口使用的就是这种变换。 固定比例映射模式: MM_LOENGLISH、MM_HIENGLISH、MM_LOMETRIC、MM_HIMETRIC、MM_TWIPS这一组是Windows提供的重要的固定比例映射模式。 它们都是x值向右方向递增,y值向下递减,并且无法改变。它们之间的区别在于比例因子见下:(我想书上P53页肯定是印错了,因为通过程序实验x值向右方向也是递增的) MM_LOENGLISH 0.01英寸 看例3 //************************************************** 还有一种是可变比例映射模式,MM_ISOTROPIC、MM_ANISOTROPIC。用这种映射模式可以做到当窗口大小发生变化时图形的大小也会相应的发生改变,同样当翻转某个轴的伸展方向时图象也会以另外一个轴为轴心进行翻转,并且我们还可以定义任意的比例因子,怎么样很有用吧。 让我们看例子4 //************************************************** 怎么样,屏幕上有一个能跟随窗口大小改变而改变的椭圆。把 pDC->SetMapMode(MM_ANISOTROPIC);这句改为pDC->SetMapMode(MM_ISOTROPIC)会怎样?大家可以试试。那还有一个问题就是上例的比例因子是多少呢?看下面公式(注意是以例子4为例的) x比例因子=rectClient.right/1000 //视窗的宽除以窗口范围 从Windows的鼠标消息可以获得鼠标指针的当前坐标值(point.x和point.y)此坐标值是设备坐标。 很多MFC库函数尤其是CRect的成员函数只能工作在设备坐标下。 CDC的LPtoDP函数可以将逻辑坐标转换成设备坐标 下面列出我们应该在什么时候使用什么样的坐标系一定要记住: ◎CDC的所有成员函数都以逻辑坐标为参数 下面我们来一步步完成例子6-5: ■第徊剑河肁ppWizard创建MyApp6。除了Setp 1 选择单文档视图和Setp 6 选择基类为CScrollView外其余均为确省。 ■第二步:在CMyApp6View类中增加m_rectEllipse和m_nColor两个私有数据成员。你可以手工在myapp6View.h添加,不过雷神建议这样做,在ClassView中选中CMyApp6View类,击右键选择Add Member Variable插入它们。 //************************** //*************************************************** 问题1:CRect是什么? ■第三步:修改由AppWizard生成的OnIntitalUpdate函数 void CMyApp6View::OnInitialUpdate() 问题2:关于void CMyApp6View::OnInitialUpdate() 函数OnInitialUpdate()是一个非常重要的虚函数,在视图窗口完全建立后框架用的第一个函数,框架在第一次调用OnDraw前会调用它。因此这个函数是设置滚动视图的逻辑尺寸和映射模式的最佳地点。 ■第四步:编辑CMyApp6View构造函数和OnDraw函数 //********************************************* //********************************************* 问题3: CMyApp6View::CMyApp6View():m_rectEllipse(0,0,4000,-4000)为什么不能这样写: 我从CSDN上得到的答案:两者实际上没有区别。有两个原因使得我们选择第一种语法,它被称为成员初始化列表: 一个原因是必须的,另一个只是出于效率考虑。 class CMember { 因为Cmember有一个显式声明的构造函数, 编译器不产生一个缺省构造函数(不带参数),所以没有一个整数就无法创建Cmember的一个实例。 CMember* pm = new CMember(2); // OK 没有其它办法将参数传递给m_member,如果成员是一个常量对象或者引用也是一样。根据C++的规则,常量对象和引用不能被赋值, 它们只能被初始化。 CMyClass::CMyClass() { 在它们之间有什么不同吗?是的。编译器总是确保所有成员对象在构造函数体执行之前初始化,因此在第一个例子中编译的代码将调用CString::Cstring来初始化m_str,这在控制到达赋值语句前完成。在第二个例子中编译器产生一个对CString:: CString(LPCTSTR)的调用并将"yada yada" 传递给这个函数。结果是在第一个例子中调用了两个Cstring函数(构造函数和赋值操作符),而在第二个例子中只调用了一个函数。在Cstring的例子里这是无所谓的,因为缺省构造函数是内联的,Cstring只是在需要时为字符串分配内存(即,当你实际赋值时)。但是,一般而言,重复的函数调用是浪费资源的,尤其是当构造函数和赋值操作符分配内存的时候。在一些大的类里面,你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间的Init函数。在这种情况下,你必须使用初始化列表,以避免不要的分配两次内存。在内部类型如ints或者longs或者其它没有构造函数的类型下,在初始化列表和在构造函数体内赋值这两种方法没有性能上的差别。不管用那一种方法,都只会有一次赋值发生。有些程序员说你应该总是用初始化列表以保持良好习惯,但我从没有发现根据需要在这两种方法之间转换有什么困难。在编程风格上,我倾向于在主体中使用赋值,因为有更多的空间用来格式化和添加注释,你可以写出这样的语句:x=y=z=0; 或者memset(this,0,sizeof(this)); 注意第二个片断绝对是非面向对象的。 当我考虑初始化列表的问题时,有一个奇怪的特性我应该警告你,它是关于 C++初始化类成员的,它们是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。 class CMyClass { 你可能以为上面的代码将会首先做m_y=I,然后做m_x=m_y,最后它们有相同的值。但是编译器先初始化m_x,然后是m_y,因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。 我的例子设计来说明这一点,然而这种bug会更加自然的出现。有两种方法避免它, 一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。 ■第五步:映射WM_LBUTTONDOWN消息并编辑OnLButtonDown消息处理函数。在Class Wizard中选择CMyApp6View类,在Message列表中选择WM_LBUTTONDOWN双击,则此消息映射便完成了。用下面代码替换Wizard生成的OnLButtonDown消息处理函数。 void CMyApp6View::OnLButtonDown(UINT nFlags, CPoint point) 问题4:详解此段代码 第1行 CClientDC由CDC派生,它的对象dc是当前窗口的客户区域 void LPtoDP( LPPOINT lpPoints, int nCount = 1 ) const; 第5-11行 CRect的成员函数PtInRect(point)用来判断鼠标当前位置(point)是否在当前矩形(rectDevice)内 ■第六步:映射WM_KEYDOWN消息并编辑OnKeyDown消息处理函数。在Class Wizard中选择CMyApp6View类,在Message列表中选择WM_KEYDOWN双击。用下面代码替换Wizard生成的OnKeyDown消息处理函数。 void CMyApp6View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 问题5:此段代码详解: 先看OnVScroll和OnHScroll的函数原型 #define VK_ESCAPE 0x1B 编译运行它,怎么样成功了吧, ■第七步:做一个更复杂的程序,例如屏幕上有多个圆,然后点其中一个,则点中的变色,其他的不变。 第七篇:图形设备接口(GDI) 我想大家和我一样通过前几天的学习,对VC++MFC应用程序框架的神奇功能有了一些了解,但是还是感觉不能驾御这个强的开发工具,不过别担心,我170多斤体重不是一口吃出来的,是经过了30年不懈的努力才吃成了如此“魁梧”的体形,呵呵开个玩笑。所以学习也一样。学VC尤其如此。还有我发现很多好的技术类书籍有一个共同的特点,就是在前几章学到了一些东西在你正在疑惑或者苦苦领会的时候,接下来的章节便给你解除疑惑。雷神经验:遇到实在想不明白的地方先放下,继续向下读,也许读着读着前面的问题就明白了。『VC++技术内幕』当然属于好的技术书籍一类,所以在本书第五章开始仔细的给我们讲解设备环境类和图形设备接口(GDI),使得我们能守得云开见月明。 设备环境类CDC: CDC是设备环境类的基类直接由CObject派生。是GDI的关键元素,它代表了物理设备。每一个C++设备环境对象都有相对应Windows设备环境,并通过一个32位类型的HDC句柄来标识。CDC类的虚拟性使我们可以很容易的做到编写同时适用于多种设备的代码。例如OnDraw函数的pDC->TextOut(0,0,"Hello");既可以适用于显示器、还可以适用于打印预览和打印,只需要在CView::OnDraw函数的pDC参数指向不同的对象类。 看例子7-1 //*************************** 创建的设备环境对象具有一些默认的特性,通过CDC类的成员函数可以设定这些特性。例如前一篇笔记用到的刷子、映射模式等等。我们还可以通过重载SelectObject函数将GDI对象选进设备环境中。 GDI对象是通过CGdiObject派生类的C++对象来表示的。读着怎么这么别扭?举例说一下。 CBrush是一个GDI的派生类,它在MFC中的层次结构是这样的:CObject派生CGdiObject派生CBrush,明白了吧。CGdiObject是所有GDI对象的抽象基类。下面列出的是GDI派生类的列表: CBitmap:位图是一种位矩阵,每一个显示象素都对应于其中的一个或多个位,可以用来表示图象,也可以用来创建刷子 CBrush:刷子定义了一种位图形式的象素,可以用来对区域内部填充颜色。 CFont:字体是一种具有某种风格和尺寸的所有字符的完整集合,常常被作为资源,其中一些依赖某种设备。 CPalette:调色板是一种颜色映射接口,它允许应用程序在不影响其他应用程序的前提下,可以充分利用输出设备的颜色描绘能力。 CPen:笔是一种用来画线及绘制有形边框的工具,可以指定它的颜色及宽度,并可以指定画虚线、点线还是实线。 CRgn:区域是由多边形、椭圆二者组合形成的一种范围,可以用来进行填充、裁剪、鼠标点中测试等等。 以上很容易理解,可以用WINDOWS的画图帮助我们理解。 CGdiObject类很眼生,看过很多代码就没有看到过它,原因是由于CGdiObject类是所有GDI对象类的虚拟基类,所以我们不必创建CGdiObject类的对象,可以直接构造它的派生类的对象,例如这样 CPen newPen(PS_DASHDOTDOT,2,(COLORREF) 0); //黑色的笔宽度为2 但需要注意的是CFont和CRgn的对象建立需要先调用默认的构造函数来构造C++对象,然后再调用相应的创建函数如: CreateFont或CreatePolygonRgn等。 CGdiObject类有一个虚拟的析构函数,它派生类的析构函数需要将与C++对象相关联的GDI对象删除掉,一定要在退出程序之前把构造的CGdiObject派生类对象干掉。因为一个没有释放的GDI对象会占用很多的内存。 让我们用一个例子跟踪一下GDI对象 //************************************* 屏幕上应该显示三条线,第一条和第三条一样颜色和粗细因为他们都是用的设备环境默认的CPen对象,第二条是一条用我们自己设定的CPen对象。我们可以看出在将新对象选进设备环境的同时返回指向前一次被选对象的指针。作用保存原来的对象,以便完成任务时恢复它。 Windows还包含有一些可以利用的库存对象,它们不会簧境蛭猈indows对企图删除它们的动作不予理睬。我们可以用SelectStockObject函数将它们选进设备环境。下面列出的是所有的有关刷子、笔、字体和调色板的库存对象。 //*************************************************************************** 由于SelectObject函数返回的GDI C++对象指针具有临时性,当程序的空闲处理阶段或者控制函数返回时应用程序框架会将临时的C++对象删除,我们不能简单的把这一指针保存在类的数据成员中,而应该借助GetSafeHdc函数将它转化为Windows的句柄,以便持久的保存GDI的标识。 我们将例子7-2做些改动。 //************************************************** 第十篇:模式对话框和通用控件(下) 大家好,雷神由于出差在外,所以笔记今天才写出抱歉。不知道大家有没有做上篇提到的计算器,计算器对雷神来说可是经典的程序,学VB先搞了计算器,学VC也搞了个计算器,前不久在学扩展CBUTTON类时也是用计算器程序,为什么?按钮多呗,雷神最终做出了一个圆形按钮,液晶显示的计算器,感觉真的不错。学编程就是要编写代码,代码写的多少和编程水平是成正比的。 由对话框编辑器和Class Wizard生成的对话框可以很轻松的不需编写很多的代码而获得很多的功能,但如果我们想对对话框进一步的改进则需要手工编程了。 我们以上一篇的电脑体育彩票选号小程序为例进行一系列的改进工作。 改进一:截获退出控制权 当我们的光标不在选号按钮上时(例如在文本框内),当按下回车(Enter)键则回退出程序的运行,还有当你按下ESC键时也会退出。为什么?因为当用户按下回车键时Windows就会自动查找“输入焦点”在哪个按钮上,如果所有的按钮都没有获得输入焦点,Windows会自动的寻找程序资源指定的默认按钮,如果对话框没有默认按钮,系统会调用OnOK函数。ESC键也会触发对OnCancel函数的调用,从而导致控制从对话框中退出。如果我们想截获退出控制权该如何做呢?用哑函数,就是将按下Enter和ESC后的处理函数写成空函数。 步骤1、由于我们的对话框没有OK和CANCEL按钮,我们只能手工添加代码。在guessDlg.h文件的类声名重载:virtual void OnOK()和virtual void OnCancel();两个虚函数。 class CGuessDlg : public CDialog // ClassWizard generated virtual function overrides 步骤2、在guessDlg.cpp文件加入两个哑函数OnOK和OnCancel。 void CGuessDlg::OnOK () 好了在编译运行,按ESC和在文本框内点一下鼠标,按下回车(Enter)键,上一篇的问题不存在了,程序不会退出,彻底退不出了,只能通过任务管理器退了,这可不行,我们还需要继续改进。 步骤3、添加一个按钮,叫退出。加入这个按钮的消息控制函数如下: void CGuessDlg::OnButton2() 这回可以了,我们已经掌握了对话框退出控制权。 改进二:改变对话框的外观 我们可以改变对话框的背景颜色,或对话框中的控件颜色以使得程序界面不至于千篇一律,如何做呢?每个控件和对话框在显示之前回发送WM_CTLCOLOR消息,如果派生对话框类对WM_CTLCOLOR消息进行了映射,就可以设定文本的前景色和背景色。同时还可以为控件或对话框的非文本区域选择一个刷子。 WM_CTLCOLOR消息对应的函数为OnCtlColor,OnCtlColor函数的原型为:afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );其中参数pDC是:一个指向设备环境的指针。pWnd:指定特定的控件。nCtlColor:指明控件类型。 可以有以下类型: CTLCOLOR_BTN button control 现在为了突出显示文本框的显示号码,我们将文本框的文字背景设为黄色。 步骤1、添加一个成员变量 HBRUSH m_hBrush; HBRUSH CGuessDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 编译运行,怎么样,文本框内的文字均变为黄底了。关于对话框的外观我们将在以后再说。 改进三、增加进度条。 程序在随怕胧泵挥腥魏翁崾荆颐遣恢莱绦蚴欠裨谠诵校趺窗欤课颐窃黾右桓鼋忍蹩丶美垂鄄斐绦虻脑诵凶纯觥?br> 步骤2、将CGuessDlg::OnButton1()用下面代码替换。 void CGuessDlg::OnButton1() } 编译运行它,好了,有了进度显示我们可以知道程序的运行情况了。 在这篇笔记中我们通过对上一篇所写的小程序的改进,掌握了一些改进对话框的方法,不用书上的例子是想给大家多个例子参考,不过书上的例子一定要掌握。好了关于模式对话框我们已经掌握,下一篇我们将继续学习无模式对话框。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
78.125ms |