도서 관리 프로그램 – 6. 등록 대화상자 정의[MFC]

등록 대화상자

유튜브에 무료 동영상 강의를 업로드하였습니다.

MyRegDialog.h

등록 대화상자와 매핑하는 클래스는 MyRegDialog로 정할게요.

class MyRegDialog : public CDialogEx

이전 글에서 메인 대화상자는 등록 대화상자의 종료를 알기 위해 이벤트 핸들러를 등록하였습니다.

등록 대화상자에는 이를 기억할 멤버를 캡슐화합니다.

	EndDialogEventHandler* eeh;

종료 이벤트 핸들러를 등록하는 메서드로 캡슐화합니다.

public:
	void AddEndedEventHandler(EndDialogEventHandler *eeh);

OnCancel 메서드를 재정의합시다.

	virtual void OnCancel();

컨트롤과 매핑하는 변수를 멤버로 캡슐화합니다.

	int bno;	
	CString title;
	CString content;	
	CString image;
	CDateTimeCtrl dtp;
	CMonthCalCtrl mc;
	COleDateTime date1; //Date-Time Control과 매핑
	COleDateTime date2; //Calendar Control과 매핑
	CStatic pic_con;
	CEdit edit_cont;

세 개의 버튼과 Date-Timer 컨트롤, Calendar 컨트롤을 선택 변경하였을 때 처리기를 캡슐화합니다.

	afx_msg void OnDtnDatetimechangeDatetimepicker1(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnMcnSelchangeMonthcalendar1(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnBnClickedButtonImg();
	afx_msg void OnBnClickedButtonSub();
	afx_msg void OnBnClickedButtonCancel();

OnInitDialog와 PreTranslateMessage를 재정의합니다.

	virtual BOOL OnInitDialog();
	virtual BOOL PreTranslateMessage(MSG* pMsg);

컨트롤 상태를 초기화하는 메시지와 Picture Control에 이미지를 설정하는 메서드를 캡슐화합시다. 이 두 개의 메서드는 다른 클래스에서 호출해서 사용하지 않습니다.

private:
	void InitControls();
	void SetImage();

다음은 MyRegDialog.h 전체 소스 코드 내용입니다.

#pragma once
#include "afxdialogex.h"


// MyRegDialog 대화 상자
#include "EndDialogEventHandler.h"
class MyRegDialog : public CDialogEx
{
	EndDialogEventHandler* eeh;
	DECLARE_DYNAMIC(MyRegDialog)

public:
	MyRegDialog(CWnd* pParent = nullptr);   // 표준 생성자입니다.
	virtual ~MyRegDialog();

// 대화 상자 데이터입니다.
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DIALOG_REG };
#endif
public:
	void AddEndedEventHandler(EndDialogEventHandler *eeh);
protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 지원입니다.

	DECLARE_MESSAGE_MAP()
	virtual void OnCancel();
private:
	int bno;	
	CString title;
	CString content;	
	CString image;
	CDateTimeCtrl dtp;
	CMonthCalCtrl mc;
	COleDateTime date1;
	COleDateTime date2;
	CStatic pic_con;
	CEdit edit_cont;
public:
	afx_msg void OnDtnDatetimechangeDatetimepicker1(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnMcnSelchangeMonthcalendar1(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnBnClickedButtonImg();
	afx_msg void OnBnClickedButtonSub();
	afx_msg void OnBnClickedButtonCancel();

public:
	virtual BOOL OnInitDialog();
	virtual BOOL PreTranslateMessage(MSG* pMsg);
private:
	void InitControls();
	void SetImage();
};

MyRegDialog.cpp

생성자에서 종료 이벤트 핸들러를 기억하는 eeh 멤버를 0으로 초기화합니다.

MyRegDialog::MyRegDialog(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG_REG, pParent)	
	, title(_T(""))
	, content(_T(""))
	, bno(0)
	, date1(COleDateTime::GetCurrentTime())
	, date2(COleDateTime::GetCurrentTime())
{
	eeh = 0;
}

종료 이벤트 핸들러를 등록하는 메서드를 정의합시다.

void MyRegDialog::AddEndedEventHandler(EndDialogEventHandler* eeh)
{
	this->eeh = eeh;
}

OnCancel 메서드에서는 종료 이벤트 핸들러가 있으면 호출합니다.

void MyRegDialog::OnCancel()
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	if (eeh)
	{
		eeh->EndedDialog();//나 죽음을 통보한다.
	}
	CDialogEx::OnCancel();
}

Date-Time Control과 Calendar Control의 선택 변경 시 서로 동기화해 줍시다.

void MyRegDialog::OnDtnDatetimechangeDatetimepicker1(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNMHDR);
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	*pResult = 0;
	UpdateData();
	mc.SetCurSel(date1);
}


void MyRegDialog::OnMcnSelchangeMonthcalendar1(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMSELCHANGE pSelChange = reinterpret_cast<LPNMSELCHANGE>(pNMHDR);
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	*pResult = 0;
	UpdateData();
	dtp.SetTime(date2);
}

이미지 버튼을 클릭하면 파일 대화상자를 열어서 선택한 경로를 image에 설정하여 이미지 설정 메서드를 호출합니다.

void MyRegDialog::OnBnClickedButtonImg()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	CFileDialog* fod = new CFileDialog(TRUE, 0);
	INT_PTR re = fod->DoModal();
	if (re != IDOK)
	{
		return;
	}
	image = fod->GetPathName();
	SetImage();
}

이미지 설정 메서드에서는 경로로 CImage 개체를 Load합니다.

예외가 발생하면 미리 약속한 디폴트 이미지를 로딩합니다.

Picture Control의 DC를 얻어온 후 StretchBlt를 이용하여 컨트롤의 크기에 맞게 복사합니다.

void MyRegDialog::SetImage()
{
	CImage cimg;
	try
	{
		cimg.Load(image);
	}
	catch (...)
	{
		image = TEXT("noimage.bmp");
		cimg.Load(image);
	}

	CDC* cdc = pic_con.GetDC();
	RECT rt;
	pic_con.GetClientRect(&rt);
	cimg.StretchBlt(*cdc, 0, 0, rt.right, rt.bottom, SRCCOPY);
}

제출 버튼을 클릭하였을 때 이미지가 없으면 image를 디폴트 이미지로 설정합니다.

Book Manager 개체를 통해 도서 추가를 요청한 후 컨트롤을 초기 상태로 설정합니다.

void MyRegDialog::OnBnClickedButtonSub()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	UpdateData();//컨트롤=>변수
	if (image == TEXT(""))
	{
		image = TEXT("noimage.bmp");
	}
	BookManager& bm = BookManager::GetBookManager();
	if (bm.AddBook(title, content, image, date1) == FALSE)
	{
		MessageBox(TEXT("추가 실패"));	
	}
	InitControls();
}

컨트롤 초기화에서는 컨트롤과 매핑한 멤버 필드를 초기화하는 작업과 다음에 부여할 도서 번호를 얻어와서 설정하는 부분을 수행합니다.

void MyRegDialog::InitControls()
{
	title = TEXT("");
	content = TEXT("");
	image = TEXT("noimage.bmp");
	SetImage();
	UpdateData(FALSE);//변수=>컨트롤
	BookManager& bm = BookManager::GetBookManager();
	int bno = bm.GetNextBookNo();
	SetDlgItemInt(IDC_STATIC_BNO, bno, FALSE);
}

OnInitDialog 메서드에서는 컨트롤 초기화 메서드를 호출합니다.

BOOL MyRegDialog::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// TODO:  여기에 추가 초기화 작업을 추가합니다.	
	InitControls();
	return TRUE;  // return TRUE unless you set the focus to a control
	// 예외: OCX 속성 페이지는 FALSE를 반환해야 합니다.
}

PreTranslateMessage 메서드를 정의합시다.

여기에서 이 메서드를 재정의하는 것은 edit 컨트롤에서 엔터 키를 수용하기 위해서입니다.

메시지가 WM_KEYDOWN이고 wParam이 VK_RETURN일 때 현재 포커스가 Edit 컨트롤에 있다면 마지막 위치에 개행을 추가해 줍니다.

BOOL MyRegDialog::PreTranslateMessage(MSG* pMsg)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		if (GetFocus() == GetDlgItem(IDC_EDIT_CONTENT))
		{
			int len = edit_cont.GetWindowTextLength();
			edit_cont.SetSel(len,len);
			edit_cont.ReplaceSel(TEXT("\r\n"));
		}
	}
	return CDialogEx::PreTranslateMessage(pMsg);
}

Cancel버튼 클릭 처리기에서는 컨트롤 상태를 초기화시킵니다.

void MyRegDialog::OnBnClickedButtonCancel()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	InitControls();
}

다음은 MyRegDialog.cpp 전체 소스 코드 내용입니다.

// MyRegDialog.cpp: 구현 파일
//

#include "pch.h"
#include "도서 관리 프로그램.h"
#include "afxdialogex.h"
#include "MyRegDialog.h"
#include "BookManager.h"

// MyRegDialog 대화 상자

IMPLEMENT_DYNAMIC(MyRegDialog, CDialogEx)

MyRegDialog::MyRegDialog(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG_REG, pParent)	
	, title(_T(""))
	, content(_T(""))
	, bno(0)
	, date1(COleDateTime::GetCurrentTime())
	, date2(COleDateTime::GetCurrentTime())
{
	eeh = 0;
}

MyRegDialog::~MyRegDialog()
{
}

void MyRegDialog::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_DATETIMEPICKER1, dtp);
	DDX_Control(pDX, IDC_MONTHCALENDAR1, mc);
	DDX_Text(pDX, IDC_EDIT_TITLE, title);
	DDX_Text(pDX, IDC_EDIT_CONTENT, content);
	DDX_Text(pDX, IDC_STATIC_BNO, bno);
	DDX_DateTimeCtrl(pDX, IDC_DATETIMEPICKER1, date1);
	DDX_MonthCalCtrl(pDX, IDC_MONTHCALENDAR1, date2);
	DDX_Control(pDX, IDC_STATIC_IMG, pic_con);
	DDX_Control(pDX, IDC_EDIT_CONTENT, edit_cont);
}


BEGIN_MESSAGE_MAP(MyRegDialog, CDialogEx)
	ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETIMEPICKER1, &MyRegDialog::OnDtnDatetimechangeDatetimepicker1)
	ON_NOTIFY(MCN_SELCHANGE, IDC_MONTHCALENDAR1, &MyRegDialog::OnMcnSelchangeMonthcalendar1)
	ON_BN_CLICKED(IDC_BUTTON_IMG, &MyRegDialog::OnBnClickedButtonImg)
	ON_BN_CLICKED(IDC_BUTTON_SUB, &MyRegDialog::OnBnClickedButtonSub)
	ON_BN_CLICKED(IDC_BUTTON_CANCEL, &MyRegDialog::OnBnClickedButtonCancel)
END_MESSAGE_MAP()


// MyRegDialog 메시지 처리기
void MyRegDialog::AddEndedEventHandler(EndDialogEventHandler* eeh)
{
	this->eeh = eeh;
}

void MyRegDialog::OnCancel()
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	if (eeh)
	{
		eeh->EndedDialog();//나 죽음을 통보한다.
	}
	CDialogEx::OnCancel();
}


void MyRegDialog::OnDtnDatetimechangeDatetimepicker1(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNMHDR);
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	*pResult = 0;
	UpdateData();
	mc.SetCurSel(date1);
}


void MyRegDialog::OnMcnSelchangeMonthcalendar1(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMSELCHANGE pSelChange = reinterpret_cast<LPNMSELCHANGE>(pNMHDR);
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	*pResult = 0;
	UpdateData();
	dtp.SetTime(date2);
}


void MyRegDialog::OnBnClickedButtonImg()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	CFileDialog* fod = new CFileDialog(TRUE, 0);
	INT_PTR re = fod->DoModal();
	if (re != IDOK)
	{
		return;
	}
	image = fod->GetPathName();
	SetImage();
}
void MyRegDialog::SetImage()
{
	CImage cimg;
	try
	{
		cimg.Load(image);
	}
	catch (...)
	{
		image = TEXT("noimage.bmp");
		cimg.Load(image);
	}

	CDC* cdc = pic_con.GetDC();
	RECT rt;
	pic_con.GetClientRect(&rt);
	cimg.StretchBlt(*cdc, 0, 0, rt.right, rt.bottom, SRCCOPY);
}

void MyRegDialog::OnBnClickedButtonSub()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	UpdateData();//컨트롤=>변수
	if (image == TEXT(""))
	{
		image = TEXT("noimage.bmp");
	}
	BookManager& bm = BookManager::GetBookManager();
	if (bm.AddBook(title, content, image, date1) == FALSE)
	{
		MessageBox(TEXT("추가 실패"));	
	}
	InitControls();
}
void MyRegDialog::InitControls()
{
	title = TEXT("");
	content = TEXT("");
	image = TEXT("noimage.bmp");
	SetImage();
	UpdateData(FALSE);//변수=>컨트롤
	BookManager& bm = BookManager::GetBookManager();
	int bno = bm.GetNextBookNo();
	SetDlgItemInt(IDC_STATIC_BNO, bno, FALSE);
}


BOOL MyRegDialog::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// TODO:  여기에 추가 초기화 작업을 추가합니다.	
	InitControls();
	return TRUE;  // return TRUE unless you set the focus to a control
	// 예외: OCX 속성 페이지는 FALSE를 반환해야 합니다.
}


BOOL MyRegDialog::PreTranslateMessage(MSG* pMsg)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		if (GetFocus() == GetDlgItem(IDC_EDIT_CONTENT))
		{
			int len = edit_cont.GetWindowTextLength();
			edit_cont.SetSel(len,len);
			edit_cont.ReplaceSel(TEXT("\r\n"));
		}
	}
	return CDialogEx::PreTranslateMessage(pMsg);
}


void MyRegDialog::OnBnClickedButtonCancel()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	InitControls();
}