65.9K
CodeProject 正在变化。 阅读更多。
Home

创建基于对话框的应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.09/5 (8投票s)

2001 年 10 月 16 日

6分钟阅读

viewsIcon

225066

downloadIcon

1226

这是我创建基于对话框的应用程序的方法,此外,它还演示了如何处理编辑控件中的 Enter 键。

引言

关于创建基于对话框的应用程序,有大量重复出现的问题。这是我创建基于对话框的应用程序的方法,此外,它还演示了如何处理编辑控件中的 Enter 键。

人们最常问的问题是“如何捕获 ESC 键,使其不终止我的应用程序?”以及“如何捕获 Enter 键,使其不终止我的应用程序?”。这些都很容易。

首先,我不认为在 PreTranslateMessage 中处理此问题。这样做会导致知识分散,从而使代码难以维护,尤其是在添加或删除控件时。我的技术使用子类化将智能放置在它所属的位置——控件中,并将处理程序放置在一个已知且易于理解的位置——消息处理程序中。

首先,创建您的基于对话框的应用程序。您将得到一个类似于下面对话框的应用程序(我已将其缩小,以免占用页面太多空间)

进入 ClassWizard。选择 IDOK 及其 BN_CLICKED 处理程序,然后点击 Add Function。接受它给出的名称。对 IDCANCEL 也执行相同的操作。您应该会得到一个类似于下面所示的插图。我显示了 IDCANCEL 处理程序被点击,因为这是我最后添加的一个(请注意,下面两行,ON_IDOK::BN_CLICKED 处理程序已经存在)。

接下来,选择对话框类,找到 WM_CLOSE 消息处理程序。点击 Add Function。您现在应该有一个我下面所示的内容

转到您的源代码并检查它。您可以通过 OK 按钮退出 ClassWizard,或点击 Edit Code 按钮。您的代码应该如下所示

void CDialogappDlg::OnOK() 
{
    // TODO: Add extra validation here

    CDialog::OnOK();
}

void CDialogappDlg::OnCancel() 
{
    // TODO: Add extra cleanup here

    CDialog::OnCancel();
}
void CDialogappDlg::OnClose() 
{
    // TODO: Add your message handler code here and/or call default

    CDialog::OnClose();
}

将其更改为如下所示。删除 OnOKOnCancel 的主体。将 CDialog::OnOK 调用放在 OnClose 处理程序中。

void CDialogappDlg::OnOK() 
{
}

void CDialogappDlg::OnCancel() 
{
}

void CDialogappDlg::OnClose() 
{
    CDialog::OnOK();
}

返回对话框。删除 OKCancel 按钮。添加您的控件。例如,我想添加一个编辑控件,它仅在您离开或按下 Enter 键时才响应,在此之前可以更改而没有任何效果。另一个控件立即响应。

要创建一个立即响应的编辑控件,请添加一个 EN_CHANGE 处理程序。

另外,为该控件创建一个成员变量;我在配套文章中描述了如何执行此操作。

为了访问将显示效果的状态控件,您必须为其分配一个除 IDC_STATIC 之外的 ID。创建一个控件变量来表示它。

我得到的变量如下所示(请注意,我尚未为 IDC_DELAYED 创建变量)

即时响应的代码很简单

void CDialogappDlg::OnChangeImmediate() 
{
 CString s;
 c_Immediate.GetWindowText(s);
 c_Status.SetWindowText(s);
}

运行时,它会产生如下输出

在“即时反应”编辑控件中输入的每个字符都会显示在标有“我看到”的框中。

然而,我可能不希望在按下 Enter 键或失去焦点或两者兼有时才发生反应。为此,我创建了一个 CEdit 的子类

创建一个派生自 CEdit 的类,我将其命名为 CEnterEdit

现在为 IDC_DELAYED 创建该类型的成员变量

这将为您留下以下定义

通知父级

在 ClassWizard 中,选择您定义的新类。(请注意,如果您从上一步继续,系统会提示您保存更改;选择“是”)。

=EN_KILLFOCUSWM_CHARWM_GETDLGCODE 添加处理程序

您必须记住在使用新类的任何文件之前,将其头文件添加到编译中。对于基于对话框的应用程序,这意味着应用程序源和对话框源都必须包含它,例如

#include "stdafx.h"
#include "dialogapp.h"
#include "EnterEdit.h"
#include "dialogappDlg.h"

否则,每当处理您的 ...Dlg.h 文件时,您都会收到编译错误。

如果您使用自定义消息,则必须定义它。我更喜欢使用注册窗口消息,正如我在配套文章中所述,因此我在 EnterEdit.h 中添加了以下声明。请注意,当您添加用户定义的消息时,记录其参数、效果和返回结果至关重要!否则将导致难以理解和无法维护的代码。

/****************************************************************************
*                              UWM_EDIT_COMPLETE
* Inputs:
*       WPARAM: Control ID of the control whose edit completed
*    LPARAM: CWnd * of the control whose edit completed
* Result: LRESULT
*       Logically void, 0, always
* Effect: 
*       Posted/Sent to the parent of this control to indicate that the
*    edit has completed, either by the user typing <Enter> or focus leaving
****************************************************************************/

#define UWM_EDIT_COMPLETE_MSG 
  _T("UWM_EDIT_COMPLETE-{165BBEA0-C1A8-11d5-A04D-006067718D04}")

我在 EnterEdit.cpp 中声明它,如下所示,或者您可以使用我在配套文章中定义的更方便的 DECLARE_MESSAGE 宏。

static UINT UWM_EDIT_COMPLETE = ::RegisterWindowMessage(UWM_EDIT_COMPLETE_MSG);

这使我能够编写所需的处理程序。

首先,为了绕过对话框超类拦截和处理键盘输入的倾向,您应该在 OnGetDlgCode 处理程序中进行如下所示的更改

UINT CEnterEdit::OnGetDlgCode() 
{
    return CEdit::OnGetDlgCode() | DLGC_WANTALLKEYS;
}

这告诉对话框超类,当焦点在此控件中时,它应该将所有按下的键都传递给它。

然后,在您的 OnChar 处理程序中,您可以这样做

void CEnterEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
  switch(nChar)
      { /* nChar */
       case VK_RETURN:
        GetParent()->SendMessage(UWM_EDIT_COMPLETE, 
                             GetDlgCtrlID(), (LPARAM)this);
        return;
      } /* nChar */
  CEdit::OnChar(nChar, nRepCnt, nFlags);
}

为什么只有一个 case 的 switch 语句?为什么不呢?它使得添加其他 case 变得容易,并捕获了您目前关注某些有限字符的事实。在这种情况下,我更喜欢 switch 语句。它们消除了添加复杂 if-then-else 结构的诱惑,从而产生更简洁、更易于维护的代码。

当检测到 Enter 键时,消息将发送到父级,父级可以对其进行响应。

要在失去焦点时处理,请按如下所示修改反射的 =EN_KILLFOCUS 处理程序

void CEnterEdit::OnKillfocus() 
{
 GetParent()->SendMessage(UWM_EDIT_COMPLETE, GetDlgCtrlID(), (LPARAM)this);
}

现在您需要为父级添加一个处理程序。这意味着您必须像在子编辑控件中那样为注册窗口消息声明一个 UINT,并将指定的消息添加到 MESSAGE_MAP:

BEGIN_MESSAGE_MAP(CDialogappDlg, CDialog)
        ON_REGISTERED_MESSAGE(UWM_EDIT_COMPLETE, OnEditComplete)
    //{{AFX_MSG_MAP(CDialogappDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_CLOSE()
    ON_EN_CHANGE(IDC_IMMEDIATE, OnChangeImmediate)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

重要提示:这应该在神奇的 AFX_MSG_MAP 注释之外(上方或下方;我显示在上方)!否则您可能会混淆 ClassWizard。

您必须在头文件的声明中添加一个处理程序

        afx_msg LRESULT OnEditComplete(WPARAM, LPARAM);
    // Generated message map functions
    //{{AFX_MSG(CDialogappDlg)
    virtual BOOL OnInitDialog();

重要提示:这必须在神奇的 AFX_MSG 注释之外(上方或下方,我显示在上方)。否则您可能会混淆 ClassWizard。

处理程序可能看起来像我这里所示的那个

LRESULT CDialogappDlg::OnEditComplete(WPARAM, LPARAM lParam)
    {
     CEnterEdit * edit = (CEnterEdit *)lParam;
     CString s;
     edit->GetWindowText(s);
     c_Status.SetWindowText(s);
     return 0;
    } // CDialogappDlg::OnEditComplete

在这个简单的例子中,我不在乎是哪个窗口生成了消息,所以我忽略了 WPARAM,并且无论哪个控件被激活,我只是将其文本设置到状态窗口中。在一个有许多控件的对话框中,您可能希望根据 WPARAM 值进行切换。

现在,当我运行应用程序并在“延迟反应”框中输入文本时,我看到以下内容。请注意,“我看到”状态框的内容是上次输入后剩余的内容。

但如果我按下 Enter 键或更改焦点,我将得到以下结果。新文本在“我看到”框中,并且焦点(如插入符所示)仍在“延迟反应”框中。

或者,如果我切换焦点,我也会看到效果。请注意,通过在失去焦点时执行此操作,如果我切换到另一个应用程序,我也会看到文本激活。如果不需要这样做,请不要使用 =EN_KILLFOCUS 反射器,或者您必须进行更复杂的测试。请注意,从第一张图片开始,当我将焦点更改到上方窗口时,我得到以下结果。

摘要

许多人对这种技术评论道:“你正在创建另一个子类。你为什么觉得你需要这样做?”永远不要害怕创建自定义子类。我曾一天内创建过十几个。这是 MFC 编程的自然范式。这些是我用来构建由数千人使用并由我自己以外的人维护的真实软件的技术。


这些文章中表达的观点是作者的观点,不代表,也不被微软认可。

发送邮件至newcomer@flounder.com提出关于本文的问题或评论。
版权所有 © 1999 保留所有权利
www.flounder.com/mvp_tips.htm
© . All rights reserved.