MFC基于对话框程序,顺序分析

如果是6.0的朋友则首先在菜单上选择新建,在工程(Project)选项卡中选中MFC AppWizard,将工程名(Project name)中起名为Dialog,按确定(OK)。在向导第一步中选择基于对话框(Dialog based),直接按完成(Finish)就可以了。 
如果是.net的朋友则在菜单上选择新建->项目,在项目类型中选择Visual C++项目,在模板中选择MFC应用程序,在名称中输入Dialog,按确定。在应用程序类型中选择基于对话框,后按完成。 
于是一个基于对话框程序就做好了。第一次使用MFC的朋友,一定会为之喳舌。自己从零开始编程许久了,也许还不习惯别人为咱们生成代码吧。第一映象就是,这就是我的同学给我的回答。没关系,我们可以一点一点来看和理解VC给我们生成的代码。毕竟,它为我们节省了很多时间来打WindowSDK框架代码。 

请打开类视图(ClassView),如果无误的话,我们可以看到三个类。分别是CAboutDlg, CDialogApp, CDialogDlg这三个类。其中,CDialogApp是最重要的一个类。双击CDialogApp,打开其定义体。我们会看到它是这么定义的:

class CDialogApp :public CWinApp我们可以看到这个类是派生于CWinApp的。在MFC编程中,这种情况很多见,继承类库类来添加自己需要的功能,然后再去使用。在MFC应用程序中,CWinApp就是这样使用的。查一查类库关于CWinApp的描述,是这样的:

MFC
中的主应用程序类封装用于 Windows 操作系统的应用程序的初始化、运行和终止。基于框架生成的应用程序必须有且仅有一个从 CWinApp 派生的类的对象。在创建窗口之前先构造该对象。 

CWinApp
是从 CWinThread 派生的,后者表示可能具有一个或多个线程的应用程序的主执行线程。在最新版本的 MFC 中,InitInstanceRunExitInstance OnIdle 成员函数实际位于 CWinThread 类中。此处将这些函数作为 CWinApp 成员来探讨,因为探讨所关心的是对象作为应用程序对象而不是主线程的角色。 

与用于 Windows 操作系统的任何程序一样,框架应用程序也具有 WinMain 函数。但在框架应用程序中不必编写 WinMain。它由类库提供,并在应用程序启动时调用。WinMain 执行注册窗口类等标准服务。然后它调用应用程序对象的成员函数来初始化和运行应用程序。(可通过重写由 WinMain 调用的 CWinApp 成员函数来自定义 WinMain。) 

为初始化应用程序,WinMain 调用应用程序对象的InitApplication InitInstance 成员函数。为运行应用程序的消息循环,WinMain 调用Run 成员函数。在终止时,WinMain 调用应用程序对象的ExitInstance 成员函数。

上面这段里指的框架应用程序,包括了我们这种对话框应用程序。如MSDN所说,MFC类库已经为我们提供了WinMain函数,而不必我们添加。这就是为什么在MFC程序看不见主函数的原故。请看这句话基于框架生成的应用程序必须有且仅有一个从 CWinApp 派生的类的对象。在创建窗口之前先构造该对象。打开类视图的全局(Glotbals),会发现有一个theApp全局变量(或对象,我总觉得变量与对象可以归为一类,应该有一个统一的名称来讲)。双击它,就可以看到CDialogApp theApp这样的定义。因为全局变量和对象在程序中是最先被创建的,于是保证了在创建窗口之前构造一个CWinApp对象(因为CDialogApp生于CWinApp,所以theApp也是一个CWinApp对象)。这个全局对象是非常有用,因为CWinApp本身集成了所有的程序资源 WinAPI,我们可以使用它来取得程序的资源(如图标,图像,预定义字符串等等)。一般要取得此全局对象,不直接使用theApp,而是调::AfxGetApp()来取得这个全局对象的指针。

MFC
默认的主函数,会先调用theApp对象的InitApplicationInitInstance成员函数,来进行程序的初始化,在程序中一般只重写InitInstance函数。然后,建立一个消息循环,不同的是在循环不停地调用theAppRun成员函数。当收到WM_QUIT后,退出while循环。最后,执行theAppExitInstance成员函数,从而结束整个应用程序
让我们在类视图(Class View)中展开CDialogApp(点击那个+符号),我们可以看到CDialogApp重写了InitInstance()函数。它用于对应用程序主线程进行初始化。双击视图中的InitInstance()来查看此函数的定义。我这里的函数定义如下:
000:BOOL CDialogApp::InitInstance()

001:{
002: // 如果一个运行在 Windows XP 上的应用程序代码指定要
003: //
使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
004: //
则需要 InitCommonControls()。否则,将无法创建窗口。
005: InitCommonControls();//
使你的应用程序成为ActiveX控件包容器
006:

007: CWinApp::InitInstance(); //调用父类的InitInstance来进行默认的初始化
008:

009: AfxEnableControlContainer();
010:
011:
012: CDialogDlg dlg; //建立一个对话框对象,CDialogDlg是我们自定义的对话框类
013: m_pMainWnd = &dlg; //
将本线程(即程序主线程)的主窗口设置为这个对话框
014: INT_PTR nResponse = dlg.DoModal(); //
有模式地显示这个对话框,直到对话框关闭
015: if (nResponse == IDOK) //
如果对话框是用确定来关闭的,则
016: {

017: // TODO:在此放置处理用确定来关闭
018: //
对话框的代码
019: }

020: else if (nResponse == IDCANCEL) //如果对话框是用取消来关闭的,则
021: {

022: // TODO:在此放置处理用取消来关闭
023: //
对话框的代码
024: }

025:
026: // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
027: //
而不是启动应用程序的消息泵。
028: return FALSE;

029:}因为InitInstance()函数的结束返回值是false,应用程序将会立即退出。也就是只显示对话框,当对话框关闭后,程序就会结束了。这时候的InitInstance函数就有点主函数的味道了。 
下面,我们再来看看CDialogDlg类的定义,它是派生于CDialog的。它重写了以下函数CDialogDlg(CWnd* pParent = NULL); 自定义的构造函数
virtual BOOL OnInitDialog();
对话框初始化消息操作函数
afx_msg void OnSysCommand(UINT nID, LPARAMlParam);
系统菜单消息响应函数
afx_msg void OnPaint();
对话框重绘响应函数
afx_msg HCURSOR OnQueryDragIcon();
最小化图标询问响应函数另外,要注意的是在CDialogDlg类的定义体中有这么一个枚举的定义:enum { IDD = IDD_DIALOG_DIALOG };

它表明这个CDialogDlg类使用的对话框模板是IDD_DIALOG_DIALOG
CDialogDlg
派生层次如下

CDialogDlg=>CDialog=>CWnd=>CCmdTarget=>CObject
先来看看构造函数:CDialogDlg::CDialogDlg(CWnd*pParent /*=NULL*/)
: CDialog(CDialogDlg::IDD/*
这个IDD就是那个枚举的值*/, pParent)
{

m_hIcon =AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}这个函数中首先,调用父类CDialog的构造函数来完成默认构造操作。其次,它使用AfxGetApp函数取得全局CWinApp对象theApp的指针,并使用它的LoadIcon函数来取得程序中IDR_MAINFRAME图标资源,并赋给成员变量m_hIcon。这个图标可以在资源视图的ICON 中可以的查到和设定。 
CDialogDlg的实现文件CDialogDlg.cpp中,可以找到如下一段语句BEGIN_MESSAGE_MAP(CDialogDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这是一段消息映射宏定义段表示这个对话框类可以响应WM_SYSCOMMAND ,WM_PAINT,WM_QUERYDRAGICON消息。它们的响应函数,系统默认分别为 OnSysCommand,OnPaint,OnQueryDragIcon。这段的意思是说,如果CDialogDlg类的对话框接收到WM_SYSCOMMAND消息,就会调用OnSysCommand其它消息以此为例。不过,这些响应段一般是用不着我们自己手动添写的,是由系统来管理的。你如果要分析一个MFC程序代码,这一块是一个很好的切入点,可以清楚的看到这个程序到底都可以响应什么消息,都有些什么功能。以上这些宏都可以在 MSDN中查到。 
下面,我们来一个对于对话框非常重要的函数OnInitDialog(),顾名思义这是一个对话框的初始化函数。在对话框创建之后,第一次显示之前调用。BOOLCDialogDlg::OnInitDialog()
{

CDialog::OnInitDialog(); //执行父类默认的初始化对话框操作
// IDM_ABOUTBOX
必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) ==IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);
// \“关于...\”菜单项添加到系统菜单中。
CMenu* pSysMenu = GetSystemMenu(FALSE); //
取得此对话框系统菜单的CMenu对象指针,并赋给pSysMenu;
if (pSysMenu != NULL) //
如果不为空,则
{

CString strAboutMenu; //声明一个字符串对象
strAboutMenu.LoadString(IDS_ABOUTBOX); //
取得资源IDS_ABOUTBOX预定义字符串,可以
//
在资源视图中的String Table查到和设定这个预定义字符串
if (!strAboutMenu.IsEmpty()) //
如果不为空,则
{

pSysMenu->AppendMenu(MF_SEPARATOR); //向菜单添加一个分隔符
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,strAboutMenu);
 
//向菜单添加这个字符串,并将消息ID设为IDM_ABOUTBOX
}

}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
//
执行此操作
SetIcon(m_hIcon, TRUE); //
设置大图标
SetIcon(m_hIcon, FALSE); //
设置小图标
// TODO
:在此添加额外的初始化代码
return TRUE; //
除非设置了控件的焦点,否则应该返回 TRUE
}
以上,就是这个基于对话框的MFC应用程序的基础代码。现在可以直接编译运行,来查看效果。下面,我将在这些代码的基础上来添加功能,来实现一个复制文件的程序。首先,我要在资源视图的Dialog中,修改IDD_DIALOG_DIALOG模板: 我首先将对话框模板上面的所有按钮和静态文本全部删掉,添加两个文本框和四个按钮。如果要修改控件的ID值,则要右击控件,点选属性,在ID框中输入任意ID字符串即可。基本布局如下: 

:粗体字代表该控件的ID值。 

如果想向CDialogDlg类添加按钮事件,有两种简单的方法。第一种在模板设计中,双击按钮,按确定后,即添加该按钮单击事件。另一种方法是使用向导(.NET中是在CDialog类的属性对话框中的事件栏中添加),首先在视图(View)菜单中选择类向导(ClassWizard),弹出类向导对话框,在类名(ClassName)下拉框中选择我们的要添加事件的类CDialogDlg。对象ID(ObjectID)列表框中选择控件的ID,在消息(Messages)列表框中选择要添加的事件,按添加函数钮(Add Function)即可

将四个按钮分别添加单击事件,系统会为我们自动命名成员函数。如果无误的话,分别是OnBnClickedCancel();OnBnClickedCopy();OnBnClickedSrbrowse();OnBnClickedTrbrowse();因为我用的是.NET, 可能会与6.0生成的函数名略有不同。在添加完事件后,你最好去看看上面所提到过的消息映射宏有什么变化,是否能够读懂它们。
首先在OnBnClickedCancel()函数中添加这么一行语句:this->EndDialog(IDCANCEL);这行语句的作用是关闭当前的对话框,并以IDCANCEL返回,表明用户是用取消来关闭对话框的。这是CDialog类的一个方法。我们期望如果点击了取消按钮,则关闭当前的对话框。
我们再来处理一下浏览按钮的功能。我期望可以弹出一个选择文件的对话框,来选择源文件和目标文件,并把文件名显示在文本框里。这个文件对话框刚好在MFC 类库有所定义,我们可以直接拿来使用。首先,我们必须在CDialogDlg类的实现文件CDialogDlg.cpp的头几行添加一个含包头文件#include然后,在源文件浏览按钮(ID_SRBROWSE)的响应函数OnBnClickedSrbrowse里添加如下语句:CFileDialog Open(true/*如果为真则对话框为打开对话框,为否则为保存对话框*/, 
"" /*
默认后缀名*/, 
"" /*
默认文件名*/, 
0 /*
对话框风格*/, 
"All File|*.*|",
 
this /*父窗口指针*/);

CString strFilePath;

if (Open.DoModal() == IDOK) //有模式地显示对话框,如果返回确定则代表有文件选择,则
{

strFilePath = Open.GetPathName(); //取得文件路径字符串
SetDlgItemText (IDC_SOURCE, strFilePath); //
IDIDC_SOURCE的控件的文本设为该字符串
}
要说明的,CStringMFC的字符串类,在形式上可以当成字符数组。而且还可以像VB的字符串一样使用,直接进行字符串赋值。
还有就是SetDlgItemText,这是CWnd类的一个方法,功能是将改变当前窗口的某控件的文本。这个控件可以是按钮、文本框、静态文本、下拉列表框等等。其第一个参数是该控件的ID,第二个参数是以0结尾的字符串。
以这个函数类推,可以将目标浏览按钮的功能代码写成如下:CFileDialog Save(false/**/,
"" /*
默认后缀名*/, 
"" /*
默认文件名*/,
0 /*
对话框风格*/, 
"All File|*.*|",
 
this /*父窗口指针*/);
CString strFilePath;

if (Save.DoModal() == IDOK)
{
strFilePath = Save.GetPathName();
SetDlgItemText (IDC_TARGET, strFilePath);
}最后,我们再来完成复制按钮的功能。在单击事件响应函数OnBnClickedTrbrowse中添加如下代码:CString strSource,strTarget;
GetDlgItemText (IDC_SOURCE, strSource); //
取得ID名为IDC_SOURCE控件的文本
GetDlgItemText (IDC_TARGET, strTarget); //
取得ID名为IDC_TARGET控件的文本
if (CopyFile (strSource, strTarget, false)) //
复制文件,如果返回为真表示成功,则
{

MessageBox ("复制成功!", "报告", MB_OK); //弹出一个确定框
}
这里要解释的是GetDlgItemText,它也是CWnd的一个方法,是SetDlgItemText的反过程,用于取得窗口上某个控件的文本。 CopyFileWinAPI,它用于进行文件的复制,第一个参数是表示源文件名的字符串,第二个参数是表示目标文件名的字符串。如果成功的话则返回真。CWnd::MessageBox函数用于显示一个消息框,第一个参数是消息文本,第二个参数是标题文本,第三个参数是消息框种类,这里是MB_OK 确定框,还可以是MB_YESNO是否框等等,以上这些可以在MSDN中查到。 
这样,一个简单的基于对话框MFC小程序就做好了。不难吧?也相信诸位看官,已经对MFC的编程方法有一些了解了吧。
如果你想MFC编程变得更得心应手,非常非常建议你经常性的去查阅Visual Studio附带的MSDN,并且能够掌握查找MSDN的技巧,那样会使你的工作变得事半功倍。
下一个部分嘛,我想讲一讲动态链接库的编写,希望能够喜欢。 
那么祝大家愉快吧!

你可能感兴趣的:(VC)