在文章“MFC--序幕”之中,我描述了一个windows程序的基本结构,并说明,所有的windows程序,不管是MFC,还是SDK开发,甚至是NET FrameWork--CLR开发,都是按照这个思路来,之后在文章“MFC--非模式对话框中”首先通过由MFC向导生成的对话框程序,结合相关文章,描述了MFC的程序的基本结构,因为对话框程序是MFC中,最简单的程序,之后在阐述了原理的基础之上呢,又将由MFC向导生成的对话框程序(默认是模式对话框)改成了非模式对话框,论证了前面的阐述。今天在这一篇文章中,我们来讨论一下由MFC向导生成的单文档程序,样式为MFC标准的。然后再次基础上,再添加一个文档模板,让一个单文档程序支持两种文档,来说明MFC单文档程序的结构。最后呢,在完全抛弃由MFC给我们窗口创建框架,根据在“MFC--序幕”中说明的那样,创建一个windows窗口。因为有的人说MFC向导生成的程序代码太多,太多不需要的,那么我们就建立一个自己的。
当我们用向导生成了单文档程序,进入项目之后,在类视图,我们一般看到的是这样一个图,如下:
那么前面的文章提到过,app类管理主线程,MFC已经为我们写好了app这个线程类来管理程序和线程相关的各个方面,那么我们就可以直接进行界面和功能的开发。对于阅读MFC程序,通常都是从app类的Initinstance成员函数开始的,功能就像sdk中的winmain函数一样,在这个当中进行程序的一些初始化,窗口的建立,显示等等。下面就从Initinstance开始。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
这个部分在非模式对话框的程序中一样,初始化常规控件库,如按钮,编辑框等等。这样我们就可以在我们的程序中安全使用各种mfc标准控件。
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
那么这个是初始化OLE。OLE是对象连接于嵌入的简称。是一个功能很强大的东西,只有在这里初始化了之后,后面才可以使用OLE的功能,在MFC默认的OLE使用,就是拖放打开文件这个功能,也是在Initinstance中有这么一句:
m_pMainWnd->DragAcceptFiles();其实就启用了拖放打开文件和数据移动的功能。具体有关OLE,可以查阅MSDN,有详细的讲解和使用。这里不做详细讨论。
AfxEnableControlContainer();这一句主要是使程序可以使用ActiveX控件。
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));这一句可以使应用程序的设置,保存在注册表中。
LoadStdProfileSettings(4); 从注册表中导入最近打开的文件
InitKeyboardManager函数初始化一个键盘管理器,底层是一个类的一个对象,你也可以自己直接创建,它的作用的是管理程序中的快捷键。
下面是重点
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(Ctest8Doc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(Ctest8View));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
MFC中视图类其实就相对于框架窗口上的子窗口,主要用来显示数据,文档类主要是用来存储数据和提供数据给视图类显示,app类在这个地方也扮演了管理文档和视图的角色。在这里是mfc程序的最关键的地方,这里创建和添加了一个文档模板,我们可以添加多个文档模板。其实文档类,视图类,主窗口框架类都是由文档模板来管理,文档模板有单文档和多文档模板,这里是单文档模板。我们看参数由资源ID,文档类,主窗口类,视图类。其实就应该能够想到。我们把Initinstance看完,也没有看见创建窗口这个动作,但是在最后,却有这么两句:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
那么我们就奇怪了,其实事情是这样的。当我们打开这个单文档程序的时候,或是用这个程序点击打开,来打开一个文档的时候,那么MFC框架会进行新建命令或是打开文件命令,以打开文件命令为例,当我们我要打开文件的路径传给程序的时候,MFC的app类会根据文件的扩展名来判断用哪个文档模板来打开这个文档文件,在MFC内部维护了一个文档模板列表,而识别这些文档模板列表呢,就是通过文件的扩展名来确定。当找到了合适的文档模板的时候,MFC就是根据文档模板的信息,由MFC先创建文档类,并解析数据,然后创建主窗口,最后创建视图窗口,并显示文档类解析的数据。而文档模板之所以知道这些信息,其实就是在上面这个步骤,我们创建的这个单文档模板,然后添加到了MFC的文档模板列表中。另外,我们看见在创建文档模板类的第一个参数是一个资源ID,它的作用就是提供程序会用到的资源,如快捷键表,菜单,图标,文件信息等。刚才我们说到了app类是通过文档的扩展名来选择和是的文档模板来打开文件,其实这个扩展名就来自资源ID。如下
也就是说,MFC根据接受到的命令,为我们创建了窗口,下面我们看看命令的解析。
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);这个地方其实就是解析命令行,在我的博文中有专门的文章,说明了MFC命令行的使用。如果我们传递了文件名,那么就会把文件名和路径保存在对象cmdInfo的相关数据成员中,MFC有一套标准的命令行,如打开,新建,打印等,我们也可以添加更多的命令行处理,在我的博文中有该文章
// 启用“DDE 执行”
//EnableShellOpen();
//RegisterShellFileTypes(TRUE);这两句使得我们的程序可通过双击文件打开。它实际在这里通过文档模板中的信息,如程序名,扩展名,文档类别来对程序进行注册,这样,系统就可以识别我们的程序,并和指定扩展名相关联,当我们点击文件的时候,系统就会打开程序,并打开该文档
// 调度在命令行中指定的命令。如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;前面已经将命令行进行了解析,将解析的信息保存在了cmdInfo对象中,在这里就根据解析的命令进行相应的执行,比如新建,打开。其实当程序执行到这里的时候,mfc就执行了新建或是打开命令,由app类选择合适的文档模板创建窗口,初始化文档,显示视图。之后就出现了下面两句:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
MFC执行相关的命令行,创建了窗口,最后Initinstance快结束的时候,显示和更新窗口。
Initinstance的最后一句是return TRUE;我在“MFC-非模式对话框”中提到过,当我们返回TRUE的时候,app类就会为我们运行消息循环。
到此,基本就结束了,运行窗口就可以显示了。
那么MFC默认只为我们添加了一个文档模板,根据我们的需要,我们可以添加多个文档模板,来支持多种扩展名的文件。每一个文档模板我们可以传递不同的视图类和文档类,资源ID。这样当我们打开不同的文件的时候,窗口的菜单,程序图标,等等程序资源可以不同,由我们使用的文档模板来定。下面我就再添加一个文档模板,一般情况,我们要再添加一个文档模板,都是要再添加一个文档类的,或者是视图类,因为时间的问题,我用的mfc本身的文档类,因为一个文档类其实就代表一个数据结构,不同的文件,数据结构肯定是不一样的,所以一般我们再添加一个文档模板来支持新的扩展名的时候,都是要添加新文档类的,但是这里只是为了说明,就没有添加,我在资源中添加了新的资源
附:有关IDR_DOC2的结构,在msdn上有详细说明每个字段的顺序及意义,有意者请参考msdn
CSingleDocTemplate* pDocTemplate2;
pDocTemplate2 = new CSingleDocTemplate(
IDR_DOC2,
RUNTIME_CLASS(Ctest8Doc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(Ctest8View));
if (!pDocTemplate2)
return FALSE;
AddDocTemplate(pDocTemplate2);
这个时候,我编译,生成,运行,由于有两个文档模板,所以,当我们运行的时候,首先出现了一个新建对话框,要我们选择要创建的文档类别,如下:
当我们点击打开文件的时候,也有选择要打开的文件类别:
下图说明了mfc基本单文档框架类的相互关系:
下表是创建顺序及关系:
Creator | Creates |
---|---|
Application object | Document template |
Document template | Document |
Document template | Frame window |
Frame window | View |
{
WNDCLASS wc;
// Register the main window class.
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc =AfxWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = AfxGetInstanceHandle();
wc.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor =:: LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINFRAME);;
wc.lpszClassName =L"MainWnd";
if (!RegisterClass(&wc))
return FALSE;
return TRUE;
}
接着,我们用类向导从CWnd类继承一个mfc窗口类,CMyWnd,用这个继承与CWnd的类来管理我们的窗口,我们知道,在MFC中都是通过类和对象来管理的,在上面的这个设计和注册类的过程和前面我们讲过的sdk的方法和过程是一样,唯一有点区别就是窗口过程,窗口过程我们必须要用AfxWndProc。这是为了我们可以创建之后,可以使用窗口类的消息映射,只有这样,才可以把消息送如我们的消息映射过程中。
我们已经设计和注册了一个窗口类,下面我们就可以创建一个我们继承于CWnd类的我们自己的窗口类的对象,并创建窗口,如下:
if(this->RegWndClass())
return FALSE;
CMyWnd *mywnd=new CMyWnd;
this->m_pMainWnd=mywnd;
mywnd->CreateEx(0,L"MainWnd",L"MyWnd",WS_OVERLAPPEDWINDOW,CRect(0,0,500,500),NULL,NULL,0);
最后就是显示和更新窗口
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
由于我们已经把窗口类的对象mywnd的地址已经给了app类的m_pMainWnd的数据成员,由它来保存主窗口的窗口指针,所以我们这里用它调用显示和更新窗口。最后我们在Initinstance的末尾返回TRUE,开始消息循环。
这样我们就创建了窗口,但是我们还有几个步骤要做,防止内存泄露。这个已经在“MFC--非模式对话框”文章中已经说到,这里在说一遍。
首先我们添加几个消息消息映射来处理几个消息,WM_CLOSE,WM_DESTROY,WM_PAINT.
void CMyWnd::OnClose()
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
this->DestroyWindow();
//CWnd::OnClose();
}
void CMyWnd::OnDestroy()
{
CWnd::OnDestroy();
PostQuitMessage(0);
// TODO: 在此处添加消息处理程序代码
}
void CMyWnd::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 在此处添加消息处理程序代码
// 不为绘图消息调用 CWnd::OnPaint()
dc.TextOut(50,50,L"this is my window",wcslen(L"this is my window"));
}
这里的CPaintDC对象完成了在SDK中的beginpaint和endpaint的作用,我只用使用这个对象做我们的绘图工作就可以了。
最后,我们还必须要处理一个虚函数:
void CMyWnd::PostNcDestroy()
{
// TODO: 在此添加专用代码和/或调用基类
delete this;
CWnd::PostNcDestroy();
}
这个是窗口对象调用的最后一个函数,在这里面,将这个窗口对象的指针删除。这样我们运行一下,如下图:
我们看看输出窗口:
安全退出,没有内存泄露。说明我们的思路是正确的,因此,再次证明了我在“MFC--序幕“文章说的那个基本的windows窗口程序重要性,只要掌握了那个基本结构,对于我们学习和灵活使用MFC是一个根本。在MFC中仍然是按照那个结构来运行的。
由于程序摒弃了mfc向导的单文档框架,所以才到上面的命令,都需要我们自己编写代码处理,原来是有mfc框架为我们处理的。
最终类视图如下:
本文涉及的内容可以查阅MSDN和我的别的博文进行学习。
本文改写的mfc窗口程序代码:http://download.csdn.net/detail/xinzhiyounizhiyouni/6556135
我的邮箱anydhl1987@126.com,欢迎交流学习