Windows编程基础Word下载.docx
《Windows编程基础Word下载.docx》由会员分享,可在线阅读,更多相关《Windows编程基础Word下载.docx(36页珍藏版)》请在冰豆网上搜索。
还是C++/MFC?
这一直是个引起争议的论题。
单从C++/MFC程序设计来说,学习者必须跨越四大技术障碍:
1.面向对象的观念与C++语言。
2.Windows程序基本观念(程序进入点、消息流动、窗口函数、callback...)。
3.MicrosoftFoundationClasses(MFC)本身。
4.VisualC++集成环境与各种开发工具(难度不高,但需熟练)。
以下是文中提到的一些名词缩写,先列举在下面,其中部分的具体含义将在文中叙述:
Table1-1名词缩写一览
缩写
全称
API
ApplicationProgrammingInterface
SDK
SoftwareDevelopmentKit
MFC
MicrosoftFoundationClass
DLL
DynamicLinkLibrary
GUI
GraphicsUseInterface
SDI
SingleDocumentInterface
MDI
MultipleDocumentInterface
UI
UserInterface
WinApp
WindowsApplication
1面向对象的观念与C++语言
MFC是一套以C++撰写的面向对象的函数库。
本文假设读者已经对C++语言有了初步的了解,理解类、继承、虚函数,封装等相关的概念。
2Windows程序的运行机制
要理解MFC的应用程序开发过程,先要理解Windows程序的运行机制。
我们要明白在Windows环境下编程,和在Dos环境下编程的根本性差别、全面的讨论Windows的工作机制,将需要整整一本书的容量(WINDOWS核心编程,600多页),实际上我们没有必要了解所有的技术细节,但是对于Windows程序运行的一些根本性的概念,是一个VisualC++程序员必须要掌握的知识。
2.1Windows程序的开发流程
Windows程序分为“程序代码”和“UI(UserInterface)资源”两大部份,两部份最后以RC编译器集成为一个完整的EXE文件(Fig2-1)。
所谓UI资源是指功能菜单、对话框外貌、程序图标、光标形状等等东西。
这些UI资源的实际内容(二进位码)系借助各种工具产生,并以各种扩展名存在,如.ico、.bmp、.cur等等。
程序员必须在一个所谓的资源描述文档(.rc)中描述它们。
RC编译器(RC.EXE)读取RC文档的描述后将所有UI资源文档集中制作出一个.RES文件,再与程序代码结合在一起,这才是一个完整的Windows可执行程序。
Fig2-1Windows程序的开发流程
2.2基于事件驱动的程序设计模式
图2.2反映的是应用程序、操作系统以及硬件输入输出设备之间的交互关系
Fig2-2应用程序、操作系统、外设之间的交互图
Windows程序设计是一种完全不同于传统的Dos方式的程序设计方法,它是一种基于事件驱动的程序设计模式(主要是基于消息)。
当用户需要完成某种功能时,会调用操作系统的某种支持,然后操作系统会把用户的需求包装成消息,并投递到消息队列中去,最后应用程序从消息队列中取走消息,并进行相应。
图中箭头①表示操作系统能操纵输出设备,以执行特定的功能,例如让声卡发声,让显卡画出图形。
箭头②表示操作系统能够得知输入设备的状态变化,如鼠标移动,键盘输入等,并且能够知道鼠标移动到那个位置,键盘按下的是哪个字符。
这个就是操作系统与计算机硬件之间的交互关系,应用程序开发者往往不需要知道其中的细节,我们所关心的仅仅是应用程序与操作系统之间的一个交互关系。
向下的箭头③表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。
那么,应用程序是如何通知操作系统执行某个功能的呢?
有过编程经验的读者都应该知道,在应用程序中要完成某个功能,都是以函数调用的形式实现的,同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能的。
操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用,这些函数的集合就是Windows操作系统提供给应用程序编程的接口(ApplicationProgrammingInterface),简称WindowsAPI。
如CreateWindow就是一个API函数,应用程序中调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。
向上的箭头④表示操作系统能够将输入设备的变化上传给应用程序。
如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。
对事件作出反应的过程就是消息响应。
操作系统是怎样将感知到的事件传递给应用程序的呢?
这是通过消息机制(Message)来实现的。
可以想象,每一个Windows程序都应该有一个回路如下:
MSGmsg;
while(GetMessage(&
msg,NULL,NULL,NULL))
{
TranslateMessage(&
msg);
DispatchMessage(&
}
//以上出现的函数都是WindowsAPI函数
消息,也就是上面出现的MSG结构,其实是Windows内定的一种数据格式。
操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序,参看MSDN,MSG结构定义如下:
typedefstructtagMSG{
HWNDhwnd;
UINTmessage;
WPARAMwParam;
LPARAMllParam;
DWORDtime;
POINTpt;
}MSG;
其中第1个参数是指向窗口的句柄。
窗口是指应用程序的界面,一般是一个矩形,如Word的窗口等。
关于句柄
句柄(HANDLE),是资源的标识。
操作系统要管理和操作这些资源,都是通过句柄来找到对应的资源。
句柄有点类似于指针,资源在程序中占据一块内存,要想得到这块内存,就须要用指示这块资源的句柄。
按资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。
操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。
第2个参数表示具体的消息类型。
比如说按下键盘的一个键,那么相应的这么一个键被按下的消息,就被存放在这个参数中,一般是windows定义的一个宏,如WM_KEYDOWN。
第3,4个参数是指消息的附带信息。
无论我们按下那个字母的键,都会产生WM_CHAR这个消息,但如果想知道按下的到底是哪个字母,这个信息就可以从wParam,lParam中得到。
第4个参数表示消息被投递时的时间,第5个参数表示消息被投递时,光标在窗口中的位置。
接受并处理消息的主角就是窗口。
每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的“窗口函数”(windowprocedure,或称为windowfunction)。
如果窗口获得一个消息,这个窗口函数必须判断消息的类别,决定处理的方式。
Fig2-3Windows程序与系统之间的关系
最后来看一下消息队列。
对于每一个应用程序,操作系统都会给它建立一个消息队列。
这个消息队列实际上是一个先进先出(FIFO)的缓存区,通常是某种结构体的一个数组。
顾名思义,消息队列中的每个元素都是一个消息,操作系统将生成的每个消息按照先后的顺序放入消息队列里。
应用程序则每次取走消息队列中的第一个消息,消息取走后,第二个消息变为第一个,后面的消息依次迁移。
应用程序取得消息后,就能够知道用户的操作以及程序状态的变化,接着作出相应的处理(编写处理代码)。
如当用户点击窗口右上角的X后,相应的消息相应函数就需要作关闭窗口的操作。
2.3WinMain函数
main是一般C程序的进入点:
intmain(intargc,char*argv[],char*envp[]);
…
WinMain则是Windows程序的进入点:
intCALLBACKWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPSTRlpCmdLine,
intnCmdShow)
...
//在Win32中,CALLBACK被定义为__stdcall,是一种函数调用习惯,关系到
//参数挤压到堆栈的次序,以及处理堆栈的责任归属。
其它的函数调用习惯还有
//_pascal和_cdecl
当Windows的“外壳”(shell)侦测到使用者意欲执行一个Windows程序,于是调用加载器把该程序加载,然后调用Cstartupcode,后者再调用WinMain,开始执行程序。
WinMain的四个参数由作业系统传递进来。
结合实例WinMain讲解。
3MFC程序设计导论
3.1MFC简介
MFC(MicrosoftFoundationClasses)是微软提供给我们的基础类库,是一套面向对象的函数库,以类的方式提供给我们使用。
利用这些类,可以有效地帮助我们完成基于Windows的应用程序的开发。
最初,开发Windows应用程序必须使用微软的SDK(SoftwareDevelopmentKit),直接掉用WindowsAPI函数,向Windows操作系统提出各种要求,例如配置内存、开启窗口、输出图形...。
数以千计的WindowsAPIs,每个看起来都好像比重相若(至少你从手册上看不出来孰轻孰重)。
有些APIs彼此虽有群组关系,却没有相近或组织化的函数名称。
星罗棋布,雾列星驰;
又似雪球一般愈滚愈多,愈滚愈大。
撰写Windows应用程序需要大量的耐力与毅力,以及大量的小心谨慎!
MFC帮助我们把这些浩繁的APIs,利用面向对象的原理,逻辑地组织起来,使它们具备抽象化、封装化、继承性、多型性、模块化的性质。
1989年微软公司成立ApplicationFramework技术团队,名为AFX小组,用以开发C++面向对象工具给Windows应用程序开发人员使用。
AFX的"
X"
其实没有什么意义,只是为了凑成一个响亮好念的名字。
3.2四个重要的工具
图3.1是一个MFC程序的开发流程:
Fig3-1MFC程序的开发流程
VisualC++集成开发环境(IDE):
你可以从中明显地或隐喻地启动其它工具如AppWizard和ClassWizard;
你可以设定各种工具、编译并链接程序、启动除错器、启动文字编辑器、浏览类阶层。
AppWizard:
这是一个程序代码产生器。
基于applicationframework的观念,相同型态(或说风格)的MFC程序一定具备相同的程序骨干,AppWizard能让你通过挑挑选选就能够产生一个MFC程序的框架,生成的代码甚至不用添加一行代码就可以运行起来。
ResourceEditor:
这是一个总合资源编辑器,RC文件内的各种资源它统统都有办法处理。
ResourceEditor做出来的各类资源与你的程序代码之间如何维系关系?
譬如说对话框中的一个控制组件被按下后程序该有什么反应?
这就要靠ClassWizard搭起鹊桥。
ClassWizard:
AppWizard制作出来的程序的后,接下来你只需要往程序代码中添砖加瓦(最重要的工作是加上自己的成员变量并改写虚拟函数),或搭起消息与程序代码之间的鹊桥(建立MessageMap),这全得仰仗ClassWizard。
以一般文字编辑器直接修改程序代码当然也可以,但你的思维必须非常缜密才不会挂一漏万。
下面结合实际,对VisualC++的用法及相关概念作讲解。
4Scribble例子
4.1ScribbleStep0–利用AppWizard创建应用程序
Step1:
选按【File/New】,并在【New】对话框中选择【Project】标签页。
然后再在其中选择MFCApplication(exe),于是准备进入AppWizard建立"
Scribble"
project。
右边的工程目录和project名称亦需填妥。
点选“OK”。
Fig3-2
Step2:
选择SDI或MDI或Dialog-based程序风格。
预设情况是MDI。
单击“Next”。
Fig3-3
Step3:
选择是否需要数据库支持。
预设情况是None。
Fig3-4
Step4:
选择是否需要compounddocument和ActiveX支持。
本例为求简化,选择“None”。
Fig3-5
Step5:
选择使用者接口。
预设情况下【ContextSensitiveHelp】未设立。
Fig3-6
在Step5中点击【Advanced】弹出【AdvancedOptions】对话框:
Fig3-7
把上图修改为下面这个样子。
观察这些修改对程序代码带来的变化。
该完后点选“close”,再点选“Next”。
Fig3-8
Step6:
MFCAppWizard步骤五,提供另一些选项,询问要不要为你的原代码产生一些注释。
并询问你希望使用的MFC版本(动态联结版或静态联结版)。
Fig3-9
Step7:
MFCAppWizard步骤六(最后一步),允许你更改文档名或类名称。
完成后按下【Finish】。
Fig3-10
MFCAppWizard获得的清单(包括文件和类):
Fig3-11
一行代码没写,就多了这么多的代码和类:
Fig3-12Fig3-13
编译并运行程序。
你会发现一行程序代码都没写,只是点点按按,我们就获得了一个令人惊艳的程序。
基本功能一应俱全(档案对话框、打印机设定、Help、工具列、状态列...)。
Fig3-14程序运行界面
AppWizard总是为一般的应用程序产生五个类,列于Table3-1中。
Table3-1由AppWizard产生的类
类名
基类名称
声明于
定义于
CScribbleApp
CWinApp
Scribble.h
Scribble.cpp
CMainFrame
CMDIFrameWnd
Mainfrm.h
Mainfrm.cpp
CChildFrame
CMDIChildWnd
Childfrm.h
Childfrm.cpp
CScribbleDoc
CDocument
ScribbleDoc.h
ScribbleDoc.cpp
CScribbleView
CView
ScribbleView.h
ScribbleView.cpp
事实上Scribble程序中用到了9个类,不过只有上述5个类需要改写(override)。
为了对标准的MFC程序有一个大局观,Table3-2显示Scribblestep0中各重要组成部分,这些组成部分在执行时期的意义与主从关系显示于图3-15。
Table3-2Scribblestep0中各重要组成部分
MFC类名称
我的类名称
功能
Applicationobject
MDI主窗口
CMultiDocTemplate
直接使用
管理Document/View
Document,负责数据结构与档案动作
View,负责数据的显示与印表
MDI子窗口
CToolBar
工具列
CStatusBar
状态列
CDialog
CAboutDlg
About对话框
Fig3-15
4.2ScribbleStep1–实现鼠标画图功能
完全由AppWizard代劳做出的Scribblestep0,应用程序的整个架构(空壳)都已经建构起来了,但是Document和View还空着好几个最重要的函数(都是虚拟函数)等着你设计其实体。
这就像一部汽车外面的车体以及内部的油路电路都装配好了,但还等着最重要的发动机(引擎)植入,才能够产生动力,开始“有所为”。
4.2.1Document/View/DocumentFrame/DocumentTemplate
MFC之所以为ApplicationFramework,最重要的一个特征就是它能够将管理数据的程序代码和负责数据显示的程序代码分离开来,这种能力由MFC的Document/View提供。
Document/View是MFC的基石。
简单来说,Document就是数据的实体(体),View就是数据的表像(面)。
我们通过CDocument管理数据,用CollectionsClass(MFC中的一组专门用来处理数据的类)处理实际的数据;
我们用CView负责数据的显示,用CDC和CGdiObject实际绘图。
在MFC中一体可以多面:
同一份数据可以文字描述之,可以长条图描述之,亦可以曲线图描述之。
例如一份存储在Excel文档中的有关IntelCPU1年来每月的价格数据,为了表现其波动的情况,你既可以用一张柱状图来显示,已可以用一张折线图说明。
Document/View之间的关系可以由Fig4-1说明。
View就像一个观景器,使用者透过View看到Document,也透过View改变Document。
View是Document的外显接口,但它并不能完全独立,它必须依存在一个所谓的DocumentFrame窗口内。
Fig4-1Document是资料的体,View是资料的面。
MFC把Document/View/Frame视为三位一体。
每当使用者打开(或新增)一份文件,程序相应做出Document、View、Frame各一份。
这个“三口组”成为一个运作单元,由所谓的DocumentTemplate掌管。
MFC有一个CDocTemplate负责此事。
它又有两个派生类,分别是CMultiDocTemplate和CSingleDocTemplate。
4.2.2Scribble里的Document设计
Scribble允许使用者在窗口中画图,画图的方式是以鼠标做为画笔,按下左键拖曳拉出线条。
每次按下鼠标左键后一直到放开为止的连续坐标点构成线条(stroke)。
整张图(整份文件)由线条构成,线条可由点、笔宽、笔色等等资料构成。
■不定量的线条数可以利用链表(linkedlist)来表示。
MFC的CObList恰好可以用来表现这样的一个链表。
CObList规定其每个元素必须是一个“CObject派生类”的对象实体,没问题,我们就设计一个名为CStroke的类,派生自CObject,代表一条线条。
为了type-safe,我们选择template版本,所以设计出这样的Document(红色是需要增加或修改的代码):
classCScribbleDoc:
publicCDocument
{
...
public:
CTypedPtrList<
CObList,CStroke*>
m_strokeList;
};
■线条由笔宽和坐标点构成,所以CStroke应该有m_nPenWidth成员变量,但一长串的坐标点以什么来管理好呢?
数组是个不错的选择,至于数组内要放什么类型的数据,我们不妨先想想这些坐标是怎么获得的。
这些坐标显然是在鼠标左键按下时进入程序之中,也就是利用OnLButtonDown函数的参数CPoint。
CPoint符合前一节所说的数组元素型态条件,所以CStroke的成员变量可以这么设计:
classCStroke:
publicCObject
protected:
UINTm_nPenWidth;
CArray<
CPoint,CPoint>
m_pointArray;
■每根线条都应该提供画出自己的方法,这个函数内会有CreatePen、SelectObject、MoveTo、LineTo等GDI动作。
BOOLCStroke:
:
DrawStroke(CDC*pDC)
CPenpenStroke;
if(!
penStroke.CreatePen(PS_SOLID,m_nPenWidth,RGB(0,0,0)))
returnFALSE;
CPen*pOldPen=pDC->
SelectObject(&
penStroke);
pD