본문으로 건너뛰기
Previous
Next
MFC 다이얼로그 | CDialog·DDX/DDV 완벽 가이드

MFC 다이얼로그 | CDialog·DDX/DDV 완벽 가이드

MFC 다이얼로그 | CDialog·DDX/DDV 완벽 가이드

이 글의 핵심

MFC 다이얼로그는 CDialog 클래스로 구현합니다. DoModal로 모달, Create로 비모달 대화상자를 생성합니다. DDX/DDV로 컨트롤과 변수를 자동 연결하고, UpdateData로 동기화합니다.

들어가며

MFC 다이얼로그는 사용자 입력을 받는 가장 일반적인 방법입니다. 리소스 에디터로 UI를 시각적으로 디자인하고, DDX/DDV(Dialog Data Exchange/Validation)로 컨트롤과 변수를 자동으로 연결합니다. 2000년대 Windows 애플리케이션의 대부분이 MFC 다이얼로그로 구현되었습니다.

// 가장 간단한 MFC 다이얼로그
class CMyDialog : public CDialog
{
public:
    CString m_strName;
    int m_nAge;
    
    enum { IDD = IDD_MYDIALOG };
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
        DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
    }
};

// 사용
CMyDialog dlg;
if (dlg.DoModal() == IDOK) {
    CString msg;
    msg.Format(_T("%s님, %d세"), dlg.m_strName, dlg.m_nAge);
    AfxMessageBox(msg);
}

1. 다이얼로그 리소스 생성

1.1 리소스 에디터에서 다이얼로그 추가

1. 솔루션 탐색기 → 리소스 파일 → .rc 파일 더블클릭
2. Dialog 폴더 우클릭 → 리소스 추가 → Dialog
3. 속성:
   - ID: IDD_MYDIALOG
   - Caption: 사용자 정보 입력
   - Font: 맑은 고딕, 9
4. 도구상자에서 컨트롤 배치:
   - Static Text: "이름:"
   - Edit Control: ID를 IDC_EDIT_NAME으로 변경
   - Static Text: "나이:"
   - Edit Control: ID를 IDC_EDIT_AGE으로 변경
   - Button (IDOK): "확인"
   - Button (IDCANCEL): "취소"

1.2 클래스 추가

리소스 에디터에서 다이얼로그 더블클릭 → 클래스 추가:
- 클래스 이름: CMyDialog
- 기본 클래스: CDialog
- 헤더 파일: MyDialog.h
- 구현 파일: MyDialog.cpp

2. DoDataExchange와 DDX/DDV

2.1 기본 DDX

// MyDialog.h
class CMyDialog : public CDialog
{
public:
    CMyDialog(CWnd* pParent = NULL);
    
    enum { IDD = IDD_MYDIALOG };
    
    // 멤버 변수
    CString m_strName;
    int m_nAge;
    BOOL m_bAgree;
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    
    DECLARE_MESSAGE_MAP()
};

// MyDialog.cpp
CMyDialog::CMyDialog(CWnd* pParent)
    : CDialog(IDD_MYDIALOG, pParent)
    , m_strName(_T(""))
    , m_nAge(0)
    , m_bAgree(FALSE)
{
}

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    
    // DDX: 데이터 교환
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
    DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
    DDX_Check(pDX, IDC_CHECK_AGREE, m_bAgree);
}

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
END_MESSAGE_MAP()

2.2 주요 DDX 매크로

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    
    // 텍스트 (CString, int, float, double 등)
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
    DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
    DDX_Text(pDX, IDC_EDIT_PRICE, m_fPrice);
    
    // 체크박스 (BOOL)
    DDX_Check(pDX, IDC_CHECK_AGREE, m_bAgree);
    
    // 라디오 버튼 (int, 0부터 시작하는 인덱스)
    DDX_Radio(pDX, IDC_RADIO1, m_nGender);  // 0=첫번째, 1=두번째...
    
    // 리스트박스 (int = 선택 인덱스, CString = 선택 텍스트)
    DDX_LBIndex(pDX, IDC_LIST, m_nListIndex);
    DDX_LBString(pDX, IDC_LIST, m_strListText);
    
    // 콤보박스
    DDX_CBIndex(pDX, IDC_COMBO, m_nComboIndex);
    DDX_CBString(pDX, IDC_COMBO, m_strComboText);
    
    // 스크롤바 (int)
    DDX_Scroll(pDX, IDC_SCROLLBAR, m_nScrollPos);
    
    // 슬라이더 (int)
    DDX_Slider(pDX, IDC_SLIDER, m_nSliderPos);
}

2.3 DDX_Control - 컨트롤 객체 연결

class CMyDialog : public CDialog
{
public:
    CString m_strName;
    
    // 컨트롤 객체
    CEdit m_editName;
    CButton m_btnOK;
    CListBox m_listBox;
    CComboBox m_comboBox;
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        
        // 변수 연결
        DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
        
        // 컨트롤 객체 연결
        DDX_Control(pDX, IDC_EDIT_NAME, m_editName);
        DDX_Control(pDX, IDOK, m_btnOK);
        DDX_Control(pDX, IDC_LIST, m_listBox);
        DDX_Control(pDX, IDC_COMBO, m_comboBox);
    }
    
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 컨트롤 객체로 직접 제어
        m_editName.SetLimitText(50);  // 최대 50자
        m_btnOK.EnableWindow(FALSE);   // 비활성화
        
        m_listBox.AddString(_T("항목 1"));
        m_listBox.AddString(_T("항목 2"));
        
        m_comboBox.AddString(_T("선택 1"));
        m_comboBox.AddString(_T("선택 2"));
        m_comboBox.SetCurSel(0);
        
        return TRUE;
    }
};

3. DDV - 데이터 검증

3.1 기본 DDV

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
    // 이름 길이 검증 (최소 2자, 최대 20자)
    DDV_MaxChars(pDX, m_strName, 20);
    if (pDX->m_bSaveAndValidate && m_strName.GetLength() < 2) {
        AfxMessageBox(_T("이름은 최소 2자 이상이어야 합니다."));
        pDX->Fail();
    }
    
    DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
    // 나이 범위 검증 (0~150)
    DDV_MinMaxInt(pDX, m_nAge, 0, 150);
    
    DDX_Text(pDX, IDC_EDIT_PRICE, m_fPrice);
    // 가격 범위 검증
    DDV_MinMaxFloat(pDX, m_fPrice, 0.0f, 1000000.0f);
    
    DDX_Check(pDX, IDC_CHECK_AGREE, m_bAgree);
    // 약관 동의 검증
    if (pDX->m_bSaveAndValidate && !m_bAgree) {
        AfxMessageBox(_T("약관에 동의해야 합니다."));
        pDX->Fail();
    }
}

3.2 커스텀 DDV

void DDV_Email(CDataExchange* pDX, CString value)
{
    if (pDX->m_bSaveAndValidate) {
        // 이메일 형식 간단 검증
        if (value.Find(_T('@')) == -1 || value.Find(_T('.')) == -1) {
            AfxMessageBox(_T("올바른 이메일 주소를 입력하세요."));
            pDX->Fail();
        }
    }
}

void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    
    DDX_Text(pDX, IDC_EDIT_EMAIL, m_strEmail);
    DDV_Email(pDX, m_strEmail);
}

4. UpdateData

4.1 UpdateData 사용법

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    // 초기값 설정
    m_strName = _T("홍길동");
    m_nAge = 30;
    
    // 변수 → 컨트롤 (화면에 표시)
    UpdateData(FALSE);
    
    return TRUE;
}

void CMyDialog::OnBnClickedOk()
{
    // 컨트롤 → 변수 (데이터 가져오기)
    if (!UpdateData(TRUE)) {
        // DDV 실패
        return;
    }
    
    // 검증 성공
    CString msg;
    msg.Format(_T("이름: %s\n나이: %d"), m_strName, m_nAge);
    AfxMessageBox(msg);
    
    CDialog::OnOK();
}

4.2 실시간 입력 검증

class CMyDialog : public CDialog
{
protected:
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnEnChangeEditName()
    {
        // 이름 입력 시 실시간 검증
        UpdateData(TRUE);
        
        BOOL bValid = m_strName.GetLength() >= 2;
        GetDlgItem(IDOK)->EnableWindow(bValid);
    }
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_EN_CHANGE(IDC_EDIT_NAME, &CMyDialog::OnEnChangeEditName)
END_MESSAGE_MAP()

5. 모달 다이얼로그

5.1 DoModal

// MainFrm.cpp
void CMainFrame::OnFileOpen()
{
    CMyDialog dlg;
    
    // 초기값 설정
    dlg.m_strName = _T("기본값");
    dlg.m_nAge = 25;
    
    INT_PTR result = dlg.DoModal();
    
    if (result == IDOK) {
        // 확인 버튼
        CString msg;
        msg.Format(_T("입력: %s, %d세"), dlg.m_strName, dlg.m_nAge);
        AfxMessageBox(msg);
    } else if (result == IDCANCEL) {
        // 취소 버튼
        AfxMessageBox(_T("취소됨"));
    }
}

5.2 OnOK와 OnCancel 오버라이드

class CMyDialog : public CDialog
{
protected:
    virtual void OnOK()
    {
        if (!UpdateData(TRUE)) {
            return;
        }
        
        // 추가 검증
        if (m_strName.IsEmpty()) {
            AfxMessageBox(_T("이름을 입력하세요!"));
            GetDlgItem(IDC_EDIT_NAME)->SetFocus();
            return;
        }
        
        if (AfxMessageBox(_T("저장하시겠습니까?"), MB_YESNO) != IDYES) {
            return;
        }
        
        CDialog::OnOK();
    }
    
    virtual void OnCancel()
    {
        if (AfxMessageBox(_T("취소하시겠습니까?"), MB_YESNO) == IDYES) {
            CDialog::OnCancel();
        }
    }
};

6. 비모달 다이얼로그

6.1 Create로 생성

// MyDialog.h
class CMyDialog : public CDialog
{
public:
    // 생성자에 NULL 기본값 제거
    CMyDialog(CWnd* pParent);
    
    // Create 함수 추가
    BOOL Create(CWnd* pParent = NULL)
    {
        return CDialog::Create(IDD, pParent);
    }
    
protected:
    virtual void PostNcDestroy()
    {
        // 다이얼로그 닫힐 때 자동 삭제
        delete this;
    }
    
    afx_msg void OnClose()
    {
        DestroyWindow();  // EndDialog 대신
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

// MainFrm.cpp
void CMainFrame::OnViewToolbar()
{
    static CMyDialog* pDlg = NULL;
    
    if (pDlg == NULL) {
        pDlg = new CMyDialog(this);
        pDlg->Create(this);
        pDlg->ShowWindow(SW_SHOW);
    } else {
        pDlg->SetFocus();
    }
}

7. Property Sheet (탭 다이얼로그)

7.1 Property Page 생성

// Page1.h
class CPage1 : public CPropertyPage
{
public:
    CPage1();
    
    enum { IDD = IDD_PAGE1 };
    
    CString m_strName;
    int m_nAge;
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    
    DECLARE_MESSAGE_MAP()
};

// Page1.cpp
IMPLEMENT_DYNCREATE(CPage1, CPropertyPage)

CPage1::CPage1() : CPropertyPage(IDD_PAGE1)
    , m_strName(_T(""))
    , m_nAge(0)
{
}

void CPage1::DoDataExchange(CDataExchange* pDX)
{
    CPropertyPage::DoDataExchange(pDX);
    
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
    DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
}

BOOL CPage1::OnInitDialog()
{
    CPropertyPage::OnInitDialog();
    
    m_strName = _T("홍길동");
    m_nAge = 30;
    UpdateData(FALSE);
    
    return TRUE;
}

BEGIN_MESSAGE_MAP(CPage1, CPropertyPage)
END_MESSAGE_MAP()

7.2 Property Sheet 사용

// MainFrm.cpp
void CMainFrame::OnFileSettings()
{
    CPropertySheet sheet(_T("설정"));
    
    CPage1 page1;
    CPage2 page2;
    CPage3 page3;
    
    sheet.AddPage(&page1);
    sheet.AddPage(&page2);
    sheet.AddPage(&page3);
    
    if (sheet.DoModal() == IDOK) {
        // 각 페이지의 데이터 사용
        CString msg;
        msg.Format(_T("이름: %s\n나이: %d"), page1.m_strName, page1.m_nAge);
        AfxMessageBox(msg);
    }
}

8. 실전 예제: 사용자 등록 다이얼로그

// UserDialog.h
class CUserDialog : public CDialog
{
public:
    CUserDialog(CWnd* pParent = NULL);
    
    enum { IDD = IDD_USER_DIALOG };
    
    // 데이터 멤버
    CString m_strName;
    CString m_strEmail;
    CString m_strPhone;
    int m_nAge;
    int m_nGender;  // 0=남, 1=여
    CString m_strCity;
    BOOL m_bNewsletter;
    
    // 컨트롤 멤버
    CEdit m_editName;
    CEdit m_editEmail;
    CComboBox m_comboCity;
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    virtual void OnOK();
    
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnEnChangeEditName();
    afx_msg void OnEnChangeEditEmail();
};

// UserDialog.cpp
CUserDialog::CUserDialog(CWnd* pParent)
    : CDialog(IDD_USER_DIALOG, pParent)
    , m_strName(_T(""))
    , m_strEmail(_T(""))
    , m_strPhone(_T(""))
    , m_nAge(0)
    , m_nGender(0)
    , m_strCity(_T(""))
    , m_bNewsletter(FALSE)
{
}

void CUserDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
    DDV_MaxChars(pDX, m_strName, 50);
    
    DDX_Text(pDX, IDC_EDIT_EMAIL, m_strEmail);
    DDV_MaxChars(pDX, m_strEmail, 100);
    
    DDX_Text(pDX, IDC_EDIT_PHONE, m_strPhone);
    DDV_MaxChars(pDX, m_strPhone, 20);
    
    DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
    DDV_MinMaxInt(pDX, m_nAge, 1, 150);
    
    DDX_Radio(pDX, IDC_RADIO_MALE, m_nGender);
    
    DDX_CBString(pDX, IDC_COMBO_CITY, m_strCity);
    
    DDX_Check(pDX, IDC_CHECK_NEWSLETTER, m_bNewsletter);
    
    DDX_Control(pDX, IDC_EDIT_NAME, m_editName);
    DDX_Control(pDX, IDC_EDIT_EMAIL, m_editEmail);
    DDX_Control(pDX, IDC_COMBO_CITY, m_comboCity);
    
    // 이메일 검증
    if (pDX->m_bSaveAndValidate) {
        if (m_strEmail.Find(_T('@')) == -1) {
            AfxMessageBox(_T("올바른 이메일 주소를 입력하세요."));
            pDX->Fail();
        }
    }
}

BOOL CUserDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    // 도시 목록 초기화
    m_comboCity.AddString(_T("서울"));
    m_comboCity.AddString(_T("부산"));
    m_comboCity.AddString(_T("대구"));
    m_comboCity.AddString(_T("인천"));
    m_comboCity.AddString(_T("광주"));
    m_comboCity.SetCurSel(0);
    
    // 기본값
    m_nGender = 0;
    CheckRadioButton(IDC_RADIO_MALE, IDC_RADIO_FEMALE, IDC_RADIO_MALE);
    
    // 확인 버튼 초기 비활성화
    GetDlgItem(IDOK)->EnableWindow(FALSE);
    
    return TRUE;
}

void CUserDialog::OnEnChangeEditName()
{
    // 이름과 이메일이 모두 입력되면 확인 버튼 활성화
    CString name, email;
    m_editName.GetWindowText(name);
    m_editEmail.GetWindowText(email);
    
    BOOL bEnable = !name.IsEmpty() && !email.IsEmpty();
    GetDlgItem(IDOK)->EnableWindow(bEnable);
}

void CUserDialog::OnEnChangeEditEmail()
{
    OnEnChangeEditName();
}

void CUserDialog::OnOK()
{
    if (!UpdateData(TRUE)) {
        return;
    }
    
    // 최종 검증
    if (m_strName.IsEmpty()) {
        AfxMessageBox(_T("이름을 입력하세요."));
        m_editName.SetFocus();
        return;
    }
    
    if (m_strEmail.IsEmpty()) {
        AfxMessageBox(_T("이메일을 입력하세요."));
        m_editEmail.SetFocus();
        return;
    }
    
    // 저장 확인
    CString msg;
    msg.Format(_T("다음 정보로 등록하시겠습니까?\n\n이름: %s\n이메일: %s\n나이: %d\n성별: %s\n도시: %s"),
        m_strName, m_strEmail, m_nAge,
        m_nGender == 0 ? _T("남성") : _T("여성"),
        m_strCity);
    
    if (AfxMessageBox(msg, MB_YESNO | MB_ICONQUESTION) != IDYES) {
        return;
    }
    
    CDialog::OnOK();
}

BEGIN_MESSAGE_MAP(CUserDialog, CDialog)
    ON_EN_CHANGE(IDC_EDIT_NAME, &CUserDialog::OnEnChangeEditName)
    ON_EN_CHANGE(IDC_EDIT_EMAIL, &CUserDialog::OnEnChangeEditEmail)
END_MESSAGE_MAP()

// 사용
void CMainFrame::OnUserRegister()
{
    CUserDialog dlg;
    
    if (dlg.DoModal() == IDOK) {
        CString msg;
        msg.Format(_T("등록 완료!\n%s (%s)"), dlg.m_strName, dlg.m_strEmail);
        AfxMessageBox(msg);
        
        // DB에 저장 등...
    }
}

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • MFC 기초 | Microsoft Foundation Class 시작 가이드
  • Windows API 다이얼로그 | 대화상자와 공용 컨트롤 완벽 가이드
  • Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드

이 글이 도움이 되셨나요? MFC 다이얼로그와 DDX/DDV를 마스터하는 데 도움이 되었기를 바랍니다!

다음 글에서는 MFC 컨트롤 활용을 다루겠습니다. 🎛️