대화 상자란 그림 1-4-1과 같은 형태의 윈도를 이야기합니다.

대화 상자는 사용자와 프로그램간에 정보를 교류하기 위한 하나의 윈도로써 부모 윈도 안에 붙어서 출력되는 형태가 아닌 독립된 윈도 형태로 출력되는 윈도를 의미합니다. 대화 상자는 부모 윈도 영역 밖으로 이동이 가능하며 여러 자원(자식 윈도들)을 설정하여 해당 자식 윈도에 입력된 정보를 쉽게 얻고 처리할 수 있는 기능을 가지고 있습니다.

애플리케이션 프로그램을 제작할 때 View 윈도에서는 사용자가 원하는 결과를 출력하고 결과 출력에 필요한 여러 정보들은 대화 상자를 이용하는 것이 일반적입니다. 예를 들어서 그림 파일을 출력하고 이 그림 파일의 색상을 바꾸고자 할 때 파일 열기 항목을 선택하면 파일 대화 상자가 출력됩니다. 여기에서 원하는 파일을 선택하면 View 윈도에서는 선택한 파일을 로드하여 화면에 출력하고, 색상을 바꾸고자 할 때는 바꾸고자 하는 색상을 대화 상자를 통해서 입력받은 다음 해당 색을 그림에서 바꾸어 View 윈도에서 다시 출력할 수가 있습니다.

사용자가 컴퓨터에 어떤 명령을 주거나 현재 정보를 얻고자 할 때는 대화 상자를 많이 이용합니다. 그만큼 대화 상자는 많은 부분에 사용되는 중요한 부분입니다.

대화 상자 폼과 클래스 만들기

대화 상자를 컨트롤하는 클래스는 CDialog 클래스입니다. 이 클래스를 상속받아 우리가 원하는 대화 상자를 만들 수가 있습니다. CDialog를 상속받아서 하나의 대화 상자 클래스를 만드는 방법은 그림 1-4-2와 같습니다.

대화 상자를 만들고자 할 때는 메뉴의 View 항목에서 ClassWizard를 선택하거나 Ctrl+1 키를 누르면 그림 1-4-2의 ①과 같은 화면이 출력됩니다. 이 대화 상자 자원과 함께 ⑥ 항목이 가리키고 있는 컨트롤 도구바도 나타납니다. 여기에서 우리는 필요한 컨트롤을 선택하고 선택한 컨트롤을 대화 상자에 도시할 수가 있습니다. 이렇게 도시한 예가 그림 1-4-2의 ② 항목입니다. ② 항목에서는 간단한 버튼과 콤보 상자, 리스트 상자 등을 선택한 화면입니다. ② 항목을 만들고 나면 일단 대화 상자를 출력하기 위한 폼을 만듭니다. 이 폼을 클래스와 연결해야 합니다. 그림 1-4-2의 ② 항목까지 만들었을 때는 단순하게 RC 파일에 현재 해당되는 내용을 스크립트로 설정한 것 뿐입니다. 실제로 RC 파일을 TEXT 형태로 오픈하면 그림 ② 항목과 같은 내용은 다음과 같이 설정되어 있습니다.

IDD_EXDIALOG_DIALOG DIALOGEX 0, 0, 229, 191

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

EXSTYLE WS_EX_APPWINDOW

CAPTION “ExDialog”

FONT 8, “굴림”, 0, 0, 0x1

BEGIN

DEFPUSHBUTTON   “확인”,IDOK,172,7,50,14

PUSHBUTTON      “취소”,IDCANCEL,172,23,50,14

CONTROL         “Radio1”,IDC_RADIO1,”Button”,BS_AUTORADIOBUTTON, 35,20,38,

10

CONTROL         “Check1”,IDC_CHECK1,”Button”,BS_AUTOCHECKBOX |

WS_TABSTOP,34,37,39,10

EDITTEXT        IDC_EDIT1,35,60,124,14,ES_AUTOHSCROLL

PUSHBUTTON      “Button1”,IDC_BUTTON1,119,87,50,14

COMBOBOX        IDC_COMBO1,33,119,72,56,CBS_DROPDOWN | CBS_SORT |

WS_VSCROLL | WS_TABSTOP

LISTBOX         IDC_LIST,127,115,79,52,LBS_SORT | LBS_NOINTEGRALHEIGHT |

WS_VSCROLL | WS_TABSTOP

END

과거 윈도 프로그램을 작성할 때는 RC 파일이라는 스크립트 파일에 컨트롤들을 어느 위치에 배칠할 것인가를 스크립트 명령 형식으로 위와 같이 기록하였고, 이 기록된 RC 파일을 컴파일하여 RES 파일로 만든 다음 실행 파일을 만들 때 함께 링크합니다. ② 항목까지의 작업은 단순하게 어떤 형태의 대화 상자인가라는 폼만 만든 것 뿐입니다. 이 폼을 연결하는 하나의 윈도가 있어야 합니다. 폼을 연결하는 윈도로는 CDialog, CFormView, CPropertyPage 등이 있습니다. 이 중 일반적인 대화 상자는 CDialog를 이용합니다. 이 CDialog와 연결하기 위해서는 그림 1-4-2의 ② 항목에서 View 메뉴의 ClassWizard를 실행하거나 Ctrl+w 키를 클릭하여 ClassWizard를 실행시키면 됩니다. ClassWizard가 실행된 화면이 그림 1-4-2의 ③ 항목입니다.

클래스 위저드가 실행되면서 현재 대화 상자와 연결되어 있는 클래스를 찾습니다. 만일 클래스가 없다면 그림 1-4-2의 ③ 항목과 같은 화면이 나옵니다. 이 화면은 “현재 ID가 IDD_DIALOG1인 대화 상자와 연결되어 있는 클래스가 없습니다. 이 대화 상자와 클래스를 연결할까요?”라는 의미입니다. 이 상태에서 OK 버튼을 클릭하면 그림 1-4-2의 ④ 항목과 같은 화면이 나타납니다. 이 화면은 새로운 클래스를 만들 때 나타나는 화면입니다. 이 화면의 ⑩ 항목에 CMyDialog라고 입력하였다면 다음과 같은 의미가 됩니다.

“새로 만든 대화 상자 폼(ID가 IDD_DIALOG1, ⓒ 항목 참조)과 연결되는 클래스명은 CMyDialog이며(⑩ 항목 참조), 이 클래스는 CDialog를 상속받았고(ⓑ 항목 참조),  이 클래스가 설정되어 있는 파일은 MyDialog.cpp입니다(ⓐ 항목 참조).”

즉 1-4-2의 ④ 항목은 새로 만든 대화 상자 폼과 연결되는 클래스를 전반적으로 만들어주는 형태입니다. ④ 항목처럼 대화 상자를 만든 다음에 이것을 실행하고자 한다면 다음과 같이 합니다.

#include “MyDialog.h”

:// 중간 생략

CMyDialog pDlg

pDlg.DoModal(); // 대화 상자 실행

먼저 새로 만든 대화 상자 클래스의 헤더를 #include합니다. 이 때 해더는 그림 1-4-2의 ④ 항목의 ⓐ에 설정되어 있는 소스 파일과 파일명이 같고 확장자가 H인 파일이 됩니다.

새로 만든 대화 상자가 CMyDialog이면 이 클래스의 변수를 설정하고 CDialog 내부함수인 DoModal() 함수를 실행시키면 대화 상자가 화면에 나타납니다.

대화 상자 안의 컨트롤과의 연결

지금까지는 대화 상자를 만들고 실행하는 방법에 대해서 설명하였습니다. 본 항목에서는 대화 상자 안에 있는 여러 컨트롤들을 클래스와 연결하는 방법에 대해서 설명하겠습니다. 그림 1-4-3은 대화 상자 안의 컨트롤을 클래스와 연결하는 방법을 나타낸 것입니다.

그림 1-4-3은 대화 상자 안의 에디터 상자를 새로 만든 클래스의 멤버 변수와 연결하는 방법을 도시한 것입니다. 그림 1-4-3의 ①과 같은 화면에서 Ctrl 키를 누른 상태에서 에디터 상자를 더블 클릭하면 ②와 같은 화면이 나타납니다. 이 화면이 에디터 상자와 연결되는 멤버를 설정하는 대화 상자입니다.

⑥ 항목은 에디터 상자와 연결할 멤버 변수명을 입력하는 난입니다. 이 항목에 여러분이 설정하고자 하는 멤버 변수를 입력하면 됩니다.

⑦ 항목은 이 변수를 어떻게 연결할 것인가의 방법을 제시합니다. 대표적인 방법으로 Value와 Control 두 가지가 있습니다. Value일 경우에는 컨트롤에 설정되어 있는 값만 연결하겠다는 의미이며, Control일 경우에는 해당 컨트롤을 제어할 수 있는 컨트롤 클래스와 연결하겠다는 의미입니다. 본 장에서 이 내용에 대해서 자세히 설명할 것입니다.

⑧ 항목은 에디터 상자와 연결되는 변수형을 의미합니다. 현재 CString으로 설정하였기 때문에 에디터 상자는 CString형의 변수와 연결되는 것입니다. 이렇게 설정하면 대화 상자 클래스의 소스 부분에서 DoDataExchange라는 함수에 그림 1-4-3의 ③ 항목과 같이 설정됩니다. 또한 헤더 파일에는 ⑤ 항목과 같이 에디터 상자와 연결할 변수가 선언됩니다.

대화 상자 클래스, 즉 CDialog나 여러 대화 상자 폼과 연결되는 클래스에는 DoDataExchange라는 함수가 있습니다. 이 함수는 대화 상자의 여러 자원을 연결할 수 있는 기능을 가지고 있습니다. 이 함수 안에 DDX_Text 계열 함수와 DDX_Control 계열 함수 등을 이용하여 자원을 연결할 수가 있습니다. DDX_Text는 그림 1-4-3의 ⑦ 항목이 Value일 경우이며, DDX_Control은 ⑦ 항목이 Control일 경우입니다. 그림 1-4-3의 예에서는 Value이기 때문에 DDX_Text를 사용한 것입니다.

그림 1-4-3의 ① 그림에서 콤보 상자를 선택하여 같은 방법으로 클래스와 연결할 때 ⑦ 항목을 Control로 설정하면 소스에는 다음과 같은 내용이 삽입됩니다.

헤더 파일 :

CComboBox m_listCombo;

DoDataExchange:

void CExDialogDlg::DoDataExchange(CDataExchange * pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExDialogDlg)

DDX_Control(pDX, IDC_LIST1, m_listCombo);

DDX_Text(pDX, IDC_EDIT1, m_strEdit);

//}}AFX_DATA_MAP

}

즉 콤보 상자 IDC_LIST1은 m_listCombo와 DDX_Control 형태로 연결되었으며 m_listCombo는 CComboBox형을 상속받은 것을 의미합니다. 이와 같이 하였을 경우 m_listCombo는 CComboBox형이므로 CComboBox 클래스의 멤버 함수들을 마음대로 사용할 수가 있습니다. 이렇게 멤버 함수를 사용하여 대화 상자 안의 콤보 상자를 컨트롤할 수 있습니다. 한 가지 예를 들어서 CComboBox의 멤버 함수 중 데이터를 입력하는 함수는 AddString입니다. 이 함수를 이용하여 다음과 같이 하면

m_listCombo.AddString(“데이터1”);

그림 1-4-3의 ⑨ 항목인 콤보 상자에 “데이터1”이라는 문자가 삽입됩니다.

DDX_Text를 사용할 경우에는 데이터 전송 함수와 데이터 얻기 함수를 실행해야 합니다. 그림 1-4-4가 이 함수에 대한 내용을 설명한 것입니다.

DDX_Text 함수를 이용하여 CString형인 m_strEdit를 연결하였을 때 그림 1-4-4의 ⑤ 항목인 에디터 상자에 입력된 데이터를 m_strEdit로 가져오고자 한다면 UpdateData(TRUE) 함수를 사용합니다(② 항목 참조). 만약 m_strEdit에 특정값을 입력하고 이 값을 그대로 에디터 상자에 전송하고자 한다면 UpdateData(FALSE); 라는 함수를 사용하면 됩니다(③ 항목 참조).

UpdateData 함수는 DDX_Text 계열, 즉 Value 형태로 연결되었을 때 사용합니다. DDX_Control로 연결되었을 경우에는 컨트롤 자체를 핸들링하는 클래스와 연결하였기 때문에 UpdateData 함수를 사용할 필요는 없습니다.

컨트롤 연결 없이 사용하기

전 항목에서 컨트롤과 DoDataExchange 함수를 연결하여 사용하였습니다. 꼭 DoDataExchange를 이용하여야만 하는가? 아닙니다. 다른 방법도 있습니다. 즉 클래스 내부의 멤버 함수와 연결하지 않고 해당 컨트롤 핸들을 바로 얻을 수 있습니다. 이 함수가 GetDlgItem입니다. 그림 1-4-4의 ⑥의 리스트 상자를 DoDataExchange를 이용하지 않고 받는 방법은 다음과 같습니다.

CListBox * plistBox=(CListBox * )GetDlgItem(IDC_LIST1);

GetDlgItem의 인자는 바로 얻고자 하는 컨트롤의 ID들입니다. GetDlgItem은 대화 상자 안에 있는 ID의 해당 윈도 클래스 CWnd의 포인터를 리턴합니다. 실제로는 다음과 같은 형태라고 볼 수 있습니다.

CWnd * pWnd=GetDlgItem(IDC_LIST1);

위와 같은 형태에서 pWnd는 CWnd의 형이기 때문에 리스트 상자 형태의 윈도인 CListBox의 형 멤버를 이용할 수가 없습니다. 이렇기 때문에(CListBox*) 캐스팅 연산자를 이용하여 CListBox의 형인 plistBox에 연결한 것입니다.

위와 같이 연결하면 대화 상자에 있는 리스트 상자를 바로 제어할 수가 있습니다. 만약 리스트 상자에 “데이터1”이라는 함수를 실행하고자 한다면 다음과 같이 하면 됩니다.

CListBox * plistBox=(CListBox * )GetDlgItem(IDC_LIST1);

pListBox->AddString(“데이터1”);

대화 상자만 있는 프로그램

어떤 프로그램들은 대화 상자 한 개만 설정되고 이 대화 상자가 종료되면 프로그램이 종료되는 프로그램도 있습니다. 그림 1-4-5는 대화 상자 한 개만 출력되는 프로그램의 예입니다.

그림 1-4-5는 제어판에서 디스플레이 항목을 선택하였을 때 나타나는 화면입니다. 이 화면은 MFC 구조에서 볼 때 View와 프레임이 없이 단순하게 대화 상자만 출력되는 형태입니다. 이렇게 대화 상자 한 개만 출력되는 프로그램을 만들고자 할 경우 AppWizard의 Step1 항목에서 Dialog based를 선택하면 됩니다. 그림 1-4-6은 AppWizard에서 Dialog based를 선택한 화면입니다.

그림 1-4-6 상태에서 Next 버튼을 클릭하면 그림 1-4-7과 같은 화면이 나타납니다.

Dialog base 항목을 선택하면 AppWizard에서는 그림 1-4-7과 같이 다른 형태의 위저드 화면이 나타납니다. ① 항목은 대화 상자의 시스템 메뉴에 본 프로그램에 대한 안내문이 있는 대화 상자를 출력할 것인가를 설정하는 항목이며, ② 항목은 도움말 항목을 설정할 것인가이며, ③ 항목은 대화 상자의 자원 형태를 3D 스타일로 설정할 것인가를 물어보는 형태입니다. ① 항목을 체크하면 대화 상자 시스템 메뉴에 About이라는 항목이 설정되고, 이 항목을 클릭하면 일반 SDI나 MDI에 설정되어 있는 도움말의 정보 항목을 클릭했을 때와 같은 형태의 대화 상자가 출력됩니다.

위와 같은 형태로 대화 상자만 있는 프로그램을 만들면 표1과 같은 클래스가 만들어 집니다.

표1   대화 상자 형태의 프로그램을 제작하였을 경우의 클래스 리스트

MyDialog로 프로젝트를 만들었을 경우

클래스명	Base 클래스	내용

CMyDialogApp	CWinApp	애플리케이션 클래스

CMyDialogDlg	CDialog	대화 상자 클래스

CAboutDlg	CDialog	정보 설정 대화 상자 클래스

표1에서 보듯이 대화 상자 형태의 프로그램을 AppWizard로 만들면 애플리케이션 클래스와 CDialog 클래스만 생성됩니다. 이 대화 상자 형태의 프로그램이 실제 어떻게 구동되는가를 알고자 한다면 App 클래스의 InitInstance() 함수를 참조하면 됩니다. 다음은 InitInstance 함수의 내용입니다.

BOOL CMyDialogApp::InitInstance()

{

AfxEnableControlContainer();

#ifdef _AFXDLL

Enable3dControls();   // Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic(); // Call this when linking to MFC statically

#endif

CMyDialogDlg dlg;

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

}

else if (nResponse == IDCANCEL)

{

}

return FALSE;

}

실제적으로 보면 애플리케이션에 대화 상자를 만들고 이 대화 상자를 m_pMainWnd로 설정한 다음 DoModal() 함수를 이용하여 실행한 것밖에는 없습니다. 결국 대화 상자 한 개만 만드는 프로그램이나 내부에서 대화 상자를 출력할 때 대화 상자를 출력하는 방법은 같습니다. DoModal()이라는 함수를 실행하면 대화 상자가 종료되면서 종료값을 리턴합니다. OK 버튼을 클릭하였을 경우 린턴값은 IDOK이며 Cancel 버튼을 클릭하였을 경우에는 IDCANCEL이 리턴됩니다. 이외에도 사용자가 대화 상자 내부에서 적당한 값을 설정하여 리턴할 수 있습니다. 예를 들어서 456이라는 값을 리턴하면서 종료하고 싶다면 대화 상자 내부에서 다음과 같이 하면 됩니다.

EndDialog(456);

위와 같이 하면 대화 상자는 456이라는 값을 리턴하면서 종료됩니다. 456이라는 값을 받고 종료하였을 경우 특정 내용을 실행하고자 한다면 다음과 같이 하면 됩니다.

int re=pDlg->DoModal();

if(re==456)

{

//리턴값이 456일 경우

}

함수 OnInitDialog()

대화 상자를 만들고 나면 초기화할 내용들이 있습니다. 이 때 초기화하는 부분을 주로 기입하는 함수가 OnInitDialog입니다. 이 함수는 클래스 위저드를 이용하여 메시지 항목에서 WM_INITDIALOG를 선택하여 만들면 됩니다. 대화 상자가 출력될 경우 해당 항목에 어떤 값을 미리 입력하여 놓는다든지 해당 항목들을 활성/비활성한다든지 하는 것들은 모두 본 함수에서 하는 것이 좋습니다. 대화 상자만 출력되는 프로그램을 AppWizard를 이용하여 만들었을 경우 기본적인 초기화를 OnInitDialog에 해놓습니다. AppWizard를 이용하여 만든 프로그램에서 OnInitDialog는 다음과 같습니다.

BOOL CMyDialogDlg::OnInitDialog()

{

CDialog::OnInitDialog();

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

// 시스템 메뉴에 정보 항목 설정하는 루틴

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING,

IDM_ABOUTBOX, strAboutMenu);

}

}

// 아이콘 설정 루틴

SetIcon(m_hIcon, TRUE);	// Set big icon

SetIcon(m_hIcon, FALSE);	// Set small icon

return TRUE;  // return TRUE  unless you set the focus to a control

}

이미 기록되어 있는 부분들은 “정보” 항목을 시스템 메뉴에 설정하는 항목과 본 대화 상자의 아이콘을 설정하는 항목입니다. “정보” 항목을 시스템 메뉴에 설정하는 부분은 그림 1-4-7에서 ① 항목이 체크되어 있을 때 삽입됩니다. InitDialog 함수에서 위에 기록된 여러 부분을 꼭 이해할 필요는 없습니다. 본 책을 계속해서 공부하다 보면 자동적으로 알 수 있을 것입니다.

컨트롤들의 이벤트 메시지 얻기

지금까지는 대화 상자를 출력하는 방법과 대화 상자만 있는 프로그램을 작성하는 방법에 대해서 알아보았습니다. 이제 대화 상자 안에 있는 컨트롤들의 이벤트를 얻는 방법에 대해서 알아 보겠습니다. 대화 상자 안에 있는 컨트롤들의 이벤트를 얻는다는 것은 컨트롤들에서 어떤 이벤트가 발생되었을 때 구동되는 함수를 만드는 방법을 의미합니다. 예를 들어 버튼이 눌려졌을 때라든지, 리스트 상자에서 특정 항목을 선택하였을 때라든지 하는 부분들을 의미합니다. 이 이벤트를 얻는 방법들은 매우 간단합니다. 예를 들어서 설명하겠습니다. 그림 1-4-4의 ①과 같은 대화 상자에서 버튼이 눌려졌을 때의 이벤트 메시지를 가로채는 방법은 그림 1-4-8과 같습니다.

그림 1-4-8은 대화 상자 안에 리스트 상자가 있고 이 리스트 상자 아이디가 IDC_LIST1일 경우 항목 선택이 바뀌었을 때 함수를 만드는 방법을 예로 들어 이벤트 함수 만드는 방법을 설명한 것입니다.

클래스 위저드를 실행시킨 후에 그림 1-4-8의 ① 항목처럼 리스트 상자 ID를 선택합니다. 이렇게 선택하면 ② 항목에 해당 컨트롤에서 발생하는 이벤트 리스트가 나옵니다. 리스트 상자를 선택하였을 경우에는 리스트 상자에서 선택 항목이 변경되었을 경우(LBN_SELCHANGE), 더블 클릭했을 경우(LBN_DBLCLK), 포커스가 사라질 경우(LBN_KILLFOCUS) 등등의 메시지가 발생됩니다. 만약 리스트 상자에서 선택항목이 변경되었을 경우에 발생하는 함수를 만들고자 한다면 LBN_SELCHANGE를 선택하여 함수를 만들면 됩니다. ③ 항목이 바로 이렇게 선택한 형태입니다. 이렇게 함수를 만들면 이 함수가 ④ 항목에 등록되면서 만들어지고 “Edit Code” 버튼을 클릭하면 해당 함수 안에서 코딩할 수 있게 됩니다.

대화 상자 프로그램 예제 ExDialog

ExDialog 예제는 대화 상자용 프로그램을 만들고 버튼, 에디터 상자, 콤보 상자, 리스트 상자 등을 설정하고 해당 이벤트 메시지를 받아서 처리하는 예제입니다. 이 예제의 전체적인 구도는 그림 1-4-9와 같습니다.

ExDialog는 AppWizard의 Step1에서 Dialog based 항목을 선택하여 대화 상자를 만든 것입니다.

ExDialog의 출력 화면은 그림 1-4-9의 ①과 같습니다. ② 항목은 라디오 버튼입니다. 이 버튼을 클릭하면 그림 ② 항목과 같이 라디오 버튼이 클릭되었다는 메시지가 ⓐ항목에 출력됩니다. 같은 방법으로 리스트 상자와 콤보 상자, 버튼, 체크 상자 등에서 설정된 메시지들이 ⓐ인 에디터 상자에 출력됩니다. 각 자원들 중 본 항목의 두 번째 소항목에서 설명한 형태로 DoDataExchange 함수 안에 연결시킨 것도 있으며, 그렇지 않은 것들도 있습니다. ② 항목과 ⓐ 항목에서 설명한 방법을 모두 이용하고자 함입니다.

대화 상자 폼과 클래스 연결

AppWizard를 이용하여 프로젝트를 Dialog based로 만들면 빈 대화 상자 폼이 설정됩니다. 이 상태에서 그림 1-4-9와 같은 자원들을 대화 상자에 설정합니다. 자원을 설정한 후에 각 아이디는 그림 1-4-9의 각 항목을 설명한 후에 “( )” 안에 있는 내용대로 설정합니다. 이와 같이 설정한 후 DoDataExchange를 이용하여 각 자원과 클래스 멤버를 연결합니다. 연결할 컨트롤들은 그림 1-4-9의 ③, ⑥, ⓐ입니다. 각 항목을 연결하는 방법은 그림 1-4-3을 참조하기 바랍니다. 이와 같이 연결하면  CExDialogDlg의 헤더와 소스에는 다음과 같이 설정됩니다.

헤더 부분 :

CComboBox m_listCombo;// 콤보 상자 그림 1-4-9의 ⑥

BOOL m_bCheck;// 체크 버튼 그림 1-4-9의 ③

CString m_strEdit;// 에디터 상자 그림 1-4-9의 ⓐ

소스의 DoDataExchange

void CExDialogDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExDialogDlg)

DDX_Control(pDX, IDC_COMBO1, m_listCombo);// 콤보 상자 연결

DDX_Check(pDX, IDC_CHECK1, m_bCheck);// 체크 버튼 연결

DDX_Text(pDX, IDC_EDIT1, m_strEdit);// 에디터 상자 연결

//}}AFX_DATA_MAP

}

지금까지 한 것으로 대화 상자의 콤보 상자와 체크 버튼, 에디터 상자가 m_listCom- bo, m_bCheck, 그리고 m_strEdit로 연결됩니다.

OnInitDialog() 함수 수정

OnInitDialog() 함수에서는 리스트 상자와 콤보 상자에 데이터를 넣고 에디터 상자에는 “메시지 없음”이라는 내용을 설정하도록 하였습니다.

먼저 콤보 상자에 데이터를 넣는 방법을 설명하겠습니다. 콤보 상자는 CComboBox클래스의 변수형인 m_listCombo로 연결되어 있습니다. 따라서 m_listCombo에 데이터를 넣어주면 대화 상자와 콤보 상자에 데이터가 들어가게 됩니다. m_listCombo 안에 데이터를 넣는 함수는 AddString입니다. 이 함수를 이용하여 데이터를 넣은 방법은 다음과 같습니다.

m_listCombo.AddString(“Combo data1”);

위와 같이 하면 콤보 상자에 “Combo data1”이라는 내용을 삽입할 수 있습니다.

이제 m_listCombo에 총 8개의 데이터를 넣어보도록 하겠습니다. 데이터를 넣는 방법은 다음과 같습니다.

//Combo data 1 ~ Combo data 8까지의 데이터를 넣는다.

CString data;

for(int i=0;i<8;i++)

{

data.Format(“Combo data %d”,i);

m_listCombo.AddString(data);

}

이제 리스트 상자에 데이터를 넣어보겠습니다. 리스트 상자는 DoDataExchange에 멤버로 설정되어 있지 않습니다. 이 때 본 항목 ⓐ에서 설명한 GetDlgItem 함수를 이용하여 데이터를 설정합니다. 설정하는 방법은 다음과 같습니다.

for(i=0;i<8;i++)

{

data.Format(“List data %d”,i);

((CListBox *)GetDlgItem(IDC_LIST))->AddString(data);

}

위와 같이 하면 리스트 상자에 List data 1~List data 8까지의 데이터가 들어가게 됩니다.

이제 에디터 상자에 데이터를 넣어보겠습니다. 에디터 상자는 DoDataExchange에 m_strEdit로 연결되어 있습니다. 이 때 단순하게 m_strEdit에 문자열을 설정하면 됩니다.

m_strEdit=”메시지 없음”;

위와 같이 하였다고 해서 에디터 상자에 문자가 출력되는 것은 아닙니다. 한 가지를 더해주어야 합니다.(본 항목 ②와 그림 1-4-4를 참조하기 바랍니다.) UpdateData (TRUE)를 해주어야 합니다.

m_strEdit=”메시지 없음”;

UpdateData(FALSE);

위와 같이 한 OnInitDialog 전체 내용은 다음과 같습니다.

BOOL CExDialogDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Add “About...” menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING,

IDM_ABOUTBOX, strAboutMenu);

}

}

// Set the icon for this dialog.  The framework does this automatically

//  when the application’s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

// TODO: Add extra initialization here

//Combo data 1 ~ Combo data 8까지의 데이터를 넣는다.

CString data;

for(int i=0;i<8;i++)

{

data.Format(“Combo data %d”,i);

m_listCombo.AddString(data);

}

// 리스트 상자에 데이터를 넣는다.

for(i=0;i<8;i++)

{

data.Format(“List data %d”,i);

((CListBox *)GetDlgItem(IDC_LIST))->AddString(data);

}

// 에디터 상자에 데이터를 넣는다.

m_strEdit=”메시지 없음”;

UpdateData(FALSE);

return TRUE;  // return TRUE  unless you set the focus to a control

}

이벤트에 해당하는 함수 만들기

그림 1-4-9의 ②, ③, ④, ⑤, ⑥ 항목에서 이벤트가 발생될 때의 함수를 만들고 이 함수에서 이벤트가 발생되었을 때의 메시지를 ⓐ에 설정합니다. ②, ③, ④, ⑤, ⑥에서 발생하는 이벤트 메시지와 함수는 표2와 같습니다.

표2   각 컨트롤 이벤트 메시지 리스트

컨트롤	이벤트 메시지	함수명	코딩 내용

버튼	BN_CLICKED	void OnButton1	m_strEdit=”버튼이 눌려졌음”;

UpdateData(FALSE);

체크 상자	BN_CLICKED	void OnCheck1()	//체크 상자의 상태를 얻기 위해서 UpdateData 실행

UpdateData(TRUE);

// 에디터 상자에 데이터 넣고

m_strEdit.Format(“체크 상자 상태 %d”,m_b Check);

//UpdateData 실행

UpdateData(FALSE);

라디오 버튼	BN_CLICKED	void OnRadio1()	m_strEdit=”라디오 버튼 클릭”;

UpdateData(FALSE);

콤보 상자	CBN_SELCHANGE void OnSelchangeCombo1()   CString data;

// 현재 위치를 얻는다.

int num=m_listCombo.GetCurSel();

// 현재 위치를 가지고 있는 num을

// 이용하여 특정 위치의 문자열을 얻는다.

m_listCombo.GetLBText(num,data);

m_strEdit.Format(“콤보 상자:%s”,data)

// 에디터 상자에 전송;

UpdateData(FALSE);

리스트 상자	LBN_SELCHANGE  void OnSelchangeList()     CString data;

//GetDlgItem을 이용하여 자원의 윈도 핸들을 받는다.

CListBox *pList=(CListBox *)GetDlgItem (IDC_LIST);

// 현재 위치를 얻는다.

int num=pList->GetCurSel();

// 문자열을 얻는다.

pList->GetText(num,data);

m_strEdit.Format(“리스트 상자 : %s”,data);

//에디터 상자에 전송

UpdateData(FALSE);

표2와 같이 함수를 만들고 해당 내용을 코딩하면 ExDialog 예제는 완성됩니다. 클래스 위저드를 이용하여 이벤트 함수를 만드는 방법은 본 항목의 다섯 번째 소항목에서  설명하였습니다. 참고하기 바랍니다.

ExDialog 소스

다음은 ExDialog의 대화 상자 클래스인 CExDialogDlg 소스와 헤더 부분입니다. 나머지 부분은 제공되는 CD를 참조하기 바랍니다.

// ExDialogDlg.h : header file

//

#if !defined(AFX_EXDIALOGDLG_H__21237A8F_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXDIALOGDLG_H__21237A8F_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

//////////////////////////////////////////////////////////////////////////

// CExDialogDlg dialog

class CExDialogDlg : public CDialog

{

// Construction

public:

CExDialogDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CExDialogDlg)

enum { IDD = IDD_EXDIALOG_DIALOG };

CComboBox m_listCombo;

BOOL m_bCheck;

CString m_strEdit;

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExDialogDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CExDialogDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnButton1();

afx_msg void OnCheck1();

afx_msg void OnSelchangeCombo1();

afx_msg void OnSelchangeList();

afx_msg void OnRadio1();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif

// !defined(AFX_EXDIALOGDLG_H__21237A8F_20B7_11D2_8B56_006097AD9DDC__-

INCLUDED_)

// ExDialogDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExDialog.h”

#include “ExDialogDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);

// DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExDialogDlg dialog

CExDialogDlg::CExDialogDlg(CWnd* pParent /*=NULL*/)

: CDialog(CExDialogDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExDialogDlg)

m_bCheck = FALSE;

m_strEdit = _T(“”);

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CExDialogDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExDialogDlg)

DDX_Control(pDX, IDC_COMBO1, m_listCombo);// 콤보 상자 연결

DDX_Check(pDX, IDC_CHECK1, m_bCheck);// 체크 버튼 연결

DDX_Text(pDX, IDC_EDIT1, m_strEdit);// 에디터 상자 연결

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExDialogDlg, CDialog)

//{{AFX_MSG_MAP(CExDialogDlg)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_BUTTON1, OnButton1)

ON_BN_CLICKED(IDC_CHECK1, OnCheck1)

ON_CBN_SELCHANGE(IDC_COMBO1, OnSelchangeCombo1)

ON_LBN_SELCHANGE(IDC_LIST, OnSelchangeList)

ON_BN_CLICKED(IDC_RADIO1, OnRadio1)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////

// CExDialogDlg message handlers

BOOL CExDialogDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Add “About...” menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

}

}

// Set the icon for this dialog.  The framework does this automatically

//  when the application’s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

// TODO: Add extra initialization here

// Combo data 1 ~ Combo data 8까지의 데이터를 넣는다.

CString data;

for(int i=0;i<8;i++)

{

data.Format(“Combo data %d”,i);

m_listCombo.AddString(data);

}

// 리스트 상자에 데이터를 넣는다.

for(i=0;i<8;i++)

{

data.Format(“List data %d”,i);

((CListBox *)GetDlgItem(IDC_LIST))->AddString(data);

}

// 에디터 상자에 데이터를 넣는다.

m_strEdit=”메시지 없음”;

UpdateData(FALSE);

return TRUE;  // return TRUE  unless you set the focus to a control

}

void CExDialogDlg::OnSysCommand(UINT nID, LPARAM lParam)

{

if ((nID & 0xFFF0) == IDM_ABOUTBOX)

{

CAboutDlg dlgAbout;

dlgAbout.DoModal();

}

else

{

CDialog::OnSysCommand(nID, lParam);

}

}

// If you add a minimize button to your dialog, you will need the code

below

// to draw the icon.  For MFC applications using the document/view model,

// this is automatically done for you by the framework.

void CExDialogDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user

drags

// the minimized window.

HCURSOR CExDialogDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CExDialogDlg::OnButton1()

{

m_strEdit=”버튼이 눌려졌음”;

UpdateData(FALSE);

}

void CExDialogDlg::OnCheck1()

{

UpdateData(TRUE);

m_strEdit.Format(“체크 상자 상태 %d”,m_bCheck);

UpdateData(FALSE);

}

void CExDialogDlg::OnSelchangeCombo1()

{

CString data;

// 현재 위치를 얻는다.

int num=m_listCombo.GetCurSel();

// 현재 위치를 가지고 있는 num을

// 이용하여 특정 위치의

// 문자열을 얻는다.

m_listCombo.GetLBText(num,data);

// 에디터 상자에 전송

m_strEdit.Format(“콤보 상자:%s”,data);

UpdateData(FALSE);

}

void CExDialogDlg::OnSelchangeList()

{

CString data;

// GetDlgItem을 이용하여 자원의 윈도

// 핸들을 받는다.

CListBox *pList=(CListBox *)GetDlgItem(IDC_LIST);

// 현재 위치를 얻는다.

int num=pList->GetCurSel();

// 문자열을 받는다.

pList->GetText(num,data);

// 에디터 상자에 전송

m_strEdit.Format(“리스트 상자:%s”,data);

UpdateData(FALSE);

}

void CExDialogDlg::OnRadio1()

{

m_strEdit=”라디오 버튼 클릭”;

UpdateData(FALSE);

}

본 항목에서는 모달 대화 상자와 모달리스 대화 상자에 대해서 설명합니다. 모달 대화 상자는 전 항목에서 설명한 방법대로 DoModal() 함수를 이용하여 대화 상자를 출력하는 방법입니다. 모달 대화 상자는 대화 상자가 실행되면 부모 윈도가 포커스를 갖지 못합니다. 대화 상자를 실행한 후에 부모 윈도의 메뉴 항목을 클릭하거나 View 윈도 판을 마우스로 클릭하였을 때 “삑” 하는 소리만 날 뿐 활성화가 되지 않습니다. 즉 모든 포커스를 대화 상자가 갖는 형태입니다.

모달리스 대화 상자는 모달 대화 상자와 반대로 부모 윈도에서 포커스를 가질 수가 있습니다. 즉 부모 윈도를 클릭하면 부모 윈도가 활성화되는 경우를 의미합니다. 모달리스 대화 상자를 사용할 때는 주로 현재 대화 상자가 가동되면서 동시에 부모 윈도의 여러 항목들이 동시에 가동될 때입니다. 예를 들어 e글 프로그램에서 찾기 항목을 실행시켰을 때 찾기 대화 상자가 가동되면서 에디터 윈도에서 글자를 수정할 수가 있습니다. 이 때 사용하는 방법이 모달리스 대화 상자입니다.

모달리스 대화 상자를 출력할 경우에는 DoModal 함수를 사용하지 않고 윈도를 만드는 형태로 사용합니다. 그림 1-4-10은 모달리스 대화 상자를 출력하는 예를 도시한 것입니다. 모달리스와 모달 대화 상자를 사용한 예제는 ExFormView이며 다음 항목에서 설명할 것입니다.

모달리스 대화 상자는 먼저 Create 함수를 이용하여 만듭니다. CDialog의 Create 함수의 첫 번째 인자는 설정할 대화 상자 폼 ID이며, 두 번째 윈도는 부모 윈도 핸들 클래스인 CWnd입니다. 이렇게 대화 상자를 만든 후에 실행하고자 할 경우 이 때 ShowWindow라는 CWnd의 멤버 함수를 이용하여 화면에 출력합니다. CDialog 또한 CWnd를 상속받았기 때문에 CWnd의 멤버 함수를 이용할 수 있는 것입니다.

모달리스 대화 상자는 일반적으로 대화 상자 클래스를 헤더에 설정하고 부모 윈도가 만들어지는 부분에서 미리 Creat 함수를 이용하여 윈도를 만들어 두었다가 대화 상자를 실행하고자 하는 부분에서 ShowWindow 함수를 이용합니다.

FormView란 대화 상자처럼 컨트롤을 포함한 윈도이면서 CView를 상속받은 형태를 의미합니다. 그림 1-4-11은 FormView로 만들어진 프로그램입니다.

FormView는 CView를 상속받았기 때문에 템플릿의 한 구성으로 설정됩니다. 즉 View 윈도 대신으로 FormView 윈도가 들어갈 수 있다는 것입니다. FormView는 컨트롤들을 자식 윈도로 가질 수 있기 때문에 컨트롤을 설정한 형태의 프로그램을 제작하고자 할 때는 자주 사용되는 윈도입니다.

FormView를 사용한 프로젝트 만들기

FormView를 사용한 프로젝트를 만들고자 할 경우 AppWizard Step6에서 View 윈도 클래스는 CFormView를 상속받게 해야 합니다. 그림 1-4-12는 ExForm이라는 프로젝트를 만들 때 View를 FormView로 설정하는 형태입니다.

그림 1-4-12처럼 AppWizard의 Step6이 설정되었을 때 View 클래스가  CFormView를 상속받게 하면 됩니다. 이렇게 하고 프로젝트를 만들면 IDD_[프로젝트명]_FORM이라는 빈 형태의 대화 상자 폼이 만들어지게 됩니다. 이 대화 상자는 바로 View와 연결된 대화 상자입니다. 그림 1-4-13은 ExForm이라는 프로젝트를 만들었을 경우 생성되는 IDD_EXFORM_FORM이라는 대화 상자 폼이 출력된 형태입니다.

그림 1-4-13에서 보면 대화 상자를 만들 때 사용한 대화 상자 폼 형태와 FormView에서 사용한 대화 상자 폼이 약간 다르다는 것을 볼 수가 있습니다. 먼저 폼 Style 항목이 다릅니다. 그림 1-4-13의 ③ 항목처럼 Style이 Child입니다. 대화 상자일 경우에는 이 Style이 Popup입니다. Popup과 Child의 차이점은 부모 윈도에 삽입되어 설정되는가 아니면 독립적인 형태의 윈도로 나타나는가입니다.

View는 메인프레임이나 자식 프레임에 삽입된 자식 윈도 형태입니다. 따라서 대화 상자처럼 밖으로 출력되는 형태가 아닙니다. 그렇기 때문에 Child 형태가 설정된 것입니다. 또한 View는 경계가 필요 없습니다. 외곽 경계선에 해당하는 것이 바로 프레임 윈도이기 때문에 FormView에서 Border 형태는 None입니다. 이 형태를 설정한 항목은 그림 1-4-13의 ④와 같습니다.

그외에 컨트롤들을 등록하거나 컨트롤과 클래스 멤버를 연결하는 DoData-Exchange 등은 대화 상자를 만드는 것과 같습니다.

FormView에는 CDialog처럼 OnInitDialog 함수가 없습니다. 그렇기 때문에 초기화할 때는 OnInitialUpdate를 이용하면 됩니다. 이 함수는 클래스 위저드를 이용하여 OnInitialUpdate를 선택하면 됩니다.

FormView를 이용한 예제 ExFormView

ExFormView는 FormView를 이용한 예제입니다. 이 예제는 AppWizard를 이용하여 SDI 형태로 만든 다음 Step6에서 CExFormViewView 클래스는 CFormView 클래스를 상속받도록 합니다. 이렇게 만든 다음 설정한 대화 상자 폼 IDD_EXFORMV- IEW_ FORM은 그림 1-4-14와 같이 합니다.

실제적으로 보면 전에 제작한 ExDialog에서 작성한 폼 형태와 같습니다. 차이점은 리스트 상자를 ExDialog에서는 GetDlgItem을 이용하여 윈도 핸들을 얻었으나 본 예제에서는 DoDataExchange 함수를 이용하여 연결하였습니다. ExFormViewView클래스에서 각 항목을 핸들링하여 에디터 상자에 출력하는 내용은 ExDialog에서 작성한 방법과 비슷합니다. 전에 설명하였듯이, OnInitDialog에서 작성한 부분이 OnInitialUpdate로 바뀐 것과 리스트 상자를 m_listBox라는 멤버 함수를 이용하여  컨트롤하는 것을 제외하면 모든 내용이 ExDalog와 같게 프로그램을 작성하면 됩니다.

ExFormView 프로그램의 전체 구조는 그림 1-4-15와 같습니다.

ExFormView 안의 리스트 상자, 콤보 상자, 버튼 등등의 컨트롤들에서 이벤트 메시지를 받아서 처리하는 부분은 그림 1-4-14에서 설명하였습니다. ExFormView에서는 메뉴에 두 개의 항목을 설정하였습니다. 첫 번째 항목은 “모달 대화 상자”이며 두 번째 항목은 “모달리스 대화 상자”입니다.

“모달 대화 상자”가 선택되면 그림 1-4-15의 ③ 항목과 같은 대화 상자가 모달 형태로 출력되며, “모달리스 대화 상자”가 출력되면 ③ 항목과 같은 대화 상자가 모달리스 형태로 출력됩니다. 실제 프로그래밍 방법은 다음과 같습니다.

대화 상자 폼에 컨트롤 설정과 멤버 연결

프로젝트를 만든 다음에 그림 1-4-14와 같이 IDD_EXFORM_FORM을 만들고 각 자원의 ID를 그림 1-4-14에 설명한 내용대로 설정합니다. 이렇게 한 후에 ③, ⑤, ⑥, ⓐ 항목을 DoDataExchange를 이용하여 클래스 멤버와 연결시킵니다. 이렇게 연결된 헤더와 DoDataExchange 함수는 다음과 같습니다.

헤더부 :

CListBox m_listBox;// 리스트 상자

CComboBox m_listCombo;// 콤보 상자

CString m_strEdit;// 에디터 상자

BOOL m_bCheck;// 체크 버튼

DoDataExchange 함수 :

void CExFormViewView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExFormViewView)

DDX_Control(pDX, IDC_LIST1, m_listBox);

DDX_Control(pDX, IDC_COMBO1, m_listCombo);

DDX_Text(pDX, IDC_EDIT1, m_strEdit);

DDX_Check(pDX, IDC_CHECK1, m_bCheck);

//}}AFX_DATA_MAP

}

CMyDlg 대화 상자 클래스 만들기

메뉴 항목에서 “모달 대화 상자”와 “모달리스 대화 상자” 항목을 선택하였을 때 출력할 대화 상자 폼을 만듭니다. 이 대화 상자 폼은 그림 1-4-16과 같습니다.

그림 1-4-16처럼 대화 상자 폼을 만든 다음 이 대화 상자 폼을 연결할 클래스를 만듭니다. 연결할 클래스를 만드는 방법은 본 장의 1항목에서 자세히 설명하였습니다. 이 방법으로 위의 대화 상자를 구동하는 클래스를 CMyDlg로 만듭니다. 각 자원의 ID명은 상관없이 제작하여도 됩니다. CMyDlg는 단순히 아무런 기능이 없이 대화 상자가 모달 또는 모달리스 형태로 출력되는 예를 위한 클래스이기 때문입니다.

이와 같이 CMyDlg를 만든 다음 CMyDlg 형태의 멤버 변수를 View 클래스 헤더에 설정합니다.

헤더 부분 :

CMyDlg dDlg;

위와 같이 설정하고 View 클래스 헤더 상단부에 CMyDlg가 설정되어 있는 헤더를 #include합니다.

#include “MyDlg.h”

위의 항목을 #include하지 않으면 CMyDl 클래스를 인식할 수 없기 때문에 컴파일시 에러가 좌르르 나오게 될 것입니다.

OnInitialUpdate() 수정하기

FormView에서는 OnInitialUpdate 함수에서 초기화를 한다고 전에 설명하였습니다. 클래스 위저드를 이용하여 View 클래스에서 OnInitialUpdate() 함수를 선택하면 해당 함수로 이동할 수 있습니다. 이 함수를 다음과 같이 수정합니다.

void CExFormViewView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

// 콤보 상자에 데이터 넣기

CString data;

for(int i=0;i<8;i++)

{

data.Format(“Combo data %d”,i);

m_listCombo.AddString(data);

}

// 리스트 상자에 데이터 넣기

for(i=0;i<8;i++)

{

data.Format(“List data %d”,i);

m_listBox.AddString(data);

}

// 에디터 상자에 데이터 넣기

m_strEdit=”메시지 없음”;

UpdateData(FALSE);

// 대화 상자 만들기. 그림 1-4-10의 ① 항목 참조

dDlg.Create(IDD_DIALOG1,this);

}

OnInitialUpdate 함수의 마지막 부분에 dDlg.Create라는 함수 부분이 있을 것입니다. 이 부분은 모달리스 대화 상자를 출력하기 위해서 미리 윈도를 만들어 놓은 부분입니다. 초기화 부분에서 윈도를 만들고 모달리스 대화 상자 항목을 선택하면 ShowWindow 함수로서 대화 상자를 구동하기 때문입니다. 그림 1-4-10을 참조하기 바랍니다.

모달 대화 상자 선택시 함수 만들기

메뉴에서 모달 대화 상자를 선택하였을 때 구동하는 함수를 만듭니다. 이 함수를 만든 다음 모달 대화 상자를 구동하는 내용을 다음과 같이 코딩합니다.

// 모달 대화 상자 메뉴를 선택했을 때

void CExFormViewView::OnModaldlg()

{

CMyDlg *pDlg = new CMyDlg;

if(pDlg->DoModal()==IDOK)

{

m_strEdit=”모달 대화 상자 OK 클릭”;

UpdateData(FALSE);

}

delete pDlg;

}

위와 같이 하면 “모달 대화 상자” 항목을 선택했을 때 모달 형태의 대화 상자가 출력됩니다.

모달리스 대화 상자 선택시 함수 만들기

“모달리스 대화 상자” 메뉴 항목을 선택했을 때 모달리스 대화 상자를 출력하는 내용은 ShowWindow 함수 단 한 개를 이용합니다.(그림 1-4-10을 참조하기 바랍니다.) 모달리스 대화 상자를 선택했을 때의 함수는 다음과 같습니다.

// 모달리스 대화 상자 메뉴 선택

void CExFormViewView::OnModalless()

{

dDlg.ShowWindow(SW_SHOW);

}

나머지 부분은 소스를 참조하기 바랍니다. 본 항목에서 설명하지 않은 부분들은 ExDialog 예제에서 설명한 내용과 비슷합니다. ExDialog를 이해했다면 소스만 보고 알 수 있을 것입니다.

예제 ExFormView 소스

ExFormView 예제는 View 클래스에서만 코딩하였습니다. 다음은 View 클래스 소스 부분입니다. 나머지 부분은 제공되는 CD를 참조하기 바랍니다.

// ExFormViewView.h : interface of the CExFormViewView class

//

//////////////////////////////////////////////////////////////////////////

#if

!defined(AFX_EXFORMVIEWVIEW_H__21237AA3_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXFORMVIEWVIEW_H__21237AA3_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#include “MyDlg.h” // Added by ClassView

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExFormViewView : public CFormView

{

protected: // create from serialization only

CExFormViewView();

DECLARE_DYNCREATE(CExFormViewView)

public:

//{{AFX_DATA(CExFormViewView)

enum { IDD = IDD_EXFORMVIEW_FORM };

CListBox m_listBox;// 리스트 상자

CComboBox m_listCombo;// 콤보 상자

CString m_strEdit;// 에디터 상자

BOOL m_bCheck;// 체크 버튼

//}}AFX_DATA

// Attributes

public:

CExFormViewDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExFormViewView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV support

virtual void OnInitialUpdate(); // called first time after construct

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

CMyDlg dDlg;

virtual ~CExFormViewView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExFormViewView)

afx_msg void OnButton1();

afx_msg void OnCheck1();

afx_msg void OnSelchangeCombo1();

afx_msg void OnSelchangeList1();

afx_msg void OnRadio1();

afx_msg void OnModaldlg();

afx_msg void OnModalless();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExFormViewView.cpp

inline CExFormViewDoc* CExFormViewView::GetDocument()

{ return (CExFormViewDoc*)m_pDocument; }

#endif

///////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //

!defined(AFX_EXFORMVIEWVIEW_H__21237AA3_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExFormViewView.cpp : implementation of the CExFormViewView class

//

#include “stdafx.h”

#include “ExFormView.h”

#include “ExFormViewDoc.h”

#include “ExFormViewView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////

// CExFormViewView

IMPLEMENT_DYNCREATE(CExFormViewView, CFormView)

BEGIN_MESSAGE_MAP(CExFormViewView, CFormView)

//{{AFX_MSG_MAP(CExFormViewView)

ON_BN_CLICKED(IDC_BUTTON1, OnButton1)

ON_BN_CLICKED(IDC_CHECK1, OnCheck1)

ON_CBN_SELCHANGE(IDC_COMBO1, OnSelchangeCombo1)

ON_LBN_SELCHANGE(IDC_LIST1, OnSelchangeList1)

ON_BN_CLICKED(IDC_RADIO1, OnRadio1)

ON_COMMAND(ID_MODALDLG, OnModaldlg)

ON_COMMAND(ID_MODALLESS, OnModalless)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

END_MESSAGE_MAP()

///////////////////////////////////////////////

// CExFormViewView construction/destruction

CExFormViewView::CExFormViewView()

: CFormView(CExFormViewView::IDD)

{

//{{AFX_DATA_INIT(CExFormViewView)

m_strEdit = _T(“”);

m_bCheck = FALSE;

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CExFormViewView::~CExFormViewView()

{

}

void CExFormViewView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExFormViewView)

DDX_Control(pDX, IDC_LIST1, m_listBox);

DDX_Control(pDX, IDC_COMBO1, m_listCombo);

DDX_Text(pDX, IDC_EDIT1, m_strEdit);

DDX_Check(pDX, IDC_CHECK1, m_bCheck);

//}}AFX_DATA_MAP

}

BOOL CExFormViewView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

void CExFormViewView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

// 콤보 상자에 데이터 넣기

CString data;

for(int i=0;i<8;i++)

{

data.Format(“Combo data %d”,i);

m_listCombo.AddString(data);

}

// 리스트 상자에 데이터 넣기

for(i=0;i<8;i++)

{

data.Format(“List data %d”,i);

m_listBox.AddString(data);

}

// 에디터 상자에 데이터 넣기

m_strEdit=”메시지 없음”;

UpdateData(FALSE);

// 대화 상자 만들기

dDlg.Create(IDD_DIALOG1,this);

}

/////////////////////////////////////////////////////////

// CExFormViewView printing

BOOL CExFormViewView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExFormViewView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExFormViewView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CExFormViewView::OnPrint(CDC* pDC, CPrintInfo* /*pInfo*/)

{

// TODO: add customized printing code here

}

/////////////////////////////////////////////////////////

// CExFormViewView diagnostics

#ifdef _DEBUG

void CExFormViewView::AssertValid() const

{

CFormView::AssertValid();

}

void CExFormViewView::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CExFormViewDoc* CExFormViewView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExFormViewDoc)));

return (CExFormViewDoc*)m_pDocument;

}

#endif //_DEBUG

//////////////////////////////////////////////////////

// CExFormViewView message handlers

// 버튼이 눌려졌을 때

void CExFormViewView::OnButton1()

{

m_strEdit=”버튼이 눌려졌음”;

UpdateData(FALSE);

}

// 체크 버튼이 눌려졌을 때

void CExFormViewView::OnCheck1()

{

UpdateData(TRUE);

m_strEdit.Format(“체크 상자 상태 %d”,m_bCheck);

UpdateData(FALSE);

}

// 콤보 상자에서 새로운 항목이 선택되었을 때

{{

}}void CExFormViewView::OnSelchangeCombo1()

{

CString data;

int num=m_listCombo.GetCurSel();

m_listCombo.GetLBText(num,data);

m_strEdit.Format(“콤보 상자:%s”,data);

UpdateData(FALSE);

}

// 리스트 상자에서 새로운 항목이 선택되었을 때

void CExFormViewView::OnSelchangeList1()

{

CString data;

int num=m_listBox.GetCurSel();

m_listBox.GetText(num,data);

m_strEdit.Format(“리스트 상자:%s”,data);

UpdateData(FALSE);

}

// 라디오 버튼이 클릭되었을 때

void CExFormViewView::OnRadio1()

{

m_strEdit=”라디오 버튼 클릭”;

UpdateData(FALSE);

}

// 모달 대화 상자 메뉴 선택

void CExFormViewView::OnModaldlg()

{

CMyDlg *pDlg = new CMyDlg;

if(pDlg->DoModal()==IDOK)

{

m_strEdit=”모달 대화 상자 OK 클릭”;

UpdateData(FALSE);

}

delete pDlg;

}

// 모달리스 대화 상자 메뉴 선택

void CExFormViewView::OnModalless()

{

dDlg.ShowWindow(SW_SHOW);

}

지금까지는 대화 상자를 출력하는 방법, 즉 대화 상자 폼을 만든 후에 이 폼을 CDialog나 CFormView로 연결하는 방법에 대해서 설명하였습니다.

본 항목 이후부터는 각 대화 상자에 설정하는 컨트롤들에 대해서 설명하겠습니다. 이번 장 이후부터 다음 장까지 전부 컨트롤에 대한 설명입니다. 그만큼 다양하고 많은 컨트롤들이 있으며, 이런 컨트롤을 이용하는 방법도 여러 가지입니다. 따라서 컨트롤을 사용하는 방법을 될 수 있는 대로 많이 소개할까 합니다.

일반적인 버튼

일반적인 버튼은 ExFormView나 ExDialog에서 사용한 버튼을 의미합니다. 이 버튼은 대화 상자 폼을 선택하면 나타나는 컨트롤 도구바에서  버튼을 클릭하면 됩니다. 이 때□ 설정되는 버튼은 기본적으로 CButton 클래스입니다. CButton을 이용하여 버튼을 만드는 예제는 Part 1의 2장에서 설명하였습니다.

실제로 버튼을 사용할 때는 Create 함수를 이용하는 것보다 본 장에서 설명하는 방식으로 FormView나 Dialog에 설정하여 많이 사용합니다. 이렇게 버튼을 설정하면 매우 편리하게 이벤트 메시지를 얻을 수 있기 때문입니다.

예를 들어 IDC_BUTTON1이라는 것을 대화 상자에 설정하고 클래스 위저드를 실행시켜 Object IDs 항목에서 IDC_BUTTON1이라는 항목을 선택하면 BN_CLICKED와 BN_DOUBLECLICKED 두 개의 이벤트 메시지가 나타납니다. BN_CLICKED는 버튼을 클릭했을 때 발생되는 메시지이며, BN_DOUBLECLI-CKED는 버튼을 더블 클릭했을 때 발생되는 메시지입니다. 실제로 버튼에서 발생되는 메시지는 이 두 가지밖에 없기 때문에 더 많은 부분이 필요하지는 않을 것입니다.

버튼에는 여러 가지 종류가 있습니다. 지금까지 설명한 일반적 형태의 버튼 외에 체크 버튼과 라디오 버튼, 그룹 상자 등등이 모두 버튼입니다. 다만 스타일을 달리하여 형태가 바뀐 것 뿐입니다. 이런 버튼들을 핸들링하는 방법들을 각각 설명하겠습니다.

체크 버튼

체크 버튼은 그림 1-4-17과 같은 형태의 버튼을 의미합니다.

체크 버튼은 클릭하면 좌측 사각 형태에 v자 형태로 표시되며 다시 누르면 해제되는 버튼입니다. 이 버튼을 이용하면 어떤 옵션을 설정하거나 해제할 때 유용합니다. 체크 버튼은 DoDataExchange 함수를 멤버와 연결하고자 할 때 Value 옵션을 이용하면 DDX_Check 함수로 연결되며 BOOL 형태와 연결됩니다.

BOOL 형태는 1과 0을 가지는 정수형 변수입니다. 즉 2진 값을 가지고 있기 때문에 체크 버튼이 체크되면 1로, 체크 버튼에서 해제되면 0으로 설정되는 것입니다. Control로 연결될 때는 CButton으로 연결됩니다. 필자가 여러 프로그램을 제작하는 동안 체크 버튼을 CButton으로 연결한 적은 한 번도 없었습니다. 연결할 필요가 없었기 때문입니다.

DDX_Check로 연결하면 UpdateData 함수를 이용하여 현재 설정되어 있는 체크 버튼의 값을 얻을 수 있습니다. 예를 들어 IDC_CHECK1이라는 버튼이 클래스 멤버 m_bCheck와 연결되었다고 했을 경우 클래스 위저드를 이용하여 BN_CLICK이라는 이벤트 메시지에 의한 함수를 만들고 이 함수에서 다음과 같이 하여 현재 버튼의 체크 유무를 확인할 수가 있습니다.

void CExButtonDlg::OnCheck1()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

// 이 때□ m_bCheck에 값이 설정됨

}

UpdateData(TRUE)라고 하면 현재 대화 상자에 있는 컨트롤의 상태값을 클래스 변수로 전송하는 형태이기 때문에 m_bCheck에 값이 설정되게 됩니다. 즉 이 함수를 호출시킨 후 m_bCheck의 값을 확인하여 해당 내용에 따라 알맞게 처리하면 됩니다.

반대로 체크 버튼을 체크하고자 한다면 m_bCheck에 값을 설정하고 UpdateData (FALSE);라고 하면 됩니다.

다음은 체크 버튼이 체크되도록 하는 예입니다.

m_bCheck=TRUE;

UpdateData(FALSE);//m_bCheck값을 대화 상자로 전송

위와 같이 하면 대화 상자의 체크 버튼에 체크가 됩니다.

라디오 버튼

라디오 버튼은 그림 1-4-18과 같은 형태를 의미합니다.

라디오 버튼은 여러 선택 사항에서 한 개를 선택하고자 할 때 주로 많이 사용하는 버튼입니다. 그림 1-4-18처럼 세 개의 선택 버튼이 있을 경우 한 개를 선택하면 나머지 항목들은 해제됩니다. 라디오 버튼은 DoDataExchange 함수를 이용하여 클래스 멤버와 연결하고자 할 때 지금까지 사용한 방법(대화 상자 해당 내용을 Ctrl 키를 누르고 있는 상태에서 마우스 좌측 버튼을 클릭하는 방법)으로는 연결되지 않습니다. 이 라디오 버튼을 연결하고자 할 경우에는 직접 코딩을 해주어야 합니다. 예를 들어서 IDC_RADIO1이라는 버튼이 있다고 가정했을 때 이 버튼을 BOOL형인 m_bRadio1과 연결하고자 한다면 먼저 헤더에 다음과 같이 m_bRadio1을 선언합니다.

BOOL m_bRadio1;

위와 같이 선언한 후에 DoDataExchange 함수에 다음과 같이 선언합니다.

DDX_Check(pDX, IDC_RADIO1, m_bRadio1);

위와 같이 선언한 후 해당 클래스 생성자에 m_bRadio1을 초기화시켜 주면 됩니다.

m_bRadio1=TRUE; // 초기화를 1로 설정

위와 같이 초기화를 1로 설정하면 버튼이 나타날 때 선택된 형태로 나타나게 됩니다.

라디오 버튼은 위와 같은 방법으로  클래스의 멤버와 연결할 수 있습니다. 라디오 버튼은 한 개의 버튼만 선택된다고 설명하였습니다. 그러나 옵션을 변경하면 라디오 버튼도 여러 개를 선택할 수 있습니다. 대화 상자 폼에서 라디오 버튼이 설정되어 있을 때 해당 라디오 버튼을 선택한 상태에서 Alt+Enter 키를 눌러 속성 대화 상자를 실행시키고 General 항목에서 Group 항목을 체크하면 라디오 버튼도 독립적으로 구동됩니다. 그림 1-4-19는 라디오 버튼을 여러 개 선택할 수 있도록 속성 대화 상자에서 Group 항목을 체크한 형태입니다.

Group을 설정하면 각각 독립된 형태로 구동이 되나 문제가 생깁니다. 문제는 한 번 라디오 버튼을 클릭하여 체크 상태로 놓게 되면 다시 해제가 안 된다는 것입니다. 즉 라디오 버튼을 클릭하면 ON-OFF의 토글 형태가 되지 않습니다. 이 문제를 해결하기 위해서는 바로 DDX_Check를 사용하여 멤버와 연결시킵니다. 위에 설명하였듯이 DDX_Check를 이용하여 m_bRadio1과 IDC_RADIO1을 연결시켰다고 가정한다면, 버튼을 클릭했을 때 함수에서 다음과 같이 설정하여 주면 라디오 버튼이 ON-OFF가 자동적으로 구동됩니다.

void CExButtonDlg::OnRadio1()

{

// 토글시킨다.

m_bRadio1=1-m_bRadio1;

// 토글된 값을 다시 대화 상자로 전송한다.

UpdateData(FALSE);

}

위의 예에서 토글이란 한 번 누르면 0, 다시 누르면 1이 계속적으로 반복된다는 것을 의미합니다. m_bRadio1=1-m_bRadio1에서 m_bRadio1이 1이면 0으로, 0이면 1로 계속 반복되기 때문에 토글이 되는 것입니다. 이렇게 토글된 값을 대화 상자로 전송합니다. 이렇게 보면 대화 상자에 있는 라디오 버튼은 m_bRadio1의 값이 어떤 값인지 알려주는 기능을 합니다. 즉 m_bRadio 버튼의 현재 상태를 얻어서 m_bRadio1에 설정하는 방법이 아니라는 것입니다.이렇게 하면 라디오 버튼은 쉽게 설정할 수가 있습니다.

그룹 상자와 Static 윈도

그룹 상자와 Static 윈도는 그림 1-4-20과 같습니다.

그룹 상자와 Static 윈도는 아무런 이벤트를 발생하지 않고 다만 대화 상자를 보기 좋게 장식하는 데 사용합니다. 여기에서 그룹 상자는 버튼 형태이며, Static 윈도는 그냥 Static 윈도라고 합니다.

그룹 상자가 어째서 버튼인가 하는 의문이 있을 수도 있을 것입니다. 그런 분들은 필자가 쓴 「Windows Programming Bible」에서 Part 2의 7장을 한 번 보기 바랍니다.(굳이 책을 살 필요 없이 버튼 윈도 항목만 한 번 보면 됩니다. 사서 보면 더욱 좋겠지만…….)

SDK에서 윈도를 만들 때 CreateWindow라는 함수를 이용합니다. 이 함수를 이용하고 첫 번째 인자에 클래스명을 설정하여 주는데, 이 클래스명이 “button”이면 버튼 형태의 윈도를 만들게 됩니다. 이 때□ 버튼 윈도 스타일을 BS_GROUPBOX로 설정하여 주면 그룹 상자가 되는 것입니다. 다음이 그 예입니다.

HWND hwnd=CreateWindow(“button”, ”그룹 상자”,

WS_CHILD|WS_VISIBLE|BS_GROUPBOX,

0,0,100,100,parendwnd,(HMENU)1,hinst,NULL);

CreateWindow 함수에 대한 내용은 도움말을 참조하거나 위에서 말한 제 책을 참조하면 됩니다. 중요한 것은 그룹 상자도 버튼 형태로 만든다는 것입니다. 그렇기 때문에 아무런 이벤트가 발생되지 않는 버튼 형태입니다.

그룹 상자는 그림 1-4-18처럼 라디오 버튼을 그룹화할 때 주로 사용하며 static 윈도는 윈도 영역을 알고자 할 때나 상자를 칠할 때 주로 많이 사용합니다.

대화 상자의 특정 영역의 좌표를 얻고자 할 때 그냥 static 윈도를 이용하여 특정 영역을 설정한 다음 Alt+Enter 키를 눌러서 Visible 속성을 제거합니다. 그러면 static 윈도는 화면에 나타나지 않습니다. 그러나 이 감추어진 상자를 이용하여 윈도에 특정 영역을 얻을 수 있습니다. 예를 들어서 IDC_STATIC1이라는 ID를 가진 static 윈도의 영역을 얻고자 한다면 다음과 같이 하면 됩니다.

RECT rect;

GetDlgItem(IDC_STATIC1)->GetWindowRect(&rect);

ScreenToClient(&rect);

GetDlgItem 함수를 이용하면 CWnd 클래스 포인터를 얻을 수 있다는 설명은 이전에 했습니다. CWnd의 멤버 함수 중에 GetWindowRect는 윈도 영역을 알아내는 함수입니다. 이 함수에 RECT 인자를 넘기면 윈도 영역이 rect에 저장됩니다. 이 때□ 좌표값은 스크린 좌표를 기준으로 설정됩니다. 그렇기 때문에 이것을 현재 윈도에서의 위치로 얻고자 할 때 ScreenToClient 함수를 이용하여 좌표를 변환시킵니다. 이렇게 함으로써 대화 상자에 static 윈도가 있는 영역을 알 수 있습니다. 위의 방법은 꼭 static 윈도에만 해당되는 것은 아닙니다. 모든 윈도에 다 해당됩니다. 윈도 위치를 얻고자 할 때 자주 사용되는 루틴입니다.

비트맵 버튼

비트맵 버튼을 만드는 방법은 세 가지입니다. 첫 번째 방법은 자동으로 설정하는 방법입니다. 자동으로 설정할 때는 그림 1-4-21과 같은 형태로 만들면 됩니다.

자동으로 만드는 비트맵 버튼을 만들 경우 먼저 자원에서 비트맵을 만들고 들어간 형태는 문자열 “BUTTOND”로, 나온 버튼 그림은 “BUTTONU”으로 설정합니다. ② 항목과 ③ 항목이 들어가고 나온 버튼 형태이며, ① 항목은 이 버튼의 이름이 설정되어 있는 Project Workspace 윈도의 Resource View 항목입니다.

이렇게 한 후 자동으로 설정되는 비트맵을 만들려면 버튼명을 위에서 만든 비트맵 이름과 같게 BUTTON으로 설정합니다. 이 때□ 끝에 U와 D는 쓰지 않습니다. 그림 1-4-21의 ⑤ 항목이 바로 이것을 설명하는 것입니다.

마지막으로 버튼 속성에서 Style 항목의 Owner draw 항목을 체크해줍니다. 그림 1-4-21의 ⑦ 항목이 바로 이것을 나타내는 것입니다. 이렇게 한 후에 실제적으로 코딩을 할 때는 먼저 헤더에 다음과 같이 비트맵 버튼 클래스의 변수를 설정합니다.

CBitmapButton m_bitButton;

위와 같이 설정한 후에 CBitmapButton의 멤버 변수 AutoLoad 함수를 이용하여 다음과 같이 설정하면 자동적인 비트맵 버튼이 됩니다.

m_bitButton.AutoLoad(IDC_BUTTON,this);

IDC_BUTTON은 그림 1-4-21의 ④인 버튼의 ID입니다. AutoLoad 함수는 첫 번째 인자는 비트맵을 설정할 ID로, 두 번째 인자는 부모 윈도 포인터를 설정해주어야 합니다.

두 번째로 비트맵 버튼을 로드하는 방법은 비트맵 버튼 클래스와 대화 상자 버튼을 DDX_Control로 연결시켜 주고 LoadBitmaps 함수를 이용하여 비트맵 버튼을 만드는 것입니다. 이 방법은 그림 1-4-22와 같습니다.

LoadBitmaps를 사용하여 비트맵 버튼을 설정하면 네 가지 형태의 비트맵을 로드할 수 있습니다. 즉 나온 형태, 들어간 형태, 현재 포커스가 설정되어 있는 형태, 비활성화 되는 형태를 의미합니다.

그림 1-4-22에서 ① 항목은 이 네 가지 비트맵이 설정되어 있는 것을 의미합니다. ③ 항목의 버튼을 비트맵으로 하고자 한다면 Alt+Enter 키를 클릭하여 ②번 대화 상자를 출력시키고 ⑤ 항목인 Owner draw 항목을 체크합니다. 이렇게 한 후 버튼을 DDX_Control을 이용하여 ⑥ 항목처럼 연결시킵니다. 클래스 위저드로 사용하면 그림 1-4-22의 ⑦처럼 헤더에 CBitmapButton형의 멤버가 연결될 것 같으나 유감스럽게도 그렇게 되지 않습니다. 결국 ⑥ 항목과 ⑦ 항목을 직접 코딩해주어야 합니다. 이렇게 한 후 대화 상자 초기화 부분에서 ⑧ 항목처럼 코딩하면 비트맵 버튼이 로드됩니다.

LoadBitmaps 함수는 비트맵 버튼에 설정할 네 개의 비트맵 아이디를 인자로 받습니다. 첫 번째부터 나옴, 들어감, 포커스 설정, 비활성 상태입니다.

세 번째 항목은 직접 Owner draw 메시지를 받아서 출력하는 형태인데, 이것은 바로 다음 항목에서 설명하겠습니다.

트루컬러 비트맵 버튼 만들기

자원에서 비트맵을 설정할 때 최대 256컬러까지는 지원이 되나 트루 컬러는 지원이 되지 않습니다. C자원되지 않는다는 의미는 그리소스 에디터에서 그릴 수 없다는 뜻입니다. 트루컬러 비트맵 버튼을 설정하려면 사용자가 직접 비트맵을 그려주는 방법과 외부에서 그린 다음 비트맵에 등록하는 방법이 있습니다. 이 때 직접 비트맵을 그려주고자 할 경우 자원에서 로드하지 않고 DIB를 이용합니다. Dib를 raw 파일로 만들어서 화면에 출력하는 방법을 설명할 때(Part 1의 3장) 트루컬러 Dib를 출력하는 방법을 설명하였습니다.

본 장에서는 Dib를 만드는 방법은 생략하고 트루컬러 비트맵 버튼을 만드는 방법에 대해 설명하겠습니다. 트루컬러 비트맵은 만든다기보다 비트맵 버튼 만들기 세 번째 항목, 즉 프로그래머가 버튼 DC를 받아서 직접 그리는 것입니다. 이 형태는 그림 1-4-23과 같습니다.

실제 버튼을 만들고 그림 1-4-23의 ② 항목처럼 Owner draw 옵션을 설정하면 부모 윈도로 WM_DRAWITEM이라는 메시지가 설정됩니다. 이 때 CDialog 클래스나 CFormView 클래스에서는 내부적으로 비트맵 버튼인가를 확인하여 위에서 설정한 두 가지 방법을 자동으로 설정해 줍니다. 이 자동 장치를 사용 안하고 직접 그릴 때는 그림 1-4-23의 ③ 항목처럼 WM_DRAWITEM 메시지를 받는 함수를 만듭니다. 이 함수를 만들면 처음에는 다음과 같은 함수가 만들어집니다.

void CExButtonDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

{

// TODO: Add your message handler code here and/or call default

CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);

}

OnDrawItem 함수는 버튼에서만 발생되는 것이 아니라 컨트롤의 속성을 Owner draw으로 설정하고 그 컨트롤을 화면에 출력할 때마다 계속적으로 발생됩니다. 여기에서 어느 자원을 그려야 하는가가 문제가 됩니다. 예를 들어 IDC_BUTTON도 사용자 그리기를 설정하였고, 리스트 상자(ID가 IDC_LIST)도 사용자 그리기를 설정하였다고 할 때, 두 개 다 화면에 그릴 때는 똑같이 OnDrawItem 함수가 실행됩니다. 이 때□ 어느 컨트롤을 그려야 할 것인가를 아는 방법이 바로 nIDCtl을 분석하는 방법입니다. nIDCtl에는 현재 새로 그려야 할 컨트롤 ID가 설정됩니다. 만약 IDC_DRAWBUTTON이라는 비트맵 버튼을 직접 그리고자 할 경우 위의 함수에서 다음과 같이 하면 됩니다.

void CExButtonDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

{

// TODO: Add your message handler code here and/or call default

if(nIDCtl==IDC_DRAWBUTTON)

{

버튼 형태를 그린다.

}

CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);

}

버튼을 그릴려면 할 경우에는 현재 상태에 따라서 다르게 그려야 합니다. 즉 눌려 있는 상태라든지 나와 있는 상태 등등을 설정해야 합니다. 그리고 버튼의 DC 또한 알아야 합니다. 이 모든 정보가 lpDrawItemStruct에 설정되어 있습니다. 이 구조체는 DRAWITEMSTRUCT 구조체의 포인터로써 DRAWITEMSTRUCT 구조체는 다음과 같습니다.

DRAWITEMSTRUCT

typedef struct tagDRAWITEMSTRUCT {  // dis

UINT  CtlType; 컨트롤 스타일

UINT  CtlID; 컨트롤 ID

UINT  itemID; 메뉴에서 설정할 때 메뉴 ID

UINT  itemAction; 아이템이 현재 활동하고 있는 상태

UINT  itemState; 현재 아이템이 수행된 결과 상태

HWND  hwndItem; 아이템 윈도 핸들

HDC   hDC; 아이템 DC 핸들

RECT  rcItem; 출력할 영역 좌표

DWORD itemData; 아이템에 설정되어 있는 데이터

} DRAWITEMSTRUCT;

CtlType에는 다음의 일곱 가지가 있습니다.

ODT_BUTTON : 사용자 그리기 버튼

ODT_COMBOBOX : 사용자 그리기 콤보 상자

ODT_LISTBOX : 사용자 그리기 리스트 상자

ODT_LISTVIEW : 사용자 그리기 리스트 View

ODT_MENU : 사용자 그리기 메뉴

ODT_STATIC : 사용자 그리기 Static 윈도

ODT_TAB : 사용자 그리기 Tab 컨트롤

itemAction에는 세 가지 값이 설정됩니다.

ODA_DRAWENTIRE : 현재 컨트롤 전체가 다시 그려져야 한다.

ODA_FOCUS : 지금 현재 포커스 상태가 변하고 있다. 이 때에는itemState의 값을 참조하여 결과를 설정한다.

ODA_SELECT : 현재 선택된 아이템이 바뀌고 있다.

itemState에는 다음과 같은 값을 가질 수 있다.

ODS_CHECKED : 현재 메뉴 아이템이 설정되었음

ODS_COMBOBOXEDIT : 콤보 상자에서 에디팅이 되고 있음

ODS_DEFAULT : 현재 아이템이 default 아이템임

ODS_DISABLED : 현재 아이템이 비활성화됨

ODS_FOCUS : 현재 아이템이 활성화됨

ODS_GRAYED : 현재 아이템이 비활성화됨

ODS_SELECTED : 현재 아이템이 선택됨

눌려진 상태와 놓여진 상태에 따라서 버튼을 다르게 그릴 때의 내용은 그림 1-4-23의 ⑥ 항목입니다. 이 상태에서 트루컬러 버튼을 그리려면 트루컬러 버튼 그림의 눌려진 것과 나온 것을 로드해야 합니다.

이 버튼을 로드하고자 할 때는 초기화 루틴에서 미리 하는 것이 좋습니다. 초기화 루틴에 다음과 같이 설정하여 raw 파일을 읽고 트루컬러 버튼을 로드합니다.

// 나온 형태의 트루컬러 버튼

HFILE hfi;

int size;

hfi=_open(“butu.raw”,_O_BINARY | _O_RDONLY);

size=_lseek(hfi,0,SEEK_END);

_lseek(hfi,0,SEEK_SET);

m_pDibu= new char[size];

_read(hfi,m_pDibu,size);

_close(hfi);

// 들어간 형태의 트루 컬러 버튼

hfi=_open(“butd.raw”,_O_BINARY | _O_RDONLY);

size=_lseek(hfi,0,SEEK_END);

_lseek(hfi,0,SEEK_SET);

m_pDibd= new char[size];

_read(hfi,m_pDibd,size);

_close(hfi);

위와 같이 로드할 때 DIB를 설정할 m_pDibd와 m_pDibu를 헤더에 다음과 같이 설정하는 것은 이미 기본적으로 알고 있을 것입니다.

// 트루컬러 버튼 이미지

LPSTR m_pDibu;

LPSTR m_pDibd;

또한 Dib를 출력하기 위해서는 BITMAPINFO 구조체를 이용하여 정보를 설정해야 합니다. 먼저 헤더에 BITMAPINFO 구조체의 변수를 설정해야 합니다.

BITMAPINFO szBitmapInfo;

위와 같이 설정한 후에 szBitmapInfo에 해당 raw 파일의 정보를 설정합니다.

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

szBitmapInfo.bmiHeader.biWidth=256;

szBitmapInfo.bmiHeader.biHeight=72;

szBitmapInfo.bmiHeader.biPlanes=1;

szBitmapInfo.bmiHeader.biBitCount=24;

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

szBitmapInfo.bmiHeader.biSizeImage=256*72;

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

이 부분들은 3장의 Dib 부분에서 이미 설명되었습니다. 이와 같이 한 후에 완전히 수정된 OnDrawItem은 다음과 같습니다.

void CExButtonDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

{

if(nIDCtl==IDC_DRAWBUTTON)

{

// 버튼 눌려짐

if(lpDrawItemStruct->itemState & ODS_SELECTED)

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,                 // DestX

0,                // DestY

256,            // nDestWidth

72,              // nDestHeight

0,                // SrcX

0,                // SrcY

0,               // nStartScan

(WORD)72,  // nNumScans

m_pDibd,    // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);// wUsage

}

else// 버튼 나옴

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,                    // DestX

0,                    // DestY

256,                // nDestWidth

72,                 // nDestHeight

0,                   // SrcX

0,                  // SrcY

0,                  // nStartScan

(WORD)72,  // nNumScans

m_pDibu,           // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);            // wUsage

}

}

CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);

}

이와 같이 하면 트루 컬러 비트맵을 로드할 수 있습니다. 본 항목의 요점은 트루 컬러 비트맵 출력이 아니라 사용자 그리기를 어떻게 하는가입니다. 사용자 그리기 방법을 이해하면 콤보 상자, 리스트 상자, 메뉴 등등을 모두 사용자 그리기로 그릴 수 있습니다. 리스트 상자나 콤보 상자에 이상한 그림이 들어가 있거나 메뉴에 글자와 그림이 함께 있는 프로그램을 본 적이 있을 것입니다. 이런 것들은 모두 사용자 그리기 항목을 이용한 것입니다. 트루 컬러 비트맵만 출력했다고 기뻐하지 말고 본 항목을 통해서 사용자 그리기를 어떻게 하는가를 이해하기 바랍니다. 모든 컨트롤과 메뉴에 대한 사용자 그리기 방법을 이 책에 다 설명할 수는 없습니다. 여기서는 버튼과 리스트 상자로 사용자 그리기를 하였습니다. 메뉴와 콤보 상자, 탭, 컨트롤 등도 이 두 개의 예제를 이해하면 쉽게 그릴 수 있을 것입니다.

버튼 위에 있을 때 마우스 커서 바꾸기

이제 버튼의 마지막입니다. 가끔 프로그램에서 버튼 위로 커서가 옮겨지면 마우스 커서가 바뀌는 것을 본 적이 있을 것입니다. 지킴이 웹 사이트에도 이 부분에 대한 질문을 한 사람들이 있었습니다. 그런데 이들이 알고자 한 것은 버튼 위에 마우스 커서가 있을 때 마우스 커서를 바꾸는 함수였습니다. 이 함수는 아주 간단하게 처리할 수 있습니다. 만일 CButton m_pButton으로 연결되어 있다면 다음과 같이 하면 간단하게 마우스 커서를 바꿀 수 있습니다.

SetClassLong(m_pButton.m_hWnd,GCL_HCURSOR,(long)AfxGetApp()->LoadCursor(IDC_CURSOR));

위와 같이 하면 IDC_CURSOR 형태의 커서가 버튼 위에서 구동됩니다. 이 부분을 좀더 확장해서 생각해 봅시다. 버튼의 내부에서 발생하는 우측 마우스 버튼 클릭, 또는 키보드 누름 등을 어떻게 가로챌 수 있을까요? 이것은 상속을 받으면 가능해집니다. 예를 들어 다음과 같이 CButton을 상속받은 CMyButton을 만들었다고 가정합시다.

CMyButton:public CButton

실제 CButton은 CWnd를 상속받아서 버튼 형태를 DC에 일일이 그려준 것입니다. 그렇기 때문에 CButton 안에는 좌측 마우스 버튼을 클릭했을 때 내용들이 모두 설정되어 있습니다. 이렇게 설정되어 있는 CButton을 상속받은 CMyButton 또한 좌측 마우스 버튼 이벤트를 받을 수 있으며, base 클래스인 CButton에서 했던 내용들을 모두 무시할 수도 있습니다.

CButton에서 마우스 버튼이 눌려졌을 때의 내용을 모두 무시하려면 마우스 버튼이 눌려졌을 때 모든 내용을 여러분이 그려야 하기 때문에 무척 복잡해질 것입니다. 그러므로 이런 부분을 가로채기보다는 CButton에서 외부적으로 이벤트 메시지가 발생되지 않는 우측 마우스 버튼을 클릭한다든지 키보드에서 키 누름 같은 것은 상속을 받은 후에 WM_RBUTTONDOWN 메시지나 WM_KEYDOWN 메시지에 의한 함수를 만들면 됩니다. 이렇게 만든 후에 이 안에서 원하는 내용을 코딩하면 됩니다. 이렇게 상속받은 클래스를 만들고자 할 때도 클래스 위저드를 사용합니다.

그림 1-4-24는 클래스 위저드를 이용하여 CButton을 상속받은 CMyButton을 만드는 내용입니다.

새로운 클래스를 만들고자 할 때는 ClassWizard를 실행하고 그림 1-4-24의 ④ 항목처럼 Add class 버튼을 선택한 뒤에 New 항목을 선택합니다. 이때 그림 1-4-24의 ②와 같은 대화 상자가 나타납니다. ② 항목과 같은 대화 상자는 대화 상자 폼을 만들고 클래스 위저드를 실행시킬 때 나타나는 항목과 같습니다.

새로운 클래스를 만들고자 할 경우 ②와 같은 대화 상자가 나타납니다. 이 상태에서 ⑤와 같이 만들고자 하는 클래스명을 설정하고 ⑦ 항목에서 상속받을 클래스명을 설정하면 ⑥ 항목에 해당 클래스의 파일이 설정됩니다. 이런 상태에서 OK 버튼을 클릭하면 ③ 항목처럼 해당 클래스가 만들어지고 해당 클래스가 설정되어 있는 ClassWizard가 실행됩니다. 이 클래스 위저드에서 상속받은 클래스에 설정할 이벤트를 만든 다음 이곳에 알맞게 코딩해주면 됩니다.

우리가 만들고자 하는 CMyButton은 자신의 영역 위에서 마우스가 움직이면 손가락 모양의 마우스 커서가 설정되는 예입니다. 이 때에는 그림 1-4-24의 ③과 같은 항목에서 WM_MOUSEMOVE 메시지에 의한 함수를 만들고 이 함수에 ⑨ 항목과 같이 코딩해주면 됩니다.

그림 1-4-24는 CButton을 상속받은 버튼을 만든 경우인데, 이를 응용하여 많은 클래스를 만들고, 상속받아서 자신만의 클래스를 만들 수 있습니다. CButton뿐만 아니라 MFC에서 제공하는 모든 클래스들을 상속받아서 현재 클래스에 존재하지 않는 새로운 기능을 넣을 수 있다는 것입니다. 이렇게 하면 다양한 기능을 처리할 수 있습니다.

위에서도 언급하였듯이, 한 가지 예로 마우스 우측 버튼이 눌려졌을 때 이벤트가 발생하는 항목을 만들려면 이 때 또한 간단합니다. 우측 마우스 버튼이 눌려진 상태의 함수를 만든 다음 이 함수를 수정하면 됩니다. 그림 1-4-24과처럼 만든 버튼에서 우측 마우스 버튼을 눌렀을 때의 이벤트를 만들어 보겠습니다. ClassWizard를 실행하고 그림 1-4-24의 ③과 같은 상태에서 WindowProc라는 메시지를 받은 함수를 만듭니다. 이렇게 함수를 만들면 다음과 같은 함수가 나올 것입니다.

LRESULT CMyButton::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

return CButton::WindowProc(message, wParam, lParam);

}

WindowProc는 모든 메시지를 처리하는 루틴을 가진 프로시저 함수입니다. 프로시저 함수의 기능이 이해되지 않으면 필자가 쓴 「Windows Programming Bible」의 Part 2의 1장을 참조하기 바랍니다.(내가 쓴 책에 대해서 너무 PR이 심한 것 같아서 죄송스럽군요.)

이 함수에서 WM_RBUTTONDOWN과 WM_RBUTTONUP 메시지가 발생하면 이 메시지를 다시 WM_LBUTTONDOWN과 WM_LBUTTONUP 메시지로 전환시키면 좌측 마우스 버튼을 클릭했을 때의 버튼의 움직임이 우측 마우스 버튼을 클릭했을 때에도 똑같이 발생합니다. 다음은 이 함수를 변화시킨 형태입니다.

LRESULT CMyButton::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

switch(message)

{

case WM_RBUTTONDOWN:

SendMessage(WM_LBUTTONDOWN,wParam,lParam);

return 1;

case WM_RBUTTONUP:

SendMessage(WM_LBUTTONUP,wParam,lParam);

return 1;

}

return CButton::WindowProc(message, wParam, lParam);

}

SendMessage 함수는 자신의 윈도에 메시지를 전달하는 함수인데, WM_RBU- TTONDOWN일 경우에는 WM_LBUTTONDOWN을 발생시키고 WM_RBUT- TONUP일 경우에는 WM_LBUTTONUP을 발생시켜 우측 마우스 버튼 움직임이 좌측 마우스 버튼 움직임으로 설정됩니다.

컨트롤들 중에 메시지가 없거나 기능이 없는 부분들이 많습니다. 이런 부분들에 대해서 “이 컨트롤은 이런 기능이 없어! 그래서 안돼!”라고 단정짓지 말고 컨트롤을 상속받은 자신의 클래스를 만든 다음 해당 기능을 설정하면 됩니다. 그렇게 하기 위해서 C++라는 것이 있는 것이고, 허구한 날 객체 지향 언어, 객체 지향 프로그래밍 등등을 외치는 것입니다.

버튼 예제 ExButton

ExButton은 지금까지 설명한 버튼의 모든 기능을 이용한 예제입니다. ExButton의 전체 형태는 그림 1-4-25와 같습니다.

ExButton 예제는 지금까지 설명한 버튼에 대한 방법을 모두 설정한 것입니다. ②번의 일반 버튼을 누르면 ④번의 비트맵 버튼을 활성/비활성 상태로 만들어 활성화되었을 때와 비활성화되었을 때의 버튼이 비트맵 버튼으로 변하는 것을 알 수 있으며, ⑥번 버튼은 우측 마우스 버튼을 클릭해도 좌측 마우스 버튼을 클릭한 것과 같은 기능을 구현하는 형태입니다. ⑦ 항목의 라디오 버튼은 모두 Group 속성을 설정하여 각각 구동이 되도록 하였습니다(그림 1-4-19 참조). 이 예제를 만드는 방법은 다음과 같습니다.

Dialog based 프로젝트 만들기

AppWizard를 이용하여 프로젝트를 Dialog based 형태로 만듭니다. 이렇게 프로젝트를 ExButton으로 설정하면 IDD_EXBUTTON_DIALOG라는 대화 상자 폼이 나타납니다. 이 폼에 그림 1-4-25의 형태로 대화 상자를 설정합니다. 각 대화 상자 ID는 그림 1-4-25를 참조하기 바랍니다. 이 때 ⑦ 항목은 Group 속성을 설정하여야 하며  ④와 ⑤ 버튼은 Owner draw를 설정해 주어야 합니다.(그림 1-4-23의 ④ 항목을 참조하세요.) 이렇게 하여 대화 상자의 기본 폼을 설정합니다.

대화 상자 컨트롤과 클래스 멤버와의 연결

DoDataExchange 안에 DDX_Text나 DDX_Control, DDX_Check를 이용하여 대화 상자의 컨트롤과 클래스 멤버를 연결시키겠습니다. 지금까지는 대화 상자 폼에서 해당 컨트롤을 선택하고 Ctrl 키를 누르고 있는 상태에서 좌측 마우스 버튼을 더블 클릭하여 연결하였으나 이번에는 그냥 코딩으로 연결하겠습니다. 체크 버튼을 제외한 나머지 버튼은 위와 같은 방법으로 연결이 안 되기 때문입니다. DoDataExchange를 이용하여 연결할 컨트롤들은 그림 1-4-25에서 ②, ④, ⑥, ⑦번의 선택항목1입니다. 이 네 개를 연결할 멤버를 대화 상자 클래스 헤더에 설정합니다.

BOOL m_bRadio1;// 라디오 버튼 선택항목1

BOOL m_bCheck;// 체크 버튼

CMyButton m_myButton;// 내 버튼

CBitmapButton m_bBitmapButton;// 비트맵 버튼

위에서 CMyButton은 본 장의 4항목에서 설명한 방법대로 만든 클래스입니다. 헤더에 설정하기 전에 먼저 CMyButton을 4항목에서 설명한 방법대로 클래스를 만든 다음 CMyButton을 설정하여야 합니다. 그리고 #include를 삽입하는 것도 잊으면 안 됩니다.

#include “MyButton.h”

위와 같이 하여 대화 상자 컨트롤과 연결될 멤버를 만든 다음 DoDataExchange에 다음과 같이 연결합니다.

void CExButtonDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExButtonDlg)

DDX_Control(pDX,IDC_BITMAPBUTTON,m_bBitmapButton);// 비트맵 버튼

DDX_Control(pDX,IDC_MYBUTTON,m_myButton);// 내 버튼

DDX_Check(pDX, IDC_CHECK1, m_bCheck);// 체크 버튼

DDX_Check(pDX, IDC_RADIO1, m_bRadio1);// 라디오 버튼

//}}AFX_DATA_MAP

}

위와 같이 함으로써 대화 상자 컨트롤과 클래스 멤버 연결이 끝나게 됩니다.

비트맵 버튼 설정

그림 1-4-25의 ④ 항목처럼 비트맵을 로드하기 위해서 네 개의 비트맵을 만듭니다. 이 비트맵은 그림 1-4-26과 같습니다.

IDC_BUTTOND는 눌려져 있는 버튼 형태이며, IDC_BUTTONU는 나온 형태, IDC_BUTTONF는 포커스가 설정되어 있는 형태, IDC_BUTTONE는 비활성화되어 있는 형태입니다. 이렇게 비트맵을 만든 다음 초기화 부분(OnInitDialog)에서 다음과 같이 초기화를 합니다.

BOOL CExButtonDlg::OnInitDialog()

{

CDialog::OnInitDialog();

:// 생략

// 비트맵 버튼에 비트맵을 로드한다.

m_bBitmapButton.LoadBitmaps(IDB_BUTTONU,

IDB_BUTTOND,IDB_BUTTONF,IDB_BUTTONE);

return TRUE;  // return TRUE  unless you set the focus to a control

}

위와 같이 하면 비트맵 버튼에 비트맵을 로드하게 됩니다.

트루컬러 이미지 만들고 로드하기

트루컬러 버튼을 만들기 위해서 raw 이미지를 만든 다음 이 이미지를 로드하는 멤버 변수를 헤더에 설정합니다. raw 이미지를 만드는 부분은 본 Part의 3장을 참조하기 바랍니다.

// 트루컬러 버튼 이미지

LPSTR m_pDibu;

LPSTR m_pDibd;

위와 같이 헤더에 들어가 형태의 이미지를 설정하기 위해서는 m_pDibd에, 나온 형태 이미지는 m_pDibu에 설정되도록 합니다. 이 두 개의 멤버에 Dib를 연결합니다. 초기화 부분에서 다음과 같이 두 개의 멤버에 Dib를 로드하는 부분을 설정합니다.

BOOL CExButtonDlg::OnInitDialog()

{

CDialog::OnInitDialog();

:// 생략

// 비트맵 버튼에 비트맵을 로드한다.

m_bBitmapButton.LoadBitmaps(IDB_BUTTONU,

IDB_BUTTOND,IDB_BUTTONF,IDB_BUTTONE);

// 나온 형태의 트루 컬러 버튼

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“butu.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibu를 메모리에 할당하고

m_pDibu= new char[size];

// Dib를 읽는다.

_read(hfi,m_pDibu,size);

_close(hfi);

// 들어간 형태의 트루 컬러 버튼

// 파일을 열고

hfi=_open(“butd.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibd를 메모리에 할당하고

m_pDibd= new char[size];

// Dib를 읽는다.

_read(hfi,m_pDibd,size);

_close(hfi);

return TRUE;  // return TRUE  unless you set the focus to a control

}

위와 같이 하면 m_pDibu와 m_pDibd에 트루 컬러 비트맵이 설정됩니다. 이제 BMPINFO 구조체를 하나 만들고 이곳에 두 개의 Dib 정보를 설정해야 합니다.

헤더 부분에 다음과 같이 BMPINFO 구조체를 설정합니다.

// Dib 정보를 설정하는 구조체

BITMAPINFO szBitmapInfo;

위와 같이 설정한 후에 szBitmpaInfo에 정보를 설정합니다.

BOOL CExButtonDlg::OnInitDialog()

{

CDialog::OnInitDialog();

:// 생략

// 비트맵 버튼에 비트맵을 로드한다.

m_bBitmapButton.LoadBitmaps(IDB_BUTTONU,

IDB_BUTTOND,IDB_BUTTONF,IDB_BUTTONE);

// 나온 형태의 트루 컬러 버튼

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“butu.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibu를 메모리에 할당하고

m_pDibu= new char[size];

// Dib를 읽는다.

_read(hfi,m_pDibu,size);

_close(hfi);

// 들어간 형태의 트루 컬러 버튼

// 파일을 열고

hfi=_open(“butd.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibd를 메모리에 할당하고

m_pDibd= new char[size];

// Dib를 읽는다.

_read(hfi,m_pDibd,size);

_close(hfi);

// BMPINFO 정보를 설정한다.

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

szBitmapInfo.bmiHeader.biWidth=256;

szBitmapInfo.bmiHeader.biHeight=72;

szBitmapInfo.bmiHeader.biPlanes=1;

szBitmapInfo.bmiHeader.biBitCount=24;

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

szBitmapInfo.bmiHeader.biSizeImage=256*72;

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

return TRUE;  // return TRUE  unless you set the focus to a control

}

Owner Draw에서 트루 컬러 버튼 설정

ClassWizard에서 WM_DRAWITEM 항목을 설정하여 함수를 만든 다음 이 함수에서 트루 컬러를 로드합니다. 이 부분에 대한 것은 본 장의 4항목에서 설명하였습니다(그림 1-4-23을 참조). 이와 같이 만든 함수에 다음과 같이 설정하여 트루 컬러 비트맵이 설정되도록 합니다.

void CExButtonDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

{

if(nIDCtl==IDC_DRAWBUTTON)

{

// 버튼 눌려짐

if(lpDrawItemStruct->itemState & ODS_SELECTED)

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,                    // DestX

0,                    // DestY

256,                // nDestWidth

72,                  // nDestHeight

0,                   // SrcX

0,                   // SrcY

0,                   // nStartScan

(WORD)72,  // nNumScans

m_pDibd,                  // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);            // wUsage

}

else// 버튼 나옴

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,             // DestX

0,              // DestY

256,        // nDestWidth

72,       // nDestHeight

0,            // SrcX

0,   // SrcY

0,                          // nStartScan

(WORD)72,  // nNumScans

m_pDibu,                  // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);            // wUsage

}

}

CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);

}

위와 같이 하면 트루 컬러 비트맵이 설정됩니다.

버튼 활성화와 비활성화 설정

그림 1-4-25에서 보듯이 일반 버튼을 클릭하면 비트맵 버튼이 활성/비활성이 토글화됩니다. 이렇게 하기 위해서 일반 버튼이 클릭되었을 때의 함수를 클래스 위저드로 만듭니다. 대화 상자에서 일반 버튼 ID를 선택한 다음 BN_CLICKED 함수를 만들면 됩니다. 이렇게 만든 함수에 다음과 같이 수정합니다.

void CExButtonDlg::OnButton()

{

// 비트맵 버튼이 활성화 상태이면

if(m_bBitmapButton.IsWindowEnabled())

// 비트맵 버튼을 비활성화

m_bBitmapButton.EnableWindow(FALSE);

else // 그 밖에는 활성화한다.

m_bBitmapButton.EnableWindow(TRUE);

}

InWindowEnabled 함수는 활성화일 경우에는 1, 그렇지 않을 경우에는 0을 리턴합니다. 이 함수를 이용하여 활성화일 경우에는 EnableWindow 함수를 이용하여 비활성화시키고 그렇지 않을 경우에는 활성화시킵니다.

라디오 버튼 토글 설정

라디오 버튼을 토글하기 위해서 m_bRadio1의 값을 토글시키고 이것을 라디오 버튼에 설정하면 됩니다. 라디오 버튼이 클릭되었을 때의 함수를 만든 다음 이 함수를 다음과 같이 수정합니다.

void CExButtonDlg::OnRadio1()

{

// 토글시킨다.

m_bRadio1=1-m_bRadio1;

// 토글된 값을 다시 대화 상자로 전송한다.

UpdateData(FALSE);

}

위와 같이 하면 라디오 버튼을 토글되는 형태로 만들 수가 있습니다.

예제 ExButton 소스

ExButton 소스 중에서 대화 상자 클래스와 CButton을 상속받은 CMyButton 클래스 소스는 다음과 같습니다.

// ExButtonDlg.h : header file

//

#if !defined(AFX_EXBUTTONDLG_H__21237AB1_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXBUTTONDLG_H__21237AB1_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

////////////////////////////////////////////////////

// CExButtonDlg dialog

#include “MyButton.h”

class CExButtonDlg : public CDialog

{

// Construction

public:

CExButtonDlg(CWnd* pParent = NULL); // standard constructor

// Dib 정보를 설정하는 구조체

BITMAPINFO szBitmapInfo;

// 트루 컬러 버튼 이미지

LPSTR m_pDibu;

LPSTR m_pDibd;

// Dialog Data

//{{AFX_DATA(CExButtonDlg)

enum { IDD = IDD_EXBUTTON_DIALOG };

BOOL m_bRadio1;// 라디오 버튼 선택항목1

BOOL m_bCheck;// 체크 버튼

CMyButton m_myButton;// 내 버튼

CBitmapButton m_bBitmapButton;// 비트맵 버튼

// }}AFX_DATA

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExButtonDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

// }}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CExButtonDlg)

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);

afx_msg void OnButton();

afx_msg void OnDrawbutton();

afx_msg void OnCheck1();

afx_msg void OnRadio1();

afx_msg void OnMybutton();

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //

!defined(AFX_EXBUTTONDLG_H__21237AB1_20B7_11D2_8B56_006097AD9DDC__

INCLUDED_)

// ExButtonDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExButton.h”

#include “ExButtonDlg.h”

#include <io.h>

#include <fcntl.h>

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////

// CExButtonDlg dialog

CExButtonDlg::CExButtonDlg(CWnd* pParent /*=NULL*/)

: CDialog(CExButtonDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExButtonDlg)

m_bCheck = FALSE;

m_bRadio1= TRUE;

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CExButtonDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExButtonDlg)

DDX_Control(pDX,IDC_BITMAPBUTTON,m_bBitmapButton);// 비트맵 버튼

DDX_Control(pDX,IDC_MYBUTTON,m_myButton);// 내 버튼

DDX_Check(pDX, IDC_CHECK1, m_bCheck);// 체크 버튼

DDX_Check(pDX, IDC_RADIO1, m_bRadio1);// 라디오 버튼

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExButtonDlg, CDialog)

//{{AFX_MSG_MAP(CExButtonDlg)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_WM_DRAWITEM()

ON_BN_CLICKED(IDC_BUTTON, OnButton)

ON_BN_CLICKED(IDC_DRAWBUTTON, OnDrawbutton)

ON_BN_CLICKED(IDC_CHECK1, OnCheck1)

ON_BN_CLICKED(IDC_RADIO1, OnRadio1)

ON_BN_CLICKED(IDC_MYBUTTON, OnMybutton)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////

// CExButtonDlg message handlers

BOOL CExButtonDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Add “About...” menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.

ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);

ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);

if (pSysMenu != NULL)

{

CString strAboutMenu;

strAboutMenu.LoadString(IDS_ABOUTBOX);

if (!strAboutMenu.IsEmpty())

{

pSysMenu->AppendMenu(MF_SEPARATOR);

pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

}

}

// Set the icon for this dialog.  The framework does this automatically

// when the application’s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

m_bBitmapButton.LoadBitmaps(IDB_BUTTONU,IDB_BUTTOND,IDB_BUTTONF,IDB_BUTTONE);

// BMPINFO 정보를 설정한다.

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

szBitmapInfo.bmiHeader.biWidth=256;

szBitmapInfo.bmiHeader.biHeight=72;

szBitmapInfo.bmiHeader.biPlanes=1;

szBitmapInfo.bmiHeader.biBitCount=24;

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

szBitmapInfo.bmiHeader.biSizeImage=256*72;

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

// 나온 형태의 트루 컬러 버튼

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“butu.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibu를 메모리에 할당하고

m_pDibu= new char[size];

// Dib를 읽는다.

read(hfi,m_pDibu,size);

close(hfi);

// 들어간 형태의 트루 컬러 버튼

// 파일을 열고

hfi=_open(“butd.raw”,_O_BINARY | _O_RDONLY);

// 파일 크기를 얻은 다음

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 이동

_lseek(hfi,0,SEEK_SET);

// m_pDibd를 메모리에 할당하고

m_pDibd= new char[size];

// Dib를 읽는다.

read(hfi,m_pDibd,size);

close(hfi);

m_bCheck=1;

UpdateData(FALSE);

return TRUE;  // return TRUE  unless you set the focus to a control

}

void CExButtonDlg::OnSysCommand(UINT nID, LPARAM lParam)

{

if ((nID & 0xFFF0) == IDM_ABOUTBOX)

{

CAboutDlg dlgAbout;

dlgAbout.DoModal();

}

else

{

CDialog::OnSysCommand(nID, lParam);

}

}

// If you add a minimize button to your dialog, you will need the code below

//  to draw the icon.  For MFC applications using the document/view model,

//  this is automatically done for you by the framework.

void CExButtonDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CExButtonDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CExButtonDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)

{

if(nIDCtl==IDC_DRAWBUTTON)

{

// 버튼 눌려짐

if(lpDrawItemStruct->itemState & ODS_SELECTED)

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,       // DestX

0,       // DestY

256,     // nDestWidth

72,      // nDestHeight

0,       // SrcX

0,       // SrcY

0,       // nStartScan

(WORD)72,// nNumScans

m_pDibd, // lpBit

&szBitmapInfo,// lpBitsInfo

DIB_RGB_COLORS);// wUsage

}

else// 버튼 나옴

{

::SetStretchBltMode(lpDrawItemStruct->hDC, COLORONCOLOR);

::SetDIBitsToDevice(lpDrawItemStruct->hDC,

0,     // DestX

0,     // DestY

256,  // nDestWidth

72,   // nDestHeight

0,      // SrcX

0,      // SrcY

0,     // nStartScan

(WORD)72,

// nNumScans

m_pDibu,     // lpBit

&szBitmapInfo,// lpBitsInfo

DIB_RGB_COLORS);  // wUsage

}

}

CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);

}

void CExButtonDlg::OnButton()

{

if(m_bBitmapButton.IsWindowEnabled())

m_bBitmapButton.EnableWindow(FALSE);

else

m_bBitmapButton.EnableWindow(TRUE);

}

void CExButtonDlg::OnDrawbutton()

{

AfxMessageBox(“TRUE COLOR 버튼 눌림”);

}

void CExButtonDlg::OnCheck1()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

CString data;

data.Format(“체크 상태 %d”,m_bCheck);

AfxMessageBox(data);

}

void CExButtonDlg::OnRadio1()

{

// 토글시킨다.

m_bRadio1=1-m_bRadio1;

// 토글된 값을 다시 대화 상자로 전송한다.

UpdateData(FALSE);

}

void CExButtonDlg::OnMybutton()

{

// TODO: Add your control notification handler code here

AfxMessageBox(“사용자 버튼이 눌려짐”);

}

// CMyButton 클래스

#if !defined(AFX_MYBUTTON_H__21237AB9_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_MYBUTTON_H__21237AB9_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MyButton.h : header file

//

////////////////////////////////////////////////////////

// CMyButton window

class CMyButton : public CButton

{

// Construction

public:

CMyButton();

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMyButton)

protected:

virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMyButton();

// Generated message map functions

protected:

//{{AFX_MSG(CMyButton)

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MYBUTTON_H__21237AB9_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// MyButton.cpp : implementation file

//

#include “stdafx.h”

#include “ExButton.h”

#include “MyButton.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////

// CMyButton

CMyButton::CMyButton()

{

}

CMyButton::~CMyButton()

{

}

BEGIN_MESSAGE_MAP(CMyButton, CButton)

//{{AFX_MSG_MAP(CMyButton)

ON_WM_MOUSEMOVE()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

////////////////////////////////////////////////////////

// CMyButton message handlers

// 마우스 움직일 때

void CMyButton::OnMouseMove(UINT nFlags, CPoint point)

{

SetClassLong(m_hWnd,GCL_HCURSOR,(long)AfxGetApp()->LoadCursor(IDC_CURSOR));

CButton::OnMouseMove(nFlags, point);

}

// 우측 마우스 버튼 메시지를 좌측 마우스 버튼 메시지로 바꿈

LRESULT CMyButton::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

switch(message)

{

case WM_RBUTTONDOWN:

SendMessage(WM_LBUTTONDOWN,wParam,lParam);

return 1;

case WM_RBUTTONUP:

SendMessage(WM_LBUTTONUP,wParam,lParam);

return 1;

}

return CButton::WindowProc(message, wParam, lParam);

}

리스트 상자에 데이터를 넣고 리스트 상자에서 선택된 항목을 알아보는 것은 ExDialog 예제와 ExFormView를 만들 때 설명하였습니다. 본 항목에서는 리스트 상자에서 좀더 향상된 방법을 이용하는 부분에 대해서 설명하겠습니다.

수정 모드가 있는 리스트 상자

리스트 상자에서 데이터를 수정하는 방법은 여러 가지가 있을 수 있습니다. 일반적인 방법으로 항목을 선택하고 수정 버튼을 클릭하면 해당 항목이 대화 상자에 나타나고 이 때 대화 상자에서 수정한 내용이 다시 리스트 상자에 설정되는 방법을 이용하는 것이 일반적입니다. 즉 다음과 같은 루틴이 일반적일 것입니다.

num=m_listBox.GetCurSel();// 현재 선택된 리스트 항목을 얻은 다음

m_listBox.GetText(num,data);// 현재 항목 문자열을 얻고

: 대화 상자를 이용하거나 적당한 방법을 이용하여 수정

m_listBox.DeleteString(num);// 해당 항목을 삭제하고

m_listBox.InsertString(num,data);// 해당 항목에 수정된 데이터를 넣는다.

이런 방법은 지금까지 설명한 내용을 이해했다면 충분히 할 수가 있습니다. 이 방법 외에 그냥 현재 리스트 상자에서 우측 마우스 버튼을 클릭하거나 좌측 마우스 버튼을 살짝 클릭하면 수정 모드 상태가 되어서 수정이 가능한 형태가 되는 프로그램도 있습니다. 즉 리스트 상자가 수정이 가능한 형태입니다.

그림 1-4-27은 본 항목에서 제작한 ExEditList 예제 실행 모습으로 특정 항목 수정을 리스트 상자 위에서 구동하는 형태입니다.

ExEditList는 리스트 상자에서 우측 마우스 버튼을 클릭하면 해당 데이터가 수정이 되는 형태가 되고, 이 때 수정을 하고 Enter 키를 치면 수정된 데이터가 해당 리스트에 설정되는 형태입니다.

이런 형태의 프로그램은 어떻게 만들까요? 일반적으로 많은 분들이 이런 함수가 없는가를 열심히 찾을 것입니다. 사실 MFC 함수가 너무 강력한 것이 많고 다양하기 때문에 그럴 수도 있겠지만 찾지 말고 응용을 했으면 하는 바람입니다.

리스트 상자에서 해당 항목을 우측 마우스 버튼을 클릭하였을 때 수정이 가능하게 하려면 먼저 리스트 상자에서 우측 마우스 버튼 메시지를 가로채야 합니다. 리스트 상자를 설정하고 외부에서 이벤트 메시지를 이용해서는 우측 마우스 버튼을 가로채지 못합니다. 결국 리스트 상자를 상속받은 자신만의 클래스를 만들면 내부에서 우측 마우스 버튼 메시지를 가로챌 수 있습니다. 이렇게 하여 우측 마우스 버튼을 클릭하면 현재 리스트 상자에 선택된 항목의 번호와 문자열을 얻을 수 있습니다.

이제 현재 리스트 해당 항목을 수정 상태로 만들어야 합니다. 수정 상태로 만든다기 보다 리스트 상자의 선택된 라인 위에 에디터 상자를 하나 만들어 넣고 에디터 상자에 해당 항목의 문자열을 설정하면 됩니다. 이렇게 하면 에디터 상자에 현재 선택한 라인의 문자열이 들어가게 되고 그 문자열을 수정할 수가 있습니다.

여기에서 또 하나의 문제는 에디터 상자에서 리턴키를 쳤을 때 수정된 데이터가 다시 리스트 상자에 설정되어야 한다는 것입니다. 그러나 에디터 상자 외부에서 WM_KEYDOWN의 메시지를 가로채서 키값이 Enter 키일 경우를 확인할 수가 없습니다. 결국 에디터 상자를 상속받은 나만의 에디터 상자를 만든 다음 이 에디터 상자에서 WM_KEYDOWN을 받고 여기에서 Enter 키를 누르면 현재 에디터 상자에 설정되어 있는 문자열이 리스트 상자의 해당 라인에 설정됩니다. 이런 방식으로 설정하려면 리스트 상자를 상속받은 나만의 리스트 상자와 에디터 상자를 상속받은 나만의 에디터 상자가 필요합니다.

이 둘의 관계는 또 어떻게 설정해야 할까요? 바로 나만의 리스트 상자 클래스 안에 멤버로 에디터 상자를 상속받은 나만의 에디터 상자를 설정하는 것입니다. 조금 복잡한 형태가 될지는 몰라도 일반적인 MFC 프로그래밍은 이 방법을 이용하는 것이 기본입니다.

그림 1-4-28은 수정 모드가 있는 리스트 상자를 만드는 형태롤 도시한 것입니다.

그림 1-4-28의 ①처럼 CEdit를 상속받은 CListChildEdit라는 클래스를 설정합니다. 이 클래스에서 설정하고자 하는 메시지 함수는 키 눌림(WM_KEYDOWN), 문자키 눌림(WM_CHAR), 그리고 포커스 사라짐(WM_KILLFOCUS)이 수행될 때의 내용입니다.

키 눌림 메시지를 가로채는 OnKeyDown 함수가 필요한 이유는 Enter 키가 눌려졌을 때 현재까지 입력된 데이터를 부모 윈도인 리스트 상자의 해당 항목에서 설정하기 위해서입니다. WM_KEYDOWN이 발생되면 WM_CHAR 키도 동시에 발생됩니다. WM_KEYDOWN에서 Enter 키의 내용을 처리하였으므로 WM_CHAR에서는 이 키가 눌려졌을 때 아무것도 수행하지 않도록 무시하는 루틴을 설정해야 합니다. 에디터 상자가 현재 수정 상태에서 수정되지 않고 다른 항목을 선택하였다면, 즉 포커스가 사라진다면 현재 설정된 에디터 상자는 감추어져야 하고 다시 리스트 상자의 원래 데이터로 복원되어야 합니다. 이 처리는 OnKillFocus 함수에서 합니다. 그림 1-4-28의 ⑤ 항목이 이에 해당됩니다.

리스트 상자는 그림 1-4-28의 ② 항목처럼 CListBox를 상속받고 멤버 변수로 위에서 만든 CListChildEdit형인 m_Edit를 설정합니다. 이렇게 한 후에 우측 마우스 버튼을 클릭했을 때의 메시지 OnRButtonDown을 설정합니다. 이 함수에서는 우측 마우스 버튼이 클릭되었을 때 해당 리스트 상자의 항목의 위치를 확인하고 이 위치에 에디터 상자를 설치합니다. 이렇게 설치한 후에 에디터 상자에 현재 리스트 상자에서 선택된 데이터의 문자열을 넣습니다. 이렇게 되면 모든 포커스는 에디터 상자에 넘어가게 되고, 에디터 상자에서 Enter 키를 누르면 수정이 되고, 에디터 상자에서 포커스를 다른 곳에 두면 수정이 취소되고 원래대로 복원되는 것입니다.

EnableEdit 함수는 상속받은 것이 아니라 CEditListBox에 새로 만든 함수입니다. 이런 함수를 만드는 이유는 CEditListBox를 수정 모드 없이 일반 형태로 사용할 때는 에디터 상자를 활성화하지 않고 수정 모드가 필요할 경우에 이 함수를 이용하여 수정이 가능하도록 하기 위해서입니다.

좀 복잡한 느낌이 들 수 있으나 상속을 받아서 새로운 윈도 클래스를 만드는 방법만이 좀더 멋지고 다양한 기능의 윈도를 만들 수가 있는 것입니다. 그림 1-4-28을 유념해서 봐주기 바랍니다.

수정 모드 리스트 상자 예제 ExEditList

ExEditList 예제는 대화 상자 형태로 만들어졌습니다. 이 예제는 단순하게 리스트 상자만 있고 이 리스트 상자에서 수정이 가능한 형태로 만든 것입니다. 본 예제를 만드는 순서는 다음과 같습니다.

프로젝트를 만든다.

프로젝트는 Dialog base를 선택하여 대화 상자 형태로 만듭니다. 이 대화 상자에 IDC_LIST인 리스트 상자를 설정합니다. 그림 1-4-27의 ① 항목이 본 예제에서 만드는 대화 상자 폼입니다.

CListChildEdit 클래스를 만든다.

리스트 상자 안에서 특정 리스트를 수정할 때 사용하는 에디터 상자를 먼저 만들어야 합니다. 이 클래스를 CListChildEdit로 만듭니다.

그림 1-4-28에서 ①, ③, ④, ⑤ 항목을 만들고자 하는 것입니다. ClassWizard를 실행시키고 Add class 항목을 선택하고 New 항목을 선택하여 Add Class 대화 상자가 나타나면 이곳에 CListChildEdit를 클래스명으로 Base class를 CEdit로 설정하여 만듭니다. 지금부터는 새로운 클래스를 만드는 방법에 대해서 간단하게 설명하겠습니다. 그림 1-4-24에 설정되어 있는 방법과 비슷한 방법으로 클래스를 만들면 됩니다.

이렇게 클래스를 만든 다음 그림 1-4-28의 ③, ④, ⑤ 항목을 만들기 위해서 클래스 위저드를 이용하여 WM_KEYDOWN, WM_CHAR, WM_KILLFOCUS 메시지에 의해서 수행되는 함수를 만듭니다. 먼저 WM_KEYDOWN 메시지가 발생되었을 때 수행되는 함수를 만들고(OnKeyDown 함수) 다음과 같이 기록합니다.

void CListChildEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// 만약 Enter 키를 눌렀다면

if(nChar==VK_RETURN)

{

// 부모 윈도를 받고

CListBox *pList=(CListBox *)GetParent();

// 현재 위치를 받는다.

int num=pList->GetCurSel();

// 현재 위치의 문자를 지운다.

pList->DeleteString(num);

// 에디터 상자의 데이터를 얻고

CString data;

GetWindowText(data);

// 이 데이터를 리스트 상자에 넣는다.

pList->InsertString(num,data);

// 리스트 상자에서 현재 위치를 다시 설정하고

pList->SetCurSel(num);

// 포커스를 리스트 상자로 넘긴다.

pList->SetFocus();

// 에디터 상자를 한 점으로 이동하여 사라지게 한다.

MoveWindow(0,0,0,0);

return;

}

CEdit::OnKeyDown(nChar, nRepCnt, nFlags);

}

OnKeyDown 함수에서 인자로 넘어오는 nChar에는 현재 눌려진 키값이 설정됩니다. 이 키값이 VK_RETURN이면 바로 Enter 키를 누른 형태입니다. 이 상태에서 GetParent() 함수를 이용하여 부모 윈도를 받습니다. 에디터 상자는 리스트 상자의 자식 윈도로 설정되기 때문에 GetParent를 설정하면 바로 부모 윈도인 리스트 상자 포인터를 얻게 됩니다. 이렇게 리스트 상자 포인터를 얻은 다음에 해당 데이터를 지우고 에디터 상자에 있는 데이터를 해당 데이터가 있는 번호로 설정한 다음 포커스를 리스트 상자로 넘기고 자신의 윈도 한 점으로 이동하여 사라지게 한 것입니다. MoveWindow 대신에 ShowWindow(SW_HIDE)를 사용하여도 됩니다.

MoveWindow 함수에서 한 점으로 설정하면 이 때는 ShowWindow(SW_HIDE)와 같은 형태가 됩니다. 필자는 ShowWindow(SW_HIDE)를 주로 선호하는데, 다른 프로그램은(특히 유명한 예제 소스들을 분석해보면) 자식 윈도를 사라지게 할 때 MoveWindow(0,0,0,0)를 많이 사용하는 것 같습니다. 둘 중에 아무거나 여러분이 마음에 드는 것을 사용하면 됩니다.

이제 WM_CHAR 메시지에 의해서 발생되는 함수(OnChar)를 만든 후에 이 함수에 다음과 같이 기록합니다.

// 문자키가 눌려졌을 때

void CListChildEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

if(nChar==VK_RETURN)

return;

CEdit::OnChar(nChar, nRepCnt, nFlags);

}

위와 같이 기록하는 이유는 ‘삑’ 소리를 제거하기 위함입니다. WM_KEYDOWN 함수에 의해서 현재 에디터 상자는 포커스를 읽어 버립니다. 이 상태에서 OnChar 함수는 실행되고 이 때□ ‘삑’하는 소리가 나게 됩니다. 따라서 문자키로 VK_RETURN일 경우에는 return문을 이용하여 상위 클래스의 OnChar를 실행하지 않도록 한 것입니다.

다음은 WM_KILLFOCUS 메시지에 의해서 발생되는 함수를 만들고 다음과 같이 기록합니다.

// 포커스가 사라질 때

void CListChildEdit::OnKillFocus(CWnd* pNewWnd)

{

CEdit::OnKillFocus(pNewWnd);

MoveWindow(0,0,0,0);

}

현재 에디터 상자에 포커스를 맞추었다가 다시 리스트 상자에서 새로운 항목을 클릭하면 현재 에디터 상자는 사라져야 합니다. 이 때 발생되는 메시지는 자신의 윈도에서 포커스가 사라질 때입니다. 이 때는 당연히 MoveWindow(0,0,0,0)를 사용하여 윈도를 사라지게 하면 됩니다.

CEditListBox 클래스를 만든다.

CEditListBox 클래스는 CListBox를 상속받은 형태로 제작합니다. 이 제작 방법에 대한 설명은 생략하겠습니다. 지금까지 새로운 클래스를 만드는 방법을 계속 설명하였기 때문입니다. 이렇게 클래스를 만든 후에 그림 1-4-28의 ②, ⑥, ⑦ 항목을 만듭니다. 먼저 헤더부에 다음과 같이 설정합니다.

// 에디터가 가능한 형태로 설정하는 함수

void EnableEdit(BOOL ibEnalbe=TRUE);

// 에디터 상자

CListChildEdit m_Edit;

// 에디터 가능 플러그

BOOL m_bEnableEdit;

EnableEdit 함수는 다음과 같습니다.

void CEditListBox::EnableEdit(BOOL ibEnable)

{

// 에디터 모드 설정

m_bEnableEdit=ibEnable;

// 에디터 모드가 TRUE이면

if(m_bEnableEdit)

{

// 에디터 윈도를 만든다.

m_Edit.Create(WS_VISIBLE|WS_BORDER|ES_AUTOHSCROLL|

ES_WANTRETURN|ES_MULTILINE,CRect(0,0,0,0),this,6000);

// 폰트를 리스트 상자에 있는 폰트로 등록한다.

m_Edit.SetFont(GetFont());

}

}

EnableEdit 함수는 먼저 ibEnable로 넘어온 인자값을 m_bEnableEdit에 저장합니다. 그리고 이 값이 TRUE이면(1이면) 에디터 윈도를 만든 다음 이 에디터 윈도의 폰트를 리스트 상자의 폰트로 설정합니다. CWnd::GetFont 함수는 현재 윈도에 설정되어 있는 폰트를 리턴합니다. 리턴된 폰트를 에디터 상자에 설정함으로써 에디터 상자의 폰트와 리스트 상자의 폰트가 같아집니다. 이렇게 하는 이유는 에디터 상자의 폰트와 리스트 상자의 폰트가 다를 경우 에디터 상자와 리스트 상자에 설정하면 글자 모양이 변해서 다른 윈도라는 느낌을 받게 되는데 이런 문제점을 없애고자 하는 것입니다.

이제 우측 마우스 버튼을 클릭하였을 때 해당 라인에 에디터 상자를 설정하는 내용을 만들어 보겠습니다. WM_RBUTTONDOWN이라는 메시지에 의한 함수를 만들고 이 함수에 다음과 같이 기록합니다.

void CEditListBox::OnRButtonDown(UINT nFlags, CPoint point)

{

// 만약 m_bEnableEdit가 FALSE일 경우 수정 불가능이므로

// 리턴한다.

if(!m_bEnableEdit)

return;

// 현재 위치를 얻는다.

int num=GetCurSel();

if(num<0)

return;

RECT rect;

// 현재 위치의 위치 좌표를 얻는다.

GetItemRect(num,&rect);

// 에디터 상자를 해당 위치로 이동시킨다.

m_Edit.MoveWindow(&rect,TRUE);

// 에디터 상자를 화면에 보여준다.

m_Edit.ShowWindow(SW_SHOW);

// 현재 위치의 데이터를 받아서

CString data;

GetText(num,data);

// 에디터 상자에 설정한다.

m_Edit.SetWindowText(data);

CListBox::OnRButtonDown(nFlags, point);

}

위와 같이 하면 리스트 상자 또한 설정이 끝난 것입니다.

아! 또 실수할까봐 짚고넘어가겠는데 헤더 포함하는 거 잊지 마십시오. CEditList- Box에는 CListChildEdit라는 형의 멤버가 설정되어 있습니다. 이 클래스는 우리가 만든 클래스입니다. 이 클래스 헤더를 CEditListBox 헤더부에 설정해야 합니다. 다음과 같이 말입니다.

#include “ListChildEdit.h”

이제는 헤더를 설정하는 부분에 대해서는 설명하지 않겠습니다. 한 가지 말하자면 다음과 비슷한 컴파일 에러 메시지가 발생하면 헤더를 포함시키지 않았는가를 꼭 확인해보기 바랍니다.

error C2146: syntax error : missing ‘;’ before identifier ‘m_Edit’

error C2501: ‘CListChildEdit’ : missing storage-class or type specifiers

대화 상자에 설정되어 있는 리스트 상자와 멤버 연결

이제 대화 상자에 설정되어 있는 리스트 상자와 CEditListBox 클래스의 멤버를 연결시키겠습니다. 이것을 연결하고자 할 때는 지금까지 사용한 방법으로 가능합니다. 즉 리스트 상자를 선택하고 Ctrl 키를 누른 상태에서 해당 아이템을 더블 클릭하면 Add Member 대화 상자가 나오고, 이 대화 상자에서 Category를 Control로 설정하면 Variable Type에 CEditListBox라는 형이 설정됩니다. 이제 형을 선택하면 됩니다. 선택하면 대화 상자가 나타나면서 헤더를 포함시켜야 된다는 메시지가 나올 것입니다. 즉 CEditListBox를 선택하였으므로 이 클래스형이 선언되어 있는 EditListBox.h를 포함시켜야 한다는 것입니다. 헤더부에 다음과 같이 하여 CEditListBox 클래스를 포함시킵니다.

#include “EditListBox.h”

위와 같이 한 후 대화 상자 초기화 함수에서(OnInitDialog 함수) 리스트 상자에 데이터를 넣고 수정 모드를 설정합니다. 이 내용은 다음과 같습니다.

BOOL CExEditListDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

// 수정 모드 가능하게 함

m_listBox.EnableEdit();

// 리스트 상자에 50개의 데이터를 기록한다.

CString data;

for(int i=0;i<50;i++)

{

data.Format(“data %d”,i);

m_listBox.AddString(data);

}

return TRUE;  // return TRUE  unless you set the focus to a control

}

지금까지는 수정 모드가 있는 리스트 상자를 설정한 예제 ExEditList를 제작하는 방법을 설명하였습니다. 실제 프로그램을 제작하여서 결과를 확인해 보기 바랍니다.

예제 ExEditList 소스

다음은 ExEditList에서 사용된 소스입니다.

대화 상자 소스

// ExEditListDlg.h : header file

//

#if !defined(AFX_EXEDITLISTDLG_H__D7F7FC6F_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

#define AFX_EXEDITLISTDLG_H__D7F7FC6F_255D_11D2_8B66_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

//////////////////////////////////////////////////////////

// CExEditListDlg dialog

#include “EditListBox.h”

class CExEditListDlg : public CDialog

{

// Construction

public:

CExEditListDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CExEditListDlg)

enum { IDD = IDD_EXEDITLIST_DIALOG };

CEditListBox m_listBox;

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExEditListDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CExEditListDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //

!defined(AFX_EXEDITLISTDLG_H__D7F7FC6F_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

// ExEditListDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExEditList.h”

#include “ExEditListDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

////////////////////////////////////////////////////////

// CExEditListDlg dialog

CExEditListDlg::CExEditListDlg(CWnd* pParent /*=NULL*/)

: CDialog(CExEditListDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExEditListDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

// m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CExEditListDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExEditListDlg)

DDX_Control(pDX, IDC_LIST, m_listBox);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExEditListDlg, CDialog)

//{{AFX_MSG_MAP(CExEditListDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////

// CExEditListDlg message handlers

BOOL CExEditListDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Set the icon for this dialog.  The framework does this automatically

//  when the application’s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

// 수정 모드 가능하게 함

m_listBox.EnableEdit();

// 리스트 상자에 50개의 데이터를 기록한다.

CString data;

for(int i=0;i<50;i++)

{

data.Format(“data %d”,i);

m_listBox.AddString(data);

}

return TRUE;  // return TRUE  unless you set the focus to a control

}

// If you add a minimize button to your dialog, you will need the code below

//  to draw the icon.  For MFC applications using the document/view model,

//  this is automatically done for you by the framework.

void CExEditListDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CExEditListDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

CEditListBox 소스

#if !defined(AFX_EDITLISTBOX_H__D7F7FC77_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

#define AFX_EDITLISTBOX_H__D7F7FC77_255D_11D2_8B66_006097AD9DDC__INCLUDED_

#include “ListChildEdit.h” // Added by ClassView

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// EditListBox.h : header file

//

//////////////////////////////////////////////////////////

// CEditListBox window

class CEditListBox : public CListBox

{

// Construction

public:

CEditListBox();

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEditListBox)

//}}AFX_VIRTUAL

// Implementation

public:

// 에디터가 가능한 형태로 설정하는 함수

void EnableEdit(BOOL ibEnalbe=TRUE);

// 에디터 상자

CListChildEdit m_Edit;

// 에디터 가능 플러그

BOOL m_bEnableEdit;

virtual ~CEditListBox();

// Generated message map functions

protected:

//{{AFX_MSG(CEditListBox)

afx_msg void OnRButtonDown(UINT nFlags, CPoint point);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

///////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EDITLISTBOX_H__D7F7FC77_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

// EditListBox.cpp : implementation file

//

#include “stdafx.h”

#include “ExEditList.h”

#include “EditListBox.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////

// CEditListBox

CEditListBox::CEditListBox()

{

m_bEnableEdit=FALSE;

}

CEditListBox::~CEditListBox()

{

}

BEGIN_MESSAGE_MAP(CEditListBox, CListBox)

//{{AFX_MSG_MAP(CEditListBox)

ON_WM_RBUTTONDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////

// CEditListBox message handlers

void CEditListBox::EnableEdit(BOOL ibEnable)

{

// 에디터 모드 설정

m_bEnableEdit=ibEnable;

// 에디터 모드가 TRUE이면

if(m_bEnableEdit)

{

// 에디터 윈도를 만든다.

m_Edit.Create(WS_VISIBLE|WS_BORDER|ES_AUTOHSCROLL|ES_WANTRETURN|ES_MULTILINE,CRect(0,0,0,0),this,6000);

// 폰트를 리스트 상자에 있는 폰트로 등록한다.

m_Edit.SetFont(GetFont());

}

}

void CEditListBox::OnRButtonDown(UINT nFlags, CPoint point)

{

// 만약 m_bEnableEdit가 FALSE일 경우 수정 불가능이므로

// 리턴한다.

if(!m_bEnableEdit)

return;

// 현재 위치를 얻는다.

int num=GetCurSel();

if(num<0)

return;

RECT rect;

// 현재 위치의 위치 좌표를 얻는다.

GetItemRect(num,&rect);

// 에디터 상자를 해당 위치로 이동시킨다.

m_Edit.MoveWindow(&rect,TRUE);

// 에디터 상자를 화면에 보여준다.

m_Edit.ShowWindow(SW_SHOW);

// 현재 위치의 데이터를 받아서

CString data;

GetText(num,data);

// 에디터 상자에 설정한다.

m_Edit.SetWindowText(data);

CListBox::OnRButtonDown(nFlags, point);

}

CListChildEdit 클래스 소스

#if !defined(AFX_LISTCHILDEDIT_H__D7F7FC78_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

#define AFX_LISTCHILDEDIT_H__D7F7FC78_255D_11D2_8B66_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// ListChildEdit.h : header file

//

///////////////////////////////////////////////////////////

// CListChildEdit window

class CListChildEdit : public CEdit

{

// Construction

public:

CListChildEdit();

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CListChildEdit)

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CListChildEdit();

// Generated message map functions

protected:

//{{AFX_MSG(CListChildEdit)

afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);

afx_msg void OnKillFocus(CWnd* pNewWnd);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_LISTCHILDEDIT_H__D7F7FC78_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

// ListChildEdit.cpp : implementation file

//

#include “stdafx.h”

#include “ExEditList.h”

#include “ListChildEdit.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////

// CListChildEdit

CListChildEdit::CListChildEdit()

{

}

CListChildEdit::~CListChildEdit()

{

}

BEGIN_MESSAGE_MAP(CListChildEdit, CEdit)

//{{AFX_MSG_MAP(CListChildEdit)

ON_WM_KEYDOWN()

ON_WM_CHAR()

ON_WM_KILLFOCUS()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////

// CListChildEdit message handlers

void CListChildEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// 만약 Enter 키를 눌렀다면

if(nChar==VK_RETURN)

{

// 부모 윈도를 받고

CListBox *pList=(CListBox *)GetParent();

// 현재 위치를 받는다.

int num=pList->GetCurSel();

// 현재 위치의 문자를 지운다.

pList->DeleteString(num);

// 에디터 상자의 데이터를 얻고

CString data;

GetWindowText(data);

// 이 데이터를 리스트 상자에 넣는다.

pList->InsertString(num,data);

// 리스트 상자에서 현재 위치를 다시 설정하고

pList->SetCurSel(num);

// 포커스를 리스트 상자로 넘긴다.

pList->SetFocus();

// 에디터 상자를 한 점으로 이동하여 사라지게 한다.

MoveWindow(0,0,0,0);

return;

}

CEdit::OnKeyDown(nChar, nRepCnt, nFlags);

}

//문자키가 눌려졌을 때

void CListChildEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

if(nChar==VK_RETURN)

return;

CEdit::OnChar(nChar, nRepCnt, nFlags);

}

// 포커스가 사라질 때

void CListChildEdit::OnKillFocus(CWnd* pNewWnd)

{

CEdit::OnKillFocus(pNewWnd);

MoveWindow(0,0,0,0);

}

Insert 키와 Delete 키에 대응하는 리스트 상자

본 항목에서는 리스트 상자에서 키 이벤트에 의해 데이터가 삭제되고 설정되는 방법에 대해서 설명하겠습니다. 일반 애플리케이션 프로그램 중 리스트 상자에서 Delete 키를 누르면 해당 데이터가 삭제되거나 Insert 키를 누르면 새로운 데이터가 삽입되는 기능이 있는 리스트 상자를 종종 볼 수 있습니다. 그러나 실제적으로 CListBox를 설정하여 이벤트 메시지를 보면 키가 눌려졌을 때 발생되는 이벤트가 없습니다.

그렇다면 이런 이벤트는 어떻게 만드는 것일까? 이것 또한 상속을 이용한 것입니다. CListBox를 상속받은 하나의 클래스를 만들고 이 클래스에서 WM_KEYDOWN 메시지에 의한 함수를 만든 다음 이 키가 Insert 키이면 새로운 데이터를 삽입하고 이 키가 Delete 키이면 현재 설정된 데이터를 삭제하면 됩니다. 전 항목에 대한 내용을 이해하였다면 지금까지의 내용을 보고 “아하! 어떻게 하면 되겠구나” 하는 감이 바로 올 것입니다. 클래스 위저드를 이용하여 CListBox를 상속받은 CAddDelListBox를 만들고 난 후에 WM_KEYDOWN 메시지에 의거된 함수를 만들고, 이 함수에 다음과 같이 설정하면 Insert 키와 Delete 키에 대응되는 리스트 상자를 만들 수가 있습니다.

void CAddDelListBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// 리스트 상자에서 Delete 키가 눌려지면

if(nChar==VK_DELETE)

{

// 현재 위치를 받은 다음

int num=GetCurSel();

// 현재 선택된 항목이 없으면 리턴한다.

if(num<0)

return;

// 현재 선택된 항목을 지운다.

DeleteString(num);

}

else if(nChar==VK_INSERT)

{

CString data;

: // 새로 입력할 데이터를 data에 설정하는 루틴

// 현재 위치를 받고

int num=GetCurSel();

// 현재 선택된 항목이 없으면 데이터를 추가

if(num<0)

AddString(data);

// 설정된 항목이 있으면 해당 항목 사이로 추가

else

InsertString(num,data);

}

CListBox::OnKeyDown(nChar, nRepCnt, nFlags);

}

여기서 Delete 키를 누른 경우에는 해당 항목을 지우고, Insert 키를 누른 경우에는 대화 상자를 이용하거나 다른 방법으로 새로 입력될 데이터를 data에 설정한 후에 이것을 리스트 상자에 삽입합니다.

예제 ExAddDelList는 리스트 상자에서 Delete 키를 누르면 해당 항목이 삭제되고, Insert 키를 누르면 새로운 항목이 삽입되는 형태입니다. 이 프로그램 형태는 그림 1-4-29와 같습니다.

그림 1-4-29와 같이 리스트 상자를 우리가 만든 CAddDelListBox와 DDX_Control로 연결합니다. 이렇게 연결한 후에 CAddDelListBox 내부에 OnKeyDown 함수를 만들고 Delete 키를 누르면 리스트 상자의 멤버 함수인 DeleteString의 이용으로 해당 리스트가 삭제되고, Insert 키를 누르면 새로운 대화 상자, 즉 CAddDataDlg인 대화 상자가 실행됩니다. 이 대화 상자는 그림 1-4-29의 ② 항목과 같습니다. 이렇게 대화 상자를 실행시킨 후에 대화 상자의 에디터 상자에 입력된 데이터를 data에 받고 이 값을 리스트 상자에 삽입합니다.

본 예제의 소스는 제공되는 CD를 참조하기 바랍니다. 책의 부피를 최대한 줄이기 위해서 가급적 소스의 기재를 자제하려고 합니다. 지금까지의 내용을 이해하였다면 쉽게 ExAddDelList와 같은 프로그램을 제작할 수 있을 것입니다.

사용자 그리기 리스트 상자

사용자 그리기란 리스트 상자의 스타일에 Owner draw 옵션을 설정한 것입니다. 이렇게 설정하면 대화 상자에서는 DrawItem이 발생되는데, 이 DrawItem에서 직접 사용자가 리스트 상자에 들어갈 항목들을 그려줍니다. 버튼에서도 사용자 그리기 버튼을 설정하여 WM_DRAWITEM 메시지를 받아서 함수를 만들고, 이것을 이용하여 트루 컬러를 설정하는 방법을 설명하였습니다. 이와 같은 방법으로 리스트 상자에서도 사용자 그리기를 할 수가 있습니다.

사용자 그리기를 어떻게 하는가에 대한 기본 개념은 버튼에서 트루 컬러를 출력할 때 설명하였으므로, 이번에는 예제를 작성해 가면서 사용자 그리기 리스트 상자를 어떻게 구현하는가를 설명하겠습니다. 본 예제는 ExOwDrawList로써 프로그램 전체 구조는 그림 1-4-30과 같습니다.

그림 1-4-30의 ①은 본 예제의 출력 형태입니다. 리스트 상자에 글자가 들어가 있는 것이 아니라 색상 밖에 들어가 있으며 해당 색상바를 선택하였을 때 선택된 항목이 하단부에 출력되는 형태입니다. 이런 사용자 그리기 리스트 상자를 만들려면 리스트 상자 속성을 그림 1-4-30의 ④번처럼 Owner draw 항목을 Variable이나 Fixed로 설정해야 합니다. Fixed로 설정하면 리스트 상자에 설정되는 항목의 크기가 변하지 않고, Variable로 설정하면 항목의 크기를 자유자재로 변경하면서 설정할 수가 있습니다. 그림 1-4-30의 ① 항목과 같은 대화 상자를 만들고 리스트 상자를 설정한 후에 ④ 항목을 선택하여 사용자 그리기로 한 다음에는 실제로 화면에 그리는 일만 남았습니다. 이 때□ 단계적으로 하는 방법은 두 가지로 나눌 수 있습니다.

리스트 상자의 항목 크기를 설정한다.

리스트 상자에서 사용자 그리기를 Variable로 설정하였으므로 프로그래머는 현 아이템의 크기를 설정해주어야 합니다. 이것을 설정하기 위해서는 리스트 상자를 상속받은 또 다른 클래스를 만들어야 합니다. 본 예제에서는 CListBox를 상속받은 COwDrawListBox 리스트 상자를 만들었습니다. 이 클래스를 만든 다음에 ClassWizard를 이용하여 MeasureItem이라는 메시지가 발생될 때의 함수를 만듭니다. 이렇게 만든 함수는 다음과 같습니다.

void COwDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT

lpMeasureItemStruct)

{

}

위와 같이 함수가 만들어지면서 인자로 MEASUREITEMSTRUCT 구조체의 포인터가 넘어옵니다. 이 구조체는 다음과 같습니다.

typedef struct tagMEASUREITEMSTRUCT {   // mis

UINT  CtlType;          // 컨트롤 타입

UINT  CtlID;              // 컨트롤 ID

UINT  itemID;           // 메뉴 ID

UINT  itemWidth;    // 아이템의 가로 크기

UINT  itemHeight;   // 아이템의 세로 크기

DWORD itemData;  // 아이템 데이터 포인터

} MEASUREITEMSTRUCT;

MEASUREITEMSTRUCT 구조체는 사용자 그리기를 설정하여 그리고자 하는 아이템의 크기를 설정할 때 사용합니다. CtlType은 리스트 상자일 경우 ODT_LIST- BOX, 콤보 상자일 경우 ODT_COMBOBOX, 버튼일 경우 ODT_BUTTON, 메뉴일 경우 ODT_MENU, 리스트 View일 경우 ODT_LISTVIEW값이 설정됩니다.

CtlID는 현재 컨트롤의 ID를 의미하며, itemID는 보통 사용자 그리기 메뉴를 선택하면 설정되는 메뉴 ID입니다.

itemData는 현재 데이터의 32비트 포인터입니다. 이 포인터를 설정하는 이유는 설정되는 아이템이 문자열 데이터도 있지만 여러 가지 다른 형태일 수도 있기 때문입니다. 이 구조체의 인자를 받아서 우리가 설정해야 할 필요가 있는 항목은 itemHeight입니다. 이것은 세로 크기를 의미하는데, 리스트 상자에서 색상바를 그릴 때 세로 크기를 만드는 항목입니다. 이 항목은 일률적으로 20으로 설정하였습니다. 다음은 세로 크기를 20으로 설정한 형태입니다.

void COwDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT

lpMeasureItemStruct)

{

// 세로 크기만 20으로 설정한다.

lpMeasureItemStruct->itemHeight=20;

}

MeasureItem은 사용자 그리기 옵션을 Variable로 설정하였을 때 한 항목마다 계속적으로 발생됩니다. 따라서 각 아이템에 따라서 다르게 설정할 수 있습니다. 예를 들어서 아이템이 문자열로 들어오는데, 이 문자열이 “만득이”이면 세로 크기를 30, “천순이”이면 50으로 하고자 한다면 다음과 같이 할 수도 있습니다.

void COwDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT

lpMeasureItemStruct)

{

CString data=(LPSTR)lpMesureItemStruct->itemData;

if(data==”만득이”)

lpMeasureItemStruct->itemHeight=30;

else if(data==”천순이”)

lpMeasureItemStruct->itemHeight=50;

else

lpMeasureItemStruct->itemHeight=20;

}

위와 같은 방법으로 항목마다 크기를 제어할 수 있습니다.

사용자 그리기

사용자 그리기를 하고자 할 때는 CListBox를 상속받은 COwDrawListBox 리스트 상자에서 DrawItem 메시지를 받은 함수를 만듭니다. 이렇게 함수를 만들면 다음과 같은 함수가 만들어집니다.

void COwDrawListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

}

DRAWITEMSTRUCT에 대해서는 트루 컬러 버튼을 출력할 때 설명하였습니다. 여기에서는 다음과 같이 코딩하여 해당 항목마다 색상바를 출력합니다.

void COwDrawListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

// DC를 받는다.

CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);

CPen pen,*oldpen;

CBrush brush,*oldbrush;

// itemDdata에서 색상값을 받는다.

COLORREF color=(COLORREF)lpDrawItemStruct->itemData;

// 브러시를 만든다.

brush.CreateSolidBrush(color);

// 현재 아이템이 선택되어 있거나 포커스가 설정되어 있으면

// 검정색 펜을 설정하고

if(lpDrawItemStruct->itemState==ODS_SELECTED || lpDrawItemStruct->itemAction==ODA_FOCUS )

pen.CreatePen(PS_SOLID,5,RGB(0,0,0));

// 그렇지 않다면 그냥 현재 설정한 색으로 펜을 만든다.

else

pen.CreatePen(PS_SOLID,1,color);

// 펜과 브러시를 설정한다.

oldpen=pDC->SelectObject(&pen);

oldbrush=pDC->SelectObject(&brush);

// 상자를 그린다.

pDC->Rectangle(&lpDrawItemStruct->rcItem);

// 이전 펜과 브러시로 복귀

pDC->SelectObject(oldpen);

pDC->SelectObject(oldbrush);

pen.DeleteObject();

brush.DeleteObject();

}

DC를 받은 다음 펜과 브러시를 만들 때 lpDrawItemStruct 멤버인 itemData를 COLORREF의 포인터로 받아서 이 색을 이용합니다. 그러나 현재 아이템이 선택되어 있을 경우에는 선택되었다는 것을 알려주기 위해서 펜을 5 두께의 검정색 펜으로 만들었습니다. 해당 아이템을 선택하면 선택한 항목에 검정색 박스가 쳐지기 때문입니다. 실제 그리는 루틴은 본 책 Part 1의 3장에서 설명하였습니다. 그리는 루틴이 이해가 안 된다면 3장을 참고하기 바랍니다. 이제 컬러바를 그리는 리스트 상자 클래스는 완성된 것입니다.

색상을 리스트 상자에 삽입하기

색상을 리스트 상자에 삽입하려면 다음과 같은 방법을 사용하면 됩니다.

// 색값을 데이터로 리스트 상자에 넣는다.

m_listBox.AddString((LPCTSTR)RGB(0,0,0));

m_listBox.AddString((LPCTSTR)RGB(255,0,0));

m_listBox.AddString((LPCTSTR)RGB(0,255,0));

m_listBox.AddString((LPCTSTR)RGB(0,0,255));

m_listBox.AddString((LPCTSTR)RGB(255,255,0));

m_listBox.AddString((LPCTSTR)RGB(255,0,255));

m_listBox.AddString((LPCTSTR)RGB(255,255,255));

위와 같이 하면 색상값이 리스트 상자에 전달됩니다. 이 전달된 데이터가 DrawItem 아이템의 인자 lpDrawItemStruct의 itemData에 넘겨지고, 이것을 위에서처럼 다시 COLORREF로 받아서 색상을 그려줍니다.

선택 색상값을 받아서 static 상자에 설정하기

선택된 색상값을 받으려면 클래스 위저드에서 해당 리스트 상자를 선택하고 LBN_SELCHANGE 항목을 선택하여 함수를 만든 다음 해당 항목의 아이템을 받으면 됩니다. 이 내용은 다음과 같습니다.

void CExOwDrawListDlg::OnSelchangeList1()

{

// 리스트 상자에서 현재 선택된 위치를 얻는다.

int num=m_listBox.GetCurSel();

// 아이템 데이터를 얻는다.

COLORREF color=(COLORREF)m_listBox.GetItemData(num);

CRect rect;

STATIC 윈도 좌표를 얻는다.

GetDlgItem(IDC_COLORRECT)->GetWindowRect(&rect);

// 스크린 좌표를 클라이언트 좌표로 전환

ScreenToClient(rect);

// 해당 좌표에 상자를 그린다.

CDC *pDC=GetDC();

CBrush brush,*oldbrush;

brush.CreateSolidBrush(color);

oldbrush=pDC->SelectObject(&brush);

pDC->Rectangle(rect);

pDC->SelectObject(oldbrush);

brush.DeleteObject();

ReleaseDC(pDC);

}

지금까지는 리스트 상자에서 데이터를 얻고자 할 때 GetText를 사용하였습니다. 그러나 현재 리스트 상자에는 문자열이 들어가 있는 것이 아니고 컬러값이 들어가 있기 때문에 GetText를 사용하지 않고 GetItemData 함수를 사용하였으며, 이렇게 넘어온 데이터를 [COLORREF]로 형 전환하여 색상값을 받았습니다. 이렇게 색상값을 받은다음 이 색상을 Static 윈도에 영역을 상자로 그린 것입니다. Static 윈도를 사용하는 방법은 본 장 1항목에서 설명하였습니다. 그 내용을 참고하면 될 것입니다.

지금까지의 방법을 이해하였다면 프로그램을 제작할 수 있을 것입니다.

ExOwDrawList 예제 소스

ExOwDrawList 예제의 소스는 다음과 같습니다. 필요없는 부분들은 설정하지 않았습니다. 나머지 부분은 제공되는 CD를 참조하기 바랍니다.

대화 상자 소스

///////////////////////////////////////////////////////////////////

// CExOwDrawListDlg dialog

#include “OwDrawListBox.h”

class CExOwDrawListDlg : public CDialog

{

// Construction

public:

: 중간 생략

// Dialog Data

//{{AFX_DATA(CExOwDrawListDlg)

enum { IDD = IDD_EXOWDRAWLIST_DIALOG };

COwDrawListBox m_listBox;

//}}AFX_DATA

: 중간 생략

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXOWDRAWLISTDLG_H__D7F7FC7F_255D_11D2_8B66_006097AD9DDC__INCLUDED_)

// ExOwDrawListDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExOwDrawList.h”

#include “ExOwDrawListDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////

// CExOwDrawListDlg dialog

CExOwDrawListDlg::CExOwDrawListDlg(CWnd* pParent /*=NULL*/)

: CDialog(CExOwDrawListDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExOwDrawListDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CExOwDrawListDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExOwDrawListDlg)

DDX_Control(pDX, IDC_LIST1, m_listBox);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExOwDrawListDlg, CDialog)

//{{AFX_MSG_MAP(CExOwDrawListDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_LBN_SELCHANGE(IDC_LIST1, OnSelchangeList1)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

////////////////////////////////////////////////////

// CExOwDrawListDlg message handlers

BOOL CExOwDrawListDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Set the icon for this dialog.  The framework does this automatically

// when the application’s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

// 색상값을 데이터로 리스트 상자에 넣는다.

m_listBox.AddString((LPCTSTR)RGB(0,0,0));

m_listBox.AddString((LPCTSTR)RGB(255,0,0));

m_listBox.AddString((LPCTSTR)RGB(0,255,0));

m_listBox.AddString((LPCTSTR)RGB(0,0,255));

m_listBox.AddString((LPCTSTR)RGB(255,255,0));

m_listBox.AddString((LPCTSTR)RGB(255,0,255));

m_listBox.AddString((LPCTSTR)RGB(255,255,255));

return TRUE;  // return TRUE  unless you set the focus to a control

}

// If you add a minimize button to your dialog, you will need the code below

//  to draw the icon.  For MFC applications using the document/view model,

//  this is automatically done for you by the framework.

void CExOwDrawListDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CExOwDrawListDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CExOwDrawListDlg::OnSelchangeList1()

{

// 리스트 상자에서 현재 선택된 위치를 얻는다.

int num=m_listBox.GetCurSel();

// 아이템 데이터를 얻는다.

COLORREF color=(COLORREF)m_listBox.GetItemData(num);

CRect rect;

// STATIC 윈도 좌표를 얻는다.

GetDlgItem(IDC_COLORRECT)->GetWindowRect(&rect);

// 스크린 좌표를 클라이언트 좌표로 전환

ScreenToClient(rect);

// 해당 좌표에 상자를 그린다.

CDC *pDC=GetDC();

CBrush brush,*oldbrush;

brush.CreateSolidBrush(color);

oldbrush=pDC->SelectObject(&brush);

pDC->Rectangle(rect);

pDC->SelectObject(oldbrush);

brush.DeleteObject();

ReleaseDC(pDC);

}

COwDrawListBox의 소스부

// OwDrawListBox.cpp : implementation file

//

#include “stdafx.h”

#include “ExOwDrawList.h”

#include “OwDrawListBox.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////////////

// COwDrawListBox

COwDrawListBox::COwDrawListBox()

{

}

COwDrawListBox::~COwDrawListBox()

{

}

BEGIN_MESSAGE_MAP(COwDrawListBox, CListBox)

//{{AFX_MSG_MAP(COwDrawListBox)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////

// COwDrawListBox message handlers

void COwDrawListBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

// DC를 받는다.

CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);

CPen pen,*oldpen;

CBrush brush,*oldbrush;

// itemDdata에서 색상값을 받는다.

COLORREF color=(COLORREF)lpDrawItemStruct->itemData;

// 브러시를 만든다.

brush.CreateSolidBrush(color);

// 현재 아이템이 선택되어 있거나 포커스가 설정되어 있으면

// 검정색 펜을 설정하고

if(lpDrawItemStruct->itemState==ODS_SELECTED ||

lpDrawItemStruct->itemAction==ODA_FOCUS )

pen.CreatePen(PS_SOLID,5,RGB(0,0,0));

// 그렇지 않다면 그냥 현재 설정한 색으로 펜을 만든다.

else

pen.CreatePen(PS_SOLID,1,color);

// 펜과 브러시를 설정한다.

oldpen=pDC->SelectObject(&pen);

oldbrush=pDC->SelectObject(&brush);

// 상자를 그린다.

pDC->Rectangle(&lpDrawItemStruct->rcItem);

// 이전 펜과 브러시로 복귀

pDC->SelectObject(oldpen);

pDC->SelectObject(oldbrush);

pen.DeleteObject();

brush.DeleteObject();

}

void COwDrawListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)

{

// 세로 크기만 20으로 설정한다.

lpMeasureItemStruct->itemHeight=20;

}

리스트 상자와 콤보 상자는 멤버 함수들이 매우 비슷하고 기능 또한 비슷합니다. ExDialog나 ExFormView를 작성하면서 콤보 상자의 사용법 또한 알 수 있을 것입니다. 콤보 상자와 리스트 상자와의 차이점은 콤보 상자는 에디터할 수 있는 창이 하나 있다는 것입니다. 이 창에 문자를 치면 콤보 상자 안에 있는 데이터를 찾아서 항목이 이동됩니다. 주로 많은 데이터 중에서 문자열을 검색할 때 편리하게 되어 있는 형태가 콤보 상자입니다. 이 콤보 상자의 에디터 상자에 데이터를 입력할 때 Enter 키를 치면 입력된 데이터가 콤보 상자 리스트로 들어가는 프로그램을 본 적이 있을 것입니다.

입력된 데이터가 콤보 상자 안으로 들어가는 방법은 여러 가지가 있을 수 있습니다. 일반적인 방법으로는 콤보 상자에서 CBN_EDITCHANGE를 선택하여 문자열이 입력되면 이 문자열을 GetWindowText 함수를 이용하여 받은 다음 AddString을 이용하여 데이터를 넣을 수도 있습니다. 그러나 이 방법은 Enter 키에 대응되지 않습니다.결국 Enter 키를 눌렀을 때 이벤트를 받아서 해당 데이터를 콤보 리스트에 넣으려면 지금까지 계속적으로 설명한 상속 방법이 가장 적절할 것입니다.

CComboBox를 상속받은 새로운 클래스의 예를 들면, CEditCombo 클래스를 만들고 이 클래스에서 PreTranslateMessage 함수를 이용하여 메시지가 처리되기 이전에 Enter 키를 누르면 현재 입력된 데이터를 얻은 다음 이것을 콤보 상자에 넣으면 됩니다. 상속을 받아서 사용한다는 점을 빼고는 아주 간단합니다. PreTranslateMessage를 다음과 같이 수정하면 콤보 상자의 에디터란에서 입력한 데이터를 콤보 리스트로 설정할 수가 있습니다.

BOOL CEditCombo::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

// 메시지가 WM_KEYDOWN이고 키값이 VK_RETURN이면

if(pMsg->message==WM_KEYDOWN &&

pMsg->wParam==VK_RETURN)

{

// 입력된 데이터를 받은 다음

CString data;

GetWindowText(data);

// 이 데이터를 콤보 리스트에 넣는다.

AddString(data);

// 방금 입력된 데이터가 어느 위치에 있는가 번호를 얻은 다음

int num=FindString(0,data);

// 그 위치로 설정한다.

SetCurSel(num);

}

return CComboBox::PreTranslateMessage(pMsg);

}

예제 ExEditCombo는 이런 콤보 상자를 설정하여 문자를 입력하면 입력된 문자가 콤보 리스트에 들어갑니다. ExEditCombo의 전체 구조는 그림 1-4-31과 같습니다.

그림 1-4-31을 보고 위에 설명한 PreTranslateMessage 함수를 이해하면 ExEditCombo를 작성하는 데 어려움이 없을 것입니다. 본 예제의 소스와 작성 방법은 생략하겠습니다. 지금까지 예제를 작성하는 방법을 설명하였으므로 응용하면 됩니다. 소스는 제공되는 CD를 참조하기 바랍니다.

본 항목에서는 컨트롤의 배경 색상을 바꾸는 방법과 파일 대화 상자, 프린터 대화 상자, 색상 대화 상자 등 윈도에서 제공하는 일반적인 대화 상자를 출력하는 방법에 대해서 설명합니다.

컨트롤 배경색 바꾸기

컨트롤의 배경색을 바꾸고자 할 경우에는 WM_CTRCOLOR 메시지를 이용하여 함수를 만들고 이 함수에서 브러시를 리턴하면 컨트롤의 배경색을 바꿀 수가 있습니다. WM_CTRCOLOR 메시지에 의해 만들어진 함수는 다음과 같습니다.

HBRUSH CExCommDlgView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

return hbr;

}

이 함수에서 pDC는 해당 컨트롤의 DC이며 pWnd는 윈도 핸들입니다. 중요한 것은 nCtlColor인데, 어떤 스타일의 컨트롤인가가 여기에 설정되어 있습니다.

이 값은 다음과 같습니다.

CTLCOLOR_BTN : 버튼 컨트롤

CTLCOLOR_DLG : 대화 상자 컨트롤

CTLCOLOR_EDIT : 에디터 상자 컨트롤

CTLCOLOR_LISTBOX : 리스트 상자 컨트롤

CTLCOLOR_MSGBOX : 메시지 상자 컨트롤

CTLCOLOR_SCROLLBAR : 스크롤바 컨트롤

CTLCOLOR_STATIC :Static : 윈도 컨트롤

OnCtlColor 함수에서 리턴하는 브러시값이 바로 컨트롤의 배경색이 됩니다. 만약 에디터 상자 컨트롤의 배경색을 파랑색으로 바꾸고자 한다면 다음과 같이 하면 됩니다.

HBRUSH CExCommDlgView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

if(nCtlColor == CTLCOLOR_EDIT)

{

return CreateSolidBrush(RGB(0,0,255));

}

return hbr;

}

위의 예는 nCtlColor가 에디터 상자라면 청색 브러시를 리턴하고 그렇지 않을 경우에는 기본적으로 설정되어 있는 브러시를 리턴하는 형태입니다. OnCtlColor 함수를 만들고 간단하게 브러시를 리턴하면 컨트롤의 색이 바뀌게 됩니다. 그러나 일반 버튼은 색이 바뀌지 않습니다. 아니 바뀌는데, 그 위에 버튼 형식을 다시 그리기 때문에 배경색이 나타나지 않는다고 하는 것이 옳을 것 같습니다.

일반 버튼은 위의 버튼처럼 보이기 위해서 새로 그리기 때문에 배경색이 보이지 않는 것입니다. 일반 버튼에서 배경색을 보려면 버튼은 큰 영역을 설정하고 그 안에 작은 크기의 비트맵 버튼을 설정하면 배경색을 볼 수 있습니다. 그런데 그렇게 해서 배경색을 볼 필요는 없을 것 같군요.

공용 대화 상자들

공용 대화 상자는 윈도에서 제공하는 기본적인 대화 상자를 의미합니다. 가장 일반적으로 많이 사용하는 파일 대화 상자, 색상을 선택하는 색상 대화 상자, 폰트를 설정하는 폰트 대화 상자, 인쇄할 때 사용하는 프린트 대화 상자 등이 공용 대화 상자입니다. 이 공용 대화 상자들은 이미 만들어져 있는 상태이기 때문에 해당 클래스를 호출하여 DoModal() 함수를 이용하면 바로 실행할 수가 있습니다.

다음은 일반 공용 대화 상자들의 설명입니다.

파일 대화 상자

파일 대화 상자는 그림 1-4-32와 같은 대화 상자를 의미합니다.

그림 1-4-32와 같은 파일 대화 상자를 만들고자 할 때 사용되는 클래스는 CFileDialog입니다. CFileDialog는 생성자가 조금 복잡합니다. 이 생성자는 다음과 같습니다.

CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );

bOpenFileDialog는 파일을 열 경우와 저장할 경우에 설정하는 플러그입니다. 열 경우에는 이 값을 TRUE로, 저장할 경우에는 FALSE로 설정합니다. FALSE로 설정하고 대화 상자를 실행하였을 때 이미 존재하는 파일명을 선택하면 “파일이 있습니다. 덮어쓸가요?”라는 메시지 상자가 출력되고 이 메시지 상자에서 OK 버튼을 클릭하면 OK를 리턴하고, 그렇지 않을 경우에는 CANCEL을 리턴합니다.

lpszDefExt는 기본적으로 설정된 확장자를 의미합니다. 만약 이 문자열을 “TXT”라고 하면 그림 1-4-32의 ① 항목에는 확장자가 “TXT”인 파일만 나타나게 됩니다. lpszFileName은 처음에 설정된 파일명을 의미합니다. 이곳에 파일명을 설정하면 그림 1-4-32의 ② 항목에 파일명이 설정됩니다. dwFlags는 파일 대화 상자에서 검색할 파일 리스트의 형태를 설정하는 플러그입니다.

OFN_HIDEREADONLY는 ReadOnly라는 체크 상자를 감추라는 의미이며, OFN_OVERWRITEPROMPT는 저장 상태로 대화 상자를 출력하여 이미 존재하는 파일을 선택하였을 때 메시지 상자가 출력되는 것을 설정하는 옵션입니다. 이외에도 많은 옵션이 있는데 이 옵션 리스트는 도움말에서 OPENFILENAME을 찾아보면 알 수 있을 것입니다.

lpszFilter는 검정색 파일 필터입니다. 만일 TXT 파일만 검색하고자 한다면 “○○○○(*.TXT) |*.TXT”라고 하면 됩니다. 이렇게 하면 그림 1-4-32의 ③항목에 문자열이 뜨면서 *.TXT 파일을 ①에 리스트로 보여줍니다. 때로는 *.TXT와 *.CPP를 선택하여 보여주고 싶을 경우도 있을 것입니다. 이 때는 ‘|’ 기호를 이용하여 연속해서 설정해 주면 됩니다. 즉 다음과 같이 합니다.

“텍스트 파일(*.TXT) |*.TXT| CPP 파일(*.CPP) |*.CPP”

이렇게 하면 그림 1-4-32의 ③항목에 두 개의 리스트가 설정됩니다. Visual C++에서 파일 대화 상자를 오픈하면 cpp, h, rc 파일이 모두 화면에 뜨는데, 이렇게 여러 개의 확장자 파일을 함께 출력하려면 ‘;’ 기호를 이용하여 확장자를 연속해서 설정하여 주면 됩니다. 다음은 cpp와 h를 함께 설정한 예입니다.

“CPP 파일(*.CPP;*.H)|*.CPP;*.H|”

위와 같이 설정하면 그림 1-4-32의 ① 항목에 확장자가 CPP인 것과 H인 것이 모두 나타나게 됩니다.

이렇게 생성자를 설정하고 DoModal() 함수를 이용하면 파일 대화 상자가 실행됩니다. 이렇게 실행한 후에 파일 대화 상자가 종료되었을 때 선택된 파일명을 알려면 GetFileName이라는 함수를 사용하면 되고, 전체 경로까지 알고자 한다면 GetPathName을 사용하면 됩니다. 다음은 파일 대화 상자를 실행하고 선택한 파일의 내용을 읽어서 CString에 설정하는 예입니다.

// 파일 대화 상자 생성

CFileDialog pDlg(TRUE,”TXT”,”*.TXT”,

OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

“텍스트 파일(*.TXT)|*.TXT| CPP 파일 (*.CPP;*.H)|*.CPP;*.H|”);

// 대화 상자 실행

if(pDlg.DoModal()==IDOK)

{

// GetPathName을 이용하여 파일명을 알고 파일을 오픈한다.

FILE *out=fopen(pDlg.GetPathName(),”r”);

char *buff;

long size;

if(out==NULL)

return;

// 파일 크기를 알고

fseek(out,0,SEEK_END);

size=ftell(out);

// 해당 크기만큼 buff를 메모리에 설정

buff= new char[size];

memset(buff,0,size);

// 데이터를 읽는다.

fseek(out,0,SEEK_SET);

fread(buff,size,1,out);

fclose(out);

// 읽은 데이터를 data에 설정

CString data=buff;

delete buff;

}

폰트 대화 상자

폰트 대화 상자는 그림 1-4-33과 같은 형태를 의미합니다.

폰트 대화 상자를 사용하고자 할 경우에는 CFontDialog 클래스를 사용하면 됩니다. 이 클래스의 생성자는 다음과 같습니다.

CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );

lplflnitial은 현재 폰트 대화 상자의 처음에 설정될 폰트의 구조체입니다. 이 구조체는 LOGFONT의 포인터입니다. dwFlag에 설정되는 값은 폰트 대화 상자의 옵션입니다. 이 옵션이 CF_EFFECTS이면 굵게, 보통 기울임 등이 설정되며,  SF_SCREEN- FONTS로 설정하면 모니터에 출력되는 폰트들이 설정됩니다. 나머지 옵션은 도움말에서 CHOOSEFONT를 검색하면 알 수 있습니다.

pdcPrinter는 특정 프린터의 DC를 선택하고자 할 때 사용하며 pParentWnd는 부모 윈도입니다. 보통 폰트 대화 상자를 사용할 때는 lplflnitial만 이용하거나 그냥 다음과 같이 CFontDialog에 어떤 인자도 설정하지 않고 기본적인 인자를 설정하고 DoModal 을 이용하기도 합니다.

CFontDialog szFont();

szFont.DoModal();

위와 같이 한 다음 현재 선택된 폰트를 얻으려면 GetCurrentFont 함수를 사용합니다. 이 함수를 사용하면 LOGFONT를 얻을 수 있습니다. 다음은 폰트 대화 상자를 실행하고 설정된 폰트를 얻는 예입니다.

CFontDialog pDlg();

if(pDlg.DoModal()==IDOK)

{

LOGFONT lfont;

pDlg.GetCurrentFont(&lfont); // 현재 선택된 폰트를 얻는다.

}

컬러 대화 상자

컬러 대화 상자는 그림 1-4-34와 같은 대화 상자를 의미합니다.

이 대화 상자를 사용하고자 할 때 이용하는 클래스가 CColorDilaog입니다. 이 클래스의 생성자는 다음과 같습니다.

CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL );

clrInit는 처음에 설정될 컬러값입니다. dwFlags는 이 컬러 대화 상자를 출력할 때 사용하는 옵션인데, 여러 다른 옵션은 CHOOSECOLOR를 참조하기 바랍니다. 꼭 기억해야 할 옵션 중에 CC_FULLOPEN이 있습니다. 이 옵션을 설정하면 그림 1-4-34의 ② 항목과 같이 사용자 색상의 항목이 열려 있는 상태에서 컬러 대화 상자가 실행됩니다. 이 컬러 대화 상자를 실행한 후 현재 설정된 색상을 얻는 함수는 GetColor입니다. 다음은 컬러 대화 상자를 실행한 후에 컬러값을 얻는 예입니다.

COLORREF m_fColor;

CColorDialog  pDlg(0,CC_FULLOPEN,NULL);

if(pDlg.DoModal()==IDOK)

{

m_fColor=pDlg.GetColor();

}

프린트 대화 상자

프린트 대화 상자는 CPrintDialog 클래스를 이용하면 됩니다. 이 함수를 사용하여 프린트 대화 상자를 출력하는 예는 다음과 같습니다.

CPrintDialog pDlg(TRUE);

pDlg.DoModal();

만약 TRUE값을 FALSE로 설정하면 실제 프린트하는 대화 상자가 출력되고, TRUE로 설정하면 프린트 설정 대화 상자가 출력됩니다. 공용 대화 상자는 각 클래스 함수를 잘 이용하면 쉽게 사용할 수 있습니다.

파일 대화 상자를 변형해서 사용하기

Visual C++나 기타 애플리케이션 프로그램에서 보면 파일 대화 상자가 일반 파일 대화 상자와 다른 경우가 있습니다. 다르다기보다 파일 대화 상자 안에 몇 가지 컨트롤들이 삽입되어 설정되어 있는 것을 본 적이 있을 것입니다. 이것은 파일 대화 상자를 자신의 대화 상자에 삽입해서 사용하는 방법입니다. 그림 1-4-35는 파일 대화 상자를 자신의 대화 상자에 삽입하는 방법을 도시한 것입니다.

왜 그렇게 해야 하는가에 대해서는 별로 중요하지 않습니다. 추가적으로 설명하자면 ID를 0x045f로 설정하면(10진수로 1119) COMMDLG32에서 현재 그룹 영역에 파일 대화 상자를 설정하는 곳이라는 것을 인식하고 그룹 상자 영역에 파일 대화 상자의 내용을 도시해줍니다. 나머지 부분에 새로 설정하는 부분들은 CFileDialog를 상속받은(그림 1-4-35에서 CMyFileDlg) 부분에서 컨트롤하면 되기 때문입니다. 그룹 상자  외의 나머지 설정한 컨트롤들을 사용하는 방법은 본 장에서 지금까지 설명한 내용을 참고하면 됩니다.

공용 대화 상자들을 이용한 예제 ExCommDlg

ExCommDlg는 지금까지 설명한 공용 대화 상자와 컨트롤 색상 바꾸기를 이용하여 에디터 윈도에 여러 속성을 설정하는 예제입니다. 이 예제의 전체 구성도는 그림 1-4-36과 같습니다.

그림 1-4-36에서 보듯이 ExCommDlg는 SDI 형태이며 View 윈도는 CFormView입니다. 또한 화면에는 에디터 상자가 설정되어 있고 이 에디터 상자를 DDX_Control로 연결하였습니다(⑨ 항목 참조).

본 예제는 그림 1-4-36과 아래 소스를 연결하면 이해할 수 있을 것입니다.

다음은 본 프로그램의 소스입니다.

// ExCommDlgView.h : interface of the CExCommDlgView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXCOMMDLGVIEW_H__41ED27AD_25E7_11D2_B2F4_006097AEB8A7__INCLUDED_)

#define AFX_EXCOMMDLGVIEW_H__41ED27AD_25E7_11D2_B2F4_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExCommDlgView : public CFormView

{

protected: // create from serialization only

CExCommDlgView();

DECLARE_DYNCREATE(CExCommDlgView)

public:

//{{AFX_DATA(CExCommDlgView)

enum { IDD = IDD_EXCOMMDLG_FORM };

CEdit m_eEdit;

//}}AFX_DATA

COLORREF m_bColor;

COLORREF m_fColor;

CFont cFont;

// Attributes

public:

CExCommDlgDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExCommDlgView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

virtual void OnInitialUpdate(); // called first time after construct

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CExCommDlgView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExCommDlgView)

afx_msg void OnDialogColor();

afx_msg void OnDialogFile();

afx_msg void OnDialogPrint();

afx_msg void OnDialogColorf();

afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

afx_msg void OnDialogFont();

afx_msg void OnDialogMyfile();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExCommDlgView.cpp

inline CExCommDlgDoc* CExCommDlgView::GetDocument()

{ return (CExCommDlgDoc*)m_pDocument; }

#endif

////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXCOMMDLGVIEW_H__41ED27AD_25E7_11D2_B2F4_006097AEB8A7__INCLUDED_)

// ExCommDlgView.cpp : implementation of the CExCommDlgView class

//

#include “stdafx.h”

#include “ExCommDlg.h”

#include “ExCommDlgDoc.h”

#include “ExCommDlgView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

////////////////////////////////////////////////////////

// CExCommDlgView

IMPLEMENT_DYNCREATE(CExCommDlgView, CFormView)

BEGIN_MESSAGE_MAP(CExCommDlgView, CFormView)

//{{AFX_MSG_MAP(CExCommDlgView)

ON_COMMAND(ID_DIALOG_COLOR, OnDialogColor)

ON_COMMAND(ID_DIALOG_FILE, OnDialogFile)

ON_COMMAND(ID_DIALOG_PRINT, OnDialogPrint)

ON_COMMAND(ID_DIALOG_COLORF, OnDialogColorf)

ON_WM_CTLCOLOR()

ON_COMMAND(ID_DIALOG_FONT, OnDialogFont)

ON_COMMAND(ID_DIALOG_MYFILE, OnDialogMyfile)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////

// CExCommDlgView construction/destruction

CExCommDlgView::CExCommDlgView()

: CFormView(CExCommDlgView::IDD)

{

//{{AFX_DATA_INIT(CExCommDlgView)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// TODO: add construction code here

m_bColor=RGB(255,0,255);

m_fColor=RGB(0,0,0);

}

CExCommDlgView::~CExCommDlgView()

{

}

void CExCommDlgView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExCommDlgView)

DDX_Control(pDX, IDC_EDIT, m_eEdit);

//}}AFX_DATA_MAP

}

BOOL CExCommDlgView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

void CExCommDlgView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

LOGFONT olFont;

CFont *ocFont;

// 에디터 상자의 폰트를 받는다.

ocFont=m_eEdit.GetFont();

// 이 폰트의 LOGFONT를 얻는다.

ocFont->GetLogFont(&olFont);

// LOGFONT를 이용하여 cFont를 만든다.

cFont.CreateFontIndirect(&olFont);

}

//////////////////////////////////////////////////////////

// CExCommDlgView printing

BOOL CExCommDlgView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExCommDlgView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExCommDlgView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CExCommDlgView::OnPrint(CDC* pDC, CPrintInfo* /*pInfo*/)

{

// TODO: add customized printing code here

}

/////////////////////////////////////////////////////////

// CExCommDlgView diagnostics

#ifdef _DEBUG

void CExCommDlgView::AssertValid() const

{

CFormView::AssertValid();

}

void CExCommDlgView::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CExCommDlgDoc* CExCommDlgView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExCommDlgDoc)));

return (CExCommDlgDoc*)m_pDocument;

}

#endif //_DEBUG

//////////////////////////////////////////////////

// CExCommDlgView message handlers

void CExCommDlgView::OnDialogColor()

{

CColorDialog  pDlg;

if(pDlg.DoModal()==IDOK)

{

m_bColor=pDlg.GetColor();

m_eEdit.RedrawWindow();

}

}

void CExCommDlgView::OnDialogColorf()

{

CColorDialog  pDlg(0,CC_FULLOPEN,NULL);

if(pDlg.DoModal()==IDOK)

{

m_fColor=pDlg.GetColor();

m_eEdit.RedrawWindow();

}

}

void CExCommDlgView::OnDialogFile()

{

// 파일 대화 상자 생성

CFileDialog pDlg(TRUE,”TXT”,”*.TXT”,

OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

“텍스트 파일 (*.TXT)|*.TXT| CPP 파일 (*.CPP;*.H)|*.CPP;*.H|”);

// 대화 상자 실행

if(pDlg.DoModal()==IDOK)

{

//GetPathName을 이용하여 파일명을 알고 파일을 오픈한다.

FILE *out=fopen(pDlg.GetPathName(),”r”);

char *buff;

long size;

if(out==NULL)

return;

// 파일 크기를 알고

fseek(out,0,SEEK_END);

size=ftell(out);

// 해당 크기만큼 buff를 메모리에 설정

buff= new char[size];

memset(buff,0,size);

// 데이터를 읽는다.

fseek(out,0,SEEK_SET);

fread(buff,size,1,out);

fclose(out);

// 읽은 데이터를 data에 설정

CString data=buff;

data.Replace(“\n”,”\r\n”);

m_eEdit.SetWindowText(data);

delete buff;

}

}

void CExCommDlgView::OnDialogPrint()

{

CPrintDialog pDlg(TRUE);

pDlg.DoModal();

}

HBRUSH CExCommDlgView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)

{

HBRUSH hbr = CFormView::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: Change any attributes of the DC here

if(nCtlColor == CTLCOLOR_EDIT)

{

// 문자 출력시 배경색과 문자색도 설정한다.

pDC->SetBkColor(m_bColor);

pDC->SetTextColor(m_fColor);

return CreateSolidBrush(m_bColor);

}

else if(nCtlColor == CTLCOLOR_BTN)

{

return CreateSolidBrush(RGB(255,0,0));

}

return hbr;

}

void CExCommDlgView::OnDialogFont()

{

LOGFONT olFont;

cFont.GetLogFont(&olFont);

그림 ⑦ 항목

CFontDialog pDlg(&olFont);

if(pDlg.DoModal()==IDOK)

{

LOGFONT lfont;

pDlg.GetCurrentFont(&lfont);

cFont.DeleteObject();

cFont.CreateFontIndirect(&lfont);

m_eEdit.SetFont(&cFont);

}

}

#include “MyFileDlg.h”

void CExCommDlgView::OnDialogMyfile()

{

CMyFileDlg pDlg(TRUE,”TXT”,”*.TXT”,

OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

“텍스트 파일 (*.TXT)|*.TXT| CPP 파일 (*.CPP;*.H)|*.CPP;*.H|”);

if(pDlg.DoModal()==IDOK)

{

FILE *out=fopen(pDlg.GetPathName(),”r”);

char *buff;

long size;

if(out==NULL)

return;

fseek(out,0,SEEK_END);

size=ftell(out);

buff= new char[size];

memset(buff,0,size);

fseek(out,0,SEEK_SET);

fread(buff,size,1,out);

fclose(out);

CString data=buff;

data.Replace(“\n”,”\r\n”);

m_eEdit.SetWindowText(data);

delete buff;

}