MFC终究是避不开的,那就慢慢来学习吧
看网上都推荐《深入浅出MFC》。结果发现并没有很容易的入门。我个人还是推荐《MFCWindows 应用程序设计》。任哲
一、预备知识
代码运行机理探究:
我们运行程序时,程序是怎么运作的呢。这一点分析清楚,有助于我们理解窗口运行机制。我们编写完代码后,点击编译,把所有文件都连接起来,形成一个可以被操作系统执行的文件。但操作系统怎么来执行我们的程序呢?它是通过调用系统调用来执行。正如我们在操作系统上所知道的,操作系统会提供我们用户接口。通过这个接口来调用我们的程序,由于语言的特性,调用我们的程序后,要有一个主函数。我们平常编写的console程序,入口是main。即控制台通过main函数进入执行我们的代码。而Windows程序则不同,它的入口函数是winmain。直观来说:Windows程序的架构是这样的:
Windows程序代码---------(winmain)-----------Windows操作平台-------------------硬件系统
而console程序的架构是:
console application 程序代码-------(main)----------MSDOS-------------Windows操作平台-----------硬件系统。(其中,MSDOS实质是一个运行平台,在Windows操作系统之上,与Windows程序相比,增加了一个中间层。提供了一个可视化界面。MSDOS不仅可以调用库函数,也可以调用Windows的API。)
Windows程序的特点:
Windows程序除了入口为winmain的主函数,还有一个消息处理函数,两者并不是调用关系,这两个函数都是系统调用的。主函数主要进行的操作是:1.创建程序图形窗口界面。2.进入一个叫做消息循环的循环中,并在这个循环中等待用户事件的产生。当收到事件后,主函数把事件的消息传到系统。系统再次调用Windows程序的消息处理函数。循环这种操作,直到事件是一个终止程序运行的事件。
Windows API的特点:
为了支持应用程序的设计和运行,Windows在系统函数中,向用户开放了一些可以由应用程序调用的函数(API)。实质上,这些API函数是一种软中断。因此称为系统调用。我们在操作系统中,也简要的学过:用户只有通过中断才能进入操作系统。
Windows API主要分为三大类型:
1.窗口管理函数:窗口的创建,移动,修改。
2.图形设备函数:实现图形的绘制及操作,图形设备接口(GDI)
3.系统服务函数:与操作系统有关的功能。
Windows开发工具:SDK,实际上就是利用Windows API。
Windows的特殊数据类型:
1.用typedef 起的别名。
2.特殊的数据类型:
1.句柄。是一个结构体。用来隐藏内部的信息。句柄实质上是结构类型变量指针的再封装。避免用户直接操作指针操作的危险。虽然与指针形式相同,但不能像指针那样参与运算。
HINSTACE句柄:应用程序实例句柄。在用户眼里程序可能就是静态的代码,操作系统中,程序是动态的过程。程序利用HINSTACE这种结构来管理这种动态行为。这个变量就代表了正在运行的一个程序。
2.WNDCLASS。结构体。这个结构体是Windows程序创建窗口的参数。其声明如下:
typedef struct _WNDCLASS { UNIT style;//指定窗口风格,通常选择style=0 WNDPROC lpfnWndproc;//函数指针,指向消息处理函数 int cbClsExTra; int cbWndExTra; HANDLE hInstance;//多窗口时,这个是和主窗口相同的。 HICON hIcon;//用户使用的图标。 HCURSOR hCursor;//窗口所使用的光标。 HBRUSH hbrBackground;//窗口背景颜色 LPCTSTR lpseMenuName;//窗口的菜单 LPCTSTR lpszClassName;//窗口的类名 }WNDCLASS;
3.Windows函数的调用。
主要是宏定义的理解。像WINAPI CALLBACK 等。这些属于修饰符,用来说明调用的顺序。程序被系统调用时,总要传一些参数,这些修饰符就是来说明传递参数的顺序的,因为使用堆栈来进行存储的,所以对参数顺序肯定是有要求的。比如你的main函数是应该先传递左边的参数,还是括号右边的。比较常用的就是_stdcall和_cdecl。_cdecl是默认vc++调用方式。但是,在winAPI中,都遵循_stdcall.所以在编写Windows程序时,也要声明这种修饰符。声明调用方式。WINAPI 和CALLBACK这些都是经过宏定义之后别名。其实质是_stdcall.
窗口的创建:
步骤是先定制窗口,在注册窗口,然后是创建,最后是显示在屏幕上。
代码结构是:
//1.定制窗口,利用结构体赋值来声明。 WNDCLASS wc; wc.style=0; ... //2.窗口的注册 BOOL RegisterClass (WNDCLASS & wc); //3.窗口的创建 //利用CreateWindow()来创建,用很多参数。 HWND hwnd; hwnd=CreateWindow("class name","title","style",x,y,w,h,fatherhandle,hMenu,hInstance,NULL) //4.窗口的显示 BOOL ShowWindow( HWND hwnd, int nCmdshow //窗口的显示方式 ); BOOL UpdateWindow(HWND hwnd);
事件、消息循环和窗口函数。
用户所提出的一个服务就是一个事件。这些事件的信息在Windows程序中构造了一个结构体用来存储这些消息。MSG。其结构是:
typedef struct tagMsg { HWND hwnd;//产生消息的窗口句柄,即事件的地点 UNIT messeage;//窗口消息的标识码,用宏定义来标识的。 WPARAM wparam;//事件的附加消息 LPARAM lparam;//事件的附加消息 DWORD time;//产生的时间 POINT pt;//发送消息时,光标所在的位置。 }MSG;
系统消息的标识码一般以VM_开头。
消息队列和消息循环:
MSG msg; while(GetMessage(&msg,NULL,NULL,NULL)){ TranslateMessage(&msg);//将键盘码消息转换为字符消息 DispatchMessage(&msg);//把消息派送给系统 }
消息派送给系统后,系统根据消息的hwnd找到窗口,然后根据窗口的lpfwinproc来找到消息处理函数,所以说消息处理函数是由系统调用的。消息处理完成后,会返回循环函数继续等待。多个消息时,会有一个消息队列进行缓存。也有不经过消息队列直接送达窗口的。
windows程序的结构:
一个入口为winmain的主函数(包括窗口的创建,以及消息的循环)
int WINAPI WinMain(HINSTANCE hInstance, hINSTANCE PreInstance, LPSTR lpCmdbine, int nCmdshow){ //窗口的创建 //消息循环 };
另一个是窗口函数(消息处理函数。)
LRESULT CALLBACK WndProc(HWND hwnd, UNIT messeage, WPARAM wparam, LPARAM lparam){ //消息处理函数 }
学到现在应该形成这样一个概念:
消息映射表的实现原理:
在消息处理函数即窗口函数中,我们可以用 switch和case结构来进行消息处理。但为了能够进行代码重构以及编写的方便,我们可以利用消息映射表来重写。实际上就是封装一下。
1.从函数中,我们可以发现,我们可以用一个结构体来封装消息的标识,以及消息对应的处理函数。
struct MSGMAP_ENTRY { UNIT nmessage; void (*pfn)(HWND,UNIT,WPARAM,LPARAM); };
2.我们可以用一个结构体数组来标识窗口需要处理的消息,即可以定义一个数组。
MSGMAP_ENTRY _messageEntres[]={
{WM_PAINT,On_Paint,},
...
}
3.在窗口函数中,我们就可以利用循环来遍历。
4.为了更进一步的完善我们的代码。可以利用宏定义来实现。因为宏定义的意思是在代码出现的位置替换掉。即可以更加简单。
//1.结构体声明 struct MSGMAP_ENTRY { UNIT nmessage; void (*pfn)(HWND,UNIT,WPARAM,LPARAM); }; //2.宏定义。这个要放在开头,只不过现在是为直观 //2.1 宏定义结构体数组。为了直接在源码中利用宏定义声明消息映射表 #define DECLARE_MESSAGE_MAP() MSGMAP_ENTRY _messageEntres[]; //2.2 宏定义 消息映射表的实现 #define BEGIN_MESSAGE_MAP() MSGMAP_ENTRY _messageEntres[]={\ #define ON_WM(messageID,msgFuc) messageID,msgFuc,\ #define END_MESSAGE_MAP()\ }; //3声明消息处理函数原型 void On_Paint(HWND,UNIT,WPARAM,LPARAM);
在代码中,我们就可以这样添加消息处理的内容。
//消息映射表的声明 DECLARE_MESSAGE_MAP() //消息映射表的实现 BEGIN_MESSAGE_MAP() ON_WM(VM_Paint,On_Paint) ...//添加消息即消息处理函数 END_MESSAGE_MAP()
消息映射表体现了设计模式中的观察者模式,用来实现分离。被观察者进行消息循环,观察者用来处理。
MFC入门:
我们上面讲到的是利用WindowsAPI来进行编写应用程序的步骤,MFC实际是一个类库,利用MFC我们可以很简单的就可以编写出应用程序,因为这个类库它对很多通用的代码进行了封装。我们在利用vs创建MFC程序时,vs会自动帮我们把框架改好,我们只需要把我们需要修改和添加的代码进行封装即可。关于利用vs编写MFC程序,可以参考一下这个网址。我感觉还不错。 http://www.jizhuomi.com/catalog.asp?tags=MFC&page=4
下面简要写一下我自己的理解:
MFC发展至今已经是一套比较流行的框架,现行的框架结果是采用文档/视图结构。就是把以前winAPP类分开。因为现在窗口处理的任务越来越复杂。
文档/视图结构:
把窗口类的功能分为三个部分:1.数据存储管理部分(文档类CDocument),2.数据显示与用户交互部分(视图类CView)。3.管理窗口的状态(CFrameWnd)。
用这三个类创建3个对象来构建一个窗口。这三个对象又由一个叫做文档模板的对象统一来创建和管理。
单文档结构(SDI)和多文档结构(MDI)的应用程序。
1.1.1文档类:是应用程序的数据库。是程序员定义程序数据和对这些数据进行操作的成员函数的地方。用户可以重写的两个虚函数,其中Serialize()函数比较重要。这个函数是负责从文件中读取数据,或者向文件存储数据的。
1.1.2视图类:为框架窗口提供用户区。创建的对象时一个交互界面 。类中比较重要的两个成员函数:
1.GetDocument():用于获取文档类对象的指针。是与文档类进行通信的通道。视图类对象必须通过这个指针来访问文档类对象中的数据。
2.OnDraw():重画函数,每次应用窗口出现及大小发生变化时,系统会自动调用OnDraw()函数。
1.1.3窗口框架类CFrameWnd:类中有获取文档对象和视图对象指针的函数
1.1 文档模板类:是管理视图对象、框架窗口对象和文档对象的管理器。
文档模板类(CDocTemplate)又派生出单文档模板类(CSingleDocTemplate)和多文档模板类(CMultiDocTemplate)。
1.应用程序类:是上述各类对象的容器。主要功能是实现应用程序的初始化和消息循环。
应用程序各个对象之间的创建顺序:
1.系统会创建应用程序对象。
2.应用程序对象会在InitInstance()函数中,创建文档模板对象。
3.创建文件模板对象时,构造函数会直接创建文档类对象,窗口框架类对象。
4.窗口框架对象会创建视图对象。
****各个对象之间,通过指针相互协同。
类信息表CRuntimeClass:结构体
实现这个类信息表的首要目的是:可以动态的创建对象。
我们用vs创建程序后,我们需要做的事:
1.重写CWinAPP派生类中的虚函数 InitInstance();
2.CDocument的派生类中,声明程序所需要的数据和对这些数据进行必要的操作。
3.在CView的派生类中,编写消息处理的代码,如果需要用到文档中的数据,需用用GetDocument()函数来获取文档对象。
4.在CView重写OnDraw()。重绘时的代码。
5.用宏定义实现消息映射表。
图形:
1.DC与GDI 。
DC是Windows系统中在驱动层的一个抽象层,是一种设备描述表,DC为应用程序提供了画板,程序可以在DC上进行操作,再由DC与底层进行交互。
GDI是图形设备接口,这些接口可以用来改变DC属性。
2.CDC。和CWND类似。MFC把进行图形操作的一些操作,封装成类。利用这个类,来进行操作。HDC是进行图形操作的句柄。获得这个句柄就可以进行图形操作。
3.CDC又派生出其他特定功能的类。