MFC--单文档程序(框架)

来源:互联网 发布:jquery处理json数组 编辑:程序博客网 时间:2024/05/21 06:15

在文章“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基本单文档框架类的相互关系:


下表是创建顺序及关系:

Object Creators

Creator

Creates

Application object

Document template

Document template

Document

Document template

Frame window

Frame window

View

我们看到了,有app对象创建了文档模板,文档模板创建文档和主窗口,主窗口创建了视图窗口。在这些对象之间是有相关的函数可以获取必要的指针的,如在视图类中,可以获取文档类对象的指针的。文档类主要是通过串行化来存储数据到磁盘文件的。
另外,对于菜单上的如新建,打开等一些命令,是被路由到了app的基类,由mfc框架来执行的,我们也可以修改,由我们自己处理,如:

上面基本简单的将MFC单文档程序阐述了一遍了。我这里要说明的是,上面这些其实我们可以根据自己的需求,进行相应的修改,虽然,基于MFC这个框架,但是不失灵活性,我们可以通过由app类,文档模板类,文档类,等等成员函数,重写虚函数,来自定义我们的程序。我还是那句话,MFC是一个框架,一方面我们要依耐它,另一方面,我们要利用它。MFC的框架确实是复杂的,但是我说过,按照“MFC--序幕”描述的程序结构,那么来理解也不难。它的骨架其实就是那样的。我们掌握了最根本的东西,学习它别的东西,也就容易了。那么,下面我就按照"MFC--序幕"里的过程,在MFC中手动创建一个窗口。
在“MFC--序幕”文章中介绍了一个基本的windows窗口程序的结构,在“MFC---非模式对话框”中有讲解,在这里,我按照,设计和注册窗口类,创建,显示,更新,消息循环,窗口过程的步骤,在MFC中,实现一遍。我们先把MFC向导生成的文档类,视图类,框架类的头文件和源文件删除。如图:
另外还要清理掉在原来Initinstance中的一些初始化函数,因为这些初始化也是用于MFC单文档框架的,而现在我们要建立自己的窗口,摒弃原来的单文档框架,所以,这些初始化函数就不需要了。
首先设计和注册一个窗口,我在app类中添加了一个成员函数,RegWndClass,用来设计和注册窗口类。如下:
BOOL Ctest8App::RegWndClass(void)
{
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,欢迎交流学习

原创粉丝点击