MFC对话框框架详解

MFC对话框类允许程序员更多的发挥自己的想象,不必拘泥于MFC AppWizard生成代码。
我们从最开始讲起,使用VC++工具的MFC AppWizard可以帮我们生成一个基于对话框的MFC应用程序。其中包含了三大内容:头文件、源文件、资源文件。

(以下假设工程名为“MyDialog”)

头文件中包含MyDialog.h,MyDialogDlg.h,Resource.h,stdafx.h,targetver.h。

源文件中包含MyDialog.cpp,MyDialogDlg.cpp,stdafx.cpp。

targetver.h头文件定义了版本宏,即宏定义要求的最低平台,
stdafx.h以及stdafx.cpp文件用于实现预编译。由于使用VC++生成的文件一般都比较多,但是一些文件比较稳定(afxwin.h、afxext.h等),我们可以确定它们在建立项目后一般不会被修改,所以为了提高编译速度,VC++中提出了预编译头文件,VC++默认使用stdafx.h文件包含那些比较稳定文件的头文件,然后结合stdafx.cpp文件在第一次编译项目的时候生成.PCH文件。
Resource.h文件中定义了资源ID,使用宏定义,使得程序中使用的是便于理解的标识符ID,而不是生涩难于理解的数字。
MyDialog.h以及MyDialog.cpp是关于应用程序类的定义和实现。观察MyDialog.h文件的代码。首先是一个宏“#pragma once”,这个宏的意思是在一个文件中多次引用该头文件时该宏指示编译器只包含一次。
接下来又是一个宏
#ifndef __AFXWIN_H__
              #error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
#endif
也就是说在其他文件中引用该文件时,必须在“#include “MyDialog.h””前加上“#include “stdafx.h””的文件包含,并且应该位于所有文件的最开始,否则编译器将提示错误。
再下来是包含头文件Resource.h。
接下来是CMyDialogApp类的声明,在其中“CMyDialogApp();”声明了无参构造函数,
“virtual BOOL InitInstance()”声明了重载CWinApp类的InitInstance()函数,“DECLARE_MESSAGE_MAP()”声明了消息映射声明宏。
在类的声明之后又有一句“extern CMyDialogExp3App theApp”声明了一个全局的CMyDialogApp对象。
下面我们再看MyDialog.cpp文件。按照前面的要求,该文件应该包含stdafx.h头文件、MyDialog.h头文件,另外就是主窗口类的声明文件MyDialogDlg.h头文件。
然后又是一个宏
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
即如果是调试版本将把new编译为DEBUG_NEW。
接下来是消息映射实现宏以及消息映射结束宏,其中“ON_COMMAND(ID_HELP, &CWinApp::OnHelp)”即为将ID_HELP与基类的OnHelp函数关联。

再下来是无参构造函数的实现,全局应用程序类对象的创建。
最后是对基类CWinApp类InitInstance()函数的重载的实现。我们来逐段解释:

INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);

这句话总体的作用就是实现了控件的初始化。
InitCommonControlsEx()函数的原型为:

BOOL InitCommonControlsEx( LPINITCOMMONCONTROLSEX lpInitCtrls);

这个函数需要一个指向INITCOMMONCONTROLSEX类型结构体的指针作为参数来完成MFC通用控件的初始化。INITCOMMONCONTROLSEX类型的结构体有两个成员变量dwSize以及dwICC。dwSize记录这个结构的大小,dwICC的标志位用来决定那些控件将从DLL中加载,即初始化。dwICC这里为ICC_WIN95_CLASSES,表示注册InitCommonControls函数注册的所有类。

CWinApp::InitInstance();

调用基类的InitInstance()完成必要的初始化。

AfxEnableControlContainer();

即使当我们需要调用OLE控件时,系统会自动调用该函数完成OLE控件的初始化。并且在有些情况下需要自己添加OLE控件的初始化函数。

SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

SetRegistryKey()函数的功能是设置MFC程序的注册表访问键,并把读写ini文件的成员函数映射到读写注册表,只要调用一下SetRegistryKey并指定注册表键值,那么WriteProfileBianry()、WriteProfileInt()、WriteProfileString()、GetProfileBinary()、GetProfileInt()、GetProfileString()这些函数就被映射到注册表读取了。

CMyDialogDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
    // TODO: 在此放置处理何时用
    //  “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
    // TODO: 在此放置处理何时用
    //  “取消”来关闭对话框的代码
}

这段代码实现了将CMyDialogDlg对话框设为主对话框并显示的功能,最后对CMyDialogDlg的返回值做相关的处理。
最后返回false从中可以看出这个应用程序类可以管理主窗口类,做一些全局性的工作。
看完应用程序类的声明以及实现文件,我们接着看主窗口类的声明以及实现文件CMyDialogDlg.h和CMyDialogDlg.cpp文件。CMyDialogDlg.h文件:

#pragma once

同上面的作用一样,防止重复包含。
接下来是CMyDialogDlg类的声明,从中可以看出该窗口类派生自CDialog类,并且几乎所有的窗口类都派生自CDialog类。

CMyDialogDlg(CWnd* pParent = NULL);

标准构造函数,参数为父窗口的句柄,默认为空。

enum { IDD = IDD_DIALOG_FIRST };

定义了一个枚举类型数据,几乎所有窗口类都有这样一句,这样使得通过统一的标志“IDD”可以访问窗口各自的ID。

virtual void DoDataExchange(CDataExchange* pDX);//重载基类的DoDataExchange()函数。
HICON m_hIcon;      //图标句柄变量m_hIcon
virtual BOOL OnInitDialog()  //重载基类的OnInitDialog函数
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();

三个消息处理函数,由系统调用

DECLARE_MESSAGE_MAP()     //消息映射声明宏

下面主要讲解CMyDialogDlg类的实现,以便于我们定制属于我们自己的窗口。CMyDialogDlg.cpp文件:

#include "stdafx.h"
#include "MyDialogDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif     

这三段代码的意义与上面的相同其中又插入了一段CAboutDlg类的声明和实现,其中的意义可以参考CMyDialogDlg类。

CMyDialogDlg::CMyDialogDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CMyDialogDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON_FIRST);
}

这是CMyDialogDlg类的构造函数的实现,其中调用了基类的构造函数(以CMyDialogDlg的ID以及父窗口句柄为参数),其后通过AfxGetApp()函数获得全局的唯一的应用程序对象(即theApp),然后调用theApp对象的LoadIcon函数获得指定图标的句柄。
LoadIcon函数的函数原型:

HICON LoadIcon( LPCTSTR lpszResourceName ) const
HICON LoadIcon( UINT nIDResource ) const;
void CMyDialogExp3Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}

DoDataExchange函数在这里主要是用于数据的交换,但是应该注意的是这个函数是不可以直接调用的,通常是通过调用UpdateData()函数来间接调用DoDataExchange函数。DoDataExchange函数其实是一项数据动态绑定技术,所以你要在对话框的构造函数里面初始化一个变量,再用DoDataExchange函数将它绑定到你的动态按扭中。

BEGIN_MESSAGE_MAP(CMyDialogExp3Dlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

这是消息映射的实现,其中有三个消息:WM_SYSCOMMAND、WM_PAINT、WM_QUERYDRAGICON。并且WM_PAINT这个消息是在窗口被最大化或最小化等需要重绘窗口时系统自动发出的,当然也可以强行发出该消息。WM_SYSCOMMAND是在调用系统菜单时被调用,不知道大家注意到没有,在主窗口的标题栏上右击会弹出如图所示 的菜单,并且当我们选择移动、关闭、关于MyDialogExp3的菜单项时就会使系统发出WM_SYSCOMMAND这个消息。WM_QUERYDRAGICON消息是当我们拖动最小化窗口时发出的。

BOOL CMyDialogDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(false);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING,IDM_ABOUTBOX,strAboutMenu);
        }
    }

    // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);         // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

CMyDialogDlg类的OnInitDialog()函数主要完成了三个任务:完成必要的初始化、将“关于CMyDailogDlg…”添加到对话框的菜单选项中、设置对话框左上角的图标。通过调用CDialog类的OnInitDialog函数完成必要的初始化,如果注释掉这一句,窗口将无法显示,可以看出其“必要性”。

    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

完成对IDM_ABOUTBOX消息的有效性验证。
添加对话框菜单的代码解释:

CMenu* pSysMenu = GetSystemMenu(false)

这句话是pSysMenu获得对话框菜单的句柄,GetSystemMenu 函数的原型:

HMENU GetSystemMenu(HWND hWnd,BOOL bRevert);

其中的布尔类型数据用于判断是返回原菜单的拷贝,还是NULL,因为这里我们是要添加项到原菜单中,所以参数是false,返回值的类型为菜单句柄。

        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);

这段代码是说在pSysMenu成功获得菜单的句柄后,用一个字符串的LoadString函数获得IDS_ABOUTBOX(字符创资源ID)中包含的字符串。

          pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
     这段代码是说在上面的字符串不为空的情况下,通过菜单类的AppendMenu函数为菜单添加选项,其中添加了一条横线,然后有添加了“关于..”选项。
         SetIcon(m_hIcon, TRUE);            
         SetIcon(m_hIcon, FALSE);

用于设置大图标与小图标。
最后返回TRUE;

void CMyDialogDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}

这个函数是WM_SYSCOMMAND消息对应的响应函数,参数中nID是消息的ID,lParam中包含了消息的额外信息。在函数体内,对消息ID为IDM_ABOUTBOX的消息作出弹出CAboutDlg对话框的动作。如果不是IDM_ABOUTBOX消息,则只执行基类的OnSysCommand函数。

void CMyDialogDlg::OnPaint()
{
    if (!IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

            SendMessage(WM_ICONERASEBKGND,reinterpret_cast(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
    }
}

这个函数是WM_PAINT消息的响应函数。其中通过IsIconic函数判断窗口是否最小化。如果不是,则只执行基类的OnPaint()函数。如果是,则重画图标。
重画图标的过程:

    CPaintDC dc(this);

创建一个绘图的设备环境,以this为参数,用于绘图。

 SendMessage(WM_ICONERASEBKGND,reinterpret_cast(dc.GetSafeHdc()), 0)

SendMessage区别与PostMessage函数,SendMessage直接将消息发送到指定的窗口,直到窗口程序处理完消息再返回。而函PostMessage不同,将一个消息寄送到一个线程的消息队列后立即返回。WM_ICONERASEBKGND消息用于重绘背景,reinterpret_cast(dc.GetSafeHdc())获得绘图设备环境(reinterpret_cast进行强制转换)。

  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() - cxIcon + 1) / 2;
  int y = (rect.Height() - cyIcon + 1) / 2;
 // 绘制图标
    dc.DrawIcon(x, y, m_hIcon);

这段代码把图标绘制在窗口的中央。其中带哦用了CDC类的DrawIcon函数。GetSystemMetrics可以获得的系统默认图标的缺省尺寸。

HCURSOR CMyDialogDlg::OnQueryDragIcon()
{
    return static_cast(m_hIcon);
}

这个函数是WM_QUERYDRAGICON消息的响应函数。该函数由系统调用,函数内部直接返回图标,同样static_cast也是一个强制转换说明符。
这样我们就可以定制自己的对话框了!

你可能感兴趣的:(mfc)