一.窗口类型的概述
窗口类型主要有二种即:文档类型(Document)和对话框类型(Dialog) 表单类型(Form)是介于这2种类型之间。文档类型的窗口的编程相对对话框来说要复杂得多,是我们学习的主要对象。
a) 文档类型(Document):文档类型的应用程序的界面,大部分区域用于文字或画图等读写操作,常见的有Word、Excel、写字板等。文档类型的窗口又分单文档和多文档2种类型
i. 多文档类型就是在一个窗口框架内有多个子窗口,常见的有Word、Excel
ii. 单文档类型当然就不可能有多个子窗口了,常见的有附件中的写字板、画图等。
b) 对话框类型(Dialog):对话框类型的界面中使用了很多控件,输入输出主要是针对控件,常见的有计算器、音量控制等。共对话框也是这种类型,只调用操作系统提供的就可以了。编程用的最多的也就是表单类型。对话框类型也可以象文档类型一样进行文字、画图等处理,而文档类型窗口中同样也可以使用控件。对话框窗口有模态和非模态2种类型。所谓模态对话框就是当前对话框不关闭,就不能切换到调用该对话框的父窗口;而所谓非模态窗口就是不关闭当前对话框,也同样可以切换到调用该对话框的父窗口。
单文档 | 对话框 | ||
CAboutDlg | “关于”对话框类。无须修改。 | CAboutDlg | “关于”对话框类。无须修改。 |
CTest2App | 主应用程序类,调用框架类、文档类、视图类,程序类。无须修改。 | CTest3App | 主应用程序类,调用主对话框类。无须修改。 |
CMainFrame | 主框架类,即主窗口。很少需要修改。 | Ctest3Dlg | 主对话框类。 |
CTest2Doc | 文档类,即存放数据主要在这儿编写代码。 | ||
CTest2view | 视图类,即用于屏幕显示的类,文字显示和画图主要在这儿编写代码。 |
二.CmainFrame类
类中的PreCreateWindow()成员函数中,通过修改 CREATESTRUCT cs 来修改窗口类或样式。
typedef struct tagCREATESTRUCTW {
LPVOID lpCreateParams; //指向创建窗口用的数据的指针
HANDLE hInstance; //窗口的实例句柄
HMENU hMenu; //窗口的菜单句柄
HWND hwndParent; //窗口句柄
int cy; //窗口的高度
int cx; //窗口的宽度
int y; //窗口的左上角的y座标
int x; //窗口的左上角的x座标
LONG style; //窗口的类型
LPCSTR lpszName; //指向窗口的名称的指针
LPCSTR lpszClass; //指向窗口类的名称的指针
DWORD dwExStyle; //窗口的扩展类型
} CREATESTRUCTW, *PCREATESTRUCTW,*LPCREATESTRUCTW;
框架类主要处理窗口的类型、边框、位置、大小以及子窗口(对于多文档来说)等内容,工具条、状态条也是这个类的成员变量。如果要添加成员变量时,初始化要在CMainFrame()构造函数中添加代码;OnCreate()成员函数是消息WM_CREATE的处理函数,这里面根据需要修改代码(一般不需要);PreCreateWindow()成员函数是创建主窗口前的设置,调整窗口位置、大小、类型等都是在这儿修改代码。
三.文档类
文档类主要用来处理数据。例如画一个图,图的各节点座标、颜色、线条的粗细等都有文档类来处理,文件的保存和打开等当然也是文档类来处理。
四. 视图类
主要处理数据的显示,例如:文字在窗口中显示、键盘鼠标操作等都是由视图类来处理。视图类不断地从文档类取数据,不断地对文档类传送数据。当文档类数据较多时,视图类显示的数据只是文档类的一部分。
五. 主应用程序类
主应用程序类是程序的入口,其它各个类都是在这儿集成的。这个类除构造函数之外,唯一要注意的就是初始化函数。这个类一般不修改。
单文档 | 对话框 | ||
Ctest2App() | 构造函数 | Ctest2App() | 构造函数 |
InitInstance() | 初始化成员函数 | InitInstance() | 初始化成员函数 |
OnAppAbout() | 由菜单发出的消息处理函数 |
|
|
单文档 | 对话框 |
BOOL Ctest2App::InitInstance() { InitCommonControls(); CWinApp::InitInstance(); if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; } AfxEnableControlContainer(); SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
//加载标准 INI 文件选项(包括 MRU) LoadStdProfileSettings(4);
//上面内容不用太关注,以下是重点 //注册应用程序的文档模板。文档模板 //将用作文档、框架窗口和视图之间的连接 CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(Ctest2Doc), RUNTIME_CLASS(CMainFrame), RUNTIME_CLASS(Ctest2View)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate); //分析标准外壳命令、DDE、打开文件操作的命令行 CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); //调度在命令行中指定的命令。如果 //用 /RegServer、/Register、/Unregserver //或 /Unregister 启动应用程序,则返回 FALSE。 if (!ProcessShellCommand(cmdInfo)) return FALSE; //唯一的一个窗口已初始化,因此显示它并对其进行更新 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); //仅当存在后缀时才调用 DragAcceptFiles, //在 SDI 应用程序中,这应在 ProcessShellCommand //之后发生 return TRUE; } |
BOOL Ctest3App::InitInstance() { InitCommonControls(); CWinApp::InitInstance();
AfxEnableControlContainer(); SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
//上面内容不用太关注,以下是重点 Ctest3Dlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); //下面的程序注意:对话框窗口已经被关闭 if (nResponse == IDOK) { // TODO: 在此放置处理何时用“确定”来关闭 //对话框的代码 } else if (nResponse == IDCANCEL) { // TODO: 在此放置处理何时用“取消”来关闭 //对话框的代码 }
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序, // 而不是启动应用程序的消息泵。 return FALSE; }
|
六. WinMain()函数在哪儿
MFC把千篇一律的WinMain()函数写在①crtexec.c中,在编译完后链接时才组装到你的exe程序中。运行时,这个WinMain()函数调用MFC的全局函数AfxWinMain(),这个全局函数AfxWinMain()是写在②appmodul.cpp中的。在AfxWinMain()函数中做三件事,③注册窗口类、④调用应用程序类的初始化函数InitInstance()、⑤调用应用程序类的Run()函数(实际上是父类的父类CWinThread的Run()成员函数)。
上面这个Run()函数中包含消息循环,而InitInstance()函数又将框架类、文档类、视图类结合在一起,于是所有的类全部集成起来了,然后在主应用程序类、框架类、文档类、视图类里面接受这些消息并处理。编程的重点在文档类和视图类,什么样的消息,执行什么样的处理函数,这样就有效地将数据和控制分离开来。
七. 消息处理
与窗口有关的消息(即以“WM_”开头的消息),只能在框架类和视图类中处理;来自菜单或工具条的命令事件,则框架类、视图类、主应用程序类、文档类全部可以处理。这其中有优先级别高低的问题,一个类接受到命令消息时,先传给比自己级别高的类,如果未处理再自己处理。如果自己也不处理,则传给比自己级别低的类。这个优先级别是:视图类>文档类>文档模板类>框架类>主应用程序类>MFC缺省的处理程序中。
八. 添加、删除消息处理
添加一个消息处理,一般要改动三个地方,以WM_LBUTTONDOWN消息为例,①在视图类的定义(test2View.h)中添加一个成员函数,②在视图类(test2View.c)中添加一条消息映射,③在视图类(test2View.c)中实现消息处理的成员函数。
九. 程序入口
主应用程序类是从CWinApp类继承的,作用相当于WinMain程序;CMainFrame是从CFrameWnd类继承的,其作用相当于WndProc程序。也就是说,消息首先由框架类接受,然后按优先顺序传给其它的类。文档类和视图类由消息来驱动(视图类的PreCreateWindow()除外)。
“Ctest2App theApp;”,这便是整个应用程序的总入口。MFC的crtexec.c的WinMain()函数调用test2.cpp中的“Ctest2App theApp;”。theApp是类的实例,自然也是必然要调用主应用程序类的构造函数,包括父类的构造函数,之后再调用MFC的appmodul.cpp中的AfxWinMain()函数,由AfxWinMain()函数调用主应用程序类的InitInstance()成员函数。
在主应用程序类的InitInstance()成员函数中,定义了框架类、文档类、视图类的实例,从而间接地调用了框架类的PreCreateWindow()和OnCreate()成员函数,由OnCreate()函数再调用视图类的PreCreateWindow()成员函数。然后再回到主应用程序类的InitInstance()成员函数中,执行“m_pMainWnd->ShowWindow(SW_SHOW);”和“m_pMainWnd->UpdateWindow();”。
十. 公共对话框MessageBox
在MFC的CWnd类中封装了一个MessageBox()成员函数,但MSDN并不推荐使用,一般用AfxMessageBox()全局函数
intAfxMessageBox(
intAFXAPI AfxMessageBox(
LPCTSTR lpszText,
UINT nIDPrompt, //要表示的字符串
UINT nType = MB_OK,
UINT nType = MB_OK, //画面形状(按钮、模式、图标、缺省值4个值组合使用)
UINT nIDHelp = 0
UINT nIDHelp = (UINT) -1 //帮助ID,缺省为0
); );
返回值 | 说明 |
0 | 不能正常表示 |
IDYES | 被按下 |
IDNO | 被按下 |
IDOK | 被按下 |
IDCANCEL | 被按下或【ESC】被按下 |
IDABORT | 被按下 |
IDIGNORE | 被按下 |
IDRETRY | 被按下 |
显示按钮 | 说明 |
ID_ABORTRETRYIGNORE |
|
ID_OK |
|
ID_OKCANCEL |
|
ID_RETRYCANCEL |
|
ID_YESNO |
|
ID_YESNOCANCEL |
|
模式 | 说明 |
ID_APPMODAL | 模态(缺省值)即不关闭就不能回到父窗口,但可以切换到其它应用程序。 |
ID_SYSTEMMODAL | 系统模态即不关闭就不能回到任何窗口。 |
ID_TASKMODAL | 特殊用途而备用。 |
显示图标 | 说明 |
ID_ICONEXCLAMATION | 感叹符号 |
ID_ICONINFORMATION | 情报符号 |
ID_ICONquestion | 问号 |
ID_ICONSTOP | 停止符号 |
缺省按钮 | 说明 |
ID_DEFBUTTON1 | 第一个探针为缺省按钮。 |
ID_DEFBUTTON2 | 第二个探针为缺省按钮。 |
ID_DEFBUTTON3 | 第三个探针为缺省按钮。 |
十一. MFC特有的规则
a) 类型:MFC将C++的关键字用宏定义成以下内容。
MFC类型 | 意思 | C++类型 |
BOOL,BOOLEAN | 布尔型 | boolean |
BYTE | 1字节数值型 | char |
WORD | 2字节数值型 | short |
DWORD | 4字节数值型 | int |
UINT | 无符号整型 | unsigned int |
VOID | void型 | void |
LPDWORD | DWORD的指针型 | int* |
LPCSTR | 常量字符串 | const char* |
LPSTR | 字符串 | char* |
LPCVOID | 常量void指针 | const void* |
LPVOID | void指针 | void* |
b) 命名规则:MFC有以下命名规则。
i. 类名:以大写的C开头,单词的首字母大写,单词之间不用下划线。
ii. 成员函数和全局函数:以单词的首字母大写,单词之间不用下划线。全局函数前再加Afx
iii. 成员变量:匈牙利命名法,以“m_”开头,单词的首字母大写。
iv. 宏、类型名:全部使用大写。
十二. 用TRACE宏调试
TRACE()宏函数来调试程序,TRACE()宏函数只在DEBUG状态有效,即按<F5>运行时有效,<Ctrl + F5>时无效。断点也可以设置条件。如,断点的条件是“i>5”。
for(int i=0; i<10; i++) {
TRACE("i = [%d]\n", i);