18、对话框与通用控件全解析

对话框与通用控件全解析

1. 对话框控件基础

在开发过程中,为每个控件赋予标识符是很重要的,不过像静态文本控件这类不产生消息的控件除外。同时,对话框资源也需要一个 ID 以便在源代码中被访问。我们还能利用属性窗口来选择和更改控件的各种样式。例如,图 8.3 展示了图 8.2 中密码编辑框的属性窗口,标识符 IOC_EOIT_PASSWORD 会被添加到 resource.h 中,用于在源代码中将该编辑框的消息映射到指定的处理程序。

在 MFC 里, CDialog 类用于封装对话框资源并在程序中使用。它继承自 CWnd 类,通常我们会从 CDialog 派生自己的类来表示对话框窗口。这个派生类会为对话框窗口中接收或显示数据的每个控件包含一个成员变量,数据在 CDialog DoDataExchange() 成员函数中在这些成员变量之间进行传输。

2. 模态对话框

创建 CDialog 对象有两种方式,具体使用哪种取决于所需对话框的类型。模态对话框使用以下构造函数创建:

CDialog(UINT nIDTemplate, CWnd* pParentWnd = NULL);

其中, nIDTemplate 参数是对话框的资源标识符, pParentWnd 参数是父窗口。模态对话框的父窗口会被阻塞,直到模态对话框关闭。

要显示模态对话框,需调用其 DoModal() 成员函数,该函数声明如下:

virtual int DoModal();

此函数直到用户关闭对话框才会返回值,更准确地说,直到程序调用 EndDialog() 函数才会返回。通常,模态对话框有“确定”和“取消”按钮,标识符分别为 IDOK IDCANCEL 。我们可以为这些按钮编写消息处理程序来调用 EndDialog() ,也可以使用从 CDialog 继承的 OnOK() OnCancel() 消息处理程序。

对于模态对话框, OnOK() OnCancel() 的默认行为就足够了。 DoModal() 的返回值是传递给 EndDialog() 的参数, EndDialog() 声明如下:

void EndDialog(int nResult);

默认的 OnOK() 函数会以 IDOK 调用 EndDialog() CDialog 中的 OnCancel() 函数会以 IDCANCEL 调用 EndDialog() ,这些值随后会由 DoModal() 返回。需要注意的是, OnOK() 会将对话框中控件的数据传输到对话框类的成员变量中,而如果用户按下“取消”并调用 OnCancel() ,控件的数据不会被传输。 OnOK() 通过调用 UpdateData() 来执行此传输。

下面是一个基于对话框的应用程序示例,展示了创建和显示模态对话框的步骤:

class ModalDemoApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};

BOOL ModalDemoApp::InitInstance()
{
    // 实例化对话框对象
    MainDlgWnd mainWnd(IDD_MODAL_DEMO);
    // 将主窗口分配给 m_pMainWnd
    m_pMainWnd = &mainWnd;
    // 显示模态对话框
    int response = mainWnd.DoModal();
    if (response == IDOK)
    {
        // 用户选择了确定按钮
    }
    else if (response == IDCANCEL)
    {
        // 用户选择了取消按钮
    }
    // 函数返回 FALSE,因为程序执行完毕
    return FALSE;
}

// 应用程序类的单个全局实例
ModalDemoApp theApp;

在这个示例中, InitInstance() 函数不会显示和更新窗口,而是通过调用 DoModal() 函数来显示对话框。该函数直到用户关闭对话框才会返回,此对话框有“确定”和“取消”按钮,所以 response 的值要么是 IDOK ,要么是 IDCANCEL

MainDlgWnd 类的声明如下:

class MainDlgWnd : public CDialog
{
public:
    MainDlgWnd(UINT); // 构造函数
protected:
    afx_msg void OnPaint(); // WM_PAINT 消息处理程序
    DECLARE_MESSAGE_MAP()
};

其构造函数有一个 UINT 参数,表示对话框的资源标识符,该构造函数会将此信息传递给 CDialog 构造函数。 OnPaint() 函数用于在屏幕上显示当前时间,不过通常在对话框中不需要处理 WM_PAINT 消息,因为其视图由资源决定。

3. 无模式对话框

无模式对话框的创建方式与模态对话框不同,它的创建方式类似于框架窗口。在程序逻辑中,需要包含在用户关闭对话框时销毁对话框窗口的操作。创建和销毁无模式对话框的步骤如下:
1. 编写一个继承自 CDialog 的类,如果对话框包含“确定”和“取消”按钮,重写 OnOK() OnCancel() 函数。
2. 创建该类的实例,调用父类 CDialog 的默认构造函数。
3. 调用 CDialog 对象的 Create() 函数,将对话框与资源关联。
4. 调用 DestroyWindow() 关闭无模式对话框,通常在 OnOK() OnCancel() 消息处理程序中完成。

CDialog 的默认构造函数具有受保护的访问权限,这就要求我们先编写一个继承自 CDialog 的类才能调用该默认构造函数。同时,必须确保不调用 CDialog 中的 OnOK() OnCancel() 函数,因为这些函数是虚函数,子类可以重写它们。如果无模式对话框不包含“确定”或“取消”按钮,就必须提供某种机制来关闭对话框并调用 DestroyWindow()

以下是一个创建无模式对话框的示例:

class ModelessDemo : public CDialog
{
public:
    ModelessDemo(); // 构造函数
protected:
    virtual void OnOK();
};

ModelessDemo::ModelessDemo()
{
    // 无需初始化,但强调调用了默认 CDialog 构造函数
}

void ModelessDemo::OnOK()
{
    // 用户选择确定以关闭对话框
    DestroyWindow();
}

在这个示例中,对话框不包含“取消”按钮,所以不需要重写 OnCancel() OnOK() 处理程序通过调用 DestroyWindow() 关闭无模式对话框。需要注意的是, ModelessDemo 类不需要声明和定义消息映射来处理点击“确定”按钮生成的 WM_COMMAND 消息,因为 CDialog 类用 OnOK() 处理该消息,而 OnOK() 是虚函数,通过多态性会调用子类中的 OnOK() 函数。

要显示无模式对话框,需调用其 Create() 函数,若对话框来自资源,使用的 Create() 版本声明如下:

BOOL Create(UINT nIDResource, CWnd* pParentWnd = NULL);

其中, nIDResource 参数是对话框的资源标识符, pParentWnd 参数表示父窗口。无模式对话框会始终显示在其父窗口之上,即使它失去焦点给父窗口。

在资源编辑器中创建对话框时,要确保无模式对话框具有 WS_VISIBLE 窗口样式,否则调用 Create() 会在内存中创建对话框,但它不会显示,需要调用 ShowWindow() 。可以在对话框属性窗口的“更多样式”选项卡中选择“可见”来添加该样式。

以下是显示无模式对话框的示例代码:

void TimeDisplayFrameWnd::OnAbout()
{
    if (!m_Dialog)
    {
        m_Dialog.Create(IDD_ABOUT, this);
    }
    else
    {
        m_Dialog.SetFocus();
    }
}

这里的 m_Dialog TimeDisplayFrameWnd 类的 ModelessDemo 类型的成员变量。要注意, Create() 函数会立即返回,如果变量是局部声明的,当对话框在屏幕上可见时对象会超出作用域,导致未定义行为。所以要确保无模式对话框对象在对话框的生命周期内保持在当前作用域内,可以是全局变量、静态变量或成员变量。

4. 通用对话框

Windows 提供了一系列被许多 Windows 程序频繁使用的对话框,这些对话框被称为“通用对话框”,是操作系统的内置功能。每个通用对话框类都封装在一个从 CDialog 派生的 MFC 类中,如下表所示:
| 类 | 对话框类型 |
| ---- | ---- |
| CColorDialog | 颜色选择对话框 |
| CFileDialog | 打开或保存文件对话框 |
| CFindReplaceDialog | 查找和替换文本对话框 |
| CFontDialog | 字体选择对话框 |
| CPrintDialog | 打印机选择对话框 |

CFileDialog 为例,它允许用户打开或保存文件。以下代码创建一个查找要打开的 .cpp 文件的 CFileDialog

CFileDialog openFile(TRUE, "cpp", "*.cpp", OFN_FILEMUSTEXIST);

第一个参数指定为“文件打开”对话框,如果为 FALSE 则创建“文件保存”对话框。第二个参数是默认文件名扩展名,第三个参数是初始文件名,在这个例子中显示所有 .cpp 扩展名的文件,最后一个参数指定要打开的文件必须存在。

CFileDialog 类表示模态对话框,所以在对话框显示之前需要调用 DoModal() 函数。以下代码展示了如何检索在“文件打开”对话框中选择的文件名:

int result = openFile.DoModal();
if (result == IDOK)
{
    CString fileName = openFile.GetFileName();
    // 使用文件进行相应操作
}
5. DoDataExchange() 函数

在应用程序中使用对话框时,通常会为每个对话框编写一个类,该类从 CDialog 派生, CDialog 为显示对话框以及 OnOK() OnCancel() 消息处理程序等功能提供默认行为。另一个从 CDialog 类继承的重要行为是在代码和对话框中各种控件之间传输数据的能力,这由 CWnd 类的 DoDataExchange() 成员函数负责。

5.1 UpdateData() 函数

CDialog 派生类时,MFC 应用程序的典型设计是为对话框中包含数据的每个控件在派生类中提供一个成员变量。例如,若对话框中有一个用于输入字符串的编辑框和一个整数组合框,派生类可能包含一个 CString 和一个 int 成员变量,示例类如下:

class SomeDialog : public CDialog
{
private:
    CString m_NameOfItem;
    int m_StyleOfItem;
    // 类的其余定义
};

目标是让 m_NameOfItem 是编辑框中字符串的值, m_StyleOfItem 是组合框中所选整数的值。这些成员变量可能有一个初始值,希望在对话框首次出现时显示。当用户在编辑框和组合框中完成更改后,如果用户选择“确定”或“应用”,希望这些成员变量更新为新值;如果用户选择“取消”,则跳过更新。

CWnd 类包含一个 UpdateData() 成员函数来执行此数据交换,其声明如下:

BOOL UpdateData(BOOL bSaveAndValidate = TRUE);

如果 bSaveAndValidate 参数为 FALSE ,则将存储在成员变量中的值传输到相应的对话框控件。MFC 框架在对话框首次显示时以 FALSE 参数调用 UpdateData() 。如果 bSaveAndValidate 参数为 TRUE ,则将数据从控件传输到相应的成员变量,例如在默认的 OnOK() 消息处理程序中会这样做。

UpdateData() 函数会确定数据传输的方向,然后调用 CWnd 类的 DoDataExchange() 函数。 DoDataExchange() 是一系列数据交换和验证函数调用,其声明如下:

virtual void DoDataExchange(CDataExchange * pDX);

其中, pDX 参数指向一个 CDataExchange 对象,用于数据交换和验证函数。在源代码中不要直接调用 DoDataExchange() ,如果需要在成员变量和对话框控件之间传输数据,使用 UpdateData() 函数,它会随后调用 DoDataExchange()

5.2 DDX 和 DDV 函数

MFC 包含一组称为 DDX 和 DDV 的对话框数据交换和对话框数据验证函数,这些函数在 DoDataExchange() 中被调用。DDX 函数用于在成员变量和对话框控件之间来回交换数据,DDV 函数用于验证输入到控件中的数据。如果输入了无效数据,它们会显示警告消息,并且可以用于使对话框保持活动状态,直到在特定控件中输入有效数据。

以下是一些常见的 DDX 和 DDV 函数及其描述:
| 函数 | 描述 |
| ---- | ---- |
| DDX_Text | 在编辑控件和 CString 成员变量之间传输 int UINT long DWORD CString float double 类型的数据 |
| DDX_Check | 在复选框和 int 成员变量之间传输 int ,该成员变量包含复选框的状态信息 |
| DDX_LBindex | 在列表框和 int 成员变量之间传输 int ,该成员变量是当前所选项目的索引 |
| DDX_LBString | 在列表框和 CString 成员变量之间传输 CString ,该成员变量是当前所选项目的值 |
| DDX_Radio | 在单选按钮和 int 成员变量之间传输 int ,该成员变量包含单选按钮的状态信息 |
| DDX_Scroll | 在滚动条和 int 成员变量之间传输 int ,该成员变量是滚动条的当前位置值 |
| DDV_MaxChars | 验证字符串的长度不超过指定的字符数 |
| DDV_MinMaxInt | 验证 int 值落在指定范围内 |

通过合理使用这些函数和类,我们可以更高效地开发出具有各种对话框功能的 Windows 应用程序。整个开发过程可以用以下 mermaid 流程图表示:

graph LR
    A[开始] --> B[创建对话框资源]
    B --> C{对话框类型}
    C -->|模态对话框| D[使用 CDialog 构造函数创建]
    C -->|无模式对话框| E[从 CDialog 派生类并按步骤创建]
    D --> F[调用 DoModal() 显示]
    E --> G[调用 Create() 显示]
    F --> H{用户操作}
    G --> H
    H -->|确定| I[数据传输并关闭]
    H -->|取消| J[不传输数据关闭]
    I --> K[结束]
    J --> K

这个流程图展示了从开始创建对话框资源到最终根据用户操作结束对话框的整个过程,涵盖了模态和无模式对话框的处理流程。

6. 对话框开发的综合应用与注意事项

在实际的 Windows 应用程序开发中,对话框的使用是非常普遍且关键的。下面我们将结合前面介绍的知识,探讨一些综合应用场景以及需要注意的事项。

6.1 综合应用场景
  • 数据输入与验证 :在许多应用程序中,需要用户输入各种数据,如用户名、密码、年龄等。我们可以使用模态对话框来实现数据输入界面,利用 DDX 和 DDV 函数进行数据交换和验证。例如,在一个用户注册界面中,使用 DDX_Text 函数将用户输入的用户名和密码传输到对话框类的成员变量中,同时使用 DDV_MaxChars DDV_MinMaxInt 等函数对输入的数据进行验证,确保数据的有效性。
class RegisterDialog : public CDialog
{
private:
    CString m_UserName;
    CString m_Password;
    int m_Age;
    // 类的其余定义
protected:
    virtual void DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Text(pDX, IDC_EDIT_USERNAME, m_UserName);
        DDX_Text(pDX, IDC_EDIT_PASSWORD, m_Password);
        DDX_Text(pDX, IDC_EDIT_AGE, m_Age);
        DDV_MaxChars(pDX, m_UserName, 20);
        DDV_MinMaxInt(pDX, m_Age, 18, 100);
    }
};
  • 信息提示与确认 :当应用程序需要向用户提示某些信息或请求用户确认操作时,可以使用模态对话框。例如,在删除重要文件时,弹出一个模态对话框询问用户是否确认删除,用户点击“确定”或“取消”后,根据 DoModal() 的返回值进行相应的处理。
void MainFrame::OnDeleteFile()
{
    int response = MessageBox(_T("确定要删除该文件吗?"), _T("提示"), MB_OKCANCEL);
    if (response == IDOK)
    {
        // 执行删除操作
    }
}
  • 设置与配置 :无模式对话框适合用于应用程序的设置与配置界面,用户可以在不关闭主窗口的情况下随时调整设置。例如,在一个文本编辑器中,用户可以通过点击“设置”菜单打开一个无模式的设置对话框,在对话框中调整字体、字号、颜色等设置,设置完成后点击“确定”保存设置。
6.2 注意事项
  • 内存管理 :在使用无模式对话框时,要特别注意内存管理。由于无模式对话框的生命周期较长,需要确保对话框对象在其生命周期内不会被意外销毁。如前面提到的,不要将无模式对话框对象声明为局部变量,而应将其作为全局变量、静态变量或类的成员变量。
  • 消息处理 :对于对话框中的各种控件,要正确处理它们产生的消息。例如,当用户点击按钮时,要编写相应的消息处理程序来执行特定的操作。同时,要注意消息处理程序的调用顺序和逻辑,避免出现意外的结果。
  • 用户体验 :在设计对话框时,要充分考虑用户体验。对话框的布局要合理,控件的使用要符合用户的操作习惯。例如,按钮的位置和标签要清晰明了,输入框要有适当的提示信息,避免用户产生困惑。
7. 总结

通过本文的介绍,我们详细了解了 Windows 应用程序中对话框与通用控件的相关知识,包括对话框控件基础、模态对话框、无模式对话框、通用对话框以及 DoDataExchange() 函数等内容。

在对话框控件基础部分,我们知道了为控件和对话框资源赋予标识符的重要性,以及如何使用 CDialog 类来封装和使用对话框资源。

模态对话框和无模式对话框是两种常见的对话框类型,它们的创建和使用方式有所不同。模态对话框会阻塞父窗口,直到对话框关闭;无模式对话框则不会阻塞父窗口,用户可以在对话框和主窗口之间自由切换。

通用对话框是操作系统提供的内置对话框,如颜色选择、文件打开保存等,使用它们可以方便地实现各种常见的功能。

DoDataExchange() 函数及其相关的 UpdateData() 、DDX 和 DDV 函数在数据交换和验证方面发挥了重要作用,通过合理使用这些函数,可以确保对话框中的数据在成员变量和控件之间正确传输和验证。

在实际开发中,我们要根据具体的需求选择合适的对话框类型,并注意内存管理、消息处理和用户体验等方面的问题。通过不断地实践和总结,我们可以更加熟练地运用对话框和通用控件,开发出更加优秀的 Windows 应用程序。

下面是一个简单的总结表格,概括了本文的核心内容:
| 内容 | 描述 |
| ---- | ---- |
| 对话框控件基础 | 为控件和对话框资源赋予标识符,使用 CDialog 类封装和使用对话框资源 |
| 模态对话框 | 使用特定构造函数创建,调用 DoModal() 显示,阻塞父窗口 |
| 无模式对话框 | 从 CDialog 派生类,按步骤创建和销毁,不阻塞父窗口 |
| 通用对话框 | 操作系统内置,如 CColorDialog CFileDialog 等 |
| DoDataExchange() 函数 | 负责代码和控件之间的数据传输,通过 UpdateData() 调用,结合 DDX 和 DDV 函数进行数据交换和验证 |

通过对这些知识的掌握,我们可以在 Windows 应用程序开发中更加灵活地使用对话框和通用控件,提升应用程序的功能和用户体验。希望本文能对大家的开发工作有所帮助。

最后,为了更清晰地展示整个开发过程中的关键步骤和决策点,我们再给出一个 mermaid 流程图:

graph LR
    A[需求分析] --> B{选择对话框类型}
    B -->|模态| C[设计资源与类]
    B -->|无模式| D[派生类并设计资源]
    C --> E[实现数据交换与验证]
    D --> E
    E --> F[编写消息处理程序]
    F --> G[测试与优化]
    G --> H[部署应用]

这个流程图从需求分析开始,涵盖了对话框类型的选择、资源和类的设计、数据交换与验证、消息处理程序的编写、测试与优化,最后到应用的部署,完整地展示了一个对话框应用程序的开发流程。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值