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 컨트롤 활용을 다루겠습니다. 🎛️