主题:MFC是什么?谢谢!!
怪杰
[专家分:40] 发布于 2002-09-30 19:06:00
大虾们,是否能告诉我MFC是什么?
回复列表 (共13个回复)
沙发
hellchinaren [专家分:0] 发布于 2002-10-01 15:09:00
MFC使用教程
第一部分:MFC简介
Visual C++ 不仅仅是一个编译器。它是一个全面的应用程序开发环境,使用它你充分利用具有面向对象特性的 C++ 来开发出专业级的 Windows 应用程序。为了能充分利用这些特性,你必须理解 C++ 程序设计语言。掌握了C++,你就必须掌握 Microsoft 基本类库 (MFC) 的层次结构。该层次结构包容了 Windows API 中的用户界面部分,并使你能够很容易地以面向对象的方式建立 Windows 应用程序。这种层次结构适用于所有版本的 Windows 并彼此兼容。你用 MFC 所建立的代码是完全可移植的。
简单介绍MFC的基本概念和术语以及事件驱动程序设计方法。输入、编译和运行一个简单的MFC程序。第二部分中将详细解释这些代码。第三部分讨论了MFC控件和如何定制它们。
什么是MFC?
如果你要建立一个 Windows 应用程序,应该如何下手?
好的开端是从设计用户界面开始。首先,你要决定什么样的用户能使用该程序并根据需要来设置相应的用户界面对象。Windows 用户界面有一些标准的控件,如按钮、菜单、滚动条和列表等,这对那些 Windows 用户已经是很熟悉了。 要记住的是,作为程序员必须选择一组控件并决定如何把它们安排到屏幕上。传统上,你需要在纸上做一下用户界面的草图,直到对各元素感到满意为止。这对于一些比较小的项目,以及一些大项目的早期原型阶段是可以的。下一步,是要实现代码。为任何 Windows 平台建立应用程序时,程序员都有两种选择:C 或 C++。 使用 C,程序员是在 Windows 应用程序界面 ( API ) 的水平上编写代码。该界面是由几百个 C 函数所组成,这些函数在Windows API 参考手册中都有介绍。对于Windows NT, API 被称为 "Win32 API",以区别于其用于Windows 3.1的16位 API, Microsoft 也提供了 C++ 库,它位于任何 Windows API 之上,能够使程序员的工作更容易。它就是Microsoft基本类库 (MFC),该库的主要优点是效率高。它减少了大量在建立 Windows 程序时必须编写的代码。同时它还提供了所有一般 C++ 编程的优点,例如继承和封装。MFC 是可移植的,例如,在 Windows 3.1下编写的代码可以很容易地移植到Windows NT 或 Windows 95 上。因此,MFC 很值得推荐的开发Windows 应用程序的方法。在本教程自始至终使用的都是 MFC。当是使用MFC时,你编写的代码是用来建立必要的用户界面控件并定制其外观。同时你还要编写用来响应用户操作这些控件的代码。例如,如果用户单击一个按钮时,你应该有代码来响应。这就是事件驱动代码,它构成了所有应用程序。一旦应用程序正确的响应了所有允许的控制,它的任务也就完成了 。
Visual C++ 应用程序开发程序环境特别适合于使用 MFC (也有其它开发环境使用MFC,如C++ builder也包含了对MFC的支持),所以一起学习 MFC和 Visual C++ 能够增强你的开发程序的能力。
Windows应用程序使用几个标准的控件:
静态文本标签
按钮
列表框
组合框(一种更高级的列表框)
单选按钮 (单选框)
检查按钮 (复选框)
编辑框(单行和多行)
滚动条
你可以通过代码或"资源编辑器"来建立这些控件,在资源编辑器中可以建立对话框和这些控件。在本教程中,我们将使用代码来建立它们。
Windows支持几种类型的应用程序窗口。一个典型的应用程序应该活动在称为"框架窗口"中。一个框架窗口是一个全功能的主窗口,用户可以改变尺寸、最小化、最大化等。Windows也支持两种类型的对话框:模式和无模式对话框。模式对话框一旦出现在屏幕上只有当它退出时,屏幕上该应用程序的其余部分才能响应。无模式对话框出现在屏幕上时,程序的其余部分也可以作出响应,它就象浮动在上面一样。
最简单的 Windows 应用程序是使用单文档界面(SDI),只有一个框架窗口。Windows的钟表、PIF 编辑器、记事本等都是 SDI 应用程序的例子。Windows 也提供了一种称为多文档界面的组织形式,它可用于更复杂的应用程序。MDI 系统允许用户在同一应用程序中同时可以查看多个文档。例如,一个文本编辑器可以允许用户同时打开多个文本文件。使用 MDI时,应用程序有一个主窗口,在主窗口中有一些子窗口,每个子窗口中各自包含有各自的文档。在MDI框架中,主窗口有一个主菜单,它对主框架中最顶端窗口有效。各子窗口都可以缩成图标或展开,MDI主窗口也可以变成桌面上的一个图标。MDI界面可能会给你一种第二桌面的感觉,它对窗口的管理和删除混乱的窗口有很大的帮助。
你所建立的每一个应用程序都会使用它自己的一套控件、菜单结构以及对话框。应用程序界面的好坏取决于你如何选择和组织这些界面对象。Visual C++ 中的资源编辑器可以使你能容易的建立和定制这些界面对象。
事件驱动软件和词汇
所有基于窗口的GUI 都包含相同的基本元素,它们的操作方式都是相同的。在屏幕上,用户所看到的是一组窗口,每个窗口都包含有控件、图标、对象以及一些处理鼠标和键盘的元素。从用户角度来看,各系统的界面对象都是相同的:按钮、滚动条、图标、对话框以及下拉菜单等等。尽管这些界面元素的"外观和感觉"可能有些不同,但这些界面对象的工作方式都是相同的。例如,滚动条对于Windows、Mac和Motif 可能有些不同,但他们的作用完全是一样的。
从程序员的角度来看,这些系统在概念上是相似的,尽管它们可能有很大的不同。为了建立GUI 程序,程序员第一步要把所有需要的用户界面控件都放到窗口上。例如,如果程序员要建立一个从摄氏到华氏的转换的简单程序,则程序员所选择的用户界面对象来完成并在屏幕上把结果显示出来。在这个简单的程序中,程序员可能需要用户在一个可编辑的编辑框中输入温度值,在一个不可编辑的编辑框中显示转换结果,然后让用户可以单击一个标有"退出"的按钮来退出应用程序。
因为是用户来操作应用程序的控制,所以程序必须作出响应。所做的响应依赖于用户使用鼠标或键盘在不同控制上的操作。屏幕上的每个用户界面对象对事件的响应是不同的。例如,如果用户单击退出按钮,则该按钮必须更新屏幕、加亮它自己。然后程序必须响应退出。
Windows 所用的模式也是类似的。在一个典型的应用程序中,你将建立一个主窗口,并且在其中放置了一些用户界面控件。这些控件通常被称为子窗口--它们就象一些在主窗口中的更小更特殊的子窗口。作为程序员,你应该通过函数调用来发送信息操作这些控件、通过把信息发送给你到代码来响应用户的操作。
如果你从未做过事件驱动程序设计,则所有这些对你来说可能是很陌生的。但是,事件驱动程序设计方式是很容易理解的。具体的细节对不同的系统可能有些不同,但是其基本概念是类似的。在一个事件驱动界面中,应用程序会在屏幕上绘制几个界面对象,如按钮、文本区和菜单。应用程序通常通过一段称为事件循环的的代码来响应用户的操作。用户可以使用鼠标或键盘来任意操作屏幕上的对象。例如,用户用鼠标单击一个按钮。用鼠标单击就称为一个事件。事件驱动系统把用户的动作如鼠标单击和键盘操作定义为事件,也把系统操作如更新屏幕定义为事件。
在比较低级的编程方法中,如用C直接编写Windows API应用程序,代码量是非常大的,因为你所要照顾的细节太多了。例如,你用某种类型的结构来接收单击鼠标事件。你的事件循环中的代码会查看结构中不同域,以确定哪个用户界面对象受到了影响,然后会完成相应的操作。当屏幕上有很多对象时,应用程序会变得很大。只是简单地处理哪个对象被单击和对它需要做些什么要花费大量的代码。
幸运的是,你可以在比较高级的方法来进行编程,这就是使用MFC。在MFC中,几乎所有的低级的细节处理都为你代办了。如果你把某一用户界面对象放在屏幕上,你只需要两行代码来建立它。如果用户单击一个按钮,则按钮自己会完成一切必要的操作,从更新屏幕上的外观到调用你程序中的预处理函数。该函数包含有对该按钮作出相应操作的代码。MFC 为你处理所有的细节:你建立按钮并告知它特定的处理函数,则当它被按下时,它就会调用相应的函数。第四部分介绍了怎样使用消息映射来处理事件。
例子:
理解一个典型的 MFC程序的结构和样式的最好方法是输入一段小程序,然后编译和运行它。下面的程序是一段简单的"hello world"程序。这对很多C程序员都是很熟悉了,让我们看一下如何用MFC 方法来实现。如果你是第一次看到这类程序,也许比较难理解。这没关系,我们后面会详细介绍。现在你只要用Visual C++ 环境中建立、编译和运行它就可以了。
//hello.cpp
#include <afxwin.h>
// 说明应用程序类
class CHelloApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// 建立应用程序类的实例
CHelloApp HelloApp;
// 说明主窗口类
class CHelloWindow : public CFrameWnd
{
CStatic* cs;
public:
CHelloWindow();
};
// 每当应用程序首次执行时都要调用的初始化函数
BOOL CHelloApp::InitInstance()
{
m_pMainWnd = new CHelloWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// 窗口类的构造函数
CHelloWindow::CHelloWindow()
{
// 建立窗口本身
Create(NULL,
"Hello World!",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// 建立静态标签
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80,150,150),
this);
}
上面的这段程序如果用C 来实现,得需要几页的代码。这个简单的例子做了三件事。第一,它建立了一个应用程序对象。你所编写的每个MFC 程序都有一个单一的程序对象是处理 MFC 和 Windows 的初始细节的。第二,应用程序建立了一个窗口来作为应用程序的主窗口。最后,在应用程序的窗口中建立了一个静态文本标签,它包含有"hello world"几个字。在第二部分中我们会仔细研究这段程序,以理解其结构。
建立项目和编译代码
为了在 Visual C++ 中编译代码,你必须要建立一个项目。一个项目主要保存着下面三种不同类型的信息:
1.它可以记住建立一个可执行程序所需要的所有源程序代码文件。在这个简单的例子中,文件 HELLO.CPP 是唯一的源文件,但是在一个大型的应用程序中,为了便于管理和维护,你可以会有许多个不同的源文件。项目会维护这些不同文件的列表,并当你要建立下一个新的可执行程序时,在必要时编译它们。
2.它会记住针对你的应用程序所使用的编译器和连接器选项。例如,它会记住把哪个库连接到了执行程序中,你是否预编译了头文件等等。
3.它会记住你想要建立的项目类型: 一个控制台应用程序,或一个窗口应用程序等等。如果你已经对项目文件有所了解,则会很容易明白作为机器产生的项目文件的作用。
第二部分:一个简单的MFC程序
我们将一段一段地来研究提到的 MFC 应用程序,以便能理解它的结构和概念框架。我们将先介绍 MFC,然后在介绍如何用 MFC 来建立应用程序。
MFC简介
MFC 是一个很大的、扩展了的 C++ 类层次结构,它能使开发 Windows 应用程序变得更加容易。MFC 是在整个 Windows 家族中都是兼容的,也就是说,无论是 Windows3.x、Windows95 还是 Windows NT,所使用的 MFC 是兼容的。每当新的 Windows 版本出现时,MFC 也会得到修改以便使旧的编译器和代码能在新的系统中工作。MFC 也回得到扩展,添加新的特性、变得更加容易建立应用程序。
与传统上使用 C 语言直接访问 Windows API相反,使用 MFC 和 C++ 的优点是 MFC 已经包含和压缩了所有标准的"样板文件"代码,这些代码是所有用 C 编写的 Windows 程序所必需的。因此用 MFC 编写的程序要比用C语言编写的程序小得多。另外,MFC所编写的程序的性能也毫无损失。必要时,你也可以直接调用标准 C 函数,因为 MFC 不修改也不隐藏 Windows 程序的基本结构。
使用 MFC 的最大优点是它为你做了所有最难做的事。MFC 中包含了上成千上万行正确、优化和功能强大的 Windows 代码。你所调用的很多成员函数完成了你自己可能很难完成的工作。从这点上将,MFC 极大地加快了你的程序开发速度。
MFC 是很庞大的。例如,版本4.0中包含了大约200个不同的类。万幸的是,你在典型的程序中不需要使用所有的函数。事实上,你可能只需要使用其中的十多个 MFC 中的不同类就可以建立一个非常漂亮的程序。该层次结构大约可分为几种不同的类型的类:
应用程序框架
图形绘制的绘制对象
文件服务
异常处理
结构 - List、Array 和 Map
Internet 服务
OLE 2
数据库
通用类
我们将集中讨论可视对象。下面的列表给出了部分类:
CObject
CCmdTarget
CWinThread
CWinApp
CWnd
CFrameWnd
CDialog
CView
CStatic
CButton
CListBox
CComboBox
CEdit
CScrollBar
在上面的列表中,有几点需要注意。第一,MFC 中的大部分类都是从基类 CObject 中继承下来的。该类包含有大部分MFC类所通用的数据成员和成员函数。第二,是该列表的简单性。CWinApp 类是在你建立应用程序是要用到的,并且任何程序中都只用一次。CWnd 类汇集了 Windows 中的所有通用特性、对话框和控制。CFrameWnd 类是从 CWnd 继承来的,并实现了标准的框架应用程序。CDialog 可分别处理无模式和有模式两种类型的对话框。 CView 是用于让用户通过窗口来访问文档。最后,Windows 支持六种控制类型:静态文本框、可编辑文本框、按钮、滚动条、列表框和组合框(一种扩展的列表框)。一旦你理解了这些,你也就能更好的理解 MFC 了。MFC 中的其它类实现了其它特性,如内存管理、文档控制等。
为了建立一个MFC应用程序,你既要会直接使用这些类,而通常你需要从这些类中继承新的类。在继承的类中,你可以建立新的成员函数,这能更适用你自己的需要。在简单例子中已经看到了这种继承过程,下面会详细介绍。CHelloApp 和 CHelloWindow 都是从已有的 MFC 类中继承的。
设计一个程序
在讨论代码本身之前,我们需要花些工夫来简单介绍以下 MFC 中程序设计的过程。例如,假如你要编一个程序来向用户显示"Hello World"信息。这当然是很简单的,但仍需要一些考虑。
"hello world"应用程序首先需要在屏幕上建立一个窗口来显示"hello world"。然后需要实际把"hello world"放到窗口上。我们需要三个对象来完成这项任务:
1.一个应用程序对象,用来初始化应用程序并把它挂到 Windows 上。该应用程序对象处理所有的低级事件。 2.一个窗口对象来作为主窗口。 3.一个静态文本对象,用来显示"hello world"。
你用 MFC 所建立的每个程序都会包含头两个对象。第三个对象是针对该应用程序的。每个应用程序都会定义它自己的一组用户界面对象,以显示应用程序的输出和收集应用的输入信息。
一旦你完成了界面的设计,并决定实现该界面所需要的控件,你就需要编写代码来在屏幕上建立这些控件。你还会编写代码来处理用户操作这些控件所产生的信息。在"hello world"应用程序中,只有一个控件。它用来输出"hello world"。复杂的程序可能在其主窗口和对话框中需要上百个控件。
应该注意,在应用程序中有两种不同的方法来建立用户控件。这里所介绍的是用 C++代码方式来建立控件。但是,在比较大的应用程序中,这种方法是不可行的。因此,在通常情况下要使用资源文件的图形编辑器来建立控件。这种方法要方便得多。
理解"hello world"的代码
下面列出了"hello world"程序的代码。
1 //hello.cpp
2 #include <afxwin.h>
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
9 // Create an instance of the application class
10 CHelloApp HelloApp;
11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
该程序由六小部分组成。
首先,该程序包含了头文件 afxwin.h (第 2 行)。该头文件包含有 MFC 中所使用的所有的类型、类、函数和变量。它也包含了其它头文件,如 Windows API 库等。
第 3 至 8 行从 MFC 说明的标准应用程序类 CWinApp 继承出了新的应用程序类 CHelloApp。该新类是为了要重载 CWinApp 中的 InitInstance 成员函数。InitInstance是一个应用程序开始执行时要调用的可重载函数。
在第10行中,说明了应用程序作为全局变量的一个实例。该实例是很重要的,因为它要影响到程序的执行。当应用程序被装入内存并开始执行时,全局变量的建立会执行CWinApp 类的缺省构造函数。全局变量创建完成后首先执行WinMain(),在WinMain()中将调用在18至26行定义的 InitInstance 函数。
即:
CWinApp::CWinApp()
WinMain()
{
theApp.InitInstance();
theApp.Run(){
While (GetMessage())
DispathMessage;
}
}
在第11至17中,CHelloWindow 类是从 MFC 中的 CFrameWnd 类继承来的。CHelloWindow 是作为应用程序在屏幕上的窗口。建立新的类以便实现构造函数、析构函数和数据成员。
第18至26行实现了 InitInstance 函数。该函数产生一个 CHelloWindow 类的实例,因此会执行第27行至41行中类的构造函数。它也会把新窗口放到屏幕上。
第27至41实现了窗口的构造函数。该构造函数实际是建立了窗口,然后在其中建立一个静态文本控制。
要注意的是,在该程序中没有 main 或 WinMain 函数,也没有事件循环。然而它实际上处理了事件。窗口可以最大或最小化、移动窗口等等。所有这些操作都隐藏在主应用程序类 CWinApp 中,并且我们不必为它的事件处理而操心,它都是自动执行、在 MFC 中不可见的。
程序对象
用 MFC 建立的每个应用程序都要包括一个单一从 CWinApp 类继承来的应用程序对象。该对象必须被说明成全局的(第10行),并且在你的程序中只能出现一次。从 CWinApp 类继承的对象主要是处理应用程序的初始化,同时也处理应用程序主事件循环。CWinApp 类有几个数据成员和几个成员函数。在上面的程序中,我们只重载了一个 CWinApp 中的虚拟函数 InitInstance。
应用程序对象的目的是初始化和控制你的程序。因为 Windows 允许同一个应用程序的多个实例在同时执行,因此 MFC 把初始化过程分成两部分并使用两个函数 InitApplication 和 InitInstance 来处理它。此处,我们只使用了一个 InitInstance 函数,因为我们的程序很简单。当每次调用应用程序时都会调用一个新的实例。第3至8行的代码建立了一个称为 CHelloApp 的类,它是从 CWinApp 继承来的。它包含一个新的 InitInstance 函数,是从 CWinApp 中已存在的函数(不做任何事情)重载来的:
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
在重载的 InitInstance 函数内部,第18至26行,程序使用 CHelloApp 的数据成员 m_pMainWnd 来建立并显示窗口:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
InitInstance 函数返回 TRUE 表示初始化已成功的完成。如果返回了FALSE,则表明应用程序会立即终止。在下一节中我们将会看到窗口初始化的详细过程。
当应用程序对象在第10行建立时,它的数据成员(从 CWinApp 继承来的) 会自动初始化。例如,m_pszAppName、m_lpCmdLine 和 m_nCmdShow 都包含有适当的初始化值。你可参见 MFC 的帮助文件来获得更详细的信息。我们将使用这些变量中的一个。
窗口对象
MFC 定义了两个类型的窗口: 1) 框架窗口,它是一个全功能的窗口,可以改变大小、最小化、最大化等等; 2) 对话框窗口,它不能改变大小。框架窗口是典型的主应用程序窗口。
在下面的代码中,从 CFrameWnd 中继承了一个新的类 CHelloWindow:
11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
它包括一个新的构造函数,同时还有一个指向程序中所使用的唯一用户界面控制的数据成员。你多建立的每个应用程序在主窗口中都会有唯一的一组控制。因此,继承类将有一个重载的构造函数以用来建立主窗口所需要的所有控制。典型情况下,该类会包含有一个析构函数以便在窗口关闭时来删除他们。我们这里没有使用析构函数。在第四讲中,我们将会看到继承窗口类也会说明一个消息处理函数来处理这些控制在响应用户事件所产生的消息。
典型地,一个应用程序将有一个主应用程序窗口。因此,CHelloApp 应用程序类定义了一个名为 m_pMainWnd 成员变量来指向主窗口。为了建立该程序的主窗口,InitInstance函数(第18至26行)建立了一个 CHelloWindow 事例,并使用 m_pMainWnd 来指向一个新的窗口。我们的 CHelloWindow 对象是在第22行建立的:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
只建立一个简单的框架窗口是不够的。还要确保窗口能正确地出现在屏幕上。首先,代码必须要调用窗口的 ShowWindow 函数以使窗口出现在屏幕上(第23行)。其次,程序必须要调用 UpdateWindow 函数来确保窗口中的每个控制和输出能正确地出现在屏幕上(第24行)。
ShowWindow 和 UpdateWindow 函数是在哪儿定义的。ChelloWindow继承于CFrameWnd 。但是 CFrameWnd 中并不包含有这些成员函数。CFrameWnd 是从 CWnd 类继承来的。CWnd包含有200多个不同的成员函数,包括ShowWindow 和UpdateWindow。
ShowWindow 只有一个参数,可以设置不同的参数值。我们把它设置成程序中CHelloApp 的数据成员变量 m_nCmdShow (第23行)。m_nCmdShow 变量是用来初始化应用程序启动的窗口显示方式的。例如,用户可能在程序管理器中启动应用程序,并可通过应用程序属性对话框来告知程序管理器应用程序在启动时要保持最小化状态。m_nCmdShow 变量将被设置成 SW_SHOWMINIMIZED,并且应用程序会以图标的形式来启动,也就是说,程序启动后,是一个代表该程序的图标。m_nCmdShow 变量是一种外界与应用程序通讯的方式。
第22行是初始化窗口。它为调用 new 函数分配内存。在这一点上,程序在执行时会调用CHelloWindow的构造函数。该构造函数在每次该类的实例被分配时都要调用。在窗口构造函数的内部,窗口必须建立它自己。它是通过调用 CFrameWnd 的 Create 成员函数来实现的(第31行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
建立函数共传递了四个参数。通过查看 MFC 文档,可以了解不同类型。NULL 参数表示使用缺省的类名。第二个参数为出现在窗口标题栏上的标题。第三个参数为窗口的类型属性。该程序使用了正常的、可覆盖类型的窗口。第四个参数指出窗口应该放在屏幕上的位置和大小,左上角为(0,0), 初始化大小为 200×200个象素。如果使用了 rectDefault,则 Windows 会为你自动放置窗口及大小。
静态文本控制
程序在从 CFrameWnd 类中继承 CHelloWindow 类时(第11至17行)时,说明了一个成员类型 CStatic及其构造函数。
正如在前面所见到的,CHelloWindow 构造函数主要做两件事情。第一是通过调用Create函数(第31行)来建立应用程序的窗口。然后分配和建立属于窗口的控制。在程序中,只使用了一个控制。在 MFC 中建一个对象总要经过两步。第一是为类的实例分配内存,然后是调用构造函数来初始化变量。下一步,调用 Create 函数来实际建立屏幕上的对象。代码使用这两步分配、构造和建立了一个静态文本对象(第36至40行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
CStatic 构造函数是在为其分配内存时调用的,然后就调用了 Create 函数来建立 CStatic 控制的窗口。Create 函数所使用的参数与窗口建立函数所使用的参数是类似的(第31行)。第一个参数指定了控制中所要显示的文本内容。第二个参数指定了类型属性。类型属性在下一讲中将详细介绍。在次我们使用的是子窗口类型(既在别的窗口中显示的窗口),还有它是可见的,还有文本的显示位置是居中的。第三个参数决定了控制的大小和位置。第四参数表示该子窗口的父窗口。已经建立了一个静态控制,它将出现在应用程序窗口上,并显示指定的文本。
第三部分:MFC中控件的样式
控件是用来建立Windows应用程序用户界面的用户界面对象。你所见到的大部分Windows应用程序和对话框只不过是由一些控件所组成的、用来实现程序功能的东西。为了建立有效的应用程序,你必须完全理解在Windows应用程序中应该如何合理的使用控件。有六个基本的控件:CStatic、CButton、CEdit、CList、CComboBox和CScrollBar。另外,Windows 95又增加了增强了的控件。你需要理解的是那个控件能做些什么、你应该如何控制它的外表和行为以及如何让控件能响应用户事件。只要掌握了这些,再加上掌握了菜单和对话框,你就可以建立你所想象的任何Windows应用程序。你可以用程序代码来建立控件,也可以使用资源编辑器通过资源文件来建立。当然,对话框编辑器更方便些,它对于已经基本掌握了控件的情况下特别有用。
下面以CStatic控件为例来说明控件的使用。
最简单的控件是CStatic, 它是用来显示静态文本的。CStatic类没有任何数据成员,它只有少量的成员函数:构造函数、Create函数(用于获取和设置静态控制上的图标)等等。它不响应用户事件。因为它的简单性,所以最好把它作为学习Windows控件的开端。
MFC中的CStatic类是用来显示静态文本信息的。这些信息能够可以作为纯信息(例如,显示在信息对话框中的错误消息), 或作为小的标签等。
CStatic控件还有几种其它的显示格式。你可以通过修改标签的样式来使它表现为矩形、边框或图标等。
CStatic控件总是作为子窗口的形式出现的。典型情况下,其父窗口是应用程序的主窗口或对话框。正如上一讲所介绍的,用两行代码就可以建立一个静态控件:
CStatic *cs;
....
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80, 150, 150),
this);
这两行代码是典型的MFC建立所有控件的代码。调用new来为CStatic类的实例分配内存,然后调用类的构造函数。构造函数是用来完成类所需要的初始化功能的。Create函数建立控件并把它放到屏幕上。
Create函数有五个参数:
lpszText指定了要显示的文本。rect控件文本区域的位置、大小和形状。pParentWnd指明CStatic控件的父窗口。该控件会出现在其父窗口中,且其位置是相对于其父窗口的用户区域而言的。nID整数值,表示该控件的标识符。dwStyle最重要的参数。它控制着控件的外观和行为。
CStatic样式
所有的控件都有各种显示样式。样式是在用Create函数建立控件时传递给它的dwStyle参数所决定的。对CStatic有效的样式简介如下:
从CWnd继承来的样式:
WS_CHILD CStatic所必须的。
WS_VISIBLE 表示该控件对用户应该是可见的。
WS_DISABLED 表示该控件拒绝接受用户事件。
WS_BORDER 控件的文本区域带有边框。
CStatic固有的样式:
SS_BLACKFRAME 该控件区域以矩形边界显示。颜色与窗口框架相同。
SS_BLACKRECT 该控件以填充的矩形显示。颜色与当前的窗口框架相同。
SS_CENTER 文本居中。
SS_GRAYFRAME 控件以矩形边框方式显示。颜色与当前桌面相同。
SS_GRAYRECT 该控件以填充的矩形显示。颜色与当前的桌面相同。
SS_ICON 控件以图标形式显示。文本作为图标在资源文件的名称。rect参数只控制位置。
SS_LEFT 文本居左显示。文字可回绕。
SS_LEFTNOWORDWRAP 文本居左显示。多余的文字被剪裁。
SS_NOPREFIX 表示字符串中的"&"字符不表示为加速前缀。
SS_RIGHT 文本居右显示。文字可回绕。
SS_SIMPLE 只简单的显示一行文本。任何CTLCOLOR信息都被其父窗口忽略。
SS_USERITEM 用户定义项。
SS_WHITEFRAME 控件以矩形边框方式显示。颜色与当前窗口背景颜色相同。
SS_WHITERECT 控件以填充矩形方式显示。颜色与当前窗口背景颜色相同。
这些常数中,"SS"(Static Style)开头的表示只能用于CStatic控件。以"WS"(Window Style)开头的常数表示可适用于所有窗口,它们定义在CWnd对象中。CWnd中还有很多以"WS"样式常数。可以在MFC文档中的CWnd::Create函数中找到它们。上面的四种是只用于CStatic对象的。
CStatic对象至少要带有两个样式:WS_CHILD和WS_VISIBLE。该控件必须作为另一窗口的子窗口来建立。如果不使用WS_VISIBLE,则所建立的控件是看不见的。WS_DISABLED控制着标签对事件的响应,因为CStatic不接收键盘或鼠标事件,所以使用该项是多余的。
所有的其它样式选项都是可选的,它们控制着标签的外观。在CStatic::Create函数中使用这些可以控制CStatic在屏幕上的显示。
对话框:
对话框可分为"模式"和"无模式"两类:模式对话框如Open File对话框,在该对话框被关闭之前,用户无法在同一应用程序(更确切地说,似乎在同一用户接口线程里)的其他地方进行工作;而对于无模式对话框如Word中的Find and Replace对话框,在它仍然保留在屏幕上的同时,用户还可以在应用程序的其他窗口中进行工作。这两类对话框的基类都为CDialog,下面分别就在应用程序中使用模式和无模式对话框作些说明。
模式对话框的使用:
1、利用对话框编辑器创建包含各种空间的对话框资源。
2、利用ClassWizard来创建CDialog的派生类。ClassWizard将会生成对话框代码文件:
CMyDialog::CMyDialog(CWnd * pParent)
:CDialog(CMyDialog::IDD,pParent)
{
}
3、在对话框代码文件中编写应用所需要的函数。
4、编辑视图类,通过
{
CMyDialog myDlg;
myDlg.DoModal();
}
来激活该对话框。
其中DoModal()的处理过程如下:
CDialog::DoModal()
CMyDialog::OnInitDialog()
...其他初始化...
CDialog::OnInitDialog
CWnd::UpdateData(false)
CMyDialog::DoDataExchange()
用户数据输入...
用户单击OK按扭
CMyDialog::OnOk()
...其他的确认处理...
CDialog::OnOK()
CWnd::UpdateData(TRUE)
CMyDialog::DoDataExchange()
CDialog::EndDialog(IDOK)
{注:层次的递进表示函数的调用关系)
无模式对话框的使用:
1、利用对话框编辑器创建包含各种空间的对话框资源。
2、利用ClassWizard来创建CDialog的派生类。ClassWizard将会生成对话框代码文件:
CMyDialog::CMyDialog(CWnd * pParent)
:CDialog(CMyDialog::IDD,pParent)
{
}//该构造函数是创建模式对话框所用;
3、在对话框代码文件中的模式对话框构造函数修改成无模式对话框的构造函数
CMyDialog::CMyDialog(CView * pView)
{
}
及其他需要的函数。
4、在视图类中通过
{
CMyDialog *pDlg=new CMyDialog(this);
myDlg.Create(IDD);
}来激活无模式对话框。
两者的比较(不同点):
1、使用的构造函数:模式对话框-->以资源ID作为参数的构造函数;无模式对话框-->默认构造函数(无参数).
2、建立窗口的函数:模式对话框-->DoModal();无模式对话框-->Create(IDD).
3、删除窗口(即终止)的函数:模式对话框-->EndDialog();无模式对话框-->DestroyWindow().
注:CDialog::OnOK()和OnCancel()的实现是调用EndDialog(),所以无模式对话框中应该重载OnOK()及OnCancel()函数,加入DestroyWindow()以便删除窗口。
4、正确删除对话框对象(注意不同于对话框窗口对象):
模式对话框-->在视图类中DoModal()返回后即可删除对话框对象;无模式对话框-->视图类并不知道对话框何时该删除,故应该通过CMyDialog::PostNcDestroy ( ){delete this;}来删除该对象。该函数在窗口撤消时被应用程序框架调用。
菜单:
菜单栏包含在主框架窗口(MainFrame)中,基类为CMenu.通常有两种应用。应用一:创建主菜单的使用步骤:
1、利用资源编辑器来编辑菜单。可以加入一个分隔符,可以为菜单项添加快捷键和键盘加速键以及在状态栏中显示提示字符串等属性。
2、利用ClassWizard为视图类增加命令消息(COMMAND)控制函数和更新命令UI消息(UPDATE_COMMAND_UI)控制函数。每一菜单项都有一个ID,即命令ID,只有当该ID在0x8000-0xDFFF范围内,才能够发送COMMAND消息。当我们选中某一菜单项时,将会执行相应的命令消息控制函数;每当弹出式菜单第一次显示时,都会调用更新命令用户界面(UI)控制函数。更新命令UI控制函数可以用来对菜单项的显示进行修改,但是注意只适用于弹出式菜单的菜单项,而对长久显示的顶层菜单项则不适用。
MFC中,应用程序框架提供了一个命令消息传递系统。当应用程序框架接受到框架窗口命令时,它将按如下次序来寻找相应的消息控制函数:
SDI应用程序:视图-->文档-->SDI主框架窗口-->应用程序
MDI应用程序:视图-->文档-->MDI子框架窗口-->MDI主框架窗口-->应用程序
所以,我们几乎在程序中的任何地方对消息进行控制。如果某菜单项在当前命令传递路径中无法找到相应的命令消息控制函数,则此时应用程序框架就可以禁用该菜单项。但如果我们将CFrameWnd的数据成员m_bAutoMenuEnable置成了FALSE,那么我们可以禁用这一特性。
3、当主框架窗口的Create()或LoadFrame()被调用时,菜单的资源都被直接连到了框架窗口中,因此,我们不必另外专门创建CMenu对象。我们可以通过CWnd的GetMenu()来返回一个CMenu对象的指针,通过该指针来对菜单对象进行访问和更新。
我们也可以动态创建菜单,方法如下:
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU)//菜单的ID;将菜单从资源中装入;
menu.SetMenu();//将菜单连到框架窗口中;
......
menu.Detach();//将menu对象与菜单独立出来,这样menu对象生存期
//结束后该菜单依然在内存中。
}
应用二:创建浮动的弹出式菜单
1、利用资源编辑器来编辑菜单。
2、利用ClassWizard编辑菜单项的命令控制函数。
3、在视图类中编辑WM_CONTEXTMENU消息控制函数(当鼠标右键弹起时发送此消息)。
void CMyView::OnContextMenu(CWnd *pWnd,CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU);
menu.GetSubMenu(0)->TrackPopupMenu
(TPM_LEFTALLGN|TPM_RIGHTBUTTON,point.x,point.y,this);
//显示浮动弹出菜单;
}
MFC中的文档和视图的关系
文档对象是用来保存程序运行过程中所使用的数据的,视图对象是用来显示数据的,并且允许用户对数据进行编辑,编辑的结果应该再存储在文档对象中。
1.应用程序类:
WINDOWS应用程序的初始化、运行和结束都是由应用程序类完成的。应用程序类构成了应用程序执行的主线程。每个MFC程序有且仅有一个从CwinApp类派生的类对象。如CsampleApp theApp。
2.文档模板:
应用程序对象使用文档模板存放与应用程序文档、视图和边框窗口有关的信息。应用程序使用文档模板创建文档类对象来存放文档,创建边框窗口类对象来画出视图窗口,创建视图类对象来显示文档,多个视图类对象可以共享一个边框窗口类对象。文档类,视图类和边框窗口类之间的关系由文档模板管理。
在应用程序类的InitInstance函数中创建并注册文档模板:
BOOL CsampleApp::InitInstance()
{
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CsampleDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CsampleView));
AddDocTemplate(pDocTemplate);
return TRUE;
}
传给CsingleDocTemplate构造函数的第一个参数是资源符号IDR_MAINFRAME,该资源包含边框的图标和选单等,传给CsingleDocTemplate构造函数的其余三个参数是对RUNTIME_CLASS宏的调用,每次调用都返回指定类的信息,从而使应用程序可以动态的创建该类的一个实例,即返回一个指向参数类的对象的指针。AddDocTemplate注册文档模板对象,将文档模板存放在应用程序对象中。
3.文档和视图的关系:
在创建MFC的应用程序的框架时,AppWizard为应用程序创建文档和视图类的基本结构。由AppWizard创建的文档类是从CDocument 类派生的,视图类是从CView类派生的。CDocument类为应用程序定义的文档类提供基本功能,而CView类为应用程序定义的视图类提供基本功能。视图是与文档联系在一起的,在文档与用户之间起中介作用。视图在屏幕上显示文档数据并把用户输入转换成对文档的操作。MFC中文档与视图的这种实现方法把数据与数据的显示以及用户对数据所作的操作分离开来。数据的所有更改都通过文档类管理,而视图则调用这个接口来访问和更新数据。视图类对象可以通过CsampleDoc* CsampleView::GetDocument() 函数得到其所属的文档对象的指针,以对文档的数据进行操作。
文档用于管理应用程序的数据,使用方法如下:
(1) 从CDocument类派生出各种不同类型的文档类,每种类型对应一种文档。
(2) 添加用于存储文档数据的成员变量。
(3) 根据需要重载CDocument类的其他成员函数。
视图以图形方式显示文档数据、接受用户输入并将其解释成对文档的操作:
(1) 处理视图类的OnDraw成员函数,该函数负责提供文档数据。调用函数GetDocument()得到文档数据。
(2) 映射并实现消息处理函数,以便解释用户的输入。
(3) 根据需要重载CView类的其他成员函数。如:重载OnInitialUpdate,以便进行必要的视图初始化工作,重载OnUpdate,以便在视图即将重新绘制前进行必要的处理。
应用程序可以是单文档或多文档,每个文档可以有单个或多个视图,但一个视图只隶属于一个文档。最简单的应用是单文档单视图的,MFC也支持单文档多视图。每个文档对象保存有该文档的视图列表,并提供用于添加和删除视图的成员函数,以及在文档数据发生变化时提供UpdateAllView成员函数来更新所有视图。单文档程序中主边框窗口和文档边框窗口重合,合二为一。多文档程序中主边框窗口中有客户窗口,客户窗口中又包含多个文档边框窗口。
MFC支持以下三种多视图模式:
(1) 同一文档的多个视图对象,每个对象置于独立的文档边框窗口中。
(2) 同一文档边框窗口中有同一文档的多个视图对象。如分割窗口。
(3) 单个文档边框窗口中有不同类的视图对象,多个视图共享单个边框窗口,每个视图从不同的类构造。
4.MFC中各对象之间的关系:
(1) 文档中含有该文档的视图列表和指向创建该文档的文档模板的指针。
(2) 视图中含有指向文档的指针,视图窗口是文档边框窗口的子窗口。
(3) 文档边框窗口含有指向当前活动视图的指针。
(4) 文档模板含有已打开文档的一个列表。
(5) 应用程序对象含有文档模板的一个列表
(6) Windows跟踪所有已打开的窗口并发送消息给这些窗口。
通过调用AfxGetApp,任何对象都可以获得指向应用程序对象的指针。
各对象之间的访问方法:
文档:调用GetFirstViewPosition和GetNextView函数访问文档的视图列表。调用GetDocTemplate函数获取创建该文档的文档模板的指针。
视图:调用GetDocument函数获取指向文档的指针。调用GetParentFrame函数获取指向文档边框窗口的指针。
文档边框窗口:调用GetActiveView函数获取当前视图。调用GetActiveDocument函数获取当前视图的文档。
MDI主边框窗口:调用MDIGetActive函数获取当前活动的MDI子窗口。
板凳
hellchinaren [专家分:0] 发布于 2002-10-01 15:10:00
MFC使用教程
第一部分:MFC简介
Visual C++ 不仅仅是一个编译器。它是一个全面的应用程序开发环境,使用它你充分利用具有面向对象特性的 C++ 来开发出专业级的 Windows 应用程序。为了能充分利用这些特性,你必须理解 C++ 程序设计语言。掌握了C++,你就必须掌握 Microsoft 基本类库 (MFC) 的层次结构。该层次结构包容了 Windows API 中的用户界面部分,并使你能够很容易地以面向对象的方式建立 Windows 应用程序。这种层次结构适用于所有版本的 Windows 并彼此兼容。你用 MFC 所建立的代码是完全可移植的。
简单介绍MFC的基本概念和术语以及事件驱动程序设计方法。输入、编译和运行一个简单的MFC程序。第二部分中将详细解释这些代码。第三部分讨论了MFC控件和如何定制它们。
什么是MFC?
如果你要建立一个 Windows 应用程序,应该如何下手?
好的开端是从设计用户界面开始。首先,你要决定什么样的用户能使用该程序并根据需要来设置相应的用户界面对象。Windows 用户界面有一些标准的控件,如按钮、菜单、滚动条和列表等,这对那些 Windows 用户已经是很熟悉了。 要记住的是,作为程序员必须选择一组控件并决定如何把它们安排到屏幕上。传统上,你需要在纸上做一下用户界面的草图,直到对各元素感到满意为止。这对于一些比较小的项目,以及一些大项目的早期原型阶段是可以的。下一步,是要实现代码。为任何 Windows 平台建立应用程序时,程序员都有两种选择:C 或 C++。 使用 C,程序员是在 Windows 应用程序界面 ( API ) 的水平上编写代码。该界面是由几百个 C 函数所组成,这些函数在Windows API 参考手册中都有介绍。对于Windows NT, API 被称为 "Win32 API",以区别于其用于Windows 3.1的16位 API, Microsoft 也提供了 C++ 库,它位于任何 Windows API 之上,能够使程序员的工作更容易。它就是Microsoft基本类库 (MFC),该库的主要优点是效率高。它减少了大量在建立 Windows 程序时必须编写的代码。同时它还提供了所有一般 C++ 编程的优点,例如继承和封装。MFC 是可移植的,例如,在 Windows 3.1下编写的代码可以很容易地移植到Windows NT 或 Windows 95 上。因此,MFC 很值得推荐的开发Windows 应用程序的方法。在本教程自始至终使用的都是 MFC。当是使用MFC时,你编写的代码是用来建立必要的用户界面控件并定制其外观。同时你还要编写用来响应用户操作这些控件的代码。例如,如果用户单击一个按钮时,你应该有代码来响应。这就是事件驱动代码,它构成了所有应用程序。一旦应用程序正确的响应了所有允许的控制,它的任务也就完成了 。
Visual C++ 应用程序开发程序环境特别适合于使用 MFC (也有其它开发环境使用MFC,如C++ builder也包含了对MFC的支持),所以一起学习 MFC和 Visual C++ 能够增强你的开发程序的能力。
Windows应用程序使用几个标准的控件:
静态文本标签
按钮
列表框
组合框(一种更高级的列表框)
单选按钮 (单选框)
检查按钮 (复选框)
编辑框(单行和多行)
滚动条
你可以通过代码或"资源编辑器"来建立这些控件,在资源编辑器中可以建立对话框和这些控件。在本教程中,我们将使用代码来建立它们。
Windows支持几种类型的应用程序窗口。一个典型的应用程序应该活动在称为"框架窗口"中。一个框架窗口是一个全功能的主窗口,用户可以改变尺寸、最小化、最大化等。Windows也支持两种类型的对话框:模式和无模式对话框。模式对话框一旦出现在屏幕上只有当它退出时,屏幕上该应用程序的其余部分才能响应。无模式对话框出现在屏幕上时,程序的其余部分也可以作出响应,它就象浮动在上面一样。
最简单的 Windows 应用程序是使用单文档界面(SDI),只有一个框架窗口。Windows的钟表、PIF 编辑器、记事本等都是 SDI 应用程序的例子。Windows 也提供了一种称为多文档界面的组织形式,它可用于更复杂的应用程序。MDI 系统允许用户在同一应用程序中同时可以查看多个文档。例如,一个文本编辑器可以允许用户同时打开多个文本文件。使用 MDI时,应用程序有一个主窗口,在主窗口中有一些子窗口,每个子窗口中各自包含有各自的文档。在MDI框架中,主窗口有一个主菜单,它对主框架中最顶端窗口有效。各子窗口都可以缩成图标或展开,MDI主窗口也可以变成桌面上的一个图标。MDI界面可能会给你一种第二桌面的感觉,它对窗口的管理和删除混乱的窗口有很大的帮助。
你所建立的每一个应用程序都会使用它自己的一套控件、菜单结构以及对话框。应用程序界面的好坏取决于你如何选择和组织这些界面对象。Visual C++ 中的资源编辑器可以使你能容易的建立和定制这些界面对象。
事件驱动软件和词汇
所有基于窗口的GUI 都包含相同的基本元素,它们的操作方式都是相同的。在屏幕上,用户所看到的是一组窗口,每个窗口都包含有控件、图标、对象以及一些处理鼠标和键盘的元素。从用户角度来看,各系统的界面对象都是相同的:按钮、滚动条、图标、对话框以及下拉菜单等等。尽管这些界面元素的"外观和感觉"可能有些不同,但这些界面对象的工作方式都是相同的。例如,滚动条对于Windows、Mac和Motif 可能有些不同,但他们的作用完全是一样的。
从程序员的角度来看,这些系统在概念上是相似的,尽管它们可能有很大的不同。为了建立GUI 程序,程序员第一步要把所有需要的用户界面控件都放到窗口上。例如,如果程序员要建立一个从摄氏到华氏的转换的简单程序,则程序员所选择的用户界面对象来完成并在屏幕上把结果显示出来。在这个简单的程序中,程序员可能需要用户在一个可编辑的编辑框中输入温度值,在一个不可编辑的编辑框中显示转换结果,然后让用户可以单击一个标有"退出"的按钮来退出应用程序。
因为是用户来操作应用程序的控制,所以程序必须作出响应。所做的响应依赖于用户使用鼠标或键盘在不同控制上的操作。屏幕上的每个用户界面对象对事件的响应是不同的。例如,如果用户单击退出按钮,则该按钮必须更新屏幕、加亮它自己。然后程序必须响应退出。
Windows 所用的模式也是类似的。在一个典型的应用程序中,你将建立一个主窗口,并且在其中放置了一些用户界面控件。这些控件通常被称为子窗口--它们就象一些在主窗口中的更小更特殊的子窗口。作为程序员,你应该通过函数调用来发送信息操作这些控件、通过把信息发送给你到代码来响应用户的操作。
如果你从未做过事件驱动程序设计,则所有这些对你来说可能是很陌生的。但是,事件驱动程序设计方式是很容易理解的。具体的细节对不同的系统可能有些不同,但是其基本概念是类似的。在一个事件驱动界面中,应用程序会在屏幕上绘制几个界面对象,如按钮、文本区和菜单。应用程序通常通过一段称为事件循环的的代码来响应用户的操作。用户可以使用鼠标或键盘来任意操作屏幕上的对象。例如,用户用鼠标单击一个按钮。用鼠标单击就称为一个事件。事件驱动系统把用户的动作如鼠标单击和键盘操作定义为事件,也把系统操作如更新屏幕定义为事件。
在比较低级的编程方法中,如用C直接编写Windows API应用程序,代码量是非常大的,因为你所要照顾的细节太多了。例如,你用某种类型的结构来接收单击鼠标事件。你的事件循环中的代码会查看结构中不同域,以确定哪个用户界面对象受到了影响,然后会完成相应的操作。当屏幕上有很多对象时,应用程序会变得很大。只是简单地处理哪个对象被单击和对它需要做些什么要花费大量的代码。
幸运的是,你可以在比较高级的方法来进行编程,这就是使用MFC。在MFC中,几乎所有的低级的细节处理都为你代办了。如果你把某一用户界面对象放在屏幕上,你只需要两行代码来建立它。如果用户单击一个按钮,则按钮自己会完成一切必要的操作,从更新屏幕上的外观到调用你程序中的预处理函数。该函数包含有对该按钮作出相应操作的代码。MFC 为你处理所有的细节:你建立按钮并告知它特定的处理函数,则当它被按下时,它就会调用相应的函数。第四部分介绍了怎样使用消息映射来处理事件。
例子:
理解一个典型的 MFC程序的结构和样式的最好方法是输入一段小程序,然后编译和运行它。下面的程序是一段简单的"hello world"程序。这对很多C程序员都是很熟悉了,让我们看一下如何用MFC 方法来实现。如果你是第一次看到这类程序,也许比较难理解。这没关系,我们后面会详细介绍。现在你只要用Visual C++ 环境中建立、编译和运行它就可以了。
//hello.cpp
#include <afxwin.h>
// 说明应用程序类
class CHelloApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// 建立应用程序类的实例
CHelloApp HelloApp;
// 说明主窗口类
class CHelloWindow : public CFrameWnd
{
CStatic* cs;
public:
CHelloWindow();
};
// 每当应用程序首次执行时都要调用的初始化函数
BOOL CHelloApp::InitInstance()
{
m_pMainWnd = new CHelloWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// 窗口类的构造函数
CHelloWindow::CHelloWindow()
{
// 建立窗口本身
Create(NULL,
"Hello World!",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// 建立静态标签
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80,150,150),
this);
}
上面的这段程序如果用C 来实现,得需要几页的代码。这个简单的例子做了三件事。第一,它建立了一个应用程序对象。你所编写的每个MFC 程序都有一个单一的程序对象是处理 MFC 和 Windows 的初始细节的。第二,应用程序建立了一个窗口来作为应用程序的主窗口。最后,在应用程序的窗口中建立了一个静态文本标签,它包含有"hello world"几个字。在第二部分中我们会仔细研究这段程序,以理解其结构。
建立项目和编译代码
为了在 Visual C++ 中编译代码,你必须要建立一个项目。一个项目主要保存着下面三种不同类型的信息:
1.它可以记住建立一个可执行程序所需要的所有源程序代码文件。在这个简单的例子中,文件 HELLO.CPP 是唯一的源文件,但是在一个大型的应用程序中,为了便于管理和维护,你可以会有许多个不同的源文件。项目会维护这些不同文件的列表,并当你要建立下一个新的可执行程序时,在必要时编译它们。
2.它会记住针对你的应用程序所使用的编译器和连接器选项。例如,它会记住把哪个库连接到了执行程序中,你是否预编译了头文件等等。
3.它会记住你想要建立的项目类型: 一个控制台应用程序,或一个窗口应用程序等等。如果你已经对项目文件有所了解,则会很容易明白作为机器产生的项目文件的作用。
第二部分:一个简单的MFC程序
我们将一段一段地来研究提到的 MFC 应用程序,以便能理解它的结构和概念框架。我们将先介绍 MFC,然后在介绍如何用 MFC 来建立应用程序。
MFC简介
MFC 是一个很大的、扩展了的 C++ 类层次结构,它能使开发 Windows 应用程序变得更加容易。MFC 是在整个 Windows 家族中都是兼容的,也就是说,无论是 Windows3.x、Windows95 还是 Windows NT,所使用的 MFC 是兼容的。每当新的 Windows 版本出现时,MFC 也会得到修改以便使旧的编译器和代码能在新的系统中工作。MFC 也回得到扩展,添加新的特性、变得更加容易建立应用程序。
与传统上使用 C 语言直接访问 Windows API相反,使用 MFC 和 C++ 的优点是 MFC 已经包含和压缩了所有标准的"样板文件"代码,这些代码是所有用 C 编写的 Windows 程序所必需的。因此用 MFC 编写的程序要比用C语言编写的程序小得多。另外,MFC所编写的程序的性能也毫无损失。必要时,你也可以直接调用标准 C 函数,因为 MFC 不修改也不隐藏 Windows 程序的基本结构。
使用 MFC 的最大优点是它为你做了所有最难做的事。MFC 中包含了上成千上万行正确、优化和功能强大的 Windows 代码。你所调用的很多成员函数完成了你自己可能很难完成的工作。从这点上将,MFC 极大地加快了你的程序开发速度。
MFC 是很庞大的。例如,版本4.0中包含了大约200个不同的类。万幸的是,你在典型的程序中不需要使用所有的函数。事实上,你可能只需要使用其中的十多个 MFC 中的不同类就可以建立一个非常漂亮的程序。该层次结构大约可分为几种不同的类型的类:
应用程序框架
图形绘制的绘制对象
文件服务
异常处理
结构 - List、Array 和 Map
Internet 服务
OLE 2
数据库
通用类
我们将集中讨论可视对象。下面的列表给出了部分类:
CObject
CCmdTarget
CWinThread
CWinApp
CWnd
CFrameWnd
CDialog
CView
CStatic
CButton
CListBox
CComboBox
CEdit
CScrollBar
在上面的列表中,有几点需要注意。第一,MFC 中的大部分类都是从基类 CObject 中继承下来的。该类包含有大部分MFC类所通用的数据成员和成员函数。第二,是该列表的简单性。CWinApp 类是在你建立应用程序是要用到的,并且任何程序中都只用一次。CWnd 类汇集了 Windows 中的所有通用特性、对话框和控制。CFrameWnd 类是从 CWnd 继承来的,并实现了标准的框架应用程序。CDialog 可分别处理无模式和有模式两种类型的对话框。 CView 是用于让用户通过窗口来访问文档。最后,Windows 支持六种控制类型:静态文本框、可编辑文本框、按钮、滚动条、列表框和组合框(一种扩展的列表框)。一旦你理解了这些,你也就能更好的理解 MFC 了。MFC 中的其它类实现了其它特性,如内存管理、文档控制等。
为了建立一个MFC应用程序,你既要会直接使用这些类,而通常你需要从这些类中继承新的类。在继承的类中,你可以建立新的成员函数,这能更适用你自己的需要。在简单例子中已经看到了这种继承过程,下面会详细介绍。CHelloApp 和 CHelloWindow 都是从已有的 MFC 类中继承的。
设计一个程序
在讨论代码本身之前,我们需要花些工夫来简单介绍以下 MFC 中程序设计的过程。例如,假如你要编一个程序来向用户显示"Hello World"信息。这当然是很简单的,但仍需要一些考虑。
"hello world"应用程序首先需要在屏幕上建立一个窗口来显示"hello world"。然后需要实际把"hello world"放到窗口上。我们需要三个对象来完成这项任务:
1.一个应用程序对象,用来初始化应用程序并把它挂到 Windows 上。该应用程序对象处理所有的低级事件。 2.一个窗口对象来作为主窗口。 3.一个静态文本对象,用来显示"hello world"。
你用 MFC 所建立的每个程序都会包含头两个对象。第三个对象是针对该应用程序的。每个应用程序都会定义它自己的一组用户界面对象,以显示应用程序的输出和收集应用的输入信息。
一旦你完成了界面的设计,并决定实现该界面所需要的控件,你就需要编写代码来在屏幕上建立这些控件。你还会编写代码来处理用户操作这些控件所产生的信息。在"hello world"应用程序中,只有一个控件。它用来输出"hello world"。复杂的程序可能在其主窗口和对话框中需要上百个控件。
应该注意,在应用程序中有两种不同的方法来建立用户控件。这里所介绍的是用 C++代码方式来建立控件。但是,在比较大的应用程序中,这种方法是不可行的。因此,在通常情况下要使用资源文件的图形编辑器来建立控件。这种方法要方便得多。
理解"hello world"的代码
下面列出了"hello world"程序的代码。
1 //hello.cpp
2 #include <afxwin.h>
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
9 // Create an instance of the application class
10 CHelloApp HelloApp;
11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
该程序由六小部分组成。
首先,该程序包含了头文件 afxwin.h (第 2 行)。该头文件包含有 MFC 中所使用的所有的类型、类、函数和变量。它也包含了其它头文件,如 Windows API 库等。
第 3 至 8 行从 MFC 说明的标准应用程序类 CWinApp 继承出了新的应用程序类 CHelloApp。该新类是为了要重载 CWinApp 中的 InitInstance 成员函数。InitInstance是一个应用程序开始执行时要调用的可重载函数。
在第10行中,说明了应用程序作为全局变量的一个实例。该实例是很重要的,因为它要影响到程序的执行。当应用程序被装入内存并开始执行时,全局变量的建立会执行CWinApp 类的缺省构造函数。全局变量创建完成后首先执行WinMain(),在WinMain()中将调用在18至26行定义的 InitInstance 函数。
即:
CWinApp::CWinApp()
WinMain()
{
theApp.InitInstance();
theApp.Run(){
While (GetMessage())
DispathMessage;
}
}
在第11至17中,CHelloWindow 类是从 MFC 中的 CFrameWnd 类继承来的。CHelloWindow 是作为应用程序在屏幕上的窗口。建立新的类以便实现构造函数、析构函数和数据成员。
第18至26行实现了 InitInstance 函数。该函数产生一个 CHelloWindow 类的实例,因此会执行第27行至41行中类的构造函数。它也会把新窗口放到屏幕上。
第27至41实现了窗口的构造函数。该构造函数实际是建立了窗口,然后在其中建立一个静态文本控制。
要注意的是,在该程序中没有 main 或 WinMain 函数,也没有事件循环。然而它实际上处理了事件。窗口可以最大或最小化、移动窗口等等。所有这些操作都隐藏在主应用程序类 CWinApp 中,并且我们不必为它的事件处理而操心,它都是自动执行、在 MFC 中不可见的。
程序对象
用 MFC 建立的每个应用程序都要包括一个单一从 CWinApp 类继承来的应用程序对象。该对象必须被说明成全局的(第10行),并且在你的程序中只能出现一次。从 CWinApp 类继承的对象主要是处理应用程序的初始化,同时也处理应用程序主事件循环。CWinApp 类有几个数据成员和几个成员函数。在上面的程序中,我们只重载了一个 CWinApp 中的虚拟函数 InitInstance。
应用程序对象的目的是初始化和控制你的程序。因为 Windows 允许同一个应用程序的多个实例在同时执行,因此 MFC 把初始化过程分成两部分并使用两个函数 InitApplication 和 InitInstance 来处理它。此处,我们只使用了一个 InitInstance 函数,因为我们的程序很简单。当每次调用应用程序时都会调用一个新的实例。第3至8行的代码建立了一个称为 CHelloApp 的类,它是从 CWinApp 继承来的。它包含一个新的 InitInstance 函数,是从 CWinApp 中已存在的函数(不做任何事情)重载来的:
3 // Declare the application class
4 class CHelloApp : public CWinApp
5 {
6 public:
7 virtual BOOL InitInstance();
8 };
在重载的 InitInstance 函数内部,第18至26行,程序使用 CHelloApp 的数据成员 m_pMainWnd 来建立并显示窗口:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
InitInstance 函数返回 TRUE 表示初始化已成功的完成。如果返回了FALSE,则表明应用程序会立即终止。在下一节中我们将会看到窗口初始化的详细过程。
当应用程序对象在第10行建立时,它的数据成员(从 CWinApp 继承来的) 会自动初始化。例如,m_pszAppName、m_lpCmdLine 和 m_nCmdShow 都包含有适当的初始化值。你可参见 MFC 的帮助文件来获得更详细的信息。我们将使用这些变量中的一个。
窗口对象
MFC 定义了两个类型的窗口: 1) 框架窗口,它是一个全功能的窗口,可以改变大小、最小化、最大化等等; 2) 对话框窗口,它不能改变大小。框架窗口是典型的主应用程序窗口。
在下面的代码中,从 CFrameWnd 中继承了一个新的类 CHelloWindow:
11 // Declare the main window class
12 class CHelloWindow : public CFrameWnd
13 {
14 CStatic* cs;
15 public:
16 CHelloWindow();
17 };
它包括一个新的构造函数,同时还有一个指向程序中所使用的唯一用户界面控制的数据成员。你多建立的每个应用程序在主窗口中都会有唯一的一组控制。因此,继承类将有一个重载的构造函数以用来建立主窗口所需要的所有控制。典型情况下,该类会包含有一个析构函数以便在窗口关闭时来删除他们。我们这里没有使用析构函数。在第四讲中,我们将会看到继承窗口类也会说明一个消息处理函数来处理这些控制在响应用户事件所产生的消息。
典型地,一个应用程序将有一个主应用程序窗口。因此,CHelloApp 应用程序类定义了一个名为 m_pMainWnd 成员变量来指向主窗口。为了建立该程序的主窗口,InitInstance函数(第18至26行)建立了一个 CHelloWindow 事例,并使用 m_pMainWnd 来指向一个新的窗口。我们的 CHelloWindow 对象是在第22行建立的:
18 // The InitInstance function is called each
19 // time the application first executes.
20 BOOL CHelloApp::InitInstance()
21 {
22 m_pMainWnd = new CHelloWindow();
23 m_pMainWnd->ShowWindow(m_nCmdShow);
24 m_pMainWnd->UpdateWindow();
25 return TRUE;
26 }
只建立一个简单的框架窗口是不够的。还要确保窗口能正确地出现在屏幕上。首先,代码必须要调用窗口的 ShowWindow 函数以使窗口出现在屏幕上(第23行)。其次,程序必须要调用 UpdateWindow 函数来确保窗口中的每个控制和输出能正确地出现在屏幕上(第24行)。
ShowWindow 和 UpdateWindow 函数是在哪儿定义的。ChelloWindow继承于CFrameWnd 。但是 CFrameWnd 中并不包含有这些成员函数。CFrameWnd 是从 CWnd 类继承来的。CWnd包含有200多个不同的成员函数,包括ShowWindow 和UpdateWindow。
ShowWindow 只有一个参数,可以设置不同的参数值。我们把它设置成程序中CHelloApp 的数据成员变量 m_nCmdShow (第23行)。m_nCmdShow 变量是用来初始化应用程序启动的窗口显示方式的。例如,用户可能在程序管理器中启动应用程序,并可通过应用程序属性对话框来告知程序管理器应用程序在启动时要保持最小化状态。m_nCmdShow 变量将被设置成 SW_SHOWMINIMIZED,并且应用程序会以图标的形式来启动,也就是说,程序启动后,是一个代表该程序的图标。m_nCmdShow 变量是一种外界与应用程序通讯的方式。
第22行是初始化窗口。它为调用 new 函数分配内存。在这一点上,程序在执行时会调用CHelloWindow的构造函数。该构造函数在每次该类的实例被分配时都要调用。在窗口构造函数的内部,窗口必须建立它自己。它是通过调用 CFrameWnd 的 Create 成员函数来实现的(第31行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
建立函数共传递了四个参数。通过查看 MFC 文档,可以了解不同类型。NULL 参数表示使用缺省的类名。第二个参数为出现在窗口标题栏上的标题。第三个参数为窗口的类型属性。该程序使用了正常的、可覆盖类型的窗口。第四个参数指出窗口应该放在屏幕上的位置和大小,左上角为(0,0), 初始化大小为 200×200个象素。如果使用了 rectDefault,则 Windows 会为你自动放置窗口及大小。
静态文本控制
程序在从 CFrameWnd 类中继承 CHelloWindow 类时(第11至17行)时,说明了一个成员类型 CStatic及其构造函数。
正如在前面所见到的,CHelloWindow 构造函数主要做两件事情。第一是通过调用Create函数(第31行)来建立应用程序的窗口。然后分配和建立属于窗口的控制。在程序中,只使用了一个控制。在 MFC 中建一个对象总要经过两步。第一是为类的实例分配内存,然后是调用构造函数来初始化变量。下一步,调用 Create 函数来实际建立屏幕上的对象。代码使用这两步分配、构造和建立了一个静态文本对象(第36至40行):
27 // The constructor for the window class
28 CHelloWindow::CHelloWindow()
29 {
30 // Create the window itself
31 Create(NULL,
32 "Hello World!",
33 WS_OVERLAPPEDWINDOW,
34 CRect(0,0,200,200));
35 // Create a static label
36 cs = new CStatic();
37 cs->Create("hello world",
38 WS_CHILD|WS_VISIBLE|SS_CENTER,
39 CRect(50,80,150,150),
40 this);
41 }
CStatic 构造函数是在为其分配内存时调用的,然后就调用了 Create 函数来建立 CStatic 控制的窗口。Create 函数所使用的参数与窗口建立函数所使用的参数是类似的(第31行)。第一个参数指定了控制中所要显示的文本内容。第二个参数指定了类型属性。类型属性在下一讲中将详细介绍。在次我们使用的是子窗口类型(既在别的窗口中显示的窗口),还有它是可见的,还有文本的显示位置是居中的。第三个参数决定了控制的大小和位置。第四参数表示该子窗口的父窗口。已经建立了一个静态控制,它将出现在应用程序窗口上,并显示指定的文本。
第三部分:MFC中控件的样式
控件是用来建立Windows应用程序用户界面的用户界面对象。你所见到的大部分Windows应用程序和对话框只不过是由一些控件所组成的、用来实现程序功能的东西。为了建立有效的应用程序,你必须完全理解在Windows应用程序中应该如何合理的使用控件。有六个基本的控件:CStatic、CButton、CEdit、CList、CComboBox和CScrollBar。另外,Windows 95又增加了增强了的控件。你需要理解的是那个控件能做些什么、你应该如何控制它的外表和行为以及如何让控件能响应用户事件。只要掌握了这些,再加上掌握了菜单和对话框,你就可以建立你所想象的任何Windows应用程序。你可以用程序代码来建立控件,也可以使用资源编辑器通过资源文件来建立。当然,对话框编辑器更方便些,它对于已经基本掌握了控件的情况下特别有用。
下面以CStatic控件为例来说明控件的使用。
最简单的控件是CStatic, 它是用来显示静态文本的。CStatic类没有任何数据成员,它只有少量的成员函数:构造函数、Create函数(用于获取和设置静态控制上的图标)等等。它不响应用户事件。因为它的简单性,所以最好把它作为学习Windows控件的开端。
MFC中的CStatic类是用来显示静态文本信息的。这些信息能够可以作为纯信息(例如,显示在信息对话框中的错误消息), 或作为小的标签等。
CStatic控件还有几种其它的显示格式。你可以通过修改标签的样式来使它表现为矩形、边框或图标等。
CStatic控件总是作为子窗口的形式出现的。典型情况下,其父窗口是应用程序的主窗口或对话框。正如上一讲所介绍的,用两行代码就可以建立一个静态控件:
CStatic *cs;
....
cs = new CStatic();
cs->Create("hello world",
WS_CHILD|WS_VISIBLE|SS_CENTER,
CRect(50,80, 150, 150),
this);
这两行代码是典型的MFC建立所有控件的代码。调用new来为CStatic类的实例分配内存,然后调用类的构造函数。构造函数是用来完成类所需要的初始化功能的。Create函数建立控件并把它放到屏幕上。
Create函数有五个参数:
lpszText指定了要显示的文本。rect控件文本区域的位置、大小和形状。pParentWnd指明CStatic控件的父窗口。该控件会出现在其父窗口中,且其位置是相对于其父窗口的用户区域而言的。nID整数值,表示该控件的标识符。dwStyle最重要的参数。它控制着控件的外观和行为。
CStatic样式
所有的控件都有各种显示样式。样式是在用Create函数建立控件时传递给它的dwStyle参数所决定的。对CStatic有效的样式简介如下:
从CWnd继承来的样式:
WS_CHILD CStatic所必须的。
WS_VISIBLE 表示该控件对用户应该是可见的。
WS_DISABLED 表示该控件拒绝接受用户事件。
WS_BORDER 控件的文本区域带有边框。
CStatic固有的样式:
SS_BLACKFRAME 该控件区域以矩形边界显示。颜色与窗口框架相同。
SS_BLACKRECT 该控件以填充的矩形显示。颜色与当前的窗口框架相同。
SS_CENTER 文本居中。
SS_GRAYFRAME 控件以矩形边框方式显示。颜色与当前桌面相同。
SS_GRAYRECT 该控件以填充的矩形显示。颜色与当前的桌面相同。
SS_ICON 控件以图标形式显示。文本作为图标在资源文件的名称。rect参数只控制位置。
SS_LEFT 文本居左显示。文字可回绕。
SS_LEFTNOWORDWRAP 文本居左显示。多余的文字被剪裁。
SS_NOPREFIX 表示字符串中的"&"字符不表示为加速前缀。
SS_RIGHT 文本居右显示。文字可回绕。
SS_SIMPLE 只简单的显示一行文本。任何CTLCOLOR信息都被其父窗口忽略。
SS_USERITEM 用户定义项。
SS_WHITEFRAME 控件以矩形边框方式显示。颜色与当前窗口背景颜色相同。
SS_WHITERECT 控件以填充矩形方式显示。颜色与当前窗口背景颜色相同。
这些常数中,"SS"(Static Style)开头的表示只能用于CStatic控件。以"WS"(Window Style)开头的常数表示可适用于所有窗口,它们定义在CWnd对象中。CWnd中还有很多以"WS"样式常数。可以在MFC文档中的CWnd::Create函数中找到它们。上面的四种是只用于CStatic对象的。
CStatic对象至少要带有两个样式:WS_CHILD和WS_VISIBLE。该控件必须作为另一窗口的子窗口来建立。如果不使用WS_VISIBLE,则所建立的控件是看不见的。WS_DISABLED控制着标签对事件的响应,因为CStatic不接收键盘或鼠标事件,所以使用该项是多余的。
所有的其它样式选项都是可选的,它们控制着标签的外观。在CStatic::Create函数中使用这些可以控制CStatic在屏幕上的显示。
对话框:
对话框可分为"模式"和"无模式"两类:模式对话框如Open File对话框,在该对话框被关闭之前,用户无法在同一应用程序(更确切地说,似乎在同一用户接口线程里)的其他地方进行工作;而对于无模式对话框如Word中的Find and Replace对话框,在它仍然保留在屏幕上的同时,用户还可以在应用程序的其他窗口中进行工作。这两类对话框的基类都为CDialog,下面分别就在应用程序中使用模式和无模式对话框作些说明。
模式对话框的使用:
1、利用对话框编辑器创建包含各种空间的对话框资源。
2、利用ClassWizard来创建CDialog的派生类。ClassWizard将会生成对话框代码文件:
CMyDialog::CMyDialog(CWnd * pParent)
:CDialog(CMyDialog::IDD,pParent)
{
}
3、在对话框代码文件中编写应用所需要的函数。
4、编辑视图类,通过
{
CMyDialog myDlg;
myDlg.DoModal();
}
来激活该对话框。
其中DoModal()的处理过程如下:
CDialog::DoModal()
CMyDialog::OnInitDialog()
...其他初始化...
CDialog::OnInitDialog
CWnd::UpdateData(false)
CMyDialog::DoDataExchange()
用户数据输入...
用户单击OK按扭
CMyDialog::OnOk()
...其他的确认处理...
CDialog::OnOK()
CWnd::UpdateData(TRUE)
CMyDialog::DoDataExchange()
CDialog::EndDialog(IDOK)
{注:层次的递进表示函数的调用关系)
无模式对话框的使用:
1、利用对话框编辑器创建包含各种空间的对话框资源。
2、利用ClassWizard来创建CDialog的派生类。ClassWizard将会生成对话框代码文件:
CMyDialog::CMyDialog(CWnd * pParent)
:CDialog(CMyDialog::IDD,pParent)
{
}//该构造函数是创建模式对话框所用;
3、在对话框代码文件中的模式对话框构造函数修改成无模式对话框的构造函数
CMyDialog::CMyDialog(CView * pView)
{
}
及其他需要的函数。
4、在视图类中通过
{
CMyDialog *pDlg=new CMyDialog(this);
myDlg.Create(IDD);
}来激活无模式对话框。
两者的比较(不同点):
1、使用的构造函数:模式对话框-->以资源ID作为参数的构造函数;无模式对话框-->默认构造函数(无参数).
2、建立窗口的函数:模式对话框-->DoModal();无模式对话框-->Create(IDD).
3、删除窗口(即终止)的函数:模式对话框-->EndDialog();无模式对话框-->DestroyWindow().
注:CDialog::OnOK()和OnCancel()的实现是调用EndDialog(),所以无模式对话框中应该重载OnOK()及OnCancel()函数,加入DestroyWindow()以便删除窗口。
4、正确删除对话框对象(注意不同于对话框窗口对象):
模式对话框-->在视图类中DoModal()返回后即可删除对话框对象;无模式对话框-->视图类并不知道对话框何时该删除,故应该通过CMyDialog::PostNcDestroy ( ){delete this;}来删除该对象。该函数在窗口撤消时被应用程序框架调用。
菜单:
菜单栏包含在主框架窗口(MainFrame)中,基类为CMenu.通常有两种应用。应用一:创建主菜单的使用步骤:
1、利用资源编辑器来编辑菜单。可以加入一个分隔符,可以为菜单项添加快捷键和键盘加速键以及在状态栏中显示提示字符串等属性。
2、利用ClassWizard为视图类增加命令消息(COMMAND)控制函数和更新命令UI消息(UPDATE_COMMAND_UI)控制函数。每一菜单项都有一个ID,即命令ID,只有当该ID在0x8000-0xDFFF范围内,才能够发送COMMAND消息。当我们选中某一菜单项时,将会执行相应的命令消息控制函数;每当弹出式菜单第一次显示时,都会调用更新命令用户界面(UI)控制函数。更新命令UI控制函数可以用来对菜单项的显示进行修改,但是注意只适用于弹出式菜单的菜单项,而对长久显示的顶层菜单项则不适用。
MFC中,应用程序框架提供了一个命令消息传递系统。当应用程序框架接受到框架窗口命令时,它将按如下次序来寻找相应的消息控制函数:
SDI应用程序:视图-->文档-->SDI主框架窗口-->应用程序
MDI应用程序:视图-->文档-->MDI子框架窗口-->MDI主框架窗口-->应用程序
所以,我们几乎在程序中的任何地方对消息进行控制。如果某菜单项在当前命令传递路径中无法找到相应的命令消息控制函数,则此时应用程序框架就可以禁用该菜单项。但如果我们将CFrameWnd的数据成员m_bAutoMenuEnable置成了FALSE,那么我们可以禁用这一特性。
3、当主框架窗口的Create()或LoadFrame()被调用时,菜单的资源都被直接连到了框架窗口中,因此,我们不必另外专门创建CMenu对象。我们可以通过CWnd的GetMenu()来返回一个CMenu对象的指针,通过该指针来对菜单对象进行访问和更新。
我们也可以动态创建菜单,方法如下:
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU)//菜单的ID;将菜单从资源中装入;
menu.SetMenu();//将菜单连到框架窗口中;
......
menu.Detach();//将menu对象与菜单独立出来,这样menu对象生存期
//结束后该菜单依然在内存中。
}
应用二:创建浮动的弹出式菜单
1、利用资源编辑器来编辑菜单。
2、利用ClassWizard编辑菜单项的命令控制函数。
3、在视图类中编辑WM_CONTEXTMENU消息控制函数(当鼠标右键弹起时发送此消息)。
void CMyView::OnContextMenu(CWnd *pWnd,CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MYMENU);
menu.GetSubMenu(0)->TrackPopupMenu
(TPM_LEFTALLGN|TPM_RIGHTBUTTON,point.x,point.y,this);
//显示浮动弹出菜单;
}
MFC中的文档和视图的关系
文档对象是用来保存程序运行过程中所使用的数据的,视图对象是用来显示数据的,并且允许用户对数据进行编辑,编辑的结果应该再存储在文档对象中。
1.应用程序类:
WINDOWS应用程序的初始化、运行和结束都是由应用程序类完成的。应用程序类构成了应用程序执行的主线程。每个MFC程序有且仅有一个从CwinApp类派生的类对象。如CsampleApp theApp。
2.文档模板:
应用程序对象使用文档模板存放与应用程序文档、视图和边框窗口有关的信息。应用程序使用文档模板创建文档类对象来存放文档,创建边框窗口类对象来画出视图窗口,创建视图类对象来显示文档,多个视图类对象可以共享一个边框窗口类对象。文档类,视图类和边框窗口类之间的关系由文档模板管理。
在应用程序类的InitInstance函数中创建并注册文档模板:
BOOL CsampleApp::InitInstance()
{
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CsampleDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CsampleView));
AddDocTemplate(pDocTemplate);
return TRUE;
}
传给CsingleDocTemplate构造函数的第一个参数是资源符号IDR_MAINFRAME,该资源包含边框的图标和选单等,传给CsingleDocTemplate构造函数的其余三个参数是对RUNTIME_CLASS宏的调用,每次调用都返回指定类的信息,从而使应用程序可以动态的创建该类的一个实例,即返回一个指向参数类的对象的指针。AddDocTemplate注册文档模板对象,将文档模板存放在应用程序对象中。
3.文档和视图的关系:
在创建MFC的应用程序的框架时,AppWizard为应用程序创建文档和视图类的基本结构。由AppWizard创建的文档类是从CDocument 类派生的,视图类是从CView类派生的。CDocument类为应用程序定义的文档类提供基本功能,而CView类为应用程序定义的视图类提供基本功能。视图是与文档联系在一起的,在文档与用户之间起中介作用。视图在屏幕上显示文档数据并把用户输入转换成对文档的操作。MFC中文档与视图的这种实现方法把数据与数据的显示以及用户对数据所作的操作分离开来。数据的所有更改都通过文档类管理,而视图则调用这个接口来访问和更新数据。视图类对象可以通过CsampleDoc* CsampleView::GetDocument() 函数得到其所属的文档对象的指针,以对文档的数据进行操作。
文档用于管理应用程序的数据,使用方法如下:
(1) 从CDocument类派生出各种不同类型的文档类,每种类型对应一种文档。
(2) 添加用于存储文档数据的成员变量。
(3) 根据需要重载CDocument类的其他成员函数。
视图以图形方式显示文档数据、接受用户输入并将其解释成对文档的操作:
(1) 处理视图类的OnDraw成员函数,该函数负责提供文档数据。调用函数GetDocument()得到文档数据。
(2) 映射并实现消息处理函数,以便解释用户的输入。
(3) 根据需要重载CView类的其他成员函数。如:重载OnInitialUpdate,以便进行必要的视图初始化工作,重载OnUpdate,以便在视图即将重新绘制前进行必要的处理。
应用程序可以是单文档或多文档,每个文档可以有单个或多个视图,但一个视图只隶属于一个文档。最简单的应用是单文档单视图的,MFC也支持单文档多视图。每个文档对象保存有该文档的视图列表,并提供用于添加和删除视图的成员函数,以及在文档数据发生变化时提供UpdateAllView成员函数来更新所有视图。单文档程序中主边框窗口和文档边框窗口重合,合二为一。多文档程序中主边框窗口中有客户窗口,客户窗口中又包含多个文档边框窗口。
MFC支持以下三种多视图模式:
(1) 同一文档的多个视图对象,每个对象置于独立的文档边框窗口中。
(2) 同一文档边框窗口中有同一文档的多个视图对象。如分割窗口。
(3) 单个文档边框窗口中有不同类的视图对象,多个视图共享单个边框窗口,每个视图从不同的类构造。
4.MFC中各对象之间的关系:
(1) 文档中含有该文档的视图列表和指向创建该文档的文档模板的指针。
(2) 视图中含有指向文档的指针,视图窗口是文档边框窗口的子窗口。
(3) 文档边框窗口含有指向当前活动视图的指针。
(4) 文档模板含有已打开文档的一个列表。
(5) 应用程序对象含有文档模板的一个列表
(6) Windows跟踪所有已打开的窗口并发送消息给这些窗口。
通过调用AfxGetApp,任何对象都可以获得指向应用程序对象的指针。
各对象之间的访问方法:
文档:调用GetFirstViewPosition和GetNextView函数访问文档的视图列表。调用GetDocTemplate函数获取创建该文档的文档模板的指针。
视图:调用GetDocument函数获取指向文档的指针。调用GetParentFrame函数获取指向文档边框窗口的指针。
文档边框窗口:调用GetActiveView函数获取当前视图。调用GetActiveDocument函数获取当前视图的文档。
MDI主边框窗口:调用MDIGetActive函数获取当前活动的MDI子窗口。
3 楼
jackmacc [专家分:90] 发布于 2004-03-07 10:18:00
抄书
4 楼
银河使者 [专家分:0] 发布于 2005-07-16 07:18:00
哈哈
有人不服气了啊,人家抄书有怎么样啊,至少解决了楼主的问题,而你那!!!
你有什么个更简洁,准确说明来帮助楼主吗???[em18]
5 楼
Runking [专家分:370] 发布于 2005-08-25 22:48:00
mfc是microsoft foundationg class library的简称,就是微软基础类库,哈哈,简便吧~~
6 楼
bossyu [专家分:0] 发布于 2006-06-03 18:25:00
哈哈,我也会了
7 楼
凡尘 [专家分:9680] 发布于 2006-06-04 09:53:00
好长,好贴,学习
[img]http://www.5i51.ik8.com/images/din.gif[/img]
8 楼
fanfanwing [专家分:0] 发布于 2006-09-07 04:19:00
不错,学习
9 楼
aaronwang [专家分:16870] 发布于 2006-09-07 08:40:00
这么长,郁闷.
至于抄书嘛......不做评价.
比较同意五楼的回答.没什么多余的答案.合乎楼主问题的要求.评分:80.
10 楼
dejollia [专家分:570] 发布于 2006-09-08 16:19:00
我不赞成抄书的做法,我倒是建议可以列出一个书目!
我来回复