子对话框的类






4.92/5 (58投票s)
2003年10月27日
7分钟阅读

281065

5102
如何在对话框框中放置子对话框。
引言
在一篇较早的文章中,我展示了如何将对话框中的交互式控件封装到一个自定义控件中,该控件处理控件之间的所有交互。处理交互式控件的更好方法是将它们放入一个子对话框。本文介绍了一个简单的类,可帮助您将子对话框放入对话框中。
背景
我当时正在开发一个 MFC 应用程序来播放 MIDI 文件,其中一个对话框似乎变得过于复杂。它正在处理一组音量控件(每个音轨一个)以及一组用于静音音轨的并行按钮之间的交互。我决定最好有一个对象包含单个音轨的音量控制滑块和静音按钮,它们之间的任何交互都将在对象内部处理,然后对话框可以拥有这些对象的数组。
用 C 语言来说,我不想有一个包含两个并行对象数组的结构,而是想要一个结构数组,每个结构包含两个相关对象。
在网上搜索有关如何处理控件的建议时,我获得了有关自定义控件的信息,因此我处理交互式控件的第一个尝试使用了自定义控件,并在此进行了描述。然后,我找到了一种更好的方法,即将在交互式控件放入子对话框中,本文将介绍如何做到这一点。
示例程序
本文随附的示例程序采用了标准颜色选择对话框的一部分,并通过将色相、饱和度、亮度、红色、绿色和蓝色控件放入子对话框来处理它们之间的交互。HSL 值会影响 RGB 值,反之亦然,但父对话框不必了解这一点,它只会在 RGB 值方面与子对话框进行交互。子对话框还会显示当前颜色。
创建子对话框
将子对话框显示在父对话框中的步骤如下:
- 使用对话框编辑器和类向导以常规方式创建子对话框及其关联类(例如,称为 `ChildDialog`,基于 `CDialog`),以及在子对话框中布局控件并创建关联的成员变量。但请注意以下几点:
- 更改子对话框的属性(通过右键单击其边框,然后选择“属性”)如下:
Style
:Child
Border
:None
More styles
:必须选中 `Visible` 和 `Control`。
- 删除子对话框中的“确定”和“取消”按钮(否则,如果您单击它们,子对话框将从其父对话框中消失)。
- 将参考线拖到框的边缘,并将控件放在边缘(否则,子对话框周围会留有空白)。
- 更改子对话框的属性(通过右键单击其边框,然后选择“属性”)如下:
- 将代码和数据添加到 `ChildDialog` 以使子对话框按要求工作,包括控件之间所需的任何交互(这就是将控件放入子对话框的全部意义)。
- 在 `ChildDialog.h` 中,`#include "GenericChildDialog.h"` 并将 `ChildDialog` 的派生从 `CDialog` 更改为 `CRHGenericChildDialog`。将 `ChildDialog` 的标准构造函数替换为不带参数的构造函数。(这并非必需,但我发现它使其他事情容易得多。)添加 `CRHGetDialogID()` 的声明。
#include "GenericChildDialog.h" ... // changed from CDialog class ChildDialog : public CRHGenericChildDialog { // Construction public: //ChildDialog(CWnd* pParent = NULL); // standard constructor - removed ChildDialog(); // added virtual int CRHGetDialogID(); // added ...
- 在 `ChildDialog.c` 中,将 `ChildDialog` 的标准构造函数替换为新的构造函数(并将 `AFX_DATA_INIT` 行移至新构造函数,以便类向导将任何所需的初始化放在正确的位置),并添加 `CRHGetDialogID()` 例程。
//ChildDialog::ChildDialog(CWnd* pParent /*=NULL*/) // : CDialog(ChildDialog::IDD, pParent) //{ //} // ^^^ standard constructor replaced by // constructor without parameters vvv ChildDialog::ChildDialog() { // vvv AFX_DATA_INIT stuff moved from standard constructor //{{AFX_DATA_INIT(ChildDialog) ... whatever ClassWizard might have put in ... //}}AFX_DATA_INIT } int ChildDialog::CRHGetDialogID() { return(IDD); }
将子对话框放入父对话框
要将子对话框放入父对话框:
- 使用对话框编辑器和类向导以常规方式创建父对话框及其关联类(例如,称为 `ParentDialog`,基于 `CDialog`)。
- 使用对话框编辑器以常规方式在父对话框中要放置子对话框的位置放置一个“分组框”控件。仅使用此控件的左上角的坐标。其大小不关键,因为它将由 `CRHGenericChildDialog` 调整大小以匹配子对话框的大小。将此控件的 ID 命名为(例如)`IDC_CHILDPLACEMARKER`。
- 使用类向导以常规方式向 `ParentDialog` 添加一个 `ChildDialog` 数据成员(例如,称为 `vChildDialog`)。
- 如果需要,创建 `ParentDialog::OnInitDialog()`(在类视图中,右键单击 `ParentDialog`,选择“添加 Windows 消息处理程序...”,选择 `WM_INITDIALOG`,然后单击“添加并编辑”)。
- 在 `ParentDialog::OnInitDialog()` 中,调用:
vChildDialog.CRHCreateGenericChildDialog(this, IDC_CHILDPLACEMARKER, Id, &BorderRectangle);
`Id` 是一个 `WPARAM` 值,它将出现在发送到父对话框的任何子对话框消息的 `wparam` 中,从而允许父对话框识别消息的来源(如果您在父对话框中有多个相同的子对话框,这将很有用)。`BorderRectangle` 是一个 `CRect`,可用于指定子对话框与父对话框中的分组框控件之间的间隙大小。`CRect(4,15,4,6)` 看起来效果不错,并且可用作 `CRHGenericChildDialog::CRHRectForGroupBox`。您可以使用不同的边框,或者使用 `NULL` 如果您想让子对话框出现在分组框控件的位置而不留任何边距。
试用子对话框
您只需创建父对话框,子对话框现在应该会显示在其中。如果您尚未编写代码来创建父对话框,只需在主菜单中添加一个“NewParent”项,并使用类向导以常规方式(使用“消息映射”选项卡)为它添加一个 `COMMAND` 处理程序。然后,将父对话框创建为模态对话框。
void CCc9App::OnNewparent() { // TODO: Add your command handler code here ParentDialog vParentDialog; vParentDialog.DoModal(); }
子对话框与其父对话框之间的通信
为了完成子对话框,我们必须使其与其父对话框进行通信。子对话框必须告诉其父对话框何时发生了父对话框需要了解的子对话框事件。父对话框必须能够告诉子对话框执行各种操作(具体取决于子对话框支持的功能)。
这与 Windows 公共控件如何与其父对话框通信非常相似。
- 控件通过发送消息通知其父对话框,父对话框在其类的消息处理程序例程(例如 `OnHScroll()`)中处理该消息。
- 父对话框通过调用成员函数(例如 `CSliderCtrl::SetPos()`)来告诉控件应执行的操作。
因此,我们的子对话框及其父对话框以类似的方式通信,如下所示:
- 子对话框通过发送消息(通过调用 `CRHGenericChildDialog::CRHPostMessageToParent()`)通知其父对话框任何更改,父对话框在其类的消息处理程序例程(例如 `OnCHANGED_RGB()`)中处理该消息。
- 父对话框通过调用成员函数(例如 `ChildDialog::SetRGB()`)来告诉子对话框应执行的操作。
使用子对话框
现在我们已经创建了一个能够内部处理其控件之间交互的子对话框,并且它可以与其父对话框通信,我们可以使用占位符控件将其放入对话框中。
void CRHGenericChildDialog::CRHCreateGenericChildDialog(CWnd *pParent, int PlaceMarkerCtrlID, int Id, CRect *ABorderRect)
或者,我们可以使用 `CRect` 将它们的一组放入对话框中。
void CRHGenericChildDialog::CRHCreateGenericChildDialog(CWnd *pParent, CRect *pPlaceMarkerRect, int Id, CRect *ABorderRect)
HSL 控件、RGB 控件和彩色方块之间的交互完全在子对话框内部处理。在最后一个示例中,主对话框仅了解 5 个对象的数组,这些对象的接口仅基于 RGB。与让主对话框处理 65 个独立控件之间的交互相比,这种方法要简单得多。
最后
如果您有兴趣查看促成本文的原始 MidiPlay 应用程序,可以在此处找到它(单击“MidiPlay.exe v1.06”链接)。有关使用我早期文章中描述的自定义控件的 Midiplay 版本,请单击“MidiPlay.exe v1.07”链接。有关使用本文中描述的子对话框的 Midiplay 版本,请单击“Midiplay.exe v1.08”链接。
历史
- 2003 年 10 月 19 日 - 首次提交。
- 2003 年 11 月 1 日 - 主要错误已修复(存在 GDI 资源泄漏)。请参阅Joseph M. Newcomer 的文章,了解我犯的 `SelectObject()` 错误。我最初是在Joe 的出色网站上看到这篇文章的(非常感谢 Joe!)。