注意:开发全新的 PC 桌面客户端请不要使用 MFC, 已经是过时的技术了。
有了坑爹的学校,才有了我去用 MFC
请使用 Qt, C# WPF, Node-Webkit 等

使用 AfxMessageBox 或者 MessageBox 创建模态对话框

使用 AfxMessageBox 函数可创建一个模态对话框

官方文档:https://msdn.microsoft.com/en-us/library/as6se7cb.aspx

简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
int nRet = 0;
nRet = AfxMessageBox(_T("你看见我了吗?"), MB_YESNO | MB_ICONQUESTION);
switch (nRet) {
case 0:
    AfxMessageBox(_T("对话框创建失败!"));
    break;
case IDYES:
    AfxMessageBox(_T("看见了屏幕上的自己吧"));
    break;
case IDNO:
    AfxMessageBox(_T("真的就只有你自己 ╮(╯_╰)╭"));
    break;
}

使用 CDialog::DoModal 创建模态对话框

官方文档:https://msdn.microsoft.com/en-us/library/619z63f5.aspx

文档上的例子很详细了,就不多说了

使用 CDialog::Create 创建非模态对话框

官方文档:https://msdn.microsoft.com/en-us/library/yhth57kd.aspx

首先得有个认识:在 MFC 程序中,窗口对象的生存期应长于对应的窗口,
也就是说,不能在未关闭屏幕上窗口的情况下先把对应的窗口对象删除掉

对于模态对话框来说,对话框被关闭之前 DoModal 函数并不会返回,
一旦对话框被关闭 DoModal 函数立即返回,并不会出现窗口对象先于窗口删除的情况
我们使用局部变量来创建模态对话框,因为局部变量的生命周期有限,所以我们不必担心何时来删除窗口对象

但是对于非模态对话框来说情况就不一样了,Create 函数在显示了对话框后就立即返回,
所以当调用 Create 函数的那一部分代码返回之后,窗口对象就被删除了,
如果这时对应窗口没有被关闭,就出现了窗口对象先于窗口删除的情况,这在 MFC 程序中是不允许的
为了解决这一问题,我们需要 new 操作符来创建非模态对话框对象。
但是(TM 问题又来了)在 C++ 中使用了 new 必定还需要将其创建的对象使用 delete 删除,
不然就出现内存泄漏了

那么何时来删除呢?
官方文档中有说:https://msdn.microsoft.com/en-us/library/vstudio/s97b2305(v=vs.140).aspx
摘录如下:

When you implement a modeless dialog box,
always override the OnCancel member function and call DestroyWindow from within it.
Don’t call the base class CDialog::OnCancel,
because it calls EndDialog, which will make the dialog box invisible but will not destroy it.
You should also override PostNcDestroy for modeless dialog boxes in order to delete this,
since modeless dialog boxes are usually allocated with new.
Modal dialog boxes are usually constructed on the frame and do not need PostNcDestroy cleanup.

然后我们就知道了我们该做的,需要修改两个地方:

  1. 重写 OnCancel 成员函数,将 DestroyWindow 函数加入其中

  2. 重写 PostNcDestroy 成员函数,将 delete this 代码加入其中

原成员函数说明请看文档:
CDialog::OnCancel: https://msdn.microsoft.com/en-us/library/kw3wtttf.aspx
CWnd::PostNcDestroy: https://msdn.microsoft.com/en-us/library/sk933a19.aspx

参考:
http://www.xxsof.com/?p=998

具体的例子

  1. 新建一个对话框 ID 为 IDD_POP_DIALOG, 添加类,名为 CPopDlg
    自动生成 PopDlg.hPopDlg.cpp 文件

  2. 在创建 IDD_POP_DIALOG 对话框的文件中,比如为 ModelessDialogBoxDlg.cpp,
    在其中加入头文件 PopDlg.h

    创建 IDD_POP_DIALOG 对话框的代码如下(我是在一个 Button 中创建的):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void CModelessDialogBoxDlg::OnBnClickedPopButton()
    {
        CPopDlg *popDlg = new CPopDlg(this);
    
        BOOL ret = popDlg->Create(IDD_POP_DIALOG, this);
        if (ret == FALSE) {
            AfxMessageBox(_T("对话框创建失败!"));
        }
    
        popDlg->ShowWindow(SW_SHOW);
    }
    
  3. 重写 IDD_POP_DIALOG 对话框的 OnCancelPostNcDestroy 函数

    在「类向导」中将虚函数 OnCancelPostNcDestroy 设置为重写

    然后回到实现文件 PopDlg.cpp 中,将以上两个函数重写为如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void CPopDlg::OnCancel()
    {
        DestroyWindow();
    }
    
    void CPopDlg::PostNcDestroy()
    {
        CDialogEx::PostNcDestroy();
        delete this;
    }
    

还需要注意的一点,在 IDD_POP_DIALOG 对话框中有默认的确定和取消按钮,
一般将确定按钮去掉,然后留下取消按钮做关闭窗口之用

取消按钮自动生成的代码如下:

1
2
3
4
void CPopDlg::OnBnClickedCancel()
{
    CDialogEx::OnCancel();
}

如果放任不管,又出问题了(MFC 好麻烦 (〒︿〒) 各位还是不要用了)
这家伙调用基类的 OnCancel() 函数,但是前面我们说过,销毁非模态窗口,我们并不能使用这个函数,
所以改为如下:

1
2
3
4
void CPopDlg::OnBnClickedCancel()
{
    OnCancel();
}

上面代码是正确的,因为我们已经重写过 OnCancel() 函数了


但是(WC 又来?),以上实现允许你无限创建 IDD_POP_DIALOG 对话框
我们一般需要的功能是,只创建一个窗口,如果再次创建,将只把原来的窗口激活

任重道远啊,继续…

我们只在以上基础上修改,不重写了

  1. ModelessDialogBoxDlg.h 头文件头部加入 class CPopDlg,
    并不使用到 CPopDlg 类,所以可以不用引入 CPopDlg 类的头文件
    并在类 CModelessDialogBoxDlg 声明处,加入如下成员变量:

    1
    2
    public:
        CPopDlg *m_pPopDlg;
    
  2. ModelessDialogBoxDlg.cpp 文件中的 BOOL CModelessDialogBoxDlg::OnInitDialog() 函数内部加入如下初始化代码:

    1
    m_pPopDlg = NULL;
    
  3. 将创建 IDD_POP_DIALOG 对话框的代码从

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void CModelessDialogBoxDlg::OnBnClickedPopButton()
    {
        CPopDlg *popDlg = new CPopDlg(this);
    
        BOOL ret = popDlg->Create(IDD_POP_DIALOG, this);
        if (ret == FALSE) {
            AfxMessageBox(_T("对话框创建失败!"));
        }
    
        popDlg->ShowWindow(SW_SHOW);
    }
    

    修改为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void CModelessDialogBoxDlg::OnBnClickedPopButton()
    {
        if (m_pPopDlg == NULL) {
            m_pPopDlg = new CPopDlg(this);
    
            BOOL ret = m_pPopDlg->Create(IDD_POP_DIALOG, this);
            if (ret == FALSE) {
                AfxMessageBox(_T("对话框创建失败!"));
            }
    
            m_pPopDlg->ShowWindow(SW_SHOW);
        } else {
            m_pPopDlg->SetActiveWindow();
        }
    }
    
  4. IDD_POP_DIALOG 窗口关闭之后,还需要将 m_pPopDlg 设置为 NULL
    将重写的 OnCancel 函数修改为:

    1
    2
    3
    4
    5
    6
    void CPopDlg::OnCancel()
    {
        CModelessDialogBoxDlg *pFather = (CModelessDialogBoxDlg *)GetParent();
        pFather->m_pPopDlg = NULL;
        DestroyWindow();
    }
    

写了半天,真是好麻烦,C++ 麻烦,MFC 也麻烦,所以好麻烦