리스트 컨트롤은 리스트 상자가 좀더 발전된 형태입니다. 그림 1-5-1은 리스트 컨트롤 형태입니다. 리스트 컨트롤은 네 가지 형태로 설정할 수 있습니다. 컬럼 제목이 리스트 컨트롤 상단부에 있고 아이템들이 컬럼 위치에 설정되어 있는 그림 1-5-1과 같은 REPORT 형태와 큰 아이콘과 첫 번째 컬럼의 문자열이 설정된 형태인 ICON, 작은 아이콘과 첫 번째 컬럼의 문자열이 설정되어 있는 SMALLICON 형태, 그리고 리스트 상자와 같은 형태로 설정되는 LIST 형태입니다. 컬럼이란 그림 1-5-1의 ④ 항목에서 보여주듯이, 아이템 데이터가 설정될 때 가로 항목으로 나눌 때 한 개의 나누어진 항목을 컬럼이라고 합니다. 이 컬럼에 설정될 때 첫 번째로 설정되는 것을 아이템이라고 하며, 아이템 다음으로 여러 서브아이템들이 설정될 수 있습니다. 첫 번째 컬럼에는 문자열과 그림을 동시에 설정할 수 있습니다. 이런 형태가 리스트 컨트롤입니다.

리스트 컨트롤 클래스 CListCtrl

리스트 컨트롤을 만들 때 사용하는 클래스가 CListCtrl입니다. 이 클래스를 이용하여 리스트 컨트롤을 만들 수 있습니다. CListCtrl은 CWnd를 상속받았다는 것을 꼭 기억하기 바랍니다. 따라서 CListCtrl로 CWnd의 함수들을 이용할 수가 있다는 것입니다. 그리고 CWnd를 상속받은 클래스들은 Create 함수를 이용하여 윈도를 만들 수 있습니다. 이 Create 함수는 각 클래스마다 인자들이 다르나 중요한 것은 리스트 컨트롤 같은 윈도들도 자원에 등록시키지 않고 출력할 수 있다는 것입니다. 다음은 리스트 컨트롤 윈도를 만들 때의 예입니다.

CListCtrl m_listCtrl;

m_listCtrl.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|LVS_LIST ,

CRect(0,0,100,100),this,500);

리스트 컨트롤의 첫 번째 인자는 윈도 스타일을 설정하는 항목입니다. 보통 일반적인 WS_ 계열의 스타일과 리스트 컨트롤 스타일인 LVS_ 계열을 함께 OR 연산을 사용하여 출력합니다. 두 번째 인자는 윈도의 위치를 의미하며, 세 번째 인자는 윈도의 부모 윈도 CWnd 포인터입니다. 세 번째 인자는 현재 윈도의 ID를 의미하는데, 본 예제에서는 500으로 사용하였으나 일반적인 형태에서는 리소스 헤더에 특정 ID를 설정하고 이 아이디를 부과할 수도 있습니다. 즉 다음과 같이 할 수 있습니다.

//Resource.h에 설정

#define ID_MYLIST 500

//프로그램 내부

CListCtrl m_listCtrl;

m_listCtrl.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|LVS_LIST ,

CRect(0,0,100,100),this,ID_MYLIST);

MFC를 처음 배운 분들은 대화 상자를 만들면서 리스트 컨트롤을 알게 됩니다. 그렇기 때문에 리스트 컨트롤을 대화 상자 폼의 컨트롤 도구바에서 선택해서 만들어 사용하는 것으로 생각하는 분들이 매우 많습니다. 따라서 리스트 컨트롤뿐만 아니라 만든 컨트롤들을 동적으로 생성하는 방법에 대해서 잘 모르는 분들이 많은 것 같습니다. 모든 컨트롤들은 Create 함수를 이용하여 출력할 수 있습니다. 전 장에서 배운 기본 컨트롤들은 본 Part의 2장에서 ExChildWnd 예제를 만들었을 때 동적으로 만드는 방법을 설명했습니다. 본 장에서 만드는 많은 컨트롤들을 동적으로 만드는 부분에 대해서는 설명하지 않습니다. 그러나 중요한 것은 본 장에서 설명하는 컨트롤들도 동적으로 분명이 만들 수 있다는 것을 기억하기 바랍니다.

동적으로 사용할 때는 윈도 스타일을 알아야 합니다. 리스트 컨트롤 스타일은 LVS_ 계열로 설정됩니다. 아마 List View Style의 약자일 것으로 생각됩니다. 많은 List View Style이 있는데, 이런 스타일에 대해서는 본 책과 함께 제공되는 레퍼런스 가이드에서 참조하기 바랍니다.

대화 상자에서 리스트 컨트롤을 설정하려면 대화 상자가 출력될 때 설정되는 Control 도구바에서 아이콘을 설정하면 만들 수 있습니다. 그림 1-5-2는 대화 상자에 리스트 컨트롤을 설정한 화면입니다.

리스트 컨트롤을 설정하고 Alt+Enter 키를 누르면 속성 설정 대화 상자가 출력되는데, 이 대화 상자에서 Style을 클릭한 화면이 ⓑ입니다. ⓑ 화면에서 ② 항목이 가리키는 곳이 바로 리스트 컨트롤 화면 스타일입니다. 네 개의 항목이 있는데, 이 네 개의 항목이 전에 설명한 내용입니다. 리스트 컨트롤은 다양한 스타일로 만들 수 있습니다. 특징적인 것은 Edit labels 항목입니다. 즉 아이템 항목을 수정할 수 있다는 것입니다. 리스트 컨트롤에서는 아이템을 수정하고자 할 때 자체에는 기능이 없기 때문에 에디터 상자를 자식 윈도로 가지고 있는 또 하나의 리스트 상자를 만들어 사용해야 합니다.(전 장의 예제를 참조하기 바랍니다.) 그러나 리스트 컨트롤은 컨트롤 안에 에디터 상자 윈도를 가지고 있기 때문에 윈도 스타일에서 에디트 옵션을 설정하면 현재 아이템을 수정할 수 있는 형태로 만들 수 있습니다. 많은 부분에서 리스트 상자를 사용하기보다 리스트 컨트롤을 많이 사용합니다. 그것은 리스트 상자가 기본적으로 32비트 형태로 만들어져 있기 때문에 많은 항목을 설정할 수 있으며 좀더 많은 기능이 부과되어 있기 때문입니다. 단점이라면 리스트 상자에는 데이터를 넣는 데 시간이 좀 많이 걸립니다.

리스트 컨트롤에 컬럼 설정과 데이터 넣기

리스트 컨트롤에는 컬럼을 설정해 주어야 합니다. 그림 1-5-1에서 보듯이 리스트 컨트롤은 여러 컬럼으로 되어 있기 때문에 몇 개의 컬럼이 본 리스트 컨트롤에서 사용되는가를 먼저 설정해 주어야 한다는 의미입니다. 이 컬럼을 만드는 방법은 그림 1-5-3과 같습니다.

리스트 컨트롤에 컬럼을 설정하려면 LV_COLUMN이라는 구조체를 사용합니다. 이 구조체의 형태는 그림 1-5-3의 ② 항목과 같습니다. 이 구조체에서 mask 멤버는 구조체의 다른 멤버들의 기능 유효화 플러그입니다. 예를 들어 이 플러그에 LVCF_FMT를 설정하면 멤버 중 fmt 변수가 유효해집니다. 이렇게 어떤 멤버들이 유효한가를 설정하는 플러그입니다. 이 플러그를 설정하면 해당 멤버들에 값을 대입하였을 때 리스트 상자가 해당 멤버의 값에 따라서 출력됩니다.

pszText는 컬럼 제목 문자열입니다. 컬럼이 만들어지면 그림 1-5-3의 ⑤ 항목처럼 리스트 컨트롤 컬럼의 pszText에 설정한 문자열이 설정됩니다. 이렇게 LV_CLUMN의 구조체 형인 변수를 설정하고 각 멤버에 값을 기입한 후에 리스트 컨트롤의 멤버 함수인 InsertColumn 함수를 이용하여 컬럼을 설정할 수가 있습니다. 이 내용이 그림 1-5-3의 ④ 항목입니다. InsertColumn에서 첫 번째 인자는 삽입될 위치입니다. 첫 번째일 경우 0번이며, 그 다음부터 1씩 증가되는 형태로 삽입됩니다. 이와 같이 하면 컬럼을 삽입할 수 있습니다.

다음은 데이터 설정입니다. 데이터를 설정하는 방법은 그림 1-5-4와 같습니다.

리스트 컨트롤에 아이템을 설정할 때 사용하는 구조체는 LV_ITEM입니다. 이 구조체의 멤버 형태는 그림 1-5-4의 ② 항목과 같습니다. 이 구조체 중에서 state 계열의 멤버는 아이템을 설정할 때 사용하는 항목이 아니라 아이템의 현재 상태를 설정해주는 멤버입니다. 예를 들어서 특정 아이템을 선택하면 해당 아이템의 상태를 LV_ITEM으로 넘겨줍니다. 이때 설정된 아이템의 상태를 알려주는 멤버 변수들이 state 계열 멤버입니다. 이 멤버들은 데이터를 입력할 때는 사용하지 않습니다. 멤버 중에 mask는 데이터 스타일 마스크입니다. 이 마스크가 LVIF_TEXT이면 문자이고 LVIF_IMAGE이면 그림입니다. 그림과 문자가 함께 있을 경우에는 LVIF_TEXT|LVIF_IMAGE를 사용하면 됩니다.

iItem은 현재 아이템 번호입니다. 이 번호는 쉽게 세로줄 번호로 생각하면 됩니다. 즉 몇 번째로 설정되어 있는가를 알려주는 번호가 되는 것입니다. iSubItem은 가로 번호입니다. LV_ITEM 구조체에 값을 넣고 데이터를 리스트 컨트롤에 설정하고자 할 경우 처음에 InsertItem이라는 리스트 컨트롤의 멤버 변수를 사용합니다. 처음에는 리스트 컨트롤에 새로운 아이템을 설정해야 하기 때문에 이 함수를 사용하여 새로운 아이템이 설정될 수 있도록 리스트 컨트롤에 자리를 확보합니다. 그런 후에 현재 설정된 아이템에 서브아이템으로 데이터를 넣고자 할 경우에는 SetItem 함수를 사용하면 됩니다.

리스트 컨트롤에 컬럼 설정과 데이터 넣기 예제 Exam1036

Exam1036은 대화 상자에 리스트 컨트롤을 설정하고 이곳에 데이터를 넣는 예제입니다. 이 예제의 형태는 그림 1-5-5와 같습니다.

Exam1036은 SDI 형태로 제작된 프로그램입니다. 이 프로그램에서 메뉴에 그림 1-5-5의 ⓐ 항목처럼 CListCtrl 예제라는 메뉴 항목을 두고 이 메뉴 항목을 클릭하면 그림 1-5-5의 ⓑ 항목과 같은 대화 상자가 출력되는 예제입니다. 이 예제를 만들기 위해서는 프로젝트를 만든 후에 Ctrl+1을 클릭하여 새로운 대화 상자 폼을 만듭니다. 이렇게 대화 상자 폼을 만든 후에 ClassWizard를 실행시켜 이 폼과 클래스를 연결합니다. 연결할 때는 Base class를 CDialog로 설정해야 합니다. 클래스명은 CListDlg로 하였습니다. 이렇게 클래스를 만든 다음 대화 상자 폼에서 그림 1-5-5의 ② 항목처럼 하여 리스트 컨트롤과 클래스의 멤버를 연결시킵니다. 이때 연결되는 멤버의 클래스형은 CListCtrl입니다. 이 CListCtrl형인 멤버 m_listBox를 이용하여 리스트 상자에 데이터를 넣습니다.

그림 1-5-5의 ⓑ 화면과 같이 데이터를 설정하려면 CListDlg에서 OnInitDialog 함수를 만들고 이곳에서 지금까지 설명한 방법으로 컬럼과 아이템을 설정하면 됩니다. 컬럼과 아이템을 설정하는 방법은 다음과 같습니다.

LV_COLUMN lvcolumn;//컬럼 설정 구조체

LV_ITEM lvitem;

int i;

char *list[3]={“제목”,”랭킹”,”설명”};//컬럼 제목 문자열

int width[3]={80,50,400};//컬럼의 크기

//컬럼을 설정하기 위해 세 번 반복한다.

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

{

lvcolumn.mask= LVCF_FMT | LVCF_SUBITEM

| LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText=list[i];

lvcolumn.iSubItem=i;

lvcolumn.cx=width[i];

m_listBox.InsertColumn(i,&lvcolumn);

}

//리스트의 첫 번째 컬럼 입력

lvitem.mask=LVIF_TEXT;

lvitem.iItem=0;

lvitem.iSubItem=0;

lvitem.pszText=”태권브이”;

m_listBox.InsertItem(&lvitem);

//리스트의 두 번째 컬럼 입력

lvitem.mask=LVIF_TEXT;

lvitem.iItem=0;

lvitem.iSubItem=1;

lvitem.pszText=”1위”;

m_listBox.SetItem(&lvitem);

//리스트의 세 번째 컬럼 입력

lvitem.mask=LVIF_TEXT;

lvitem.iItem=0;

lvitem.iSubItem=2;

lvitem.pszText=”지구를 지키는 용사”;

m_listBox.SetItem(&lvitem);

lvitem.mask=LVIF_TEXT;

lvitem.iItem=1;

lvitem.iSubItem=0;

lvitem.pszText=”맥칸더 브이”;

m_listBox.InsertItem(&lvitem);

lvitem.mask=LVIF_TEXT;

lvitem.iItem=1;

lvitem.iSubItem=1;

lvitem.pszText=”2위”;

m_listBox.SetItem(&lvitem);

lvitem.mask=LVIF_TEXT;

lvitem.iItem=1;

lvitem.iSubItem=2;

lvitem.pszText=”우주의 평화를 위해서”;

m_listBox.SetItem(&lvitem);

조금 쉽게 설명하기 위해서 루프를 사용하지 않고 한 아이템 아이템마다 설정하는 방법을 풀어 보았습니다. 소스의 내용만 보고도 리스트 컨트롤에 데이터를 입력하는 방법을 쉽게 알 수 있을 것입니다.

본 예제의 소스는 생략합니다. 제공되는 CD에서 소스를 참조하기 바랍니다. 위에 기록한 내용과 그림 1-5-5의 내용 외에 더 이상 첨부된 소스는 없습니다. 소스를 전부 책에 기록하여 쉽게 이해할 수 있게 하고자 하는 마음은 굴뚝 같으나 그렇게 하다 보면 소스가 많아서 책이 너무 두꺼워질까봐 어쩔 수 없이 최대한 줄이고 있습니다.

리스트 컨트롤 스타일 설정과 수정

지금까지는 리스트 컨트롤에 데이터를 설정하는 방법을 설명하였습니다. 본 항목에서는 리스트 컨트롤 스타일을 설정하고 수정하는 방법에 대해서 설명하겠습니다. 리스트 컨트롤은 WS_ 계열의 일반 윈도 스타일과 LVS_ 계열의 리스트 컨트롤 스타일을 결합해서 사용한다고 했습니다. 표1은 LVS_ 계열의 스타일 리스트입니다.

표1  리스트 컨트롤 스타일 리스트

스타일 플러그	속성 대화 상자	내용

LVS_SINGLESEL	Single Selection	리스트에서 한 개의 아이템만 설정할 수 있도록 한다.

LVS_AUTOARRANGE	Auto arrange	ICON, SMALLICON 형태에서 아이콘이 자동 정렬할 수 있도록 한다.

LVS_NOLABELWRAP	No lavel wrap	ICON 스타일에서 아이콘 옆으로 글자가 출력되도록 한다.

LVS_EDITLABELS	Edit lavels	아이템이 수정될 수 있도록 한다.

LVS_NOCOLUMNHEADER  No column header	REPORT에서 컬럼이 표시되지 않도록 한다.

스타일 플러그	속성 대화 상자	내용

LVS_NOSORTHEADER	No sort header	REPORT에서 컬럼이 버튼 형태로 표시되지 않도록 한다.

LVS_SORTASCENDING	Sort 항목의 Acending	리스트 데이터를 오름 차순으로 정렬한다.

LVS_SORTDESCENDING	Sort 항목의 Descending	리스트 데이터를 내림 차순으로 정렬한다.

표1의 속성 대화 상자 항목은 대화 상자 폼에 리스트 대화 상자를 설정하고 Alt+Enter 키를 눌러서 속성 대화 상자를 출력하였을 때 Style 항목에 설정되어 있는 이름을 의미합니다. 그림 1-5-2의 ⓑ 항목의 그림이 바로 이 상태입니다. 이 상태에서 표1에 해당하는 항목을 설정하면 내용에 기록된 형태로 스타일이 바뀌게 됩니다. 대화 상자 폼에서는 처음에만 설정되고 스타일을 바꿀 수가 없습니다. 프로그램 내부에서 윈도 스타일을 바꾸려면 GetWindowLong과 SetWindowLong을 이용합니다. 그림 1-5-6은 프로그램 내부에서 리스트 컨트롤을 바꿀 때의 방법입니다.

그림 1-5-6의 리스트 컨트롤 변수는 m_listCtrl이라는 가정하에 설정하였습니다.

그림 1-5-6처럼 먼저 스타일값을 받는 변수를 long으로 설정합니다. 이렇게 설정한 후에 GetWindowLong 함수를 이용하여 윈도 스타일을 style에 받습니다. GetWindowLong 함수는 SDK 함수로서 윈도 스타일을 얻는 함수입니다. SDK이기 때문에 윈도 클래스 핸들을 사용하는 것이 아니라 윈도 핸들을 사용합니다. 즉 CWnd가 아닌 HWND를 사용한다는 것입니다. CWnd 클래스 안에는 HWND 핸들 변수를 m_hWnd로 가지고 있습니다. 따라서 첫 번째 인자를 m_strListCtrl. m_hWnd로 설정한 것입니다. GetWindowLong의 두 번째 인자는 특정 윈도의 값을 얻고자 하는 플러그입니다. 이 플러그를 GWL_STYLE로 설정하면 윈도 스타일을 얻을 수 있습니다. 이렇게 하여 style에 스타일을 얻고 ③ 항목에서 이 스타일을 해제합니다. 해제하고자 할 경우 현재 해제하고자 하는 스타일바를(표1의 스타일 플러그) 반전시킨 다음 이 값을 AND 연산으로 대입하면 해제가 됩니다.

③ 항목까지만 하고 ④ 항목을 하지 않은 상태에서 ⑤ 항목으로 넘어가면 특정 스타일을 해제하는 항목이 됩니다. 그림 1-5-6에서 ④ 항목을 사용하지 않았다면 리스트 컨트롤에서 아이콘 자동 정렬 스타일이 해제됩니다. ④ 항목을 사용하여 스타일을 설정하면 아이콘 자동 정렬 스타일이 설정됩니다. 스타일을 설정할 때는 ③ 항목을 사용할 필요는 없습니다. 즉 ③과 ④ 항목은 각각 스타일을 설정할 때와 해제할 때의 항목인 것입니다. 리스트 컨트롤뿐만 아니라 모든 윈도 스타일은 그림 1-5-6과 비슷하게 처리할 수 있습니다. 즉 윈도 스타일을 바꾸고자 할 때는 먼저 Get WindowLong 함수를 이용하여 현재 윈도 스타일을 얻고 다시 SetWindowLong 함수를 이용하여 스타일을 수정합니다.

스타일을 수정하였을 때 바로 윈도 스타일이 바뀌는 형태도 있으나 그렇지 않은 경우도 있습니다. LVS_SORTASCENDING과 LVS_SORTDESCENDING이 그 플러그입니다. 이 플러그를 사용하여 새롭게 스타일을 수정하여도 리스트 컨트롤에 있는 데이터가 변경되지 않습니다. 그 이유는 필자도 모릅니다. 아마 리스트 컨트롤을 만든 사람이 스타일을 변경시켜도 리스트가 변하지 않게 놔두었을 것으로 생각됩니다.

이때에는 여러 방법이 있습니다. 첫 번째는 리스트 컨트롤을 지우고 다시 새롭게 윈도를 설정하는 방법이고, 두 번째는 리스트 컨트롤 안에 있는 모든 데이터를 지우고 다시 새롭게 데이터를 넣는 방법입니다. 첫 번째 방법이 두 번째 방법보다 빠릅니다. 왜냐하면 리스트 컨트롤은 데이터를 넣는 시간만큼 삭제할 때도 똑같은 시간이 들기 때문입니다. 그렇기 때문에 리스트 컨트롤에서 항목을 지우고 다시 설정하면 데이터를 설정하는 데 두 배의 시간이 듭니다. 보통 삭제하는 게 가장 빠른데, 왜 삭제할 때 똑같은 시간이 드는지는 저도 원인을 알 수가 없습니다. 다만 실행해보고 나서야 그렇게 된다는 것을 알았습니다. 이 두 가지 방법 외에 또 다른 방법은 필자도 아직 찾지 못했습니다. 혹시 이 책을 읽는 분들 중 또 다른 방법이 있다면 지킴이로 알려주었으면 합니다.

리스트 컨트롤 선택 항목 알기

리스트 컨트롤에서 선택 항목을 알고자 할 때는 통지 메시지, 즉 WM_NOTIFY에서 통지 코드로 LVN_ITEMCHANGED를 이용하면 됩니다. 이 통지 코드는 아이템 선택이 바뀌었을 때 발생하는 코드입니다. 이 코드에 의한 함수를 만들고자 한다면 그림 1-5-7처럼 클래스 위저드에서 LVN_ITEMCHANGE를 선택하면 됩니다.

지금까지 ClassWizard를 많이 사용해왔기 때문에 그림 1-5-7의 내용을 이해할 수 있을 것입니다. 이와 같이 함수를 만들면 그림 1-5-7의 ④ 항목과 같은 함수가 만들어집니다. 이 함수에 의해서 pNMListView에는 현재 선택된 아이템의 구조체가 설정됩니다. 이 구조체형은 NM_LISTVIEW로, 멤버 형태는 다음과 같습니다.

typedef struct tagNMLISTVIEW{

NMHDR   hdr;    //NMHDR 구조체

int     iItem; //선택된 아이템 번호

int     iSubItem;    //선택된 아이템의 서브아이템 번호

UINT    uNewState;    //현재 새로 선택된 상태

UINT    uOldState;//이전 상태

UINT    uChanged;    //바뀌어진 아이템 속성 플러그

POINT   ptAction;    //마우스 포인터

LPARAM  lParam;

} NMLISTVIEW, FAR *LPNMLISTVIEW

아이템이 바뀌면 pNMListView의 멤버들 중 iItem에 현재 바뀐 데이터가 설정됩니다. 따라서 이 값을 받으면 바뀐 아이템 번호를 얻을 수 있습니다. 다음은 현재 선택이 바뀌었을 때 바뀐 항목 번호를 얻는 예입니다.

void CExListCtrlDlg::OnItemchangedListctrl(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

int listdatanum;

//바뀐 선택 번호를 얻는다.

listdatanum = pNMListView->iItem;

*pResult = 0;

}

본 선택 알기 항목에서 설명한 WM_NOTIFY 메시지와 LVN_ITEMCHANGED 통지 코드, 그리고 NMHDR 구조체가 무엇인가가 이해되지 않는다면 필자의 책 「Windows Programming Bible」의 8장 대화 상자와 컨트롤을 참조하세요. 이 부분은 MFC에 대한 내용이 아니라 SDK에 대한 것이므로 본 책에서는 생략합니다.

리스트 컨트롤 데이터 수정과 삭제

리스트 컨트롤에서 LVS_EDITLABELS 스타일을 설정하고 리스트 컨트롤 아이템을 살짝 눌러주면 해당 아이템이 수정할 수 있는 형태로 바뀝니다. 이렇게 바뀌었을 때 현재 데이터를 수정하면 수정이 가능합니다. 이렇게 수정하였다고 해서 리스트 컨트롤의 데이터가 수정되는 것이 아니라 수정했을 때 수정된 데이터를 그대로 다시 리스트 컨트롤에 설정해주어야 합니다. 그러기 위해서는 수정이 종료된 순간을 얻어야 합니다. 이 메시지는 LVN_ENDLABELEDIT 통지 메시지를 이용하여 함수를 만들 수 있습니다. ClassWizard에서 리스트 컨트롤을 선택하고 LVN_ENDLABELEDIT 항목을 선택하면 함수가 만들어집니다. 이 함수는 아이템 데이터의 수정이 끝났을 때 수행되는 함수입니다. 이 함수에서 다음과 같이 하면 현재 수정된 데이터가 리스트 상자에 설정됩니다.

void CExListCtrlDlg::OnEndlabeleditListctrl(NMHDR* pNMHDR, LRESULT* pResult)

{

LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

//수정된 데이터를 리스트 컨트롤에 설정한다.

m_listCtrl.SetItemText(pDispInfo->item.iItem,0,pDispInfo->item.pszText);

*pResult = 0;

}

LVN_ENDLABELEDIT에 의해 만들어진 함수에는 pDispInfo로, LV_DISPINFO 구조체로 이벤트 데이터를 받습니다. LV_DISPINFO는 리스트 컨트롤에서 현재 출력되는 정보를 얻습니다. 수정 상태에서 수정된 데이터가 실제로 리스트 상자에 삽입된 것이 아니라 화면에 수정된 형태로만 보여주는 것입니다. 이때 수정이 끝나면 화면에 출력되는 문자열을 pDispInfo->item.pszText로 얻을 수 있습니다. 이 문자열을 현재 설정된 수정하고 있는 아이템 번호에 SetItemText 함수를 이용하여 설정하면 수정이 되는 것입니다. SetItemText 함수의 첫 번째 인자는 아이템 번호이며, 두 번째 인자는 컬럼 번호, 세 번째 인자는 수정할 텍스트 문자열입니다. 이 함수를 이용하면 현재 수정된 데이터로 리스트 상자에 새로운 데이터가 업데이트됩니다.

리스트 컨트롤에서 특정 항목을 삭제하고자 할 때는 DeleteItem 함수를 사용하면 됩니다. 이 함수는 아이템 번호만 인자로 받습니다. 예를 들어서 1번 아이템을 삭제하려면 다음과 같이 하면 됩니다.

m_listCtrl->DeleteItem(1);

리스트 컨트롤 예제 ExListCtrl

ExListCtrl은 리스트 컨트롤의 스타일을 바꾸고 데이터를 삭제하거나 수정, 추가하는 예를 작성한 것입니다. ExListCtrl의 예제 전체 구조는 그림 1-5-8과 같습니다.

ExListCtrl에서 대화 상자 형태로 프로젝트를 만들었습니다. 한 개의 리스트 상자와 일곱 개의 스타일 플러그 체크 상자와 화면 출력 스타일 네 개가 콤보 상자로 설치되어 있으며, 리스트 상자에서 아이템을 선택하면 선택된 아이템이 에디터 상자에 출력되는 예제입니다. 또한 ExListCtrl은 수정, 삭제, 추가 버튼이 있어서 해당 버튼을 누를 때마다 리스트 컨트롤에 데이터가 수정, 삭제, 추가가 됩니다.

이 프로그램을 만드는 방법은 다음과 같습니다.

(1) 프로젝트를 Dialog based로 제작한다.

AppWizard를 이용하여 프로젝트를 만들 때 Dialog based로 프로젝트를 만듭니다. 이렇게 만들면 대화 상자 ID가 IDD_EXLISTCTRL_DIALOG로 설정됩니다. 이 대화 상자에 그림 1-5-8의 ⓐ 같은 형태의 대화 상자를 선택합니다. 선택한 후 리스트 상자를 DoDataExchange 안에 DDX_Control을 이용하여 CListBox의  m_listCtrl 형태로 멤버를 설정합니다.

CExListCtrlDlg의 헤더

CListCtrl m_listCtrl;

CExListCtrlDlg의 DoDataExchange

void CExListCtrlDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExListCtrlDlg)

DDX_Control(pDX, IDC_LISTCTRL, m_listCtrl);

//}}AFX_DATA_MAP

}

(2) 리스트 컨트롤에 컬럼 만들기

리스트 컨트롤에 컬럼을 설정합니다. 이때 컬럼을 설정할 함수를 만들고 이 함수에 컬럼을 설정하는 내용을 기입합니다. 다음은 리스트 컨트롤에 컬럼을 설정하는 내용입니다.

//리스트 컨트롤에 컬럼을 설정한다.

void CExListCtrlDlg::AddColumn()

{

LV_COLUMN lvcolumn;

char *listcolumn[2]={“제목”,”가사”};

int width[2]={100,300};

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

{

lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM |

LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText = listcolumn[i];

lvcolumn.iSubItem = i;

lvcolumn.cx = width[i];

m_listCtrl.InsertColumn(i, &lvcolumn);

}

}

컬럼은 “제목”과 “가사”의 두 개의 형태로 설정합니다. 제목의 가로 크기는 100으로, 가사의 가로 크기는 300으로 설정합니다. 위와 같이 설정하고 InitDialog 함수에서 AddColumn 함수를 호출하면 컬럼이 만들어집니다.

(3) 리스트 컨트롤에 데이터 설정하기

리스트 컨트롤에 데이터를 설정하는 함수를 AddListData라는 함수로 만들었습니다. 이 함수의 내용은 다음과 같습니다.

//리스트 컨트롤에 데이터를 설정한다.

void CExListCtrlDlg::AddListData()

{

LV_ITEM lvitem;

char *songname[10]={

“마징가제트”,”그랜다이저”,”황금박쥐“,”드래곤볼”,”은하철도999”,

“원시소년돌치”,”우주소년아톰”,”맥칸더브이”,”미래소년코난”,”하여가”};

char *song[10]={

“기운센 천하장사 ...”,

“태양을 마셔라 용기를 마셔라”,

“어디 어디 어디에서 나타났나”,

“찾아라 드래곤볼 세상에서 제일”,

“기차는 어둠을 헤치고 은하수”,

“달린다 돌치 끝없는 벌판을”,

“착하고 용감하게 날아자”,

“맥칸더 1 맥칸더 2 맥칸더 쓰리~~”,

“푸른하를 저멀리 새희망이 넘실”,

“너에게 떠나버린 마음이 ! 다시”};

int curitem;

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

for (int j = 0; j < 2; j++)

{

lvitem.mask = LVIF_TEXT;

lvitem.iItem = (j==0)?i:curitem;

lvitem.iSubItem = j;

switch(j)

{

//첫 번째 컬럼일 경우에는 InsertItem을 이용

case 0:

lvitem.pszText = songname[i];

curitem=m_listCtrl.InsertItem(&lvitem);

break;

//두 번째 컬럼일 경우에는 SetItem 함수를 이용한다.

case 1:

lvitem.pszText= song[i];

m_listCtrl.SetItem(&lvitem);

break;

}

}

}

위와 같이 한 후에 OnInitDialog 함수에서 두 개의 함수를 호출함으로써 리스트 컨트롤에 데이터가 설정됩니다.

BOOL CExListCtrlDlg::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

AddColumn();//컬럼 만들기

AddListData();//데이터 설정

((CComboBox *)GetDlgItem(IDC_STYLE))->SetCurSel(0);

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

}

컬럼을 만들고 데이터를 설정한 후에 콤보 상자 윈도 클래스 포인터를 얻어서 콤보 상자의 위치를 제일 처음으로 선택하게 하였습니다. 그러나 콤보 상자에 데이터를 입력하는 부분이 InitDialog에는 없습니다. 콤보 상자에는 대화 상자 폼을 제작할 때 데이터를 입력할 수 있습니다. 대화 상자 폼에서 콤보 상자를 선택하고 Alt+Enter 키를 누르면 속성 대화 상자가 출력됩니다. 속성 대화 상자가 출력된 상태에서 Data 항목을 선택하고 데이터를 설정하면 됩니다. 그림 1-5-9는 콤보 상자의 속성 대화 상자에서 Data 항목을 선택하고 데이터를 설정한 상태입니다.

콤보 상자에는 REPORT, ICON, SMALLICON, LIST 항목이 설정되어 있습니다. 개행을 할 때는 Enter 키가 아니라 Ctrl+Enter 키를 눌러야 합니다. 이렇게 콤보 상자에 네 개의 데이터가 설정된 상태에서 커서를 0번으로 위치하여 REPORT를 선택합니다. 주의할 점은 콤보 상자 속성에서 Styles 항목을 선택한 뒤에 Sort 항목은 해제해야 합니다.

(4) 콤보 상자에서 해당 항목을 선택하여 스타일 바꾸기

스타일을 바꾸는 방법은 전에 설명하였습니다. ExListCtrl은 콤보 상자에서 새로운 항목이 설정될 때 스타일을 바꿔야 합니다. ClassWizard를 이용하여 콤보 상자에서 선택 항목이 바뀔 때의 함수를 만들고 이 함수에서 다음과 같이 코딩합니다.

//콤보 상자에서 해당 항목이 바뀌면 리스트 컨트롤

//출력 스타일을 바꾼다.

void CExListCtrlDlg::OnSelchangeStyle()

{

//콤보 상자에서 현재 선택된 번호를 얻는다.

int num=((CComboBox *)GetDlgItem(IDC_STYLE))->GetCurSel();

long setstyle;

//윈도 스타일을 받고

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

//TYPEMASK를 이용하여 출력 스타일을 0으로 설정한다.

setstyle &= ~LVS_TYPEMASK;

//해당 항목에 따라 스타일을 변경

switch(num)

{

//REPORT

case 0:

setstyle |=LVS_REPORT;

break;

//ICON

case 1:

setstyle |=LVS_ICON;

break;

//SMALL ICON

case 2:

setstyle |=LVS_SMALLICON;

break;

//LIST

case 3:

setstyle |=LVS_LIST;

break;

}

//스타일 다시 설정

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

}

(5) 리스트 컨트롤 스타일 바꾸기

그림 1-5-8의 ① 항목이 이 항목에서 프로그래밍할 내용입니다. 총 일곱 개의 스타일을 바꿉니다. 스타일을 바꾸기 전에 각 체크 상자와 연결되는 멤버 변수를 설정합니다. 이 멤버 변수들은 다음과 같습니다.

BOOL m_bLvsSingleSel;//LVS_SINGSEL

BOOL m_bLvsAutoArrange;//LVS_AUTOARRANGE

BOOL m_bLvsNolabelWrap;//LVS_NOLABELWRAP

BOOL m_bLvsEditLavels;//LVS_EDITLAVELS

BOOL m_bLvsNocolumnHeader;//LVS_NOCOLUMNHEADER

BOOL m_bLvsNoSortHeader;//LVS_NOSORTHEADER

BOOL m_bSort;//LVS_SORTASCENDING

위의 변수들과 그림 1-5-8의 ① 항목과의 연결은 DDX_Text를 이용합니다. 이렇게 연결한 후에 각 체크 버튼이 클릭되는 함수를 만들고 해당 변수를 확인한 후 스타일을 변경합니다. 총 일곱 개 스타일 중 LVS_SORTASCENDING과 LVS_NOSORTHE- ADER를 제외한 내용은 다음과 같습니다.

//LVS_AUTOARRANGE 체크 상자 선택시

void CExListCtrlDlg::OnLvsAutoarrange()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_AUTOARRANGE;

if(m_bLvsAutoArrange)

setstyle |=LVS_AUTOARRANGE;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//LVS_EDITLAVELS 체크 상자 선택시

void CExListCtrlDlg::OnLvsEditlavels()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_EDITLABELS;

if(m_bLvsEditLavels)

setstyle |=LVS_EDITLABELS;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//LVS_NOCOLUMNHEADER 체크 상자 선택시

void CExListCtrlDlg::OnLvsNocolumnheader()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOCOLUMNHEADER;

if(m_bLvsNocolumnHeader)

setstyle |=LVS_NOCOLUMNHEADER;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//LVS_NOLAVELWRAP 체크 상자 선택시

void CExListCtrlDlg::OnLvsNolabelwrap()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOLABELWRAP;

if(m_bLvsNolabelWrap)

setstyle |=LVS_NOLABELWRAP;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//LVS_SINGLESEL 체크 상자 선택시

void CExListCtrlDlg::OnLvsSinglesel()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_SINGLESEL;

if(m_bLvsSingleSel)

setstyle |=LVS_SINGLESEL;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

이처럼 스타일을 바꾸는 방법은 그림 1-5-6에서 해당하는 방법을 참조하기 바랍니다. LVS_SORTASCENDING과 LVS_NOSORTHEADER는 스타일을 바꾸는 것이 아니라 새로운 스타일로 만들어야 합니다. 그 이유는 설정된 스타일로 윈도 형태는 바뀔지 몰라도 현재 설정된 데이터는 다시 재설정되지 않기 때문입니다. LVS_SORTASCENDING과 LVS_NOSORTHEADER에 의해서 만들어진 함수들은 다음과 같습니다.

//LVS_SORTASCENDING일 경우

void CExListCtrlDlg::OnSort()

{

UpdateData(TRUE);

long style;

CRect rect;

//윈도 스타일을 얻는다.

style = GetWindowLong(m_listCtrl.m_hWnd, GWL_STYLE);

//플러그값에 의해 LVS_SORTASCENDING값 설정

style &= ~LVS_SORTASCENDING;

if(m_bSort)

style |= LVS_SORTASCENDING;

//윈도 위치를 얻는다.

m_listCtrl.GetWindowRect(rect);

ScreenToClient(rect);

m_listCtrl.DestroyWindow();

//새로 윈도를 만들고

m_listCtrl.Create(style, rect, this, IDC_LISTCTRL);

//컬럼과 데이터를 넣는다.

AddColumn();

AddListData();

}

//LVS_NOSORTHEADER일 경우

void CExListCtrlDlg::OnLvsNosortheader()

{

long setstyle;

CRect rect;

//윈도 영역을 얻는다.

m_listCtrl.GetWindowRect(&rect);

ScreenToClient(&rect);

//윈도 스타일을 얻고 m_bLvsNoSortHeader값에

//따라 setstyle값 변경

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOSORTHEADER;

if(m_bLvsNoSortHeader)

setstyle |=LVS_NOSORTHEADER;

//윈도을 다시 만든다.

m_listCtrl.DestroyWindow();

m_listCtrl.Create(setstyle, rect, this, IDC_LISTCTRL);

//컬럼과 데이터를 넣는다.

AddColumn();

AddListData();

}

(6) 항목 수정, 삭제 및 삽입

LVS_EDITLABELS 스타일이 설정된 후 해당 아이템을 좌측 마우스로 살짝 클릭하면 해당 항목이 수정 상태로 됩니다. 이렇게 수정 상태에 수정을 마친 후에 수행되는 함수를 만든 다음 이 함수에서 수정이 가능하도록 합니다.

//수정 상태가 끝났을 경우

void CExListCtrlDlg::OnEndlabeleditListctrl(NMHDR* pNMHDR, LRESULT* pResult)

{

LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

//아이템의 서브아이템 문자열을 data에 얻고

CString data=m_listCtrl.GetItemText(pDispInfo->item.iItem,1);

//현재 수정된 데이터와 서브아이템 데이터를 대화 상자에 설정한다.

CListDataDlg *pDlg = new CListDataDlg(pDispInfo->item.pszText,data);

//대화 상자에서 IDOK를 클릭하면

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

{

//대화 상자에 있는 데이터를 리스트 컨트롤에 설정한다.

m_listCtrl.SetItemText(pDispInfo->item.iItem,0,

pDlg->m_strSong);

m_listCtrl.SetItemText(pDispInfo->item.iItem,1,

pDlg->m_strData);

}

delete pDlg;

*pResult = 0;

}

수정 항목에서 수정이 끝나면 OnEndlabeleditListctrl 함수가 수행됩니다. 이 함수는 ClassWizard에서 LVN_ENDLABELEDIT 이벤트 메시지에 의해서 만들어집니다. 이 함수에서 현재 수정된 데이터를 얻고 수정된 데이터의 서브아이템 문자열을 얻은 다음 CListDataDlg에 이 두 개의 문자열을 넘겨주면서 대화 상자를 실행합니다. 이 대화 상자는 그림 1-5-8의 ⓑ 항목과 같은 형태입니다. 보통 대화 상자의 생성자에는 인자가 없으나 이 대화 상자의 클래스는 문자열 두 개를 받는 생성자 형태로 되어 있습니다. 클래스 위저드에서 이렇게 만들어 준 것이 아니라 직접 또 다른 생성자를 만든 것입니다. CListDataDlg의 헤더에 보면 다음과 같은 내용이 있을 것입니다.

CListDataDlg(LPCSTR songname,LPCSTR songdata);//새로 만든 생성자

CString m_strSong;//노래 이름을 설정하는 문자열 클래스

CString m_strData;//가사를 설정하는 문자열 클래스

CListDataDlg 함수는 그림 1-5-8의 ⓑ 항목처럼 두 개의 에디터 상자가 있으며, 이 두 개의 에디터 상자는 m_strSong과 m_strData가 DDX_Text 형태로 연결되어 있습니다. 새로 만든 생성자는 두 개의 문자열 인자를 받은 다음 이 값을 m_strSong과 m_strData에 설정합니다. 새로 만든 생성자는 다음과 같습니다.

CListDataDlg::CListDataDlg(LPCSTR songname,LPCSTR songdata)

: CDialog(CListDataDlg::IDD, NULL)

{

m_strSong = songname;

m_strData = songdata;

}

위와 같이 하면 생성자를 통해서 클래스 멤버의 값을 설정할 수가 있습니다. 이 대화 상자를 이용하여 수정 버튼을 클릭하면 현재 선택된 아이템의 데이터를 넘겨주어 대화 상자를 실행시킵니다. OK 버튼을 클릭하면 리스트 컨트롤에서 데이터를 수정해주며, 추가 버튼을 클릭하면 인자를 받지 않는 기본 생성자를 실행하여 입력된 데이터를 리스트 컨트롤에 설정해줍니다. 이 내용은 다음과 같습니다.

//수정 버튼을 클릭했을 때

void CExListCtrlDlg::OnMode()

{

//현재 선택된 아이템 문자열을 받은 다음

CString song=m_listCtrl.GetItemText(listdatanum,0);

CString data=m_listCtrl.GetItemText(listdatanum,1);

//대화 상자에 넘겨주고

CListDataDlg *pDlg = new CListDataDlg(song,data);

//OK 버튼을 클릭하면

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

{

//해당 데이터를 리스트 컨트롤에 설정한다.

m_listCtrl.SetItemText(listdatanum,0, pDlg->m_strSong);

m_listCtrl.SetItemText(listdatanum,1, pDlg->m_strData);

}

delete pDlg;

}

//추가 버튼

void CExListCtrlDlg::OnAdd()

{

//인자를 받지 않는 대화 상자를 실행한 후

CListDataDlg *pDlg = new CListDataDlg;

//OK 버튼이 클릭되면

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

{

//리스트 컨트롤에 아이템수를 얻은 다음

int num=m_listCtrl.GetItemCount();

//마지막 위치에 데이터를 넣는다.

LV_ITEM lvitem;

int curitem;

lvitem.mask = LVIF_TEXT;

lvitem.iItem = num;

lvitem.iSubItem = 0;

lvitem.pszText = (LPSTR)(LPCSTR)pDlg->m_strSong;

curitem=m_listCtrl.InsertItem(&lvitem);

lvitem.mask = LVIF_TEXT;

lvitem.iItem = curitem;

lvitem.iSubItem = 1;

lvitem.pszText = (LPSTR)(LPCSTR)pDlg->m_strData;

m_listCtrl.SetItem(&lvitem);

}

delete pDlg;

}

삭제 버튼을 클릭하여 현재 설정된 아이템을 삭제하면 됩니다. 일반적으로 메시지 상자를 사용하여 삭제할 것인가를 물어본 다음 삭제합니다. 이 내용은 다음과 같습니다.

//삭제 버튼 클릭

void CExListCtrlDlg::OnDelete()

{

//”정말로 삭제할까요”라는 메시지에 YES 버튼을 클릭하면

if(AfxMessageBox(“정말 삭제할까요”, MB_YESNO)==IDYES)

{

//현재 아이템을 삭제한다.

m_listCtrl.DeleteItem(listdatanum);

}

}

ExListCtrl 소스

ExListCtrl의 소스는 다음과 같습니다.

(1) CExListCtrlDlg 소스

//ExListCtrlDlg.h : header file

//

#if !defined(AFX_EXLISTCTRLDLG_H__CE21DD87_26F3_11D2_A6C0_00403320A530__INCLUDED_)

#define AFX_EXLISTCTRLDLG_H__CE21DD87_26F3_11D2_A6C0_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //_MSC_VER > 1000

////////////////////////////////////////////////////////////////////////////CExListCtrlDlg dialog

class CExListCtrlDlg : public CDialog

{

//Construction

public:

int listdatanum;

void AddColumn();

void AddListData();

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

//Dialog Data

//{{AFX_DATA(CExListCtrlDlg)

enum { IDD = IDD_EXLISTCTRL_DIALOG };

CListCtrl m_listCtrl;

CString m_strChooseData;

BOOL m_bLvsSingleSel;//LVS_SINGSEL

BOOL m_bLvsAutoArrange;//LVS_AUTOARRANGE

BOOL m_bLvsNolabelWrap;//LVS_NOLABELWRAP

BOOL m_bLvsEditLavels;//LVS_EDITLAVELS

BOOL m_bLvsNocolumnHeader;//LVS_NOCOLUMNHEADER

BOOL m_bLvsNoSortHeader;//LVS_NOSORTHEADER

BOOL m_bSort;//LVS_SORTASCENDING

//}}AFX_DATA

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExListCtrlDlg)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

HICON m_hIcon;

//Generated message map functions

//{{AFX_MSG(CExListCtrlDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnSort();

afx_msg void OnLvsAutoarrange();

afx_msg void OnLvsEditlavels();

afx_msg void OnLvsNocolumnheader();

afx_msg void OnLvsNolabelwrap();

afx_msg void OnLvsNosortheader();

afx_msg void OnLvsSinglesel();

afx_msg void OnSelchangeStyle();

afx_msg void OnItemchangedListctrl(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnEndlabeleditListctrl(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnMode();

afx_msg void OnDelete();

afx_msg void OnAdd();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_EXLISTCTRLDLG_H__CE21DD87_26F3_11D2_A6C0_00403320A530__INCLUDED_)

//ExListCtrlDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExListCtrl.h”

#include “ExListCtrlDlg.h”

#include “ListDataDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

////////////////////////////////////////////////////////////////////////////CExListCtrlDlg dialog

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

: CDialog(CExListCtrlDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExListCtrlDlg)

m_strChooseData = _T(“”);

m_bLvsSingleSel = FALSE;

m_bLvsAutoArrange = FALSE;

m_bLvsNolabelWrap = FALSE;

m_bLvsEditLavels = FALSE;

m_bLvsNocolumnHeader = FALSE;

m_bLvsNoSortHeader = FALSE;

m_bSort = FALSE;

listdatanum=0;

//}}AFX_DATA_INIT

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

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

}

void CExListCtrlDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExListCtrlDlg)

DDX_Control(pDX, IDC_LISTCTRL, m_listCtrl);

DDX_Text(pDX, IDC_CHOOSEDATA, m_strChooseData);

DDX_Check(pDX, IDC_LVS_SINGLESEL, m_bLvsSingleSel);

DDX_Check(pDX, IDC_LVS_AUTOARRANGE, m_bLvsAutoArrange);

DDX_Check(pDX, IDC_LVS_NOLABELWRAP, m_bLvsNolabelWrap);

DDX_Check(pDX, IDC_LVS_EDITLAVELS, m_bLvsEditLavels);

DDX_Check(pDX, IDC_LVS_NOCOLUMNHEADER, m_bLvsNocolumnHeader);

DDX_Check(pDX, IDC_LVS_NOSORTHEADER, m_bLvsNoSortHeader);

DDX_Check(pDX, IDC_SORT, m_bSort);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExListCtrlDlg, CDialog)

//{{AFX_MSG_MAP(CExListCtrlDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_SORT, OnSort)

ON_BN_CLICKED(IDC_LVS_AUTOARRANGE, OnLvsAutoarrange)

ON_BN_CLICKED(IDC_LVS_EDITLAVELS, OnLvsEditlavels)

ON_BN_CLICKED(IDC_LVS_NOCOLUMNHEADER, OnLvsNocolumnheader)

ON_BN_CLICKED(IDC_LVS_NOLABELWRAP, OnLvsNolabelwrap)

ON_BN_CLICKED(IDC_LVS_NOSORTHEADER, OnLvsNosortheader)

ON_BN_CLICKED(IDC_LVS_SINGLESEL, OnLvsSinglesel)

ON_CBN_SELCHANGE(IDC_STYLE, OnSelchangeStyle)

ON_NOTIFY(LVN_ITEMCHANGED, IDC_LISTCTRL, OnItemchangedListctrl)

ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LISTCTRL, OnEndlabeleditListctrl)

ON_BN_CLICKED(IDC_MODE, OnMode)

ON_BN_CLICKED(IDC_DELETE, OnDelete)

ON_BN_CLICKED(IDC_ADD, OnAdd)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////////////CExListCtrlDlg message handlers

BOOL CExListCtrlDlg::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

AddColumn();

AddListData();

((CComboBox *)GetDlgItem(IDC_STYLE))->SetCurSel(0);

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 CExListCtrlDlg::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 CExListCtrlDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

//LVS_SORTASCENDING일 경우

void CExListCtrlDlg::OnSort()

{

UpdateData(TRUE);

long style;

CRect rect;

//윈도 스타일을 얻는다.

style = GetWindowLong(m_listCtrl.m_hWnd, GWL_STYLE);

//플러그값에 의해 LVS_SORTASCENDING값 설정

style &= ~LVS_SORTASCENDING;

if(m_bSort)

style |= LVS_SORTASCENDING;

//윈도 위치를 얻는다.

m_listCtrl.GetWindowRect(rect);

ScreenToClient(rect);

m_listCtrl.DestroyWindow();

//새로 윈도를 만들고

m_listCtrl.Create(style, rect, this, IDC_LISTCTRL);

//컬럼과 데이터를 넣는다.

AddColumn();

AddListData();

}

//리스트 컨트롤에 데이터를 설정한다.

void CExListCtrlDlg::AddListData()

{

LV_ITEM lvitem;

char *songname[10]={

“마징가제트”,”그랜다이저”,”황금박쥐“,”드래곤볼”,”은하철도999”,

“원시소년돌치”,”우주소년아톰”,”맥칸더브이”,”미래소년코난”,”하여가”};

char *song[10]={

“기운센 천하장사 ...”,

“태양을 마셔라 용기를 마셔라”,

“어디 어디 어디에서 나타났나”,

“찾아라 드래곤볼 세상에서 제일”,

“기차는 어둠을 헤치고 은하수”,

“달린다 돌치 끝없는 벌판을”,

“착하고 용감하게 날아자”,

“맥칸더 원 맥칸더 투 맥칸더 쓰리~~”,

“푸른하를 저멀리 새희망이 넘실”,

“너에게 떠나버린 마음이 ! 다시”};

int curitem;

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

for (int j = 0; j < 2; j++)

{

lvitem.mask = LVIF_TEXT;

lvitem.iItem = (j==0)?i:curitem;

lvitem.iSubItem = j;

switch(j)

{

//첫 번째 컬럼일 경우에는 InsertItem을 이용

case 0:

lvitem.pszText = songname[i];

curitem=m_listCtrl.InsertItem(&lvitem);

break;

//두 번째 컬럼일 경우에는 SetItem 함수를 이용한다.

case 1:

lvitem.pszText= song[i];

m_listCtrl.SetItem(&lvitem);

break;

}

}

}

//리스트 컨트롤에 컬럼을 설정한다.

void CExListCtrlDlg::AddColumn()

{

LV_COLUMN lvcolumn;

char *listcolumn[2]={“제목”,”가사”};

int width[2]={100,300};

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

{

lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText = listcolumn[i];

lvcolumn.iSubItem = i;

lvcolumn.cx = width[i];

m_listCtrl.InsertColumn(i, &lvcolumn);

}

}

void CExListCtrlDlg::OnLvsAutoarrange()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_AUTOARRANGE;

if(m_bLvsAutoArrange)

setstyle |=LVS_AUTOARRANGE;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

void CExListCtrlDlg::OnLvsEditlavels()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_EDITLABELS;

if(m_bLvsEditLavels)

setstyle |=LVS_EDITLABELS;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

void CExListCtrlDlg::OnLvsNocolumnheader()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOCOLUMNHEADER;

if(m_bLvsNocolumnHeader)

setstyle |=LVS_NOCOLUMNHEADER;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

void CExListCtrlDlg::OnLvsNolabelwrap()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOLABELWRAP;

if(m_bLvsNolabelWrap)

setstyle |=LVS_NOLABELWRAP;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//LVS_NOSORTHEADER일 경우

void CExListCtrlDlg::OnLvsNosortheader()

{

long setstyle;

CRect rect;

//윈도 영역을 얻는다.

m_listCtrl.GetWindowRect(&rect);

ScreenToClient(&rect);

//윈도 스타일을 얻고 m_bLvsNoSortHeader값에

//따라 setstyle값 변경

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_NOSORTHEADER;

if(m_bLvsNoSortHeader)

setstyle |=LVS_NOSORTHEADER;

//윈도을 다시 만든다.

m_listCtrl.DestroyWindow();

m_listCtrl.Create(setstyle, rect, this, IDC_LISTCTRL);

//컬럼과 데이터를 넣는다.

AddColumn();

AddListData();

}

void CExListCtrlDlg::OnLvsSinglesel()

{

long setstyle;

UpdateData(TRUE);

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

setstyle &= ~LVS_SINGLESEL;

if(m_bLvsSingleSel)

setstyle |=LVS_SINGLESEL;

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

m_listCtrl.RedrawWindow();

}

//콤보 상자에서 해당 항목이 바뀌면 리스트 컨트롤

//출력 스타일을 바꾼다.

void CExListCtrlDlg::OnSelchangeStyle()

{

//콤보 상자에서 현재 선택된 번호를 얻는다.

int num=((CComboBox *)GetDlgItem(IDC_STYLE))->GetCurSel();

long setstyle;

//윈도 스타일을 받고

setstyle=GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);

//TYPEMASK를 이용하여 출력 스타일을 0으로 설정한다.

setstyle &= ~LVS_TYPEMASK;

//해당 항목에 따라 스타일을 변경

switch(num)

{

//REPORT

case 0:

setstyle |=LVS_REPORT;

break;

//ICON

case 1:

setstyle |=LVS_ICON;

break;

//SMALL ICON

case 2:

setstyle |=LVS_SMALLICON;

break;

//LIST

case 3:

setstyle |=LVS_LIST;

break;

}

//스타일 다시 설정

SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,setstyle);

}

void CExListCtrlDlg::OnItemchangedListctrl(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

//바뀌어진 선택 번호를 얻는다.

listdatanum = pNMListView->iItem;

//번호에 해당하는 아이템 제목을 얻는다.

CString data=m_listCtrl.GetItemText(listdatanum,0);

//에디터 상자에 얻은 제목을 설정한다.

GetDlgItem(IDC_CHOOSEDATA)->SetWindowText(data);

*pResult = 0;

}

//수정 상태가 끝났을 경우

void CExListCtrlDlg::OnEndlabeleditListctrl(NMHDR* pNMHDR, LRESULT* pResult)

{

LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

//아이템의 서브아이템 문자열을 data에 얻고

CString data=m_listCtrl.GetItemText(pDispInfo->item.iItem,1);

//현재 수정된 데이터와 서브아이템 데이터를 대화 상자에 설정한다.

CListDataDlg *pDlg = new CListDataDlg(pDispInfo->item.pszText,data);

//대화 상자에서 IDOK를 클릭하면

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

{

//대화 상자에 있는 데이터를 리스트 컨트롤에 설정한다.

m_listCtrl.SetItemText(pDispInfo->item.iItem,0, pDlg->m_strSong);

m_listCtrl.SetItemText(pDispInfo->item.iItem,1, pDlg->m_strData);

}

delete pDlg;

*pResult = 0;

}

//수정 버튼을 클릭했을 때

void CExListCtrlDlg::OnMode()

{

//현재 선택된 아이템 문자열을 받은 다음

CString song=m_listCtrl.GetItemText(listdatanum,0);

CString data=m_listCtrl.GetItemText(listdatanum,1);

//대화 상자에 넘겨주고

CListDataDlg *pDlg = new CListDataDlg(song,data);

//OK 버튼을 클릭하면

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

{

//해당 데이터를 리스트 컨트롤에 설정한다.

m_listCtrl.SetItemText(listdatanum,0, pDlg->m_strSong);

m_listCtrl.SetItemText(listdatanum,1, pDlg->m_strData);

}

delete pDlg;

}

//삭제 버튼 클릭

void CExListCtrlDlg::OnDelete()

{

//”정말로 삭제할까요”라는 메시지에 YES 버튼을 클릭하면

if(AfxMessageBox(“정말로 삭제할까요”, MB_YESNO)==IDYES)

{

//현재 아이템을 삭제한다.

m_listCtrl.DeleteItem(listdatanum);

}

}

//추가 버튼

void CExListCtrlDlg::OnAdd()

{

//인자를 받지 않는 대화 상자를 실행한 후

CListDataDlg *pDlg = new CListDataDlg;

//OK 버튼이 클릭되면

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

{

//리스트 컨트롤에 아이템수를 얻은 다음

int num=m_listCtrl.GetItemCount();

//마지막 위치에 데이터를 넣는다.

LV_ITEM lvitem;

int curitem;

lvitem.mask = LVIF_TEXT;

lvitem.iItem = num;

lvitem.iSubItem = 0;

lvitem.pszText = (LPSTR)(LPCSTR)pDlg->m_strSong;

curitem=m_listCtrl.InsertItem(&lvitem);

lvitem.mask = LVIF_TEXT;

lvitem.iItem = curitem;

lvitem.iSubItem = 1;

lvitem.pszText = (LPSTR)(LPCSTR)pDlg->m_strData;

m_listCtrl.SetItem(&lvitem);

}

delete pDlg;

}

(2) CListDataDlg 소스

#if !defined(AFX_LISTDATADLG_H__CE21DD92_26F3_11D2_A6C0_00403320A530__INCLUDED_)

#define AFX_LISTDATADLG_H__CE21DD92_26F3_11D2_A6C0_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //_MSC_VER > 1000

//ListDataDlg.h : header file

//

////////////////////////////////////////////////////////////////////////////CListDataDlg dialog

class CListDataDlg : public CDialog

{

//Construction

public:

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

CListDataDlg(LPCSTR songname,LPCSTR songdata);

//Dialog Data

//{{AFX_DATA(CListDataDlg)

enum { IDD = IDD_LISTDATA };

CString m_strSong;

CString m_strData;

//}}AFX_DATA

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CListDataDlg)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CListDataDlg)

//NOTE: the ClassWizard will add member functions here

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

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

#endif

//!defined(AFX_LISTDATADLG_H__CE21DD92_26F3_11D2_A6C0_00403320A530__INCLUDED_)

//ListDataDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExListCtrl.h”

#include “ListDataDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

////////////////////////////////////////////////////////////////////////////CListDataDlg dialog

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

: CDialog(CListDataDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CListDataDlg)

m_strSong = _T(“”);

m_strData = _T(“”);

//}}AFX_DATA_INIT

}

CListDataDlg::CListDataDlg(LPCSTR songname,LPCSTR songdata)

: CDialog(CListDataDlg::IDD, NULL)

{

m_strSong = songname;

m_strData = songdata;

}

void CListDataDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CListDataDlg)

DDX_Text(pDX, IDC_EDIT1, m_strSong);

DDX_Text(pDX, IDC_EDIT2, m_strData);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CListDataDlg, CDialog)

//{{AFX_MSG_MAP(CListDataDlg)

//NOTE: the ClassWizard will add message map macros here

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////////////CListDataDlg message handlers

리스트 컨트롤에 그림 설정하기

지금까지는 리스트 컨트롤에 문자열만 설정한 후 삽입, 수정, 삭제 등을 해보았습니다. 리스트 컨트롤에는 문자열뿐만 아니라 그림을 설정할 수 있습니다. 리스트 컨트롤에 그림을 설정하는 방법은 그림 1-5-10과 같습니다.

그림 1-5-10처럼 먼저 리스트 컨트롤에 설정할 비트맵 이미지를 만듭니다. 그림 1-5-10의 ① 항목이 새롭게 만든 여섯 개의 비트맵입니다. 이 중에서 IDB_BITMAP1~3은 큰 이미지이며, IDB_BITMAP4~6은 작은 이미지입니다. 한 개의 그림은 큰 이미지와 작은 이미지가 쌍으로 있어야 합니다. 그 이유는 리스트 컨트롤에서 ICON 스타일로 설정할 때는 큰 이미지가 필요하고 나머지 경우에는 작은 이미지가 필요하기 때문입니다. 비트맵을 만든 후에 이 비트맵 이미지 리스트를 저장할 클래스를 만듭니다. 이 클래스가 CImageList입니다. 그림 1-5-10의 ② 항목이 바로 비트맵 이미지를 저장할 클래스를 헤더부에 선언한 것입니다. 헤더부에 CImageList 클래스를 두 개 선언한 이유는 큰 이미지 리스트를 가지고 있을 클래스와 작은 이미지 리스트를 가지고 있을 클래스가 필요하기 때문입니다. 이렇게 클래스를 선언한 후에 ③ 항목처럼 먼저 클래스를 생성하고 Create 함수를 이용하여 두 개의 이미지 리스트를 만듭니다.

리스트 컨트롤에서 사용하는 비트맵은 32×32와 16×16의 크기가 필요합니다. 이 두 개를 만들기 위해서 그림 1-5-10의 ⓐ 항목과 ⓑ 항목처럼 Create 함수에서 앞의 인자를 각각 32,32와 16,16으로 설정한 것입니다. 두 번째 인자 ILC_COLOR4는 256 컬러를 의미합니다. 뒤의 인자 두 개 중 처음 인자는 생성할 때 빈통으로 놔둘 이미지 개수와 한 개씩 증가될 때마다 여분으로 증가시키는 이미지 수를 의미합니다. 3,3으로 설정되어 있기 때문에 처음 만들 당시에 세 개의 그림통이 만들어지고, 그 다음 세 개가 다 차서 또 그림통이 필요할 경우 세 개씩 증가된다는 의미입니다. 이렇게 한 다음 CImageList의 Add 함수를 이용하여 비트맵을 설정합니다. 이 Add 함수를 보면 다음과 같습니다.

① int Add( CBitmap* pbmImage, CBitmap* pbmMask );

② int Add( CBitmap* pbmImage, COLORREF crMask );

③ int Add( HICON hIcon );

Add 함수 ①은 CBitmap 클래스를 이용하여 비트맵을 설정하는 것이고, 두 번째 CBitmap* pbmMask는 선택되었을 때 반전될 이미지를 의미합니다. ② 함수는 CBitmap 이미지와 선택되었을 때 반전될 색상을 의미합니다. ③은 비트맵이 아닌 아이콘을 이미지 리스트에 저장하는 형태입니다. 그림 1-5-10에서는 ② 함수를 사용했습니다. 먼저 CBitmap 클래스의 변수를 설정하고 LoadBitmap 함수를 이용하여 비트맵을 로드한 다음 이 비트맵을 설정할 때 마스크 색상을 흰색으로 하였습니다. 이렇게 하면 선택할 때 반전되어 해당 그림이 약간 어두워지는 형태가 됩니다.

이미지 리스트에서 아이콘을 설정하는 방법은 뒤의 트리 컨트롤에서 다시 설명하겠습니다. 그림 1-5-10에서는 큰 이미지 한 개와 작은 이미지 한 개를 설정하는 방법만 도시하였습니다. 즉 IDB_BITMAP1과 IDB_BITMAP4를 설정하는 방법만 설명한 것입니다. 이 방법을 반복하여 나머지 비트맵을 설정하면 됩니다. 이와 같이 하면 리스트 컨트롤에 비트맵을 설정할 수가 있습니다.

그림 1-5-10의 ③ 항목에서 m_listCtrl은 CListCtrl의 형인 클래스입니다. 그림 1-5-10에서는 선언되어 있지 않지만 헤더부에 CListCtrl m_listCtrl로 설정되어 있다는 가정하에 설명한 것입니다. 그림 1-5-10과 같이 이미지를 설정한 후에는 이 이미지 번호를 LV_ITEM에 설정하면 됩니다.

다음은 리스트 컨트롤에 이미지를 함께 설정하여 아이템을 추가하는 예입니다.

LV_ITEM lvitem;

lvitem.mask = LVIF_TEXT|LVIF_IMAGE;

lvitem.iItem = 0;

lvitem.iSubItem = 0;

lvitem.iImage=1;//그림 번호를 1번으로

lvitem.pszText =”테스트”;

m_listCtrl.InsertItem(&lvitem);

지금까지 LV_ITEM을 사용한 것과 같은 형태이나 mask에 LVIF_IMAGE를 OR 연산으로 추가하였으며, 지금까지 사용하지 않았던 멤버 iImage를 사용하여 이미지 번호를 설정한 것입니다. 이 번호는 그림 1-5-10의 Add 함수에 의해 추가된 순서 번호입니다. ilmage 값이 1이므로 그림 리스트 상자가 두 번째로 설정된 그림이 됩니다.

전에 만들었던 예제 ExListCtrl에 그림을 설정하는 항목을 한 번 삽입해보기 바랍니다. 이 책에 있는 내용만 이해할 것이 아니라 자꾸 응용해봄으로써 프로그램 실력은 부쩍 늘어날 것입니다.

ListView

ListView란 View 자체가 ListCtrl을 가지고 있는 형태를 의미합니다. ListView를 만들고자 할 때는 AppWizard의 Step6에서 View 윈도를 CListView로 Base class를 설정하면 됩니다. 그러면 CView 대신에 CListView가 View로 설정됩니다. 이렇게 설정되었을 때 View에 설정된 리스트 컨트롤을 얻으려면 다음과 같은 함수를 사용하면 됩니다.

GetListCtrl();

위의 함수를 사용하면 CListCtrl의 정적 변수가 바로 들어옵니다. 우리가 ExListCtrl에서 리스트 컨트롤을 사용할 때 m_listCtrl을 사용한 것처럼 m_listCtrl 대신에 GetListCtrl()를 사용하면 리스트 컨트롤에 데이터를 삽입하고 수정, 삭제를 할 수가 있습니다. 다음은 ListView에서 데이터를 설정하는 예입니다.

LV_ITEM lvitem;

lvitem.mask = LVIF_TEXT|LVIF_IMAGE;

lvitem.iItem = 0;

lvitem.iSubItem = 0;

lvitem.iImage=1;//그림 번호를 1번으로

lvitem.pszText =”테스트”;

GetListCtrl().InsertItem(&lvitem);//<-- GetListCtrl()를 사용

ExListView는 ListView를 사용한 예제입니다. 이 예제에서는 리스트 컨트롤에 이미지를 설정하였습니다. ExListView의 전체 구조는 그림 1-5-11과 같습니다.

그림 1-5-11에서와 같이 ExListView는 메뉴 항목에 네 개의 스타일 출력 항목이 설정되어 있습니다(ⓑ 항목 참조). 이 항목을 클릭하면 리스트 컨트롤의 출력 스타일이 메뉴에서 설정된 스타일로 바뀌게 됩니다. 또한 본 예제에서는 그림을 설정하였습니다. 그림을 설정하는 방법은 그림 1-5-10을 참조하면 됩니다. 다음은 ExListView의 View 클래스 소스입니다. 그림 1-5-11을 참조하여 분석하기 바랍니다.

//ExListViewView.h : interface of the CExListViewView class

//

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

#if

!defined(AFX_EXLISTVIEWVIEW_H__7114DDA0_26EF_11D2_8B6C_006097AD9DDC__INCLUDED_)

#define AFX_EXLISTVIEWVIEW_H__7114DDA0_26EF_11D2_8B6C_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //_MSC_VER > 1000

class CExListViewView : public CListView

{

protected: //create from serialization only

CExListViewView();

DECLARE_DYNCREATE(CExListViewView)

//Attributes

public:

CExListViewDoc* GetDocument();

CImageList *m_largeImage;//큰 이미지 저장 클래스

CImageList *m_smallImage;//작은 이미지 저장 클래스

//Operations

public:

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExListViewView)

public:

virtual void OnDraw(CDC* pDC);  //overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

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);

//}}AFX_VIRTUAL

//Implementation

public:

virtual ~CExListViewView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

//Generated message map functions

protected:

//{{AFX_MSG(CExListViewView)

afx_msg void OnListIcon();

afx_msg void OnListList();

afx_msg void OnListReport();

afx_msg void OnListSmallicon();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  //debug version in ExListViewView.cpp

inline CExListViewDoc* CExListViewView::GetDocument()

{ return (CExListViewDoc*)m_pDocument; }

#endif

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

//{{AFX_INSERT_LOCATION}}

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

#endif

//!defined(AFX_EXLISTVIEWVIEW_H__7114DDA0_26EF_11D2_8B6C_006097AD9DDC__INCLUDED_)

//ExListViewView.cpp : implementation of the CExListViewView class

//

#include “stdafx.h”

#include “ExListView.h”

#include “ExListViewDoc.h”

#include “ExListViewView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

////////////////////////////////////////////////////////////////////////////CExListViewView

IMPLEMENT_DYNCREATE(CExListViewView, CListView)

BEGIN_MESSAGE_MAP(CExListViewView, CListView)

//{{AFX_MSG_MAP(CExListViewView)

ON_COMMAND(ID_LIST_ICON, OnListIcon)

ON_COMMAND(ID_LIST_LIST, OnListList)

ON_COMMAND(ID_LIST_REPORT, OnListReport)

ON_COMMAND(ID_LIST_SMALLICON, OnListSmallicon)

//}}AFX_MSG_MAP

//Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CListView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CListView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CListView::OnFilePrintPreview)

END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////////////CExListViewView construction/destruction

CExListViewView::CExListViewView()

{

//TODO: add construction code here

}

CExListViewView::~CExListViewView()

{

}

BOOL CExListViewView::PreCreateWindow(CREATESTRUCT& cs)

{

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

// the CREATESTRUCT cs

return CListView::PreCreateWindow(cs);

}

////////////////////////////////////////////////////////////////////////////CExListViewView drawing

void CExListViewView::OnDraw(CDC* pDC)

{

CExListViewDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//TODO: add draw code for native data here

}

void CExListViewView::OnInitialUpdate()

{

CListView::OnInitialUpdate();

그림 1-5-10 참조, 그림 1-5-11의 ① 항목

이미지 리스트에 비트맵을 설정하고 컬럼과 데이터를 설정한다.

//메모리에 할당

m_largeImage= new CImageList;

m_smallImage= new CImageList;

m_largeImage->Create(32,32,ILC_COLOR4,3,3);//큰 이미지 저장통 만들기

m_smallImage->Create(16,16,ILC_COLOR4,3,3);//작은 이미지 저장통 만들기

//이미지 리스트에 그림 설정 Step1

CBitmap cBit;

cBit.LoadBitmap(IDB_BITMAP1);

m_largeImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

cBit.LoadBitmap(IDB_BITMAP2);

m_largeImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

cBit.LoadBitmap(IDB_BITMAP3);

m_largeImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

cBit.LoadBitmap(IDB_BITMAP4);

m_smallImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

cBit.LoadBitmap(IDB_BITMAP5);

m_smallImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

cBit.LoadBitmap(IDB_BITMAP6);

m_smallImage->Add(&cBit,RGB(255,255,255));

cBit.DeleteObject();

//이미지 리스트를 컨트롤 클래스에 등록

GetListCtrl().SetImageList(m_largeImage,LVSIL_NORMAL);

GetListCtrl().SetImageList(m_smallImage,LVSIL_SMALL);

//컬럼 만들기

LV_COLUMN lvcolumn;

char *listcolumn[2]={“제목”,”가사”};

int width[2]={100,300};

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

{

lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM

| LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText = listcolumn[i];

lvcolumn.iSubItem = i;

lvcolumn.cx = width[i];

GetListCtrl().InsertColumn(i, &lvcolumn);

}

//데이터 넣기

LV_ITEM lvitem;

char *songname[10]={

“마징가제트”,”그랜다이저”,”황금박쥐“,”드래곤볼”,”은하철도999”,

“원시소년돌치”,”우주소년아톰”,”맥칸더브이”,”미래소년코난”,”하여가”};

char *song[10]={

“기운센 천하장사 ...”,

“태양을 마셔라 용기를 마셔라”,

“어디 어디 어디에서 나타났나”,

“찾아라 드래곤볼 세상에서 제일”,

“기차는 어둠을 헤치고 은하수”,

“달린다 돌치 끝없는 벌판을”,

“착하고 용감하게 날아라”,

“맥칸더 원 맥칸더 투  맥칸더 쓰리~~”,

“푸른하늘 저멀리 새희망이 넘실”,

“너에게 떠나버린 마음이 ! 다시”};

int curitem;

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

for (int j = 0; j < 2; j++)

{

lvitem.mask = (j==0)?LVIF_TEXT|LVIF_IMAGE:LVIF_TEXT;

lvitem.iItem = (j==0)?i:curitem;

lvitem.iSubItem = j;

if(j==0)

lvitem.iImage=rand()%3;

switch(j)

{

case 0:

lvitem.pszText = songname[i];

curitem=GetListCtrl().InsertItem(&lvitem);

break;

case 1:

lvitem.pszText= song[i];

GetListCtrl().SetItem(&lvitem);

break;

}

}

}

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

//CExListViewView printing

BOOL CExListViewView::OnPreparePrinting(CPrintInfo* pInfo)

{

//default preparation

return DoPreparePrinting(pInfo);

}

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

{

//TODO: add extra initialization before printing

}

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

{

//TODO: add cleanup after printing

}

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

//CExListViewView diagnostics

#ifdef _DEBUG

void CExListViewView::AssertValid() const

{

CListView::AssertValid();

}

void CExListViewView::Dump(CDumpContext& dc) const

{

CListView::Dump(dc);

}

CExListViewDoc* CExListViewView::GetDocument() //non-debug version is inline

{

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

return (CExListViewDoc*)m_pDocument;

}

#endif //_DEBUG

////////////////////////////////////////////////////////////////////////////CExListViewView message handlers

그림 1-5-11의  ③ 항목

void CExListViewView::OnListIcon()

{

DWORD style=GetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE);

style &=~(LVS_TYPEMASK);

style|=LVS_ICON;

SetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE,(LONG)style);

}

그림 1-5-11의  ⑤ 항목

void CExListViewView::OnListList()

{

DWORD style=GetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE);

style &=~(LVS_TYPEMASK);

style|=LVS_LIST;

SetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE,(LONG)style);

}

그림 1-5-11의 ② 항목

void CExListViewView::OnListReport()

{

DWORD style=GetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE);

style &=~(LVS_TYPEMASK);

style|=LVS_REPORT;

SetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE,(LONG)style);

}

그림 1-5-11의 ④ 항목

void CExListViewView::OnListSmallicon()

{

DWORD style=GetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE);

style &=~(LVS_TYPEMASK);

style|=LVS_SMALLICON;

SetWindowLong(GetListCtrl().m_hWnd,GWL_STYLE,(LONG)style);

}

트리 컨트롤은 그림 1-5-12와 같이 트리 형태의 프로그램을 제작할 때 사용하는 컨트롤을 의미합니다.

그림 1-5-12는 윈도 탐색기에서 좌측 윈도에 디렉토리 구조를 트리 형태로 출력한 화면입니다. 그림 1-5-12처럼 트리 형태를 만들 때 사용하는 컨트롤이 트리 컨트롤입니다. 이 트리 컨트롤을 만들 때 사용하는 클래스는 CTreeCtrl입니다.

트리 컨트롤에 데이터 설정

트리 컨트롤에 데이터를 설정하는 방법은 그림 1-5-13과 같은 형태입니다.

그림 1-5-13의 ⓐ 항목은 트리 컨트롤에 특정 데이터가 설정되어 있는 화면입니다. 이 트리 컨트롤에는 그림과 문자가 함께 설정되어 있습니다. 이렇게 설정되어 있을 때는 CTreeCtrl 클래스와 CImageList 클래스가 함께 필요합니다. ① 항목은 두 개의 클래스를 헤더부에 정의한 내용을 표시한 것입니다. 즉 트리 컨트롤 클래스 m_treeCtrl과 이미지 리스트 클래스 pimagelist를 설정한 것입니다. pimagelist는 그냥 헤더에 코딩으로 선언하지만 만일 대화 상자 폼에 트리 컨트롤을 설정하였다면 m_treeCtrl은 DDX_Control로 연결되어야 합니다. ② 항목은 이렇게 만든 이미지 클래스에 아이콘을 로드하고 이 이미지 클래스를 트리 컨트롤에 설정한 형태입니다. 트리 컨트롤에 이미지를 설정한 후에 트리에 데이터를 삽입하고자 할 때는 두 가지로 분리해서 삽입합니다. 즉 루트일 경우와 자식일 경우입니다. ③ 항목은 루트일 경우 삽입하는 예이며, ④ 항목은 자식일 경우 삽입하는 예입니다. 그림 1-5-13의 내용을 각 항목별로 설명하겠습니다.

(1) 트리 컨트롤 전처리 과정

트리 컨트롤의 전처리 과정이라고 하면 간단하게 말해서 이미지 리스트를 만들어 트리에 설정하는 것뿐입니다. 이 방법은 리스트 컨트롤과 비슷합니다. 이미지 리스트를 만드는 내용은 그림 1-5-13의 ② 항목입니다.

pimagelist에 메모리를 할당한 다음 Create 함수를 이용하여 32×32 형태의 이미지를 저장하는 리스트를 만듭니다. 이곳에 Add 함수를 이용하여 아이콘을 설정하는데, AfxGetApp()->LoadIcon 함수에 인자를 아이콘 아이디로 설정하여 이미지 리스트 상자에 설정합니다. AfxGetApp()라는 함수의 의미는 현재 프로그램은 CWinApp를 상속받은 클래스를 얻고자 한다는 것입니다. 이 클래스의 멤버 함수(쉽게 보면 CWinApp 멤버 함수) 중에 LoadIcon이라는 함수를 이용하여 아이콘을 로드한 것입니다. 아이콘을 로드하려면 AfxGetApp()->LoadIcon() 함수를 이용하고 이곳 인자에 ID를 넣어주면 됩니다. 이렇게 이미지 리스트를 만든 다음 CTreeCtrl의 형을 가진 m_treeCtrl에 SetImageList 함수를 이용하여 이 이미지 리스트를 등록합니다. 이렇게 하면 트리를 설정하기 위한 전처리 과정이 끝나게 됩니다.

(2) 트리 컨트롤에 데이터 입력

트리 컨트롤에 데이터를 입력하기 위해서는 TV_INSERTSTRUCT 구조체를 이용합니다. 이 구조체는 다음과 같습니다.

typedef struct _TV_INSERTSTRUCT {  tvins

HTREEITEM hParent; //부모 아이템

HTREEITEM hInsertAfter; //삽입 방법

TV_ITEM   item; //설정할 트리 아이템

} TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;

hParent에 0이 설정되면 본 항목이 루트가 되며 이곳에 부모에 해당하는 HTREEITEM 번호를 설정하면 부모 아이템의 자식으로 설정됩니다. 실제로 트리에 설정할 구조체는 item입니다.

TV_INSERTSTRUCT 구조체에 내장되어 있는 TV_ITEM은 다음과 같습니다.

typedef struct _TV_ITEM {  tvi

UINT       mask; //멤버들의 유효화 설정

HTREEITEM  hItem; //트리 아이템 포인터

UINT       state; //현재 상태

UINT       stateMask; //상태 마스크

LPSTR      pszText; //트리에 설정할 문자열

int        cchTextMax; //문자열 최대 길이

int        iImage; //이미지 번호

int        iSelectedImage; //선택되었을 때 이미지 번호

int        cChildren; 자식 아이템이 있는가의 플러그

LPARAM     lParam; //본 구조체의 LPARAM 포인터

} TV_ITEM, FAR *LPTV_ITEM;

mask는 멤버 함수의 유효성을 판단하는 플러그입니다. 이 플러그값은 다음과 같습니다.

TVIF_CHILDREN  cChildren 멤버 유효화

TVIF_HANDLE  hItem 멤버 유효화

TVIF_IMAGE  iImage 멤버 유효화

TVIF_PARAM  lParam 멤버 유효화

TVIF_SELECTEDIMAGE  iSelectedImage 멤버 유효화

TVIF_STATE  state , stateMask 멤버 유효화

TVIF_TEXT  pszText , cchTextMax 멤버 유효화

위의 구조체에 정보를 넣고 CTreeCtrl의 멤버 함수인 InsertItem 함수를 이용하면 데이터를 설정할 수가 있습니다. 실제로 루트에 데이터를 설정한 형태는 그림 1-5-13의 ③ 항목과 같습니다. ③ 항목처럼 하면 루트로 “동물”이 설정됩니다. InsertItem 함수는 HTREEITEM을 리턴합니다. 이 리턴값을 item에 받아 두었습니다. 이 아이템 포인터값을 받아두는 이유는 이 아이템의 자식 아이템으로 자식을 설정하기 위해서입니다. “동물”이라는 트리에 “원숭이”라는 자식 아이템을 삽입한 형태가 그림 1-5-13의 ④ 항목입니다.

hParent가 루트일 경우에는 0이었으나 이번에는 루트를 설정할 때 받아두었던 item을 설정하였습니다. 이렇게 함으로써 “원숭이”는 “동물”의 자식 아이템으로 들어가게 됩니다.

이와 같은 방법으로 데이터를 “토끼”, “사자”, “호랑이”로 입력시켜 트리 구조를 만든 것이 그림 1-5-13의 ⓐ와 같은 형태입니다.

트리 컨트롤에서 이벤트 얻기

트리 컨트롤에서 발생하는 여러 이벤트는 TVN_ 계열로 발생됩니다. 대화 상자 폼에 트리 컨트롤을 설정하고 해당 트리 컨트롤 ID를 선택하면, 그림 1-5-14와 같이 여러 TVN_ 계열의 메시지가 출력될 것입니다.

이 이벤트 메시지를 보고 사실 어떤 이벤트인지를 아는 방법은 도움말을 참조하거나 느낌으로 알아야 합니다.(좀 뭐한 말입니다만 통밥이라고 해야 맞겠군요.) 예를 들어서 TVN_SELCHANGED를 보고 ‘음, 이거는 분명히 항목 선택이 바뀌었을 때 발생하는 메시지일 것이다.’라고 짐작하는 것이지요. 이렇게 한 후 ClassWizard에서 함수를 만들어 봅니다. 실제로 TVN_SELCHANGED 이벤트 메시지에 의해서 함수를 만들어 보면 다음과 같은 형태로 나타납니다.

void CExTreeCtrlDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

*pResult = 0;

}

위와 같이 나타날 때 함수 내부에서 보면 pNMTreeView라는 구조체 멤버가 이벤트가 발생했을 때의 어떤 결과를 가지는 형태로 나타납니다. 이렇게 나타났을 때 NM_TREEVIEW의 구조체가 어떤 것인가를 도움말에서 찾아봅니다. 도움말에서 NM_TREEVIEW를 보면 다음과 같이 나타날 것입니다.

내용이 영어로 되어 있어서 이해가 잘 되지 않을 경우에도 느낌을 이용합니다. NM_TREEVIEW의 첫 번째 멤버 NMHDR는 WM_NOTIFY 메시지가 발생될 때 lParam에 설정되는 통지 이벤트 정보 구조체입니다.(본 내용에 대해서는 「Windows Programming Bible」에 설명되어 있습니다.) 즉 통지 정보 구조체라고 생각하면 됩니다. 두 번째 멤버는 action입니다. 이것은 어떤 작업이 수행될 때 수행되는 값을 저장하는 멤버입니다. 왜냐하면 정수형이기 때문에 번호가 설정되는 것일 것이고 번호는 어떤 “액션 번호”라고 생각할 수 있기 때문입니다. 세 번째와 네 번째 멤버는 itemOld와 itemNew인데 Old는 옛날 것이고 New는 새로운 것이니까 쉽게 생각하면 이전 아이템과 현재 변경된 아이템일 것이라는 것을 알 수 있습니다. 여기에서 아이템의 구조체 형태는 TVITEM입니다. 이 구조체에 대해서는 전에 설명했습니다. 마지막 구조체가 POINT인데, 포인트를 많이 사용하는 것은 당연히 마우스 위치입니다. 일반적으로 마우스 위치는 대부분 POINT로 넘겨주고 영역 위치는 RECT로 넘겨주는 것이 윈도 메시지 구조체입니다. 이런 형태로 이벤트 메시지를 분석할 수 있습니다. 이렇게 메시지를 분석한 다음 현재 새롭게 선택된 항목의 텍스트 문자열을 얻고자 한다면 다음과 같이 할 수가 있습니다.

void CExTreeCtrlDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

//GetItemText 함수를 이용하여 새롭게 설정된 아이템의 hItem을

//넘겨서 문자열을 얻는다.

CString data=m_treeCtrl.GetItemText(pNMTreeView->itemNew.hItem);

*pResult = 0;

}

CTreeCtrl 클래스의 멤버 중 GetItemText는 아이템 핸들을 설정하면 해당 문자열을 리턴합니다. 이 리턴된 문자열을 CString data 클래스에 저장함으로써 새로 선택된 문자열을 얻을 수 있습니다.

이런 방법으로 그림 1-5-14에서 TVN_ENDLABELEDIT라는 메시지를 보면 ‘현재 항목을 수정하고 수정이 끝난 상태의 메시지일 것이다.’라는 추측을 할 수 있습니다. 그리고 해당 메시지가 발생되었을 때 수행하는 함수는 ClassWizard를 이용하여 만듭니다. 다음은 ClassWizard를 이용하여 TVN_ENDLABELEDIT에 메시지가 발생될 때 수행하는 함수를 만든 것입니다.

//TVN_ENDLABELEDIT 메시지가 발생될 때 수행하는 함수

void CExTreeCtrlDlg::OnEndlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult)

{

TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;

//pTVDiapInfo 멤버 item의 pszText를 현재 수정하고자 하는

//아이템으로 설정한다.

m_treeCtrl.SetItemText(hCurItem,pTVDispInfo->item.pszText);

*pResult = 0;

}

수정된 데이터가 설정된 구조체가 TV_DISPINFO이며, 이 구조체의 변수인 pTVDiapInfo 안의 어느 부분인가에 수정한 내용의 문자열이 있을 것이고, 이 문자열을 찾아서 트리 컨트롤의 멤버 함수인 SetItemText 함수를 이용하여 현재 아이템을 수정하고자 하는 문자열로 대치해야 합니다. 여기에서 TV_DISPINFO라는 구조체의 멤버를 알아야 합니다. 이 멤버를 아는 방법도 도움말을 찾아서 지금까지 설명한 방법으로 멤버들을 분석해야 합니다. 사실 영어를 어느 정도 알면 도움말을 이용하는 방법만 알아도 쉽게 접근할 수 있을 것입니다. Visaul Basic 6.0은 한글 도움말이 나옵니다. 혹시 압니까, 후에 Visual C++도 한글 도움말이 나올는지요.

트리 컨트롤 예제 ExTreeCtrl

ExTreeCtrl 예제는 트리 컨트롤에 데이터를 설정하고 수정이 가능하게 하며 현재 선택된 데이터를 출력하는 예제입니다. ExTreeCtrl의 전체 구조는 그림 1-5-16과 같습니다.

그림 1-5-16에서 보듯이 ExTreeCtrl에는 트리 컨트롤이 설정되어 있으며, 트리 컨트롤에서 특정 데이터를 선택하면 선택된 데이터가 에디터 상자에 출력됩니다. 삭제 버튼이 있어 특정 항목을 선택하여 삭제하면 해당 항목이 삭제됩니다. ExTreeCtrl을 제작할 때 사용하는 삽입, 삭제, 이벤트 얻기는 모두 전 항목에서 설명하였습니다. 그림 1-5-16과 소스를 보면서 ExTreeCtrl의 제작 방법을 이해하기 바랍니다.

//ExTreeCtrlDlg.h : header file

//

#if !defined(AFX_EXTREECTRLDLG_H__3E9339A7_2718_11D2_A6C0_00403320A530__INCLUDED_)

#define AFX_EXTREECTRLDLG_H__3E9339A7_2718_11D2_A6C0_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //_MSC_VER > 1000

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

//CExTreeCtrlDlg dialog

class CExTreeCtrlDlg : public CDialog

{

//Construction

public:

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

//Dialog Data

//{{AFX_DATA(CExTreeCtrlDlg)

enum { IDD = IDD_EXTREECTRL_DIALOG };

그림 1-5-3의  ① 항목

CTreeCtrl m_treeCtrl;

CImageList *pimagelist;

특정 아이템이 선택되었을 때 선택된 아이템을 저장하는 변수

HTREEITEM hCurItem;//□-①

//}}AFX_DATA

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExTreeCtrlDlg)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

HICON m_hIcon;

//Generated message map functions

//{{AFX_MSG(CExTreeCtrlDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnDelete();

afx_msg void OnEndlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult);

virtual void OnOK();

afx_msg void OnCok();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

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

#endif //!defined(AFX_EXTREECTRLDLG_H__3E9339A7_2718_11D2_A6C0_00403320A530__INCLUDED_)

//ExTreeCtrlDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExTreeCtrl.h”

#include “ExTreeCtrlDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExTreeCtrlDlg dialog

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

: CDialog(CExTreeCtrlDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExTreeCtrlDlg)

//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 CExTreeCtrlDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExTreeCtrlDlg)

DDX_Control(pDX, IDC_TREE, m_treeCtrl);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExTreeCtrlDlg, CDialog)

//{{AFX_MSG_MAP(CExTreeCtrlDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_NOTIFY(TVN_SELCHANGED, IDC_TREE, OnSelchangedTree)

ON_BN_CLICKED(IDC_DELETE, OnDelete)

ON_NOTIFY(TVN_ENDLABELEDIT, IDC_TREE, OnEndlabeleditTree)

ON_BN_CLICKED(IDCOK, OnCok)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CExTreeCtrlDlg message handlers

데이터 설정 루틴 그림 1-5-13 참조

char *treedata[4][10]={

{“동물”,”원숭이”,”호랑이”,”사자”,”토끼”,NULL},

{“자동차”,”포니”,”사피아”,”그랑죠”,”브루샤”,NULL},

{“꽃”,”장미”,”백합”,”할미꽃”,”민들레”,”개나리”,NULL},

{“로보트”,”그렌다이저”,”마징가”,”태권브이”,”건담“,NULL}

};

BOOL CExTreeCtrlDlg::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

//이미지 리스트 설정

pimagelist= new CImageList;

pimagelist->Create(32,32,ILC_COLOR4,3,3);

pimagelist->Add(AfxGetApp()->LoadIcon(IDR_ICON1));

pimagelist->Add(AfxGetApp()->LoadIcon(IDR_ICON2));

//트리에 설정

m_treeCtrl.SetImageList(pimagelist,TVSIL_NORMAL);

TV_INSERTSTRUCT tvstruct;

HTREEITEM item;

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

{

for(int j=0;treedata[i][j];j++)

{

//j=0이면 루트임. 그렇지 않으면 루트 아이템 핸들러를 설정

tvstruct.hParent=(j==0)?0:item;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=treedata[i][j];

tvstruct.item.mask=TVIF_TEXT |

TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

//루트일 경우에는 삽입된 아이템 핸들러를 iItem으로

//받는다.

if(j==0)

item=m_treeCtrl.InsertItem(&tvstruct);

else

m_treeCtrl.InsertItem(&tvstruct);

}

}

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 CExTreeCtrlDlg::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 CExTreeCtrlDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

//항목 선택이 바뀌었을 경우 그림 1-5-16의 ③ 항목에 해당

void CExTreeCtrlDlg::OnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

//선택된 아이템을 hCurItem에 설정한다.

//헤더부의 □-① 참조

hCurItem=pNMTreeView->itemNew.hItem;

//GetItemText 함수를 이용하여 새롭게 설정된 아이템의 hItem을

//넘겨서 문자열을 얻는다.

CString data=m_treeCtrl.GetItemText(pNMTreeView->itemNew.hItem);

//에디터 윈도에 설정한다.

GetDlgItem(IDC_EDIT1)->SetWindowText(data);

*pResult = 0;

}

//현재 선택된 데이터를 삭제한다. 그림 1-5-16의 ④ 항목

void CExTreeCtrlDlg::OnDelete()

{

if(AfxMessageBox(“장말 삭제할까요?”,MB_YESNO)==IDYES)

m_treeCtrl.DeleteItem(hCurItem);

}

//TVN_ENDLABELEDIT 메시지가 발생될 때 수행하는 함수

void CExTreeCtrlDlg::OnEndlabeleditTree(NMHDR* pNMHDR, LRESULT* pResult)

{

TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;

//pTVDiapInfo 멤버 item의 pszText를 현재 수정하고자 하는

//아이템으로 설정한다.

m_treeCtrl.SetItemText(hCurItem,pTVDispInfo->item.pszText);

*pResult = 0;

}

void CExTreeCtrlDlg::OnOK()

{

// CDialog::OnOK();

}

void CExTreeCtrlDlg::OnCok()

{

//TODO: Add your control notification handler code here

CDialog::OnOK();

}

트리 컨트롤의 드래깅 앤 드롭

트리 컨트롤에서 해당 항목을 좌측 마우스 버튼을 누른 채로 이동하여 특정 위치에서 좌측 마우스 버튼을 놓으면 해당 위치로 데이터가 이동되는 것을 보았을 것입니다. 이것을 드래깅 앤 드롭이라고 하는데, 트리 컨트롤에 뿐만 아니라 리스트 컨트롤과 탭 컨트롤, 툴바 컨트롤 등에 드래깅 앤 드롭이 많이 사용됩니다. 드래깅 앤 드롭에서 가장 중요한 것은 드래깅이 되는 시점입니다. 드래깅이 되는 시점의 메시지만 알면 나머지는 WM_MOSUEMOVE와 WM_LBUTTONUP을 이용하여 드롭 부분을 처리할 수 있습니다. 드래깅이 되는 시점은 트리 컨트롤에서는 TVN_BEGINDRAG 이벤트 메시지가 발생되었을 때입니다. 이때의 함수를 클래스 위저드로 만들면 바로 드래깅의 시작 시점이 됩니다. 드래깅과 드롭의 전체 구조는 그림 1-5-17과 같습니다.

그림 1-5-17과 같이 트리 컨트롤에서 특정 아이템을 좌측 마우스 버튼으로 클릭한 채로 일정 시간이 지나면 TVN_BEGINDRAG 메시지가 발생합니다. 이 메시지가 발생되면 ① 항목과 같이 특정 플러그 m_bDrag라는 멤버에 1을 설정하고 ② 항목처럼 마우스 커서를 바꾸어줍니다. 이런 후에 WM_MOUSEMOVE가 발생되는 함수, 즉 마우스가 이동될 때 현재 m_bDrag가 1이면 드래깅 중이므로 현재 마우스가 위치해 있는 트리 아이템을 선택된 아이템으로 만듭니다. 이렇게 마우스를 움직이다가 특정 아이템 위치에 놓으면 해당 위치를 알고 그 위치를 부모로 하여 이동하자고 하는 아이템을 삽입합니다. 그리고 처음 위치에 있는 아이템을 삭제하면 됩니다. 다음은 이런 방법으로 코딩한 예입니다.

//TVN_BEGINDRAG 메시지시 수행되는 함수

//그림 1-5-17의 ① 항목

void CExTreeViewView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

//마우스 커서를 드래깅 모드로 바꾼다. 그림 1-5-17의 ② 항목

SetClassLong(this->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(IDC_CURSOR1));

//드래그 플러그를 TRUE로

m_bDrag=TRUE;

//현재 아이템을 선택된 아이템으로 하고

m_treeCtrl.SelectItem(pNMTreeView->itemNew.hItem);

//현재 아이템을 hDrawItem에 설정

hDragItem=pNMTreeView->itemNew.hItem;

*pResult = 0;

}

//마우스가 움직이면 그림 1-5-17의 ③ 항목

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

{

//현재 드래깅 중이면

if(m_bDrag)

{

//마우스 위치의 트리 아이템을 얻는다.

hCurItem=m_treeCtrl.HitTest(point);

//마우스 위치의 트리 아이템을 선택된 아이템으로 만든다.

m_treeCtrl.SelectItem(hCurItem);

}

CTreeView::OnMouseMove(nFlags, point);

}

//마우스 버튼이 떼어지면 그림 1-5-17의 ④ 항목

void CExTreeViewView::OnLButtonUp(UINT nFlags, CPoint point)

{

//드래깅 모드이면

if(m_bDrag)

{

//커서를 바꾼 다음

SetClassLong(this->m_hWnd,GCL_HCURSOR,

(long)LoadCursor(NULL,IDC_ARROW));

//드래깅 모드 해제

m_bDrag=FALSE;

//EndDrag 함수를 호출하여 드래깅 종료

EndDrag(point);

}

CTreeView::OnLButtonUp(nFlags, point);

}

//드래깅 종료

void CExTreeViewView::EndDrag(CPoint point)

{

//이동할 아이템의 문자열을 받는다.

CString src=m_treeCtrl.GetItemText(hDragItem);

TV_INSERTSTRUCT tvstruct;

HTREEITEM child,parent;

//현재 아이템을 부모로 이동할 아이템을 삽입한다.

tvstruct.hParent=hCurItem;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=(LPSTR)(LPCSTR)src;

tvstruct.item.mask=TVIF_TEXT

| TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

parent=m_treeCtrl.InsertItem(&tvstruct);

//이동할 아이템의 자식 아이템이 있으면 이 아이템을

//현재 삽입한 아이템의 자식 아이템으로 삽입

for(;;)

{

child=m_treeCtrl.GetChildItem(hDragItem);

if(child==NULL)

break;

src=m_treeCtrl.GetItemText(child);

tvstruct.hParent=parent;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=(LPSTR)(LPCSTR)src;

tvstruct.item.mask=TVIF_TEXT |

TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

m_treeCtrl.InsertItem(&tvstruct);

m_treeCtrl.DeleteItem(child);

}

//이동할 아이템의 이동이 끝났으므로

//이전 아이템은 삭제

m_treeCtrl.DeleteItem(hDragItem);

}

위의 예에서 EndDrag 함수의 기능은 이동하고자 하는 아이템을 먼저 이동 위치의 아이템을 부모로 하여 삽입하고 드래깅 아이템의 자식 아이템이 있는가를 검색한 다음 자식 아이템들을 모두 이동된 위치의 아이템으로 설정한 것입니다. 그러나 EndDrag 함수에는 단점이 있습니다. 단점이란 이동하고자 하는 아이템의 첫 번째 자식 아이템은 이동이 되나 첫 번째 자식 아이템에 또 자식 아이템이 있으면, 즉 이동하고자 하는 아이템의 두 번째 가지에 위치하는 아이템은 이동되지 않고 삭제됩니다. 이럴 경우에는 Recursive(자기 호출) 함수를 만들어야 합니다. 즉 다음과 같은 방법이 될 것입니다.

HTREEITEM MoveItem(HTREEITEM parantitem)

{

HTREEITEM parent;

parent=parentitem 자식 아이템으로 삽입;

만일 현재 아이템에 자식 아이템이 있다면

return MoveItem(parent);

그렇지 않다면

return NULL;

}

위와 같이 함수를 만든 다음 for() 루프를 이용하여 모든 자식 아이템에 MoveItem을 설정해주어야 합니다.

TreeView

TreeView는 View 자체 안에 트리 컨트롤이 있는 View입니다. 이것은 리스트 View와 마찬가지로 템플릿에서 CView 대신 설정할 수 있습니다. 하나의 예제를 작성해 가면서 TreeView의 구조를 알아보겠습니다.

ExTreeView는 TreeView를 사용한 것이며 이곳에 데이터를 넣고 드래깅과 드롭이 가능하게 만들었습니다. 이 ExTreeView의 실제 기능은 그림 1-5-17의 드래깅 앤 드롭이 주된 기능입니다.

ExTreeView의 프로그램은 MDI로 만들어져 있으며 AppWizard의 Step6에서 CView 대신에 CTreeView를 base class로 설정한 것입니다. 한 개의 자식 프레임이 만들어질 때마다 그림 1-5-17의 ⓐ와 같은 형태가 나타납니다. Treeview에서 트리 컨트롤 핸들을 얻으려면 ListView와 비슷하게 GetTreeCtrl() 함수를 호출하면 됩니다. ExTreeView의 View 클래스 소스는 다음과 같습니다.

//ExTreeViewView.h : interface of the CExTreeViewView class

//

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

#if !defined(AFX_EXTREEVIEWVIEW_H__4F0773B1_26F1_11D2_8B6C_006097AD9DDC__INCLUDED_)

#define AFX_EXTREEVIEWVIEW_H__4F0773B1_26F1_11D2_8B6C_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //_MSC_VER > 1000

class CExTreeViewView : public CTreeView

{

protected: //create from serialization only

CExTreeViewView();

DECLARE_DYNCREATE(CExTreeViewView)

//Attributes

public:

CExTreeViewDoc* GetDocument();

//이미지 클래스

CImageList *pimagelist;

//현재 선택된 아이템

HTREEITEM hCurItem;

//드래깅하고자 할 때 드래깅 target 아이템

HTREEITEM hDragItem;

//드래깅 모드

BOOL m_bDrag;

//Operations

public:

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExTreeViewView)

public:

virtual void OnDraw(CDC* pDC);  //overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

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);

//}}AFX_VIRTUAL

//Implementation

public:

void EndDrag(CPoint point);

virtual ~CExTreeViewView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

//Generated message map functions

protected:

//{{AFX_MSG(CExTreeViewView)

afx_msg void OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  //debug version in ExTreeViewView.cpp

inline CExTreeViewDoc* CExTreeViewView::GetDocument()

{ return (CExTreeViewDoc*)m_pDocument; }

#endif

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

//{{AFX_INSERT_LOCATION}}

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

#endif //!defined(AFX_EXTREEVIEWVIEW_H__4F0773B1_26F1_11D2_8B6C_006097AD9DDC__INCLUDED_)

//ExTreeViewView.cpp : implementation of the CExTreeViewView class

//

#include “stdafx.h”

#include “ExTreeView.h”

#include “ExTreeViewDoc.h”

#include “ExTreeViewView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExTreeViewView

IMPLEMENT_DYNCREATE(CExTreeViewView, CTreeView)

BEGIN_MESSAGE_MAP(CExTreeViewView, CTreeView)

//{{AFX_MSG_MAP(CExTreeViewView)

ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnEndlabeledit)

ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelchanged)

ON_WM_CREATE()

ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)

ON_WM_LBUTTONUP()

ON_WM_MOUSEMOVE()

//}}AFX_MSG_MAP

//Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CTreeView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CTreeView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CTreeView::OnFilePrintPreview)

END_MESSAGE_MAP()

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

//CExTreeViewView construction/destruction

CExTreeViewView::CExTreeViewView()

{

//TODO: add construction code here

}

CExTreeViewView::~CExTreeViewView()

{

}

BOOL CExTreeViewView::PreCreateWindow(CREATESTRUCT& cs)

{

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

// the CREATESTRUCT cs

return CTreeView::PreCreateWindow(cs);

}

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

//CExTreeViewView drawing

void CExTreeViewView::OnDraw(CDC* pDC)

{

CExTreeViewDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

//TODO: add draw code for native data here

}

char *treedata[4][10]={

{“동물”,”원숭이”,”호랑이”,”사자”,”토끼”,NULL},

{“자동차”,”포니”,”사피아”,”그랑죠”,”브루샤”,NULL},

{“꽃”,”장미”,”백합”,”할미꽃”,”민들레”,”개나리”,NULL},

{“로보트”,”그렌다이저”,”마징가”,”태권브이”,”건담“,NULL}

};

void CExTreeViewView::OnInitialUpdate()

{

CTreeView::OnInitialUpdate();

//이미지 리스트 설정

pimagelist= new CImageList;

pimagelist->Create(32,32,ILC_COLOR4,3,3);

pimagelist->Add(AfxGetApp()->LoadIcon(IDR_ICON1));

pimagelist->Add(AfxGetApp()->LoadIcon(IDR_ICON2));

//트리에 설정

GetTreeCtrl().SetImageList(pimagelist,TVSIL_NORMAL);

TV_INSERTSTRUCT tvstruct;

HTREEITEM item,root;

tvstruct.hParent=0;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=”루트”;

tvstruct.item.mask=TVIF_TEXT | TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

root=GetTreeCtrl().InsertItem(&tvstruct);

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

{

for(int j=0;treedata[i][j];j++)

{

tvstruct.hParent=(j==0)?root:item;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=treedata[i][j];

tvstruct.item.mask=TVIF_TEXT

| TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

if(j==0)

item=GetTreeCtrl().InsertItem(&tvstruct);

else

GetTreeCtrl().InsertItem(&tvstruct);

}

}

}

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

//CExTreeViewView printing

BOOL CExTreeViewView::OnPreparePrinting(CPrintInfo* pInfo)

{

//default preparation

return DoPreparePrinting(pInfo);

}

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

{

//TODO: add extra initialization before printing

}

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

{

//TODO: add cleanup after printing

}

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

//CExTreeViewView diagnostics

#ifdef _DEBUG

void CExTreeViewView::AssertValid() const

{

CTreeView::AssertValid();

}

void CExTreeViewView::Dump(CDumpContext& dc) const

{

CTreeView::Dump(dc);

}

CExTreeViewDoc* CExTreeViewView::GetDocument() //non-debug version is inline

{

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

return (CExTreeViewDoc*)m_pDocument;

}

#endif //_DEBUG

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

//CExTreeViewView message handlers

//수정 상태가 끝나면 수정시킴.

void CExTreeViewView::OnEndlabeledit(NMHDR* pNMHDR, LRESULT* pResult)

{

TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;

GetTreeCtrl().SetItemText(hCurItem,pTVDispInfo->item.pszText);

*pResult = 0;

}

void CExTreeViewView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

hCurItem=pNMTreeView->itemNew.hItem;

*pResult = 0;

}

int CExTreeViewView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CTreeView::OnCreate(lpCreateStruct) == -1)

return -1;

//트리 컨트롤 스타일을 바꾼다.

long style,oldstyle;

style=TVS_EDITLABELS|TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT;

oldstyle=GetWindowLong(GetTreeCtrl().m_hWnd,GWL_STYLE);

SetWindowLong(GetTreeCtrl().m_hWnd,GWL_STYLE,oldstyle|style);

m_bDrag=FALSE;

return 0;

}

//TVN_BEGINDRAG 메시지시 수행되는 함수

void CExTreeViewView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

//마우스 커서를 드래깅 모드로 바꾼다.

SetClassLong(this->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(IDC_CURSOR1));

//드래그 플러그를 TRUE로

m_bDrag=TRUE;

//현재 아이템을 선택된 아이템으로 하고

GetTreeCtrl().SelectItem(pNMTreeView->itemNew.hItem);

//현재 아이템을 hDrawItem에 설정

hDragItem=pNMTreeView->itemNew.hItem;

*pResult = 0;

}

//마우스가 움직이면

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

{

//현재 드래깅 중이면

if(m_bDrag)

{

//마우스 위치의 트리 아이템을 얻는다.

hCurItem=GetTreeCtrl().HitTest(point);

//마우스 위치의 트리 아이템을 선택된 아이템으로 만든다.

GetTreeCtrl().SelectItem(hCurItem);

}

CTreeView::OnMouseMove(nFlags, point);

}

//마우스 버튼이 떼어지면

void CExTreeViewView::OnLButtonUp(UINT nFlags, CPoint point)

{

//드래깅 모드이면

if(m_bDrag)

{

//커서를 바꾼 다음

SetClassLong(this->m_hWnd,GCL_HCURSOR,

(long)LoadCursor(NULL,IDC_ARROW));

//드래깅 모드 해제

m_bDrag=FALSE;

//EndDrag 함수를 호출하여 드래깅 종료

EndDrag(point);

}

CTreeView::OnLButtonUp(nFlags, point);

}

//드래깅 종료

void CExTreeViewView::EndDrag(CPoint point)

{

//이동할 아이템의 문자열을 받는다.

CString src=GetTreeCtrl().GetItemText(hDragItem);

TV_INSERTSTRUCT tvstruct;

HTREEITEM child,parent;

//현재 아이템을 부모로 이동할 아이템을 삽입한다.

tvstruct.hParent=hCurItem;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=(LPSTR)(LPCSTR)src;

tvstruct.item.mask=TVIF_TEXT | TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

parent=GetTreeCtrl().InsertItem(&tvstruct);

//이동할 아이템의 자식 아이템이 있으면 이 아이템을

//현재 삽입한 아이템의 자식 아이템으로 삽입

for(;;)

{

child=GetTreeCtrl().GetChildItem(hDragItem);

if(child==NULL)

break;

src=GetTreeCtrl().GetItemText(child);

tvstruct.hParent=parent;

tvstruct.hInsertAfter=TVI_LAST;

tvstruct.item.pszText=(LPSTR)(LPCSTR)src;

tvstruct.item.mask=TVIF_TEXT | TVIF_IMAGE |TVIF_SELECTEDIMAGE ;

tvstruct.item.iImage =1;

tvstruct.item.iSelectedImage=0;

GetTreeCtrl().InsertItem(&tvstruct);

GetTreeCtrl().DeleteItem(child);

}

//이동할 아이템의 이동이 끝났으므로

//이전 아이템은 삭제

GetTreeCtrl().DeleteItem(hDragItem);

}

그림 1-5-18은 ExTreeView의 실행 모습입니다.

본 항목에서는 탭 컨트롤, 스핀 컨트롤, 슬라이더, 프로그래스바, 애니메이트 컨트롤 등의 다양한 컨트롤에 대해서 설명합니다. 이 컨트롤들은 실제로 사용할 때 사용법이 어렵지 않습니다. 다만 처음에 설정하는 방법과 이벤트를 얻는 방법들이 처음 사용자에게는 힘든 부분에 해당할 것입니다. 본 항목에서 설명하는 다섯 개의 컨트롤은 하나의 예제를 완성해 가면서 설명하겠습니다. 본 항목에서 완성하는 예제는 ExTabCtrl입니다. 이 예제에는 다섯 개의 컨트롤이 모두 설정되어 있습니다.

탭 컨트롤

탭 컨트롤은 Visual C++의 Project Workspace와 같이 항목 버튼이 있으며, 이 항목 버튼을 클릭하면 해당 내용이 나타나도록 설정된 컨트롤입니다. 탭 컨트롤 대화 상자 폼에 설정하기 위해서는 대화 상자 폼을 선택하였을 때 나타나는 Controls 도구바에서  버튼을 클릭하여 도시하면 됩니다. 이렇게 도시한 형태가 그림 1-5-19입니다.

탭 컨트롤을 설정한 후에 DDX_Control로 연결하면 CTabCtrl과 연결됩니다. CTabCtrl 클래스는 탭 컨트롤을 핸들링하는 클래스입니다. 탭 컨트롤도 지금까지 설명한 리스트 컨트롤, 트리 컨트롤과 마찬가지로 그림을 설정할 수 있습니다. 즉 CImageList 클래스를 이용하여 그림을 설정한 후 이것을 탭 컨트롤에 설정하고 해당 탭에 그림을 설정할 수 있다는 의미입니다. 탭 컨트롤에 그림을 설정하는 방법은 트리 컨트롤이나 리스트 컨트롤처럼 CTabCtrl의 멤버 함수 SetImageList를 사용합니다. 다음은 CImageList 형태에 아이콘을 로드하고 이것을 탭 컨트롤에 설정하는 예입니다.

//헤더부에 정의

CImageList *m_pImageList;

CTabCtrl m_tabCtrl;

//이미지 리스트 탭에 설정

m_pImageList = new CImageList;

m_pImageList->Create(32,32,ILC_COLOR4,3,3);

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON1));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON2));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON3));

m_tabCtrl.SetImageList(m_pImageList);

리스트 컨트롤과 트리 컨트롤의 예제를 작성해 보았다면 이미지 설정 방법을 이해할 수 있을 것입니다. 이렇게 이미지를 설정한 후에 탭 아이템을 설정해야 합니다. 탭 아이템을 설정하는 함수는 CTabCtrl InsertItem입니다. 이 함수는 TC_ITEM 구조체의 변수를 받아서 아이템을 삽입합니다. 이때 TC_ITEM은 다음과 같은 형태를 가지고 있습니다.

typedef struct tagTCITEM {

UINT mask;//유효화 마스크

DWORD dwState;    //탭 컨트롤 아이템 상태값

DWORD dwStateMask;//마스크 상태

UINT lpReserved1;//사용 안함

UINT lpReserved2;//사용 안함

LPTSTR pszText; //문자열

int cchTextMax; //문자열의 최대 길이

int iImage;//그림 번호

LPARAM lParam;//본 구조체의 LPARAM 포인터

} TCITEM, FAR *LPTCITEM;

mask에는 TCIF_ 계열의 값을 가질 수 있습니다. TCIF_IMAGE라고 하면 그림 설정을 유효화하고, TCIF_TEXT라고 하면 문자 설정을 유효화합니다. 만일 그림과 문자를 동시에 출력하고자 한다면 TCIF_TEXT|TCIF_IMAGE의 두 개의 값을 함께 설정하면 됩니다. dwState와 dwStateMask는 이벤트에 의해서 메시지가 전달되면서 정보를 TC_ITEM으로 넘겨줄 경우 여러 상태를 설정하는 플러그입니다. 탭 컨트롤에 데이터를 설정할 때는 사용하지 않습니다. pszText가 입력될 문자열이면 iImage는 그림 번호입니다. 예를 들어서 트리 컨트롤에 현재 설정된 이미지 번호 1번과 문자열 “안녕”이라는 항목으로 탭 컨트롤에 아이템을 설정하려면 다음과 같이 합니다.

TC_ITEM item;

item.mask=TCIF_TEXT|TCIF_IMAGE;

item.pszText=”안녕”;//문자열

item.iImage=1;//그림 번호

m_tabCtrl.InsertItem(i,&item);

위와 같이 하면 탭 컨트롤에 아이템을 설정할 수 있습니다.

(1) 탭 컨트롤 이벤트 얻기

탭 컨트롤은 TCN_ 계열의 이벤트 메시지가 발생됩니다. 탭 컨트롤에서 주로 많이 이용하는 메시지는 TCN_SELCHANGE일 것입니다. 탭 컨트롤은 설정된 후에 주로 어떤 항목을 선택하였는가를 확인하는 것이 거의 전부입니다. 이 이벤트 메시지에 의해 만들어진 함수로부터 해당 이벤트 번호를 얻고자 할 때 다음과 같이 할 수 있습니다.

void CExTabCtrlDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)

{

int m_nTabNum=m_tabCtrl.GetCurSel();

*pResult = 0;

}

탭 컨트롤의 멤버 함수인 GetCurSel 함수를 호출하면 현재 설정된 번호를 리턴합니다. 이와 같은 방법으로 현재 탭에서 선택하는 것을 알 수 있습니다.

(2) 탭에 따라 항목 전환

탭 컨트롤은 주로 항목 전환용으로 많이 사용됩니다. 즉 항목을 클릭하면 해당 항목에 해당하는 내용이 나타나는 형태를 많이 취한다는 것입니다. 그림 1-5-20은 탭에 따라서 대화 상자에 다르게 나타나는 예입니다.

그림 1-5-20의 대화 상자에 세 개의 탭 항목이 있습니다. 이 항목을 클릭할 때마다 화면에 출력되는 윈도 형태들이 다릅니다. 이 부분에서 필자는 처음에 탭 컨트롤 안에 윈도를 등록하는 어떤 함수가 있지 않을까 하는 생각을 했습니다. 그러나 탭 컨트롤 안에는 윈도를 등록하는 어떤 함수도 없었습니다. 결국 탭 컨트롤을 눌렀을 때 윈도를 선택적으로 나타나게 하고 감추게 하는 것입니다. 예를 들어 임의의 윈도 세 개가 m_pWnd1∼m_pWnd3까지 있다고 가정하고, 또 이 윈도들은 세 개의 탭 컨트롤과 연결되어 나타나고 사라진다고 가정한다면, 탭 컨트롤 항목이 변할 때 예 함수(위에 설명한 내용)를 다음과 같이 추가하여 탭 항목에 따라 화면이 전환되게 만들 수 있습니다.

void CExTabCtrlDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)

{

int m_nTabNum=m_tabCtrl.GetCurSel();

switch(m_nTabNum)

{

case 0://1번 윈도만 화면에 나타내고 나머지는 감춘다.

m_pWnd1->ShowWindow(SW_SHOW);

m_pWnd2->ShowWindow(SW_HIDE);

m_pWnd3->ShowWindow(SW_HIDE);

break;

case 1://2번 윈도만 화면에 나타내고 나머지는 감춘다.

m_pWnd1->ShowWindow(SW_HIDE);

m_pWnd2->ShowWindow(SW_SHOW);

m_pWnd3->ShowWindow(SW_HIDE);

break;

case 2://3번 윈도만 화면에 나타내고 나머지는 감춘다.

m_pWnd1->ShowWindow(SW_HIDE);

m_pWnd2->ShowWindow(SW_HIDE);

m_pWnd3->ShowWindow(SW_SHOW);

break;

*pResult = 0;

}

이처럼 탭을 클릭할 때마다 화면에 출력되는 윈도를 다르게 할 수 있습니다.

(3) 탭 윈도 스타일 바꾸기

일반적인 탭은 상단부에 설정되어 있으나 필요에 따라서 하단부에 설정할 수도 있습니다. Visual C++의 Project workspace Window는 하단부에 탭을 설정한 것입니다. 탭 컨트롤의 탭을 하단부에 설정하고자 할 때는 스타일을  TCS_BOTTOM으로 설정하여 주면 됩니다. 다음은 탭 윈도 스타일을 하단부에 설정한 예입니다.

style |= TCS_BOTTOM;

SetWindowLong(m_tabCtrl.m_hWnd,GWL_STYLE,style);

슬라이더 컨트롤

슬라이더는 그림 1-5-20의 ① 항목에 설정되어 있는 바를 의미합니다. 슬라이더는 주로 레벨을 설정하는 데 많이 이용합니다. 볼륨 레벨을 올리거나 내릴 경우, 또는 윈도 크기를 줄이거나 늘릴 경우 등에 이용합니다. 슬라이더를 컨트롤하는 클래스는 CSliderCtrl입니다. 슬라이더를 대화 상자에서 설정하려면 아이콘을 Controls 도구바에서 선택하여 설정하면 됩니다. 슬라이더를 설정할 때 스타일에 따라서 여러 형태로 설정됩니다. 그림 1-5-21은 슬라이더의 속성 대화 상자에서 Style을 선택한 화면입니다.

그림 1-5-21의 ①∼⑥까지의 항목 중에서 ( )안에 있는 값들은 대화 상자에서 폼으로 설정하지 않고 Create 함수를 이용하여 동적으로 윈도를 만들 때의 플러그입니다. 각 항목을 체크한 것은 바로 Create 함수에서 해당 항목을 OR 연산으로 삽입한 것과 같습니다.

예를 들어서 m_sliderCtrl이라는 변수가 CSliderCtrl의 변수일 경우 그림 1-5-21의   ⓐ 항목에서 선택한 형태로 슬라이더를 만들려면 다음과 같이 합니다.

m_sliderCtrl.Create(TBS_HORZ|TBS_BOTH|TBS_AUTOTICKS|

WS_CHILD|WS_VISIBLE,CRect(0,0,100,100),this,ID_SLIDER);

위의 예에서 ID_SLIDER란 값을 임의로 정의한 것입니다. 여러분이 설정하고자 하는 여러 ID값을 설정하면 됩니다. 만일 위와 같이 ID_SLIDER를 설정하고자 한다면 리소스 자원에 정의되어 있거나 그렇지 않으면 헤더에 다음과 같이 적당한 값을 설정하여야 합니다.

#define ID_SLIDER 5000

(1) 슬라이더에서 구간 선택시 이벤트 얻기

슬라이더를 대화 상자 폼에 설정하고 이벤트 메시지를 ClassWizard를 이용해 확인해 보면 그림 1-5-22과 같이 세 개의 이벤트 메시지만 얻을 수 있습니다.

위의 세 개의 메시지는 NM_RELEASEDCAPTURE가 현재 특정 위치로 슬라이더를 이동시켰을 때 이동이 끝난 다음에 발생하는 메시지입니다. 이 메시지 외에 현재 슬라이더바가 이동된다든지, 특정 위치로의 이동이 불가능하게 된다든지, 좌측 마우스 버튼을 특정 위치에 클릭한다든지, 키를 누른다는지 하는 메시지들은 얻을 수 없습니다.

전 장과 본 장에서 많이 언급되고 있는 것이 상속입니다. 상속을 하면 윈도 내부를 컨트롤할 수 있기 때문에 외부에서 조종하는 것보다 매우 편리합니다. 따라서 슬라이더뿐만 아니라 모든 컨트롤에서 우리가 원하고자 하는 이벤트를 얻기 위해서는 CSliderCtrl을 상속받은 새로운 클래스를 만드는 것입니다. ClassWizard를 이용하여 새로운 클래스를 만들고 이 클래스가 CSliderCtrl를 상속받으면 모든 다양한 이벤트 메시지와 윈도 메시지들을 얻을 수가 있습니다.

NM_RELEASEDCAPTURE에 의해서 발생되는 이벤트 대신에 슬라이더에서 키와 마우스 버튼을 클릭했을 때 이벤트 메시지를 부모에 보내는 방법에 대해서 설정하겠습니다. 그림 1-5-23은 슬라이더에서 이벤트가 발생하였을 때 부모 윈도에 메시지를 보내는 루틴입니다.

그림 1-5-23의 ① 항목과 같이 CSliderCtrl을 상속받은 CMySliderCtrl 클래스를 만들고, ClassWizard를 이용하여 CMySliderCtrl 클래스에서 WM_KEYDOWN과 WM_LBUTTONDOWN 메시지가 발생되었을 때의 함수를 만들고, 이 함수에서 부모 윈도에 WM_USER+5 메시지를 발생시킵니다. 즉 슬라이더에서 키가 눌려졌거나 마우스 버튼이 클릭되었을 때 사용자 메시지를 만들어 부모 윈도에게 보내는 것입니다. 부모 윈도에서는 ⑤ 항목처럼 헤더부에 CMySldierCtrl 클래스를 상속받은 변수를 만든 다음 ⑥ 항목처럼 메시지 매크로 맵에 사용자 메시지가 발생되었을 때 수행되는 함수를 만듭니다. 그리고 이 함수에서 슬라이더 이벤트에 대응하는 내용을 기록합니다.

그림 1-5-23은 간단한 예로 슬라이더에서 키보드나 마우스 이벤트가 발생되었을 때 단순하게 현재의 위치를 얻은 것이 전부입니다. 이외의 많은 이벤트들과 그에 해당하는 루틴들은 여러분이 응용하여 작성하면 됩니다. 그림 1-5-23에서는 외부에서 슬라이더의 멤버 함수인 GetPos() 함수를 이용하여 현재 위치를 얻는 방법을 이용하였으나 내부에서 GetPos() 함수를 사용하고(그림 1-5-23의 ② 항목과 ③ 항목 안에서), WPARAM이나 LPARAM에 이 값을 설정할 수도 있습니다.

탭 컨트롤과 슬라이더 컨트롤 예제 ExTabCtrl

ExTabCtrl은 탭 컨트롤과 슬라이더 컨트롤을 이용하여 제작한 예제입니다. 그림 1-5-24는 ExTabCtrl의 프로그래밍 방법과 전체 구조입니다.

ExTabCtrl은 AppWizard를 이용하여 Dialog based 형태로 만들어져 있습니다. 이때 대화 상자의 폼은 그림 1-5-24의 ⓐ 항목처럼 설정합니다. 각 자원의 아이디는 그림 1-5-24의 각 항목에 기록하였습니다. 프로그램을 제작하는 순서는 다음과 같습니다.

(1) 각 멤버와 자원 ID의 연결

프로젝트를 Dialog based로 만든 다음 대화 상자 폼에 그림 1-5-24의 ⓐ 항목처럼 설정한 후에 각 항목을 DoDataExchange로 연결합니다. 먼저 탭 컨트롤 자원은 CTabCtrl의 형을 가진 m_tabCtrl로 연결합니다. 그림 1-5-24의 ① 항목은 탭 컨트롤을 연결하였을 때 헤더부와 DoDataExchange 안에 연결된 내용을 보여줍니다. 이렇게 연결하는 방법은 지금까지 계속 설명하였습니다. 같은 방법으로 슬라이더를 ② 항목처럼 하여 연결합니다. 슬라이더를 연결하기 전에 먼저 CSliderCtrl을 상속받은 CMySliderCtrl 클래스를 ClassWizard를 이용하여 만듭니다. 이렇게 만든 후에 ② 항목과 같이 연결하면 됩니다.

그림 1-5-24의 ③ 항목은 에디터 상자와 정수형 변수 m_nSliderNum과 연결된 형태입니다. DDX_Text는 문자열뿐만 아니라 정수형으로도 연결됩니다. Ctrl 키를 누른 상태에서 에디터 상자를 좌측 마우스 버튼으로 더블 클릭하면 Add Member Variable 대화 상자가 나타나는데, 이때 Value에서 Variable 항목을 보면 CString 외에 다양한 형들이 있습니다. 이 형에서 int를 설정하여 연결한 것입니다. 같은 방법으로 ④ 항목을 BOOL형인 m_bTabBottom으로 연결합니다. 이렇게 연결한 후에 슬라이더 컨트롤에서 부모 윈도에 보내는 WM_USER+5의 메시지에 의해서 수행되는 함수를 만듭니다. 슬라이더 컨트롤에서 메시지를 보내는 내용은 전 항목에서 설명하였습니다.(그림 1-5-23을 참조하기 바랍니다.)

슬라이더에서 보내는 메시지를 얻기 위해서 그림 1-5-24의 ⑥ 항목의 메시지처럼 함수를 선언하고 메시지 매핑 매크로에 ON_MESSAGE 매크로를 이용하여 WM_USER+5가 발생되었을 때 헤더에 선언한 함수 OnSliderChange가 수행되도록 합니다. 지금까지가 각 멤버와 자원을 연결하는 방법입니다.

(2) OnInitDialog 함수

대화 상자가 초기화되면 먼저 해야 할 일은 탭 컨트롤에 이미지 리스트를 설정하고 탭 컨트롤에 아이템을 설정하는 것입니다. 탭 컨트롤에 이미지 리스트를 설정하고 아이템을 설정하는 방법은 다음과 같습니다.

//이미지 리스트 설정

m_pImageList = new CImageList;

m_pImageList->Create(32,32,ILC_COLOR4,3,3);

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON1));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON2));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON3));

m_tabCtrl.SetImageList(m_pImageList);

//세 개의 탭 항목을 설정한다.

TC_ITEM item;

char *tabname[3]={“탭1”,”탭2”,”탭3”};

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

{

item.mask=TCIF_TEXT|TCIF_IMAGE;

item.pszText=tabname[i];

item.iImage=i;

m_tabCtrl.InsertItem(i,&item);

}

이렇게 하면 탭 컨트롤에 아이템이 설정됩니다. 위와 같이 한 후에 두 번째로 슬라이더의 구간을 정해야 합니다. 슬라이더의 구간을 정하고자 할 때는 CSliderCtrl의 멤버 함수인 SetRange 함수를 이용합니다. 다음은 슬라이더 구간을 20개로 설정한 형태입니다.

m_sliderCtrl.SetRange(1,20);

위와 같이 하면 20개의 구간을 나눌 수가 있습니다. 이렇게 한 후 슬라이더에 제일 처음으로 설정합니다.

m_sliderCtrl.SetPos(0);

위와 같이 하면 20개의 틱이 설정되고 슬라이더바는 처음에 0에 위치하게 됩니다.

(3) “하단부에 탭 버튼” 체크시

“하단부에 탭 버튼”의 체크 버튼을 클릭하였을 때 체크 버튼이 체크되면 탭 버튼을 하단부로 위치해야 합니다. 이 위치를 바꾸는 방법은 GetWindowLong과 SetWindowLong 함수를 이용하면 됩니다. 다음은 “하단부에 탭 버튼” 항목을 클릭했을 때의 함수입니다.

//”하단부에 탭 버튼 클릭시”

void CExTabCtrlDlg::OnCheck()

{

UpdateData(TRUE);

long style;

//윈도 스타일을 얻고

style=GetWindowLong(m_tabCtrl.m_hWnd,GWL_STYLE);

//체크가 아니면 TCS_BOTTOM 스타일 해제

style&=~(TCS_BOTTOM);

//체크이면 TCS_BOTTOM 스타일 설정

if(m_bTabBottom)

style |= TCS_BOTTOM;

//윈도 스타일 설정

SetWindowLong(m_tabCtrl.m_hWnd,GWL_STYLE,style);

}

(4) 탭 컨트롤 버튼이 클릭되었을 때

탭 컨트롤 버튼이 클릭되었을 때 수행되는 함수는 ClassWizard에서 TCN_ SELCHANGE 메시지에 의해서 만듭니다. 이렇게 만든 함수는 다음과 같습니다.

void CExTabCtrlDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)

{

//TODO: Add your control notification handler code here

*pResult = 0;

}

탭 컨트롤의 항목이 변경되면 선택된 항목에서만 윈도를 출력해야 합니다. 본 장 앞에서 설명한 “탭에 따라 항목 전환”을 참조하기 바랍니다. 이렇게 하기 위해서 먼저 현재 선택된 탭 번호를 멤버 변수에 받아둡니다.

//현재 선택한 탭 번호를 받아둔다.

m_nTabNum=m_tabCtrl.GetCurSel();

현재 선택한 탭 번호를 받을 때는 CSliderCtrl 클래스의 멤버 함수인 GetCurSel 함수를 사용하면 됩니다. 이렇게 받은 다음 이 번호에 따라서 윈도를 감추고 보여주는 함수를 호출합니다. 본 예제에서는 이 함수를 void SetWindow()로 만들었습니다. 이 함수 내용은 다음과 같습니다.

//현재 탭 번호에 따라서 윈도를 보이고 감춘다.

void CExTabCtrlDlg::SetWindow()

{

switch(m_nTabNum)

{

case 0:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);

break;

case 1:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

break;

case 2:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

break;

}

}

SetWindow 함수에서는 현재 출력된 슬라이더와 에디터 상자는 탭 컨트롤이 0번일 경우에만 나타나고 나머지 부분에서는 사라지게 하였습니다. 나머지 탭 컨트롤에서 여러 다양한 컨트롤들을 다음에 설정하기 위해서입니다.

이제 탭 컨트롤 선택이 바뀌었을 때의 함수 OnSelchangeTab1을 완성하겠습니다. 다음은 OnSelchangeTab1 함수입니다.

void CExTabCtrlDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)

{

//TODO: Add your control notification handler code here

//현재 선택한 탭 번호를 받아둔다.

m_nTabNum=m_tabCtrl.GetCurSel();

//탭 번호에 따라서 윈도 설정

SetWindow();

*pResult = 0;

}

(5) 슬라이더에서 WM_USER+5 메시지를 보낼 때

슬라이더에서 WM_USER+5 메시지를 보낼 때 이 메시지에 의해 수행되는 함수를 그림 1-5-24에서 보았듯이 OnSliderChange 함수로 만들었습니다. 이렇게 만든 함수에서는 현재 슬라이더 위치를 얻고 이 위치값을 에디터 상자에 출력합니다. 함수 내용은 다음과 같습니다.

//슬라이더에서 좌측 마우스 버튼 클릭이나 키보드를

//눌렀을 때

long CExTabCtrlDlg::OnSliderChange(WPARAM wParam,LPARAM lParam)

{

//슬라이더의 현재 위치를 얻는다.

m_nSliderNum=m_sliderCtrl.GetPos();

//대화 상자, 에디터 상자에 전송

UpdateData(FALSE);

return 1;

}

지금까지의 내용으로 탭 컨트롤과 슬라이더를 설정한 예제 ExTabCtrl의 1차 프로그램은 완성되었습니다. 이제 이 예제에 계속적으로 애니메이터 컨트롤, 프로그래스바, 스핀 등 다양한 컨트롤을 설정해 보겠습니다.

애니메이터 컨트롤

애니메이터 컨트롤은 윈도에서 제공하는 간단한 애니메이션들을 출력할 때 사용하는 컨트롤입니다. 애니메이터는 보통 “avi” 형태 중에서 간단하게 몇 장의 그림을 연속적으로 출력하는 형태의 파일을 지원하는 컨트롤입니다. 파일을 복사하거나 어떤 작업을 장시간에 걸쳐서 하는 경우 현재 작업중이라는 것을 알리기 위해서 이 애니메이터 컨트롤을 이용합니다. 윈도 탐색기에서 파일을 복사할 때 서류철에서 종이가 날아가는 모습 등을 애니메이터 컨트롤로 작성할 수 있습니다. 애니메이터를 수행하기 위한 컨트롤 클래스는 CAnimateCtrl입니다. 애니메이터 컨트롤을 이용하여 애니메이션을 출력하고자 할 때는 먼저 CAnimateCtrl의 멤버 함수를 설정하고 대화 상자 폼에서 애니메이터를 설정하거나 CAnimateCtrl의 Create 함수를 이용하여 윈도를 설정할 수 있습니다. 다음은 CAnimateCtrl의 Create 함수를 이용하여 윈도를 출력한 예입니다.

CAnimateCtrl m_Animate;

RECT rect={0,0,100,100};

m_Animate.Create(WS_CHILD|WS_VISIBLE|WS_BORDER|ACS_CENTER,

rect,this,500);

Create 함수를 이용하여 애니메이터를 출력하고자 할 때 ACS_ 계열의 애니메이터 스타일을 설정할 수 있습니다. ACS_CENTER는 항상 중앙에 애니메이션 윈도를 출력하는 형태이며, ACS_TRANSPARENT 옵션은 애니메이션이 윈도에 투명하게 설정되는 스타일이며, ACS_AUTOPLAY 옵션을 설정하면 애니메이션이 무한대로 계속 출력됩니다. 이 세 개의 ACS_ 계열의 옵션을 이용하여 애니메이터를 출력할 수 있습니다.

애니메이션을 설정한 후에 애니메이션 파일을 오픈해야 합니다. 이때는 CAnimateCtrl의 Open 함수를 이용합니다.  예를 들어서 “Serach.avi”라는 파일을 오픈하고자 할 때는 CAnimateCtrl의 Open 함수를 이용하여 다음과 같이 할 수가 있습니다.

m_Animate.Open(“Serach.avi”);

위와 같이 하면 애니메이터 컨트롤이 파일을 오픈하게 됩니다. 파일이 오픈되었다고 바로 애니메이션이 작동되는 것은 아닙니다. 만일 Create 함수에서 ACS_AUTO- PLAY 옵션이 설정되어 있다면 자동적으로 애니메이션이 동작되지만 그렇지 않을 경우에는 단지 현재 애니메이션의 첫 번째 그림만이 화면에 출력되고 멈춰 있게 됩니다. 이 상태에서 애니메이션을 동작하고자 한다면 Play 함수를 사용하면 됩니다. 다음은 Play 함수를 이용하여 애니메이션을 처음부터 끝까지 한 번 출력한 예입니다.

m_Animate.Play(0,-1,1);

첫 번째 인자는 어느 지점부터 출력할 것인가의 값입니다. 0으로 설정하였기 때문에 처음부터 출력됩니다. 두 번째 인자는 출력 마지막 포인터가 됩니다. -1로 설정하면 파일 끝까지의 애니메이션을 출력하게 됩니다. 세 번째 인자는 몇 번 반복하는가의 옵션입니다. 1이라고 하면 한 번만 출력됩니다. 만일 이 값이 -1일 경우에는 ACS_AUTO- PLAY 옵션을 설정한 것과 같이 무한대로 애니메이션이 출력됩니다.

애니메이션이 동작되다가 멈추게 하려면 Stop 함수를 사용하면 됩니다. 다음은 애니메이션의 현재 동작을 멈춘 예입니다.

m_Animate.Stop();

Stop 함수를 실행하면 현재 애니메이션이 동작중일 때는 애니메이션이 멈춰집니다. 모든 애니메이션을 종료하려면 Close 함수를 이용하여 애니메이션 파일을 닫아주어야 합니다. Close 함수를 사용하여 애니메이션을 종료한 예는 다음과 같습니다.

m_Animate.Close();

이제 애니메이터 컨트롤을 ExTabCtrl 예제에 설정해 보겠습니다. 전 항목에서 제작한 ExTabCtrl은 본 책과 함께 제공되는 CD의 ExTabCtrl 디렉토리에 설정되어 있습니다. 이 프로젝트에 애니메이터를 설정한 예는 ExAnimateCtrl 디렉토리에 따로 두었습니다.

ExAnimateCtrl 디렉토리에서 프로젝트를 Open하려면 ExAnimateCtrl이 아니라 ExTabCtr.dsw를 선택합니다. 이유는 전 항목에서 만든 ExTabCtrl에 애니메이션 컨트롤을 추가했기 때문입니다. 이 애니메이션 컨트롤은 ExTabCtrl에서 두 번째 탭 항목이 출력되었을 때 나타나게 됩니다. 그림 1-5-25는 탭 컨트롤에서 두 번째 탭 항목을 선택하였을 때 출력되는 애니메이터 컨트롤의 형태입니다.

그림 1-5-25의 ① 항목처럼 애니메이터 컨트롤은 대화 상자 폼에 출력하지 않고 Create 함수를 이용하여 출력하기 위해서 단지 출력할 영역을 상자로 설정하였고 이 아이디를 IDC_ANIMATEBOX로 설정하였습니다. 이 영역에 애니메이터를 출력하는 방법은 다음과 같습니다.

//IDC_ANIMATEBOX의 영역을 얻는다.

RECT rect;

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

//스크린 위치를 클라이언트 위치로 바꾼다.

ScreenToClient(&rect);

//애니메이터 윈도를 만든다.

m_Animate.Create(WS_CHILD|WS_VISIBLE|WS_BORDER|

ACS_CENTER,rect,this,500);

//파일을 오픈한다.

m_Animate.Open(“Search.avi”);

위와 같이 하면 IDC_ANIMATEBOX 영역에 애니메이터가 설정됩니다. 실제 애니메이터 컨트롤을 사용할 때는 대화 상자 폼을 이용하는 것보다 Create 함수를 많이 이용하여 동적으로 설정합니다. 그 이유는 대화 상자가 아닌 다른 곳에서도 애니메이터를 출력하는 경우가 많기 때문입니다. 예를 들어 View 윈도에서 장시간에 걸친 작업이 진행될 경우 애니메이션을 출력하면 현재 작업중임을 알리는 경우가 많습니다. 이럴 경우에는 대화 상자가 출력되는 것이 아니라 단순하게 View 윈도 위에 출력되는 경우도 있기 때문입니다.

애니메이터를 설정한 후에 윈도 스타일을 바꾸는 ②, ③, ④ 항목은 지금까지 계속적으로 설명한 GetWindowLong과 SetWindowLong을 이용합니다. ②, ③, ④ 항목을 클릭하였을 때 윈도 스타일이 바뀌는 함수는 다음과 같습니다.

//② 항목 버튼이 클릭되었을 경우

void CExTabCtrlDlg::OnAniCenter()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_CENTER;

if(m_bAcsCenter)

style |=ACS_CENTER;

AnimateChange(style);

}

//③ 항목 버튼이 클릭되었을 경우

void CExTabCtrlDlg::OnAniTransparent()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_TRANSPARENT;

if(m_AcsTransparent)

style |=ACS_TRANSPARENT;

AnimateChange(style);

}

//④ 항목 버튼이 클릭되었을 경우

void CExTabCtrlDlg::OnAniAutoplay()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_AUTOPLAY;

if(m_bAcsAutoPlay)

style |=ACS_AUTOPLAY;

AnimateChange(style);

}

위와 같이 하면 스타일이 변경되고 변경된 스타일값을 AnimateChange 함수에 넘겨줍니다. 이 함수에서는 현재 애니메이터를 Close하고 새로운 스타일로 다시 출력합니다. AnimateChange 함수는 다음과 같습니다.

void CExTabCtrlDlg::AnimateChange(long style)

{

//현재의 애니메이션을 닫는다.

m_Animate.Stop();

m_Animate.Close();

//새로운 스타일로 다시 애니메이션을 Open한다.

SetWindowLong(m_Animate.m_hWnd,GWL_STYLE,style);

CRect rect;

GetDlgItem(IDC_ANIMATEBOX)->GetWindowRect(rect);

ScreenToClient(rect);

m_Animate.SetWindowPos(NULL,0,0,rect.Width(),rect.Height(),

SWP_NOZORDER|

SWP_NOMOVE|SWP_NOACTIVATE|SWP_SHOWWINDOW);

m_Animate.Open(“Search.avi”);

}

다음은 애니메이터에서 Stop과 Play 버튼을 클릭하였을 때의 함수들입니다.

//⑤ 항목 버튼이 클릭되었을 때

void CExTabCtrlDlg::OnPlay()

{

m_Animate.Play(0,-1,1);

}

//⑥ 항목 버튼이 클릭되었을 때

void CExTabCtrlDlg::OnStop()

{

m_Animate.Stop();

}

지금까지 한 것으로 그림 1-5-25의 기능은 모두 마쳤습니다. 여기에서 전 항목에서 만든 SetWindow 함수를 기억해주기 바랍니다. 그림 1-5-25는 탭 컨트롤에서 두 번째 탭을 클릭했을 때 발생합니다. 즉 탭 버튼이 두 번째일 때(m_nTabNum이 1인 경우)에는 그림 1-5-25의 여섯 개의 윈도가 나타나고 나머지 항목에서는 감추어져야 합니다. m_nTabNum이 2일 경우만 나타나고 나머지를 감추게 수정한 SetWindow 함수는 다음과 같습니다.

//현재 탭 번호에 따라서 윈도를 보이고 감춘다.

void CExTabCtrlDlg::SetWindow()

{

switch(m_nTabNum)

{

case 0:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->

ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

break;

case 1:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_STOP)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_TRANSPARENT)->

ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_AUTOPLAY)->

ShowWindow(SW_SHOW);

m_Animate.ShowWindow(SW_SHOW);

break;

case 2:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->

ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

break;

}

}

프로그래스바와 스핀 컨트롤

프로그래스바는 현재 진행을 나타낼 때 사용하는 컨트롤입니다. 스핀 컨트롤은 보통 에디터 상자와 연결하여 숫자를 증가시키거나 감소시킬 때 사용합니다. 그림 1-5-26은 프로그래스바와 스핀 컨트롤의 형태입니다.

프로그래스바는 그림 1-5-26처럼 윈도 영역에 현재 진행된 위치에 따라 파랑색 상자가 칠해지는 형태입니다. 이 프로그래스바를 컨트롤하는 클래스는 CPrgressCtrl입니다.

스핀 컨트롤은 그림 1-5-26의 ② 항목과 같습니다. 스핀 컨트롤은 보통 그림 1-5-26의 ④ 항목과 같은 에디터 상자와 함께 사용합니다. 이때 스핀 컨트롤 대화 상자 속성에서 Auto budy를 체크하고 Set budy integer 항목을 클릭하면 에디터 상자와 연계된 스핀 버튼이 나타납니다. 이때 Visual C++ 메뉴의 Layout 항목에서 Tab Order 항목을 선택하면 에디터 상자 다음에 스핀 버튼이 순서적으로 설정되어 있어야 합니다. 이렇게 설정한 후에 ④ 항목의 에디터 상자를 정수형 변수와 DDX_Text로 연결하면 스핀 버튼을 클릭할 때마다 에디터 상자의 숫자가 증가되거나 감소됩니다. 스핀 버튼을 컨트롤하는 클래스는 CSpinButtonCtrl입니다. 이 클래스에서 SetRange 함수는 슬라이더 컨트롤과 같이 구간을 정해줍니다. 마찬가지로 CProgressCtrl에서도 SetRange라는 멤버 함수를 이용하여 구간을 정해줍니다.

다음은 스핀 버튼과 프로그래스바가 DDX_Control 함수를 이용하여 각각 m_spinCtrl과 m_progressCtrl로 설정되었을 때 구간을 설정한 예입니다.

//스핀 컨트롤 구간 설정

m_spinCtrl.SetRange(0,30);

//프로그래스바 구간 설정

m_progressCtrl.SetRange(0,30);

위와 같이 설정하면 스핀은 위아래로 움직일 때마다 0∼30까지의 숫자가 변화되며, 프로그래스바는 최대 진행 위치가 30이 됩니다. 프로그래스바에서 우리가 원하는 위치로 진행 상태를 만들고자 할 때는 SetPos 함수를 사용합니다. SetPos 함수를 사용하여 현재 진행을 5라고 설정하려면 다음과 같이 할 수 있습니다.

m_progressCtrl.SetPos(5);

이제 ExTabCtrl에 프로그래스바와 스핀 컨트롤을 설정해 보겠습니다. 본 예제는 제공되는 CD의 ExEtcCtrl에 설정되어 있습니다.

(1) OnInitDialog 함수 수정

ExTabCtrl 대화 상자에 스핀 컨트롤과 슬라이더를 그림 1-5-26과 같은 형태로 배치합니다. 지금까지의 대화 상자에 자원을 배치하면 ExTabCtrl의 대화 상자에는 복잡하게 여러 윈도들이 겹쳐 있을 것입니다. ExTabCtrl 예제에서 대화 상자를 한 번 열어 보기 바랍니다. 아마 지금까지 설정한 컨트롤들이 복잡하게 위아래로 겹쳐 있어서 대화 상자가 출력되기 이전에는 어떤 형태인지 잘 볼 수 없을 것입니다. 보통 탭 컨트롤을 사용하여 여러 자원을 두게 되면 이렇게 복잡합니다. 탭 컨트롤 프로그램을 자주 사용하다 보면 요령이 생겨서 해당 자원을 수정하는 방법을 알 수 있을 것입니다.(필자는 탭키를 이용하여 항목을 이동시키면서 해당 컨트롤을 수정합니다.)

그림 1-5-26과 같은 형태로 슬라이더와 스핀을 설정하고 ⓑ 항목과 같이 스핀 버튼의 속성을 설정합니다. 이 상태에서 자원 순서는 에디터 상자 다음에 스핀 버튼이 되어야 합니다. 이렇게 설정한 후에 DDX_Control과 DDX_Text를 이용하여 각 자원을 클래스 멤버에 연결합니다. 자원과 클래스 멤버로 연결할 때 헤더부의 내용과 DoDataExchange 내용은 다음과 같습니다.

//헤더부

CSpinButtonCtrl m_spinCtrl;//스핀 컨트롤과 연결된 클래스

CProgressCtrl m_progressCtrl;//프로그래스바와 연결된 클래스

UINT m_nspinNum;//에디터 상자와 연결된 정수형 변수

//DoDataExchange

DDX_Control(pDX, IDC_SPIN1, m_spinCtrl);

DDX_Control(pDX, IDC_PROGRESS1, m_progressCtrl);

DDX_Text(pDX, IDC_EDIT2, m_nspinNum);

위와 같이 연결한 후에 OnInitDialog 함수에 다음과 같은 부분을 추가합니다.

//스핀 컨트롤 구간 설정

m_spinCtrl.SetRange(0,30);

//프로그래스바 구간 설정

m_progressCtrl.SetRange(0,30);

m_progressCtrl.SetPos(0);

위와 같이 하면 스핀 컨트롤과 프로그래스바의 구간이 0부터 30으로 설정되고 프로그래스바의 처음 위치는 0이 됩니다. 이제 스핀 컨트롤의 기능은 모두 끝났습니다. 스핀에 의해서 변경된 값이 m_nspinNum에 설정되기 때문입니다. 특정 이벤트에서 m_nspinNum의 값을 얻고자 한다면 다음과 같이 하면 됩니다.

UpdateData(TRUE);

즉 단순하게 현재 대화 상자인 에디터 상자에 설정되어 있는 데이터를 멤버로 옮기는 함수를 호출하는 것입니다. 이렇게 호출하면 m_nspinNum에 값이 설정됩니다.

(2) 타이머 메시지를 이용하여 프로그래스바 위치 진행시키기

그림 1-5-26의 ⑤ 항목 버튼을 클릭하면 0.5초마다 프로그래스가 한 단위씩 증가되고 현재 증가되고 있는 상태이면 멈추는 내용을 ExTabCtrl에서 사용합니다. 이 방법은 먼저 현재 진행 위치를 가지고 있는 변수와 현재 프로그래스가 진행되고 있는가 아닌가를 설정하는 변수를 가지고 있어야 합니다. 헤더부에 다음과 같이 두 개의 변수를 설정합니다.

BOOL m_bpStart;//현재 프로그래스가 진행되는 유무값 저장

int m_nProgressPos;//프로그래스바 위치

위와 같이 한 다음 버튼이 눌려졌을 때의 함수를 만들고 다음과 같이 설정합니다.

//프로그래스바 진행 버튼

void CExTabCtrlDlg::OnPstart()

{

//현재 진행 상태이며 타이머를 해제하여 진행을 멈춘다.

if(m_bpStart)

{

m_bpStart=FALSE;

KillTimer(1);

}

//진행 상태가 아니면 진행 상태로 설정하고 타이머를 가동시킨다.

else

{

m_bpStart=TRUE;

SetTimer(1,500,NULL);

}

}

Start 버튼은 토글 형태입니다. 한 번 누르면 0.5초마다 한 단위씩 프로그래스바가 진행되고 다시 버튼을 누르면 현재 진행을 멈추게 합니다. 이때 OnPstart 함수에서는 다순하게 타이머를 설정하거나 해제하는 일만 합니다. 이렇게 하여 타이머를 설정하였을 때 WM_TIMER 메시지가 발생되고, 이 메시지에 의해 수행되는 함수 OnTimer에서 시간이 될 때마다 프로그래스바를 한 단위씩 증가시킵니다.

다음은 OnTimer 함수의 내용입니다.

void CExTabCtrlDlg::OnTimer(UINT nIDEvent)

{

//프로그래스바 진행을 증가시킨다.

m_nProgressPos++;

//최대값이면 다시 0으로

if(m_nProgressPos==30)

m_nProgressPos=0;

//프로그래스바 위치를 설정한다.

m_progressCtrl.SetPos(m_nProgressPos);

CDialog::OnTimer(nIDEvent);

}

이렇게 하여 타이머가 가동되면 프로그래스바가 한 칸씩 증가됩니다. 본 예제에서는 타이머를 이용하였으나 일반적인 애플리케이션에서는 현재의 진행 상태에 따라서 해당 내용을 SetPos 함수로 설정하여 주면 됩니다. 예를 들어서 파일을 읽을 경우 다음과 같이 할 수가 있습니다.

총파일의 크기 m_nTotalPos;

읽는 크기가 m_nReadBlock;

프로그래스바의 전체 구간은 range=m_nTotalPos/m_nReadBlock;

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

{

데이터를 읽고

m_progressCtrl.SetPos(i);

}

이런 방법으로 현재 진행 사항을 프로그래스바로 다양하게 표현할 수 있습니다.

(3) 세 번째 탭에서만 보이기

프로그래스바와 스핀 버튼에 대한 항목은 탭 컨트롤에서 세 번째 버튼을 클릭했을 때만 나타나게 합니다. 이럴 경우 전에 만들었던 SetWindow를 수정해야 합니다. 수정한 내용은 다음과 같습니다.

//현재 탭 번호에 따라서 윈도를 보이고 감춘다.

void CExTabCtrlDlg::SetWindow()

{

switch(m_nTabNum)

{

case 0:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_HIDE);

break;

case 1:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_STOP)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANIMATEBOX)->

ShowWindow(SW_SHOW);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_SHOW);

m_Animate.ShowWindow(SW_SHOW);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_HIDE);

break;

case 2:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_SHOW);

break;

}

}

ExTabCtrl

지금까지 설명한 내용으로 ExTabCtrl의 프로그램이 마무리되었습니다. ExTabCtrl에는 탭 컨트롤에 따라서 애니메이터, 슬라이더, 스핀, 프로그래스바 등의 다양한 컨트롤을 설정한 예제입니다. 소스를 분석하면서 다양한 컨트롤의 기능을 다시 한 번 확인해보기 바랍니다.

(1) CExTabCtrlDlg 전체 소스

//ExTabCtrlDlg.h : header file

//

#if !defined(AFX_EXTABCTRLDLG_H__252AC41A_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_EXTABCTRLDLG_H__252AC41A_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

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

//CExTabCtrlDlg dialog

#include “MySliderCtrl.h”

class CExTabCtrlDlg : public CDialog

{

//Construction

public:

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

//Dialog Data

//{{AFX_DATA(CExTabCtrlDlg)

enum { IDD = IDD_EXTABCTRL_DIALOG };

CSpinButtonCtrl m_spinCtrl;

CProgressCtrl m_progressCtrl;

CMySliderCtrl m_sliderCtrl;

CTabCtrl m_tabCtrl;

BOOL m_bTabBottom;

int m_nTabNum;

int m_nSliderNum;

BOOL m_bAcsCenter;

BOOL m_AcsTransparent;

BOOL m_bAcsAutoPlay;

CAnimateCtrl m_Animate;

UINT m_nspinNum;

BOOL m_bpStart;

int m_nProgressPos;

//}}AFX_DATA

CImageList *m_pImageList;

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExTabCtrlDlg)

protected:

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

//}}AFX_VIRTUAL

void SetWindow();

void AnimateChange(long style);

//Implementation

protected:

HICON m_hIcon;

//Generated message map functions

//{{AFX_MSG(CExTabCtrlDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnCheck();

afx_msg void OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void OnPlay();

afx_msg void OnStop();

afx_msg void OnAniAutoplay();

afx_msg void OnAniCenter();

afx_msg void OnAniTransparent();

afx_msg void OnPstart();

afx_msg void OnTimer(UINT nIDEvent);

//}}AFX_MSG

afx_msg long OnSliderChange(WPARAM wPara,LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_EXTABCTRLDLG_H__252AC41A_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//ExTabCtrlDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExTabCtrl.h”

#include “ExTabCtrlDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExTabCtrlDlg dialog

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

: CDialog(CExTabCtrlDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExTabCtrlDlg

m_bTabBottom = FALSE;

m_nTabNum=0;

m_nSliderNum = 0;

m_nspinNum=0;

m_bpStart=FALSE;

m_nProgressPos=0;

//}}AFX_DATA_INIT

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

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

}

void CExTabCtrlDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExTabCtrlDlg)

DDX_Control(pDX, IDC_SPIN1, m_spinCtrl);

DDX_Control(pDX, IDC_PROGRESS1, m_progressCtrl);

DDX_Control(pDX, IDC_SLIDER, m_sliderCtrl);

DDX_Control(pDX, IDC_TAB1, m_tabCtrl);

DDX_Check(pDX, IDC_CHECK, m_bTabBottom);

DDX_Text(pDX, IDC_EDIT1, m_nSliderNum);

DDX_Check(pDX, IDC_ANI_CENTER, m_bAcsCenter);

DDX_Check(pDX, IDC_ANI_TRANSPARENT, m_AcsTransparent);

DDX_Check(pDX, IDC_ANI_AUTOPLAY, m_bAcsAutoPlay);

DDX_Text(pDX, IDC_EDIT2, m_nspinNum);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExTabCtrlDlg, CDialog)

//{{AFX_MSG_MAP(CExTabCtrlDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_CHECK, OnCheck)

ON_NOTIFY(TCN_SELCHANGE, IDC_TAB1, OnSelchangeTab1)

ON_BN_CLICKED(IDC_PLAY, OnPlay)

ON_BN_CLICKED(IDC_STOP, OnStop)

ON_BN_CLICKED(IDC_ANI_AUTOPLAY, OnAniAutoplay)

ON_BN_CLICKED(IDC_ANI_CENTER, OnAniCenter)

ON_BN_CLICKED(IDC_ANI_TRANSPARENT, OnAniTransparent)

ON_BN_CLICKED(IDC_PSTART, OnPstart)

ON_WM_TIMER()

//}}AFX_MSG_MAP

ON_MESSAGE(WM_USER+5,OnSliderChange)

END_MESSAGE_MAP()

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

//CExTabCtrlDlg message handlers

BOOL CExTabCtrlDlg::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

//TODO: Add extra initialization here

//이미지 리스트 설정

m_pImageList = new CImageList;

m_pImageList->Create(32,32,ILC_COLOR4,3,3);

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON1));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON2));

m_pImageList->Add(AfxGetApp()->LoadIcon(IDI_ICON3));

m_tabCtrl.SetImageList(m_pImageList);

//세 개의 탭 항목을 설정한다.

TC_ITEM item;

char *tabname[3]={“탭1”,”탭2”,”탭3”};

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

{

item.mask=TCIF_TEXT|TCIF_IMAGE;

item.pszText=tabname[i];

item.iImage=i;

m_tabCtrl.InsertItem(i,&item);

}

m_sliderCtrl.SetRange(1,20);

m_sliderCtrl.SetPos(0);

//IDC_ANIMATEBOX의 영역을 얻는다.

RECT rect;

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

//스크린 위치를 클라이언트 위치로 바꾼다.

ScreenToClient(&rect);

//애니메이터 윈도를 만든다.

m_Animate.Create(WS_CHILD|WS_VISIBLE|WS_BORDER|ACS_CENTER,rect,this,500);

//파일을 오픈한다.

m_Animate.Open(“Search.avi”);

//스핀 컨트롤 구간 설정

m_spinCtrl.SetRange(0,30);

//프로그래스바 구간 설정

m_progressCtrl.SetRange(0,30);

m_progressCtrl.SetPos(0);

SetWindow();

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 CExTabCtrlDlg::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 CExTabCtrlDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

“하단부에 탭 버튼 클릭시”

void CExTabCtrlDlg::OnCheck()

{

UpdateData(TRUE);

long style;

//윈도 스타일을 얻고

style=GetWindowLong(m_tabCtrl.m_hWnd,GWL_STYLE);

//체크가 아니면 TCS_BOTTOM 스타일 해제

style&=~(TCS_BOTTOM);

//체크이면 TCS_BOTTOM 스타일 설정

if(m_bTabBottom)

style |= TCS_BOTTOM;

//윈도 스타일 설정

SetWindowLong(m_tabCtrl.m_hWnd,GWL_STYLE,style);

}

void CExTabCtrlDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)

{

//TODO: Add your control notification handler code here

//현재 선택한 탭 번호를 받아둔다.

m_nTabNum=m_tabCtrl.GetCurSel();

//탭 번호에 따라서 윈도 설정

SetWindow();

*pResult = 0;

}

//슬라이더에서 좌측 마우스 버튼 클릭이나 키보드를

//눌렀을 때

long CExTabCtrlDlg::OnSliderChange(WPARAM wParam,LPARAM lParam)

{

//슬라이더의 현재 위치를 얻는다.

m_nSliderNum=m_sliderCtrl.GetPos();

//대화 상자 에디터 상자에 전송

UpdateData(FALSE);

return 1;

}

//현재 탭 번호에 따라서 윈도를 보이고 감춘다.

void CExTabCtrlDlg::SetWindow()

{

switch(m_nTabNum)

{

case 0:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_HIDE);

break;

case 1:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_STOP)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_SHOW);

m_Animate.ShowWindow(SW_SHOW);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_HIDE);

break;

case 2:

GetDlgItem(IDC_SLIDER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);

//두 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PLAY)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STOP)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANIMATEBOX)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_STYLE)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_CENTER)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_TRANSPARENT)->ShowWindow(SW_HIDE);

GetDlgItem(IDC_ANI_AUTOPLAY)->ShowWindow(SW_HIDE);

m_Animate.ShowWindow(SW_HIDE);

//세 번째 탭에 해당하는 윈도

GetDlgItem(IDC_PROGRESS1)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_EDIT2)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_SPIN1)->ShowWindow(SW_SHOW);

GetDlgItem(IDC_PSTART)->ShowWindow(SW_SHOW);

break;

}

}

void CExTabCtrlDlg::OnPlay()

{

m_Animate.Play(0,-1,1);

}

void CExTabCtrlDlg::OnStop()

{

m_Animate.Stop();

}

void CExTabCtrlDlg::OnAniAutoplay()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_AUTOPLAY;

if(m_bAcsAutoPlay)

style |=ACS_AUTOPLAY;

AnimateChange(style);

}

void CExTabCtrlDlg::OnAniCenter()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_CENTER;

if(m_bAcsCenter)

style |=ACS_CENTER;

AnimateChange(style);

}

void CExTabCtrlDlg::OnAniTransparent()

{

UpdateData(TRUE);

long style;

style=GetWindowLong(m_Animate.m_hWnd,GWL_STYLE);

style &=~ACS_TRANSPARENT;

if(m_AcsTransparent)

style |=ACS_TRANSPARENT;

AnimateChange(style);

}

void CExTabCtrlDlg::AnimateChange(long style)

{

//현재 애니메이션을 닫는다.

m_Animate.Stop();

m_Animate.Close();

//새로운 스타일로 다시 애니메이션을 Open한다.

SetWindowLong(m_Animate.m_hWnd,GWL_STYLE,style);

CRect rect;

GetDlgItem(IDC_ANIMATEBOX)->GetWindowRect(rect);

ScreenToClient(rect);

m_Animate.SetWindowPos(NULL,0,0,rect.Width(),rect.Height(),

SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_SHOWWINDOW);

m_Animate.Open(“Search.avi”);

}

//프로그래스바 진행 버튼

void CExTabCtrlDlg::OnPstart()

{

if(m_bpStart)

{

m_bpStart=FALSE;

KillTimer(1);

}

else

{

m_bpStart=TRUE;

SetTimer(1,500,NULL);

}

}

void CExTabCtrlDlg::OnTimer(UINT nIDEvent)

{

//프로그래스바 진행을 증가시킨다.

m_nProgressPos++;

//최대값이면 다시 0으로

if(m_nProgressPos==30)

m_nProgressPos=0;

//프로그래스바 위치를 설정한다.

m_progressCtrl.SetPos(m_nProgressPos);

CDialog::OnTimer(nIDEvent);

}

(2) CMySliderCtrl 소스

#if !defined(AFX_MYSLIDERCTRL_H__252AC423_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_MYSLIDERCTRL_H__252AC423_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MySliderCtrl.h : header file

//

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

//CMySliderCtrl window

class CMySliderCtrl : public CSliderCtrl

{

//Construction

public:

CMySliderCtrl();

//Attributes

public:

//Operations

public:

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMySliderCtrl)

//}}AFX_VIRTUAL

//Implementation

public:

virtual ~CMySliderCtrl();

//Generated message map functions

protected:

//{{AFX_MSG(CMySliderCtrl)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

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

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYSLIDERCTRL_H__252AC423_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//MySliderCtrl.cpp : implementation file

//

#include “stdafx.h”

#include “ExTabCtrl.h”

#include “MySliderCtrl.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMySliderCtrl

CMySliderCtrl::CMySliderCtrl()

{

}

CMySliderCtrl::~CMySliderCtrl()

{

}

BEGIN_MESSAGE_MAP(CMySliderCtrl, CSliderCtrl)

//{{AFX_MSG_MAP(CMySliderCtrl)

ON_WM_LBUTTONDOWN()

ON_WM_KEYDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMySliderCtrl message handlers

void CMySliderCtrl::OnLButtonDown(UINT nFlags, CPoint point)

{

CSliderCtrl::OnLButtonDown(nFlags, point);

GetParent()->SendMessage(WM_USER+5,0,0);

}

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

{

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

GetParent()->SendMessage(WM_USER+5,0,0);

}

프로퍼티 시트는 두 가지 형태로 나타납니다. 위저드 형태와 카드철 형태입니다. 그림 1-5-27은 프로퍼티 시트의 두 가지 형태를 나타낸 것입니다.

그림 1-5-27의 ① 항목은 프로퍼티 시트에서 카드철 형태로 나타납니다. 카드철 형태란 그림 1-5-27의 ① 항목처럼 대화 상자 위에 탭이 있고 이 탭에 의해서 대화 상자 항목이 바뀌는 형태입니다. 그림 1-5-27은 여러분이 지금까지 사용해온 ClassWizard의 화면입니다. 즉 ClassWizard는 카드철 형태로 되어 있습니다.

그림 1-5-27의 ② 항목은 AppWizard입니다. 이것은 위저드 형태입니다. 즉 앞뒤로 버튼을 누르면 해당 대화 상자가 나타나고 이곳에 원하는 항목을 설정합니다. 카드철 형태는 보통 프로그램의 여러 옵션을 설정할 때 자주 사용하며, 위저드 형태는 프로그램을 설치하거나 어느 작업이 순서적으로 설정될 때 사용합니다. 다음의 각각의 항목은 이 두 개의 스타일로 프로그램을 만드는 방법에 대한 설명입니다.

프로퍼티 시트의 형태

프로퍼티 시트는 CPropertySheet 클래스와 CPropertyPage 클래스를 사용하는 형태입니다. 카드철 형태의 프로퍼티 시트를 보면 탭 컨트롤과 비슷하지만 실제로 내부는 카드철과 근본적으로 다른 형태를 가지고 있습니다.

프로퍼티 시트는 각 항목마다 다르게 설정된 대화 상자를 포함합니다. 이 대화 상자를 포함하는 CPropertyPage 클래스를 여러 개 가지고 있는 형태입니다. 그림 1-5-28은 프로퍼티 시트의 전체 구조입니다.

그림 1-5-28의 ⓐ와 같은 화면을 출력하기 위해서는 세 개의 대화 상자가 필요합니다. 이 세 개의 대화 상자가 ⓑ, ⓒ, ⓓ입니다. 이 세 개의 대화 상자 폼을 지금까지는 CDialog를 상속받은 클래스로 연결하였으나 이제는 CPropertyPage를 상속받은 클래스로 연결합니다. 그림 1-5-28의 ③, ④, ⑤ 항목이 CPropertyPage를 상속받은 CMyPage1∼CMyPage3까지 설정한 것입니다. 이렇게 클래스를 설정하고 각각의 클래스 변수를 ③, ④, ⑤ 항목처럼 m_pPage1, m_pPage2, m_pPage3으로 설정한 후에 이 세 개의 페이지를 CPropertySheet에 설정합니다. 이것은 그림 1-5-28의 ② 항목과 같습니다. CPropertySheet라는 클래스를 설정한 후에 이 클래스에 AddPage 함수를 이용하여 각각의 페이지들을 설정하면 그림 ⓐ와 같은 형태가 됩니다. 이렇게 설정한 후에 대화 상자 실행과 같이 DoModal() 함수를 이용하여 프로퍼티 시트를 실행하면 ⓐ와 같은 화면이 출력됩니다.

전체적으로 보면 CPropertySheet 클래스를 상속받은 변수는 단순하게 여러 개의 대화 상자를 포함하고 있습니다. 따라서 내부적으로 각 항목에 대한 여러 기능은 각각의 페이지에서 설정합니다. 각각의 페이지에 여러 컨트롤을 설정하고 여러 컨트롤에 의해서 발생되는 이벤트와 변수 처리는 각각의 페이지에서 합니다.

프로퍼티 시트 클래스, 즉 그림 1-5-28의 ② 항목에서 m_Property는 여러 개의 페이지를 가지고 이것을 카드철 형태 또는 위저드 형태로 출력하는 기능을 하게 됩니다. 다소 복잡한 형태인 것 같으나 프로퍼티 시트를 작성해 보면 의외로 간단하다는 것을 알 수 있습니다. 필요한 내용을 설정하는 대화 상자를 여러 개 만들었을 경우 이것을 실행시킬 때마다 지금까지의 대화 상자를 출력하는 방법을 이용한다면 그림 1-5-28의 경우 총 세 번의 DoModal() 함수가 실행될 것입니다. 이에 해당하는 내용들을 일일이 다시 기록해야 하는 복잡함이 있으나 프로퍼티 시트로 여러 개의 대화 상자를 묶으면 한 개의 DoModal() 함수를 이용하여 여러 대화 상자를 한꺼번에 출력할 수 있으므로 매우 단순해집니다.

카드철 형태의 예제 ExProperty

그림 1-5-28의 형태가 ExProperty 예제입니다. 이 예제를 작성해 가면서 프로퍼티 시트의 기능을 설명하겠습니다.

(1) 프로젝트 만들기

ExProperty는 AppWizard로 프로젝트를 만들 때 Dialog based 형태로 만듭니다. 이렇게 프로젝트를 만든 다음 클래스 위저드를 실행시켜 Add Class 항목을 선택하여 CPropertySheet를 상속받은 CMyProperty를 만듭니다. 그림 1-5-29는 CMyProperty 클래스를 만드는 화면입니다.

그림 1-5-29와 같이 CMyProperty를 만든 후에 대화 상자에서 대화 상자를 하나 만듭니다. 이렇게 만든 대화 상자는 그림 1-5-28의 ⓑ와 같은 형태입니다. 이렇게 대화 상자를 만들고 클래스 위저드를 실행시키면 대화 상자와 연결될 클래스를 새로 설정하도록 그림 1-5-29와 같은 화면이 또 나타납니다. 이때 Base class를 CPropertyPage로 설정하면 됩니다. 이때 클래스명을 CMyPage1으로 합니다. 이렇게 설정하면 하나의 프로퍼티 페이지가 만들어집니다. 이런 방법으로 그림 1-5-28의 ⓒ, ⓓ를 만들고 이것과 연결되는 프로퍼티 페이지를 각각 CMyPage2, CMyPage3로 만들면 됩니다. 이렇게 하면 프로퍼티 시트를 설정하는 기본 형태가 만들어진 것입니다.

(2) 애플리케이션 클래스에서 InitInstance 함수 수정

대화 상자 형태로 프로젝트를 만들면 애플리케이션 클래스의 InitInstance 함수에서는 대화 상자만 실행됩니다. 이 대화 상자가 실행되기 이전에 먼저 프로퍼티 시트를 실행하기 위해서 다음과 같이 설정합니다.

//프로퍼티 페이지1

CMyPage1 m_Page1;

//프로퍼티 페이지2

CMyPage2 m_Page2;

//프로퍼티 페이지3

CMyPage3 m_Page3;

//프로퍼티 시트 설정

CMyProperty m_Property(“노래설정”);

//프로퍼티 시트에서 페이지를 등록한다.

m_Property.AddPage(&m_Page1);

m_Property.AddPage(&m_Page2);

m_Property.AddPage(&m_Page3);

//대화 상자 실행

if(m_Property.DoModal()!=IDOK)

return FALSE;

위와 같이 하면 세 개의 프로퍼티 페이지를 가진 프로퍼티 시트가 설정됩니다. 프로퍼티 시트 클래스를 만들면 생성자가 일반 생성자와 다르게 두 개의 형태로 나타납니다. CMyProperty의 생성자는 다음과 같습니다.

CMyProperty(UINT nIDCaption, CWnd* pParentWnd = NULL,

UINT iSelectPage = 0);

CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd = NULL,

UINT iSelectPage = 0);

첫 번째 생성자는 프로퍼티 시트의 캡션바에 출력할 문자열입니다. 이때 nIDCaption은 출력할 문자열이 있는 String Table의 ID이며 pszCaption은 문자열입니다. 두 번째 인자는 부모 윈도 핸들러이며 세 번째 인자는 시작 페이지입니다. 일반적으로 첫 페이지가 시작 페이지기 때문에 미리 0으로 설정되어 있는 것입니다.

위의 예는 두 번째 생성자를 사용했습니다. 즉 문자열 테이블에서 문자를 가져온 것이 아니라 바로 문자열을 생성자에 설정한 것입니다. 이와 같이 하면 프로퍼티 시트가 실행됩니다.

(3) 프로퍼티 시트에 설정된 결과 얻기

ExProperty 예제의 첫 번째 항목에서 곡목 이름을 선택하고, 두 번째 항목에서는 음악의 출력 방식을, 세 번째 항목에서는 마이크와 스피커 출력의 볼륨을 설정합니다. 프로퍼티 시트의 실행이 종료되고 이 세 개의 대화 상자에 설정된 결과를 얻고자 할 경우에는 일반 대화 상자에서 사용한 방식과 같습니다. 예를 들어서 첫 번째 페이지에서 곡목을 선택하면 선택된 곡목 이름을 첫 번째 페이지 멤버로 설정된 CString형의 m_strSong에 설정하고 대화 상자가 종료되면 이 값을 얻으면 됩니다.

CString data;

data.Format(“선택한 곡목 %s”,m_Page1.m_strSong);

프로퍼티 시트가 동작한 후에 위와 같이 하면 첫 번째 페이지에 설정된 곡목이 바로 data에 설정됩니다. 대화 상자 실행이 종료되었다 하더라도 대화 상자 내부의 멤버 함수들에 들어 있는 데이터는 소멸되지 않습니다. 정적으로 생성하였을 경우에는 해당 루틴이 종료될 때까지 유효하며, 동적으로 설정되었을 경우에는 delete 함수를 사용하여 메모리에서 삭제하기 전까지는 유효합니다.

첫 번째 페이지에서는 선택한 곡목을 m_strSong에 저장하고, 두 번째 페이지에서는 각각 체크 버튼의 선택값을 m_bSongBack, m_bSongSpeedUp, m_bSongSpeed Down, m_bSongVoice에 저장하고, 그리고 세 번째 페이지에서는 마이크와 스피커 볼륨을 각각 m_nMike, m_nSpeek에 저장하였을 때 이 결과값을 얻어서 문자열 테이블에 설정하고자 한다면 다음과 같이 할 수가 있습니다.

CString data,songname;

songname=m_Page1.m_strSong;

data.Format(“노래곡목: %s\r\n배경음악으로:%d \

\r\n음성포함하여:%d \

\r\n빠른음악:%d\

\r\n조용한 음악:%d\

\r\n마이크:%d\

\r\n음성:%d”,

songname,m_Page2.m_bSongBack,m_Page2.m_bSongVoice,

m_Page2.m_bSongSpeedUp,m_Page2.m_bSongSpeedDown,

m_Page3.m_nMike,

m_Page3.m_nSpeak);

즉 각각의 페이지 대화 상자에서 설정된 결과값을 그대로 대화 상자 내부 멤버에 설정하고 이 값을 프로퍼티 시트가 종료된 다음 그 값을 얻을 수가 있습니다.

(4) 프로퍼티 시트의 “적용” 버튼 활성화

프로퍼티 시트를 카드철 클래스로 설정하였을 때 하단부에 “적용”이라는 버튼이 설정됩니다. 이 버튼은 데이터가 수정되었을 때 활성화됩니다. 이 버튼을 활성화하려면 프로퍼티 페이지 클래스 안에서 다음과 같은 함수를 호출하면 됩니다.

SetModified();

이 함수를 호출하면 적용 버튼이 활성화됩니다. 만일 적용 버튼을 클릭했을 때 이벤트를 얻고자 한다면 프로퍼티 페이지에서 OnApply 메시지를 선택하면 됩니다. 다음은 CMyPage2에서 OnApply 함수를 만든 예입니다.

//적용 버튼을 클릭했을 때

BOOL CMyPage3::OnApply()

{

//마이크의 위치와 스피커의 위치를

//m_nMike와 m_nSpeak에 설정한다.

m_nMike=m_sliderMike.GetPos();

m_nSpeak=m_sliderSpeak.GetPos();

return CPropertyPage::OnApply();

}

(5) 메인 대화 상자는 결과값 출력

ExProperty 예제에서는 프로퍼티 시트의 수행이 종료되고 난 후에 결과값을 메인 대화 상자에서 출력되게 설정하였습니다. 그림 1-5-30은 프로퍼티 시트에서 설정된 결과를 출력하는 메인 대화 상자의 형태입니다.

위와 같이 만드는 방법은 아주 간단합니다. 메인 대화 상자에 에디터 상자를 설정해놓고 새로운 생성자를 만든 다음 대화 상자가 생성될 때 프로퍼티 시트에 설정된 결과를 문자열로 넘겨 주면 메인 대화 상자는 이 문자열을 그대로 에디터 상자에 출력하면 됩니다. 지금까지 설명한 대화 상자와 컨트롤 부분을 이해했다면 아주 쉽게 만들 수 있을 것입니다. 프로퍼티 시트를 실행하고 이 결과를 메인 대화 상자에 출력하는 애플리케이션 클래스의 InitInstance 함수는 다음과 같습니다.

BOOL CExPropertyApp::InitInstance()

{

AfxEnableControlContainer();

//Standard initialization

//If you are not using these features and wish to reduce the size

// of your final executable, you should remove from the following

// the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls();

#else

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

#endif

//프로퍼티 페이지1

CMyPage1 m_Page1;

//프로퍼티 페이지2

CMyPage2 m_Page2;

//프로퍼티 페이지3

CMyPage3 m_Page3;

//프로퍼티 시트 설정

CMyProperty m_Property(“노래설정”);

//프로퍼티 시트에서 페이지를 등록한다.

m_Property.AddPage(&m_Page1);

m_Property.AddPage(&m_Page2);

m_Property.AddPage(&m_Page3);

//대화 상자 실행

if(m_Property.DoModal()!=IDOK)

return FALSE;

//프로퍼티 시트에서 설정된 데이터를

//data에 설정하고

CString data,songname;

songname=m_Page1.m_strSong;

data.Format(“노래곡목: %s\r\n배경음악으로:%d \

\r\n음성포함하여:%d \

\r\n빠른음악:%d\

\r\n조용한 음악:%d\

\r\n마이크:%d\

\r\n음성:%d”,

songname,m_Page2.m_bSongBack,m_Page2.m_bSongVoice,

m_Page2.m_bSongSpeedUp,m_Page2.m_bSongSpeedDown,

m_Page3.m_nMike,

m_Page3.m_nSpeak);

//이 데이터를 대화 상자에 넘겨주면서 대화 상자를

//생성한다.

CExPropertyDlg dlg(data);

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

//TODO: Place code here to handle when the dialog is

// dismissed with OK

}

else if (nResponse == IDCANCEL)

{

//TODO: Place code here to handle when the dialog is

// dismissed with Cancel

}

//Since the dialog has been closed, return FALSE so that we exit the

// application, rather than start the application’s message pump.

return FALSE;

}

ExProperty  소스

ExProperty의 각 페이지에서 수행되는 부분들은 소스를 참조하기 바랍니다. 지금까지 설명한 대화 상자 컨트롤의 내용들을 이해하였다면 소스를 보고 분석할 수 있을 것입니다.

(1) 애플리케이션 소스 부분

//ExProperty.cpp : Defines the class behaviors for the application.

//

#include “stdafx.h”

#include “ExProperty.h”

#include “ExPropertyDlg.h”

#include “MyProperty.h”

#include “MyPage1.h”

#include “MyPage2.h”

#include “MyPage3.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExPropertyApp

BEGIN_MESSAGE_MAP(CExPropertyApp, CWinApp)

//{{AFX_MSG_MAP(CExPropertyApp)

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

//   DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG

ON_COMMAND(ID_HELP, CWinApp::OnHelp)

END_MESSAGE_MAP()

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

//CExPropertyApp construction

CExPropertyApp::CExPropertyApp()

{

//TODO: add construction code here,

//Place all significant initialization in InitInstance

}

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

//The one and only CExPropertyApp object

CExPropertyApp theApp;

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

//CExPropertyApp initialization

BOOL CExPropertyApp::InitInstance()

{

AfxEnableControlContainer();

//Standard initialization

//If you are not using these features and wish to reduce the size

// of your final executable, you should remove from the following

// the specific initialization routines you do not need.

#ifdef _AFXDLL

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

#else

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

#endif

//프로퍼티 페이지1

CMyPage1 m_Page1;

//프로퍼티 페이지2

CMyPage2 m_Page2;

//프로퍼티 페이지3

CMyPage3 m_Page3;

//프로퍼티 시트 설정

CMyProperty m_Property(“노래설정”);

//프로퍼티 시트에서 페이지를 등록한다.

m_Property.AddPage(&m_Page1);

m_Property.AddPage(&m_Page2);

m_Property.AddPage(&m_Page3);

//대화 상자 실행

if(m_Property.DoModal()!=IDOK)

return FALSE;

//프로퍼티 시트에서 설정된 데이터를

//data에 설정하고

CString data,songname;

songname=m_Page1.m_strSong;

data.Format(“노래곡목: %s\r\n배경음악으로:%d \

\r\n음성포함하여:%d \

\r\n빠른음악:%d\

\r\n조용한 음악:%d\

\r\n마이크:%d\

\r\n음성:%d”,

songname,m_Page2.m_bSongBack,m_Page2.m_bSongVoice,

m_Page2.m_bSongSpeedUp,m_Page2.m_bSongSpeedDown,

m_Page3.m_nMike,

m_Page3.m_nSpeak);

//이 데이터를 대화 상자에 넘겨주면서 대화 상자를

//생성한다.

CExPropertyDlg dlg(data);

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

//TODO: Place code here to handle when the dialog is

// dismissed with OK

}

else if (nResponse == IDCANCEL)

{

//TODO: Place code here to handle when the dialog is

// dismissed with Cancel

}

//Since the dialog has been closed, return FALSE so that we exit the

// application, rather than start the application’s message pump.

return FALSE;

}

(2) 프로퍼티 시트 소스

#if !defined(AFX_MYPROPERTY_H__252AC413_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_MYPROPERTY_H__252AC413_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyProperty.h : header file

//

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

//CMyProperty

class CMyProperty : public CPropertySheet

{

DECLARE_DYNAMIC(CMyProperty)

//Construction

public:

CMyProperty(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

//Attributes

public:

//Operations

public:

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMyProperty)

//}}AFX_VIRTUAL

//Implementation

public:

virtual ~CMyProperty();

//Generated message map functions

protected:

//{{AFX_MSG(CMyProperty)

//NOTE - the ClassWizard will add and remove member functions here.

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPROPERTY_H__252AC413_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//MyProperty.cpp : implementation file

//

#include “stdafx.h”

#include “ExProperty.h”

#include “MyProperty.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyProperty

IMPLEMENT_DYNAMIC(CMyProperty, CPropertySheet)

CMyProperty::CMyProperty(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)

{

}

CMyProperty::CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(pszCaption, pParentWnd, iSelectPage)

{

}

CMyProperty::~CMyProperty()

{

}

BEGIN_MESSAGE_MAP(CMyProperty, CPropertySheet)

//{{AFX_MSG_MAP(CMyProperty)

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

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyProperty message handlers

(3) CMyPage1 소스

#if !defined(AFX_MYPAGE1_H__252AC410_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_MYPAGE1_H__252AC410_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage1.h : header file

//

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

//CMyPage1 dialog

class CMyPage1 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage1)

//Construction

public:

CMyPage1();

~CMyPage1();

//Dialog Data

//{{AFX_DATA(CMyPage1)

enum { IDD = IDD_PAGE1 };

CListBox m_listBox;

CString m_strSong;//선택된 곡목 저장 변수

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage1)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage1)

virtual BOOL OnInitDialog();

afx_msg void OnSelchangeList1();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE1_H__252AC410_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//MyPage1.cpp : implementation file

//

#include “stdafx.h”

#include “ExProperty.h”

#include “MyPage1.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage1 property page

IMPLEMENT_DYNCREATE(CMyPage1, CPropertyPage)

CMyPage1::CMyPage1() : CPropertyPage(CMyPage1::IDD)

{

//{{AFX_DATA_INIT(CMyPage1)

//NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

}

CMyPage1::~CMyPage1()

{

}

void CMyPage1::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage1)

DDX_Control(pDX, IDC_LIST1, m_listBox);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage1, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage1)

ON_LBN_SELCHANGE(IDC_LIST1, OnSelchangeList1)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage1 message handlers

BOOL CMyPage1::OnInitDialog()

{

CPropertyPage::OnInitDialog();

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

char *song[10]={

“세일러문”,

“하여가”,

“마징가 제트”,

“맨발의 청춘”,

“드래곤볼”,

“두만강”,

“님과함께”,

“사랑의 뜻으로”,

“청춘을 돌려다오”,

“맥칸더브이”};

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

m_listBox.AddString(song[i]);

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

//EXCEPTION: OCX Property Pages should return FALSE

}

//새로운 항목이 선택되면

void CMyPage1::OnSelchangeList1()

{

//”적용” 활성화

SetModified();

//현재 선택된 항목을 m_strSong에 저장

int num;

num=m_listBox.GetCurSel();

m_listBox.GetText(num,m_strSong);

}

(4) CMyPage2 소스

#if !defined(AFX_MYPAGE2_H__252AC411_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_MYPAGE2_H__252AC411_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage2.h : header file

//

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

//CMyPage2 dialog

class CMyPage2 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage2)

//Construction

public:

CMyPage2();

~CMyPage2();

//Dialog Data

//{{AFX_DATA(CMyPage2)

enum { IDD = IDD_PAGE2 };

//각 체크 버튼과 연결된 변수

BOOL m_bSongBack;

BOOL m_bSongVoice;

BOOL m_bSongSpeedUp;

BOOL m_bSongSpeedDown;

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage2)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage2)

//NOTE: the ClassWizard will add member functions here

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE2_H__252AC411_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//MyPage2.cpp : implementation file

//

#include “stdafx.h”

#include “ExProperty.h”

#include “MyPage2.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage2 property page

IMPLEMENT_DYNCREATE(CMyPage2, CPropertyPage)

CMyPage2::CMyPage2() : CPropertyPage(CMyPage2::IDD)

{

//{{AFX_DATA_INIT(CMyPage2)

m_bSongBack = TRUE;

m_bSongVoice = FALSE;

m_bSongSpeedUp = FALSE;

m_bSongSpeedDown = FALSE;

//}}AFX_DATA_INIT

}

CMyPage2::~CMyPage2()

{

}

void CMyPage2::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage2)

DDX_Check(pDX, IDC_CHECK1, m_bSongBack);

DDX_Check(pDX, IDC_CHECK2, m_bSongVoice);

DDX_Check(pDX, IDC_CHECK3, m_bSongSpeedUp);

DDX_Check(pDX, IDC_CHECK4, m_bSongSpeedDown);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage2, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage2)

//NOTE: the ClassWizard will add message map macros here

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage2 message handlers

(5) CMyPage3 소스

#if !defined(AFX_MYPAGE3_H__252AC412_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_MYPAGE3_H__252AC412_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage3.h : header file

//

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

//CMyPage3 dialog

class CMyPage3 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage3)

//Construction

public:

CMyPage3();

~CMyPage3();

//Dialog Data

//{{AFX_DATA(CMyPage3)

enum { IDD = IDD_PAGE3 };

CSliderCtrl m_sliderSpeak;

CSliderCtrl m_sliderMike;

int m_nSpeak;

int m_nMike;

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage3)

public:

virtual BOOL OnApply();

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage3)

virtual BOOL OnInitDialog();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE3_H__252AC412_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//MyPage3.cpp : implementation file

//

#include “stdafx.h”

#include “ExProperty.h”

#include “MyPage3.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage3 property page

IMPLEMENT_DYNCREATE(CMyPage3, CPropertyPage)

CMyPage3::CMyPage3() : CPropertyPage(CMyPage3::IDD)

{

//{{AFX_DATA_INIT(CMyPage3)

//NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

}

CMyPage3::~CMyPage3()

{

}

void CMyPage3::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage3)

DDX_Control(pDX, IDC_SLIDER2, m_sliderSpeak);

DDX_Control(pDX, IDC_SLIDER1, m_sliderMike);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage3, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage3)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage3 message handlers

BOOL CMyPage3::OnInitDialog()

{

CPropertyPage::OnInitDialog();

m_nSpeak=5;

m_nMike=5;

m_sliderSpeak.SetRange(0,10);

m_sliderSpeak.SetPos(m_nSpeak);

m_sliderMike.SetRange(0,10);

m_sliderMike.SetPos(m_nMike);

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

//EXCEPTION: OCX Property Pages should return FALSE

}

//적용 버튼을 클릭했을 때

BOOL CMyPage3::OnApply()

{

//마이크의 위치와 스피커의 위치를

//m_nMike와 m_nSpeak에 설정한다.

m_nMike=m_sliderMike.GetPos();

m_nSpeak=m_sliderSpeak.GetPos();

return CPropertyPage::OnApply();

}

(6) 메인 대화 상자 소스

//ExPropertyDlg.h : header file

//

#if

!defined(AFX_EXPROPERTYDLG_H__252AC408_289A_11D2_915E_006097AD7A5B__INCLUDED_)

#define AFX_EXPROPERTYDLG_H__252AC408_289A_11D2_915E_006097AD7A5B__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

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

//CExPropertyDlg dialog

class CExPropertyDlg : public CDialog

{

//Construction

public:

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

CExPropertyDlg(CString data);

CString m_strData;

//Dialog Data

//{{AFX_DATA(CExPropertyDlg)

enum { IDD = IDD_EXPROPERTY_DIALOG };

//NOTE: the ClassWizard will add data members here

//}}AFX_DATA

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExPropertyDlg)

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

HICON m_hIcon;

//Generated message map functions

//{{AFX_MSG(CExPropertyDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_EXPROPERTYDLG_H__252AC408_289A_11D2_915E_006097AD7A5B__INCLUDED_)

//ExPropertyDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExProperty.h”

#include “ExPropertyDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExPropertyDlg dialog

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

: CDialog(CExPropertyDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExPropertyDlg)

//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);

}

CExPropertyDlg::CExPropertyDlg(CString data)

: CDialog(CExPropertyDlg::IDD, NULL)

{

m_strData=data;

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

}

void CExPropertyDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExPropertyDlg)

//NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExPropertyDlg, CDialog)

//{{AFX_MSG_MAP(CExPropertyDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CExPropertyDlg message handlers

BOOL CExPropertyDlg::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

//TODO: Add extra initialization here

GetDlgItem(IDC_EDIT1)->SetWindowText(m_strData);

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 CExPropertyDlg::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 CExPropertyDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

위저드 형태의 프로퍼티 시트

전 항목에서는 카드철 형태의 프로퍼티 시트를 만드는 방법을 설명하였습니다. 위저드 형태의 프로퍼티 시트를 만드는 방법은 처음에 대화 상자를 실행하기 이전에 SetWizardMode 함수를 실행하면 위저드 형태의 프로퍼티 시트가 실행됩니다. 다음은 위저드 형태의 프로퍼티 시트를 만드는 형태입니다.

CMyProperty pdlg(“위저드 예제”);

pdlg.SetWizardMode(); //위저드 형태로 설정한다.

pdlg.DoModal();

이 외의 나머지 부분은 카드철 형태와 비슷하나 몇 가지 추가할 사항은 버튼 설정에 대한 항목입니다. 위저드 형태는 카드철 형태와 다르게 “뒤로”, “다음”, “마침”의 세 가지 버튼이 있습니다. 위저드의 처음 항목에서는 “뒤로” 버튼이 비활성화되어야 합니다. 또한 마지막에서는 “다음” 버튼은 감춰지고 “마침” 버튼이 설정되어야 합니다. 이렇게 버튼을 설정하는 함수는 SetWizardButtons 버튼입니다. 이 함수는 프로퍼티 시트의 멤버 함수로서 다음과 같습니다.

void CPropertySheet::SetWizardButtons( DWORD dwFlags );

dwFalgs는 다음과 같습니다.

PSWIZB_BACK “뒤로” 버튼 설정

PSWIZB_NEXT “다음” 버튼 설정

PSWIZB_FINISH “마침” 버튼 설정

PSWIZB_DISABLEDFINISH “마침” 버튼 비활성화 설정

예를 들어서 “뒤로”와 “다음” 버튼을 설정하고자 한다면 SetWizardButtons 함수를 이용하여 다음과 같이 할 수 있습니다.

SetwizardButtons(PSWIZB_BACK|PSWIZB_NEXT);

위와 같이 하면 “뒤로”와 “다음” 버튼이 프로퍼티 시트에 설정됩니다.

위저드 형태의 프로퍼티 시트 예제 ExWizard

ExWizard는 위저드 형태의 프로퍼티 시트 예제입니다. 이 예제의 전체 구성도는 그림 1-5-31과 같습니다.

그림 1-5-31의 ① 항목처럼 처음에는 문자열이 에디터 상자에 나오고 “동의함“과 “동의하지 않음”의 두 개의 라디오 버튼을 설정합니다. 이 두 개의 라디오 버튼 중 “동의함” 버튼을 클릭하면 “다음” 버튼이 활성화됩니다. “다음” 버튼을 활성화하는 항목은 그림 1-5-31의 ④입니다.

SetWizardButtons 함수를 수행하고자 할 때 부모 윈도 핸들을 받고 설정하는 이유는 SetWizardButtons가 CPropertySheet의 멤버 함수이기 때문입니다. “동의함“ 라디오 버튼을 클릭하면 위저드 두 번째 대화 상자가 나타납니다. 이 대화 상자는 세 개의 에디터 상자에 시리얼 번호를 입력하는 항목입니다. 이 항목에서 에디터 상자에 숫자를 입력했을 때 여덟 자리가 넘을 경우에는 다음 에디터 상자로 이동됩니다. 이 방법은 에디터 상자에서 데이터를 수정할 때, 즉 EN_CHANGE 메시지에 의한 함수를 만들고 이 함수에서 입력된 문자열을 센 다음 여덟 글자이면 다음 에디터 상자에 포커스를 넘겨주는 방식으로 합니다. 이 항목이 ⑨, ⑩ 항목입니다. 마지막 세 번째 입력란에서 여덟 글자를 다 입력하면 “다음” 버튼을 활성화시킵니다. 이 항목이 그림 1-5-31의 ⓐ 항목입니다. “다음” 버튼을 클릭하면 그림 1-5-31의 ⓒ 항목이 나타납니다. 이 항목에서 컴퓨터 이름과 사용자명을 입력하면 “마침” 항목이 활성화됩니다. 이 항목이 ⓔ 항목입니다. 이렇게 하여 위저드에서 입력된 데이터를 그대로 대화 상자에 출력합니다.

본 예제에서는 AddPage 함수를 프로퍼티 예제와 달리 프로퍼티 시트의 내부에 두었습니다. 먼저 CPropertySheet 클래스를 상속받은 CMyProperty의 헤더에 다음과 같이 세 개의 페이지 멤버를 설정합니다.

//프로퍼티 페이지

CMyPage1 cPage1;

CMyPage2 cPage2;

CMyPage3 cPage3;

위와 같이 헤더에 페이지를 설정한 후에 프로퍼티 시트 생성자에서 AddPage 함수를 이용하여 위의 세 개의 페이지를 설정하였습니다.

CMyProperty::CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(pszCaption, pParentWnd, iSelectPage)

{

AddPage(&cPage1);

AddPage(&cPage2);

AddPage(&cPage3);

}

페이지 클래스를 밖에 두고 밖에서 설정하는 방법과 클래스 내부에 두고 생성자에서 설정하는 방법 둘 다 장단점이 있습니다. 어느 방법이 좋다고 말할 수는 없으므로 프로그램 내용에 따라 알맞게 설정해 주어야 합니다. 위와 같이 설정한 후 각 페이지에 설정된 값을 문자열로 얻기 위해서 프로퍼티 시트에 ShowData 함수를 두었습니다. 이 함수는 다음과 같습니다.

CString CMyProperty::ShowData()

{

CString data;

data.Format(“다음과 같이 입력하였습니다. \r\n\r\n\

\r\n 시리얼 번호 %s-%s-%s\r\n 컴퓨터 이름 %s\r\n 사용자 이름 %s\r\n”,

Page2.m_strSer1, cPage2.m_strSer2, cPage2.m_strSer3,

cPage3.m_strName, cPage3.m_strUser); return data;

}

위와 같이 하면 애플리케이션 클래스의 InitInstance 함수에서는 다음과 같이 호출할 수 있습니다.

//프로퍼티 시트 생성

CMyProperty pdlg(“위저드 예제”);

//위저드 형태 실행

pdlg.SetWizardMode();

pdlg.DoModal();

//ShowData 함수를 이용하여 입력된 결과를 data로 받음

data=pdlg.ShowData();

//메인 대화 상자 실행

CExWizardDlg dlg(data);

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

나머지 내용은 소스를 분석해 보면 이해가 될 것입니다. 각 소스마다 그림 1-5-31의 해당 항목에 대한 번호를 설정하였습니다. 그림 1-5-31을 참고하여 분석하기 바랍니다.

(1) 애플리케이션 소스부

//ExWizard.cpp : Defines the class behaviors for the application.

//

#include “stdafx.h”

#include “ExWizard.h”

#include “ExWizardDlg.h”

#include “MyProperty.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CExWizardApp

BEGIN_MESSAGE_MAP(CExWizardApp, CWinApp)

//{{AFX_MSG_MAP(CExWizardApp)

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

//   DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG

ON_COMMAND(ID_HELP, CWinApp::OnHelp)

END_MESSAGE_MAP()

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

//CExWizardApp construction

CExWizardApp::CExWizardApp()

{

//TODO: add construction code here,

//Place all significant initialization in InitInstance

}

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

//The one and only CExWizardApp object

CExWizardApp theApp;

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

//CExWizardApp initialization

BOOL CExWizardApp::InitInstance()

{

AfxEnableControlContainer();

//Standard initialization

//If you are not using these features and wish to reduce the size

// of your final executable, you should remove from the following

// the specific initialization routines you do not need.

#ifdef _AFXDLL

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

#else

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

#endif

CString data;

//프로퍼티 시트 생성

CMyProperty pdlg(“위저드 예제”);

//위저드 형태 실행

pdlg.SetWizardMode();

pdlg.DoModal();

//ShowData 함수를 이용하여 입력된 결과를 data로 받음

data=pdlg.ShowData();

//메인 대화 상자 실행

CExWizardDlg dlg(data);

m_pMainWnd = &dlg;

int nResponse = dlg.DoModal();

if (nResponse == IDOK)

{

//TODO: Place code here to handle when the dialog is

// dismissed with OK

}

else if (nResponse == IDCANCEL)

{

//TODO: Place code here to handle when the dialog is

// dismissed with Cancel

}

return FALSE;

}

(2) CMyProperty 클래스 소스

#if !defined(AFX_MYPROPERTY_H__7C2B6E1F_284A_11D2_8F71_006097283F10__INCLUDED_)

#define AFX_MYPROPERTY_H__7C2B6E1F_284A_11D2_8F71_006097283F10__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyProperty.h : header file

//

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

//CMyProperty

#include “MyPage1.h”

#include “MyPage2.h”

#include “MyPage3.h”

class CMyProperty : public CPropertySheet

{

DECLARE_DYNAMIC(CMyProperty)

//Construction

public:

CMyProperty(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);

//Attributes

public:

//프로퍼티 페이지

CMyPage1 cPage1;

CMyPage2 cPage2;

CMyPage3 cPage3;

//Operations

public:

//Overrides

//ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMyProperty)

//}}AFX_VIRTUAL

//Implementation

public:

CString ShowData();

virtual ~CMyProperty();

//Generated message map functions

protected:

//{{AFX_MSG(CMyProperty)

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPROPERTY_H__7C2B6E1F_284A_11D2_8F71_006097283F10__INCLUDED_)

//MyProperty.cpp : implementation file

//

#include “stdafx.h”

#include “ExWizard.h”

#include “MyProperty.h”

#include “ExWizardDlg.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyProperty

IMPLEMENT_DYNAMIC(CMyProperty, CPropertySheet)

CMyProperty::CMyProperty(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)

AddPage(&cPage1);

AddPage(&cPage2);

AddPage(&cPage3);

}

CMyProperty::CMyProperty(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(pszCaption, pParentWnd, iSelectPage)

{

AddPage(&cPage1);

AddPage(&cPage2);

AddPage(&cPage3);

}

CMyProperty::~CMyProperty()

{

}

BEGIN_MESSAGE_MAP(CMyProperty, CPropertySheet)

//{{AFX_MSG_MAP(CMyProperty)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyProperty message handlers

//그림 1-5-32의 ⓗ

CString CMyProperty::ShowData()

{

CString data;

data.Format(“다음과 같이 입력하였습니다.\r\n\r\n \

\r\n 시리얼 번호 %s-%s-%s\r\n 컴퓨터 이름 %s\r\n 사용자 이름 %s\r\n”,

cPage2.m_strSer1, cPage2.m_strSer2, cPage2.m_strSer3,

cPage3.m_strName, cPage3.m_strUser);

return data;

}

(3) CMypage1 소스

#if !defined(AFX_MYPAGE1_H__7C2B6E23_284A_11D2_8F71_006097283F10__INCLUDED_)

#define AFX_MYPAGE1_H__7C2B6E23_284A_11D2_8F71_006097283F10__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage1.h : header file

//

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

//CMyPage1 dialog

class CMyPage1 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage1)

//Construction

public:

CMyPage1();

~CMyPage1();

//Dialog Data

//{{AFX_DATA(CMyPage1)

enum { IDD = IDD_PAGE1 };

//NOTE - ClassWizard will add data members here.

//   DO NOT EDIT what you see in these blocks of generated code !

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage1)

public:

virtual LRESULT OnWizardNext();

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage1)

virtual BOOL OnInitDialog();

afx_msg void OnRadioargree();

afx_msg void OnRadiodont();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE1_H__7C2B6E23_284A_11D2_8F71_006097283F10__INCLUDED_)

//MyPage1.cpp : implementation file

//

#include “stdafx.h”

#include “ExWizard.h”

#include “MyPage1.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage1 property page

IMPLEMENT_DYNCREATE(CMyPage1, CPropertyPage)

CMyPage1::CMyPage1() : CPropertyPage(CMyPage1::IDD)

{

//}}AFX_DATA_INIT(CMyPage1)

//NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

}

CMyPage1::~CMyPage1()

{

}

void CMyPage1::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage1)

//NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage1, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage1)

ON_BN_CLICKED(IDC_RADIOARGREE, OnRadioargree)

ON_BN_CLICKED(IDC_RADIODONT, OnRadiodont)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage1 message handlers

BOOL CMyPage1::OnInitDialog()

{

CPropertyPage::OnInitDialog();

//그림 1-5-32의 ⑥ 항목 버튼 초기화

((CPropertySheet *)GetParent())->SetWizardButtons(0);

CString data;

//String Table에서 IDS_PAGE1을 로드하여

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

data.LoadString(IDS_PAGE1);

GetDlgItem(IDC_EDIT)->SetWindowText(data);

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

//EXCEPTION: OCX Property Pages should return FALSE

}

LRESULT CMyPage1::OnWizardNext()

{

//그림 1-5-32의 ⑤ 항목

((CPropertySheet *)GetParent())->

SetWizardButtons(PSWIZB_NEXT|PSWIZB_BACK);

return CPropertyPage::OnWizardNext();

}

void CMyPage1::OnRadioargree()

{

//그림 1-5-32의 ④ 항목

((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_NEXT);

}

void CMyPage1::OnRadiodont()

{

//그림 1-5-32의 ③ 항목

((CPropertySheet *)GetParent())->SetWizardButtons(0);

}

(4) CMyPage2 소스

#if !defined(AFX_MYPAGE2_H__7C2B6E24_284A_11D2_8F71_006097283F10__INCLUDED_)

#define AFX_MYPAGE2_H__7C2B6E24_284A_11D2_8F71_006097283F10__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage2.h : header file

//

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

//CMyPage2 dialog

class CMyPage2 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage2)

//Construction

public:

CMyPage2();

~CMyPage2();

//Dialog Data

//{{AFX_DATA(CMyPage2)

enum { IDD = IDD_PAGE2 };

CString m_strSer1;

CString m_strSer2;

CString m_strSer3;

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage2)

public:

virtual LRESULT OnWizardBack();

virtual LRESULT OnWizardNext();

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage2)

virtual BOOL OnInitDialog();

afx_msg void OnChangeEdit1();

afx_msg void OnChangeEdit2();

afx_msg void OnChangeEdit3();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE2_H__7C2B6E24_284A_11D2_8F71_006097283F10__INCLUDED_)

//MyPage2.cpp : implementation file

//

#include “stdafx.h”

#include “ExWizard.h”

#include “MyPage2.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage2 property page

IMPLEMENT_DYNCREATE(CMyPage2, CPropertyPage)

CMyPage2::CMyPage2() : CPropertyPage(CMyPage2::IDD)

{

//{{AFX_DATA_INIT(CMyPage2)

m_strSer1 = _T(“”);

m_strSer2 = _T(“”);

m_strSer3 = _T(“”);

//}}AFX_DATA_INIT

}

CMyPage2::~CMyPage2()

{

}

void CMyPage2::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage2)

DDX_Text(pDX, IDC_EDIT1, m_strSer1);

DDX_Text(pDX, IDC_EDIT2, m_strSer2);

DDX_Text(pDX, IDC_EDIT3, m_strSer3);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage2, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage2)

ON_EN_CHANGE(IDC_EDIT1, OnChangeEdit1)

ON_EN_CHANGE(IDC_EDIT2, OnChangeEdit2)

ON_EN_CHANGE(IDC_EDIT3, OnChangeEdit3)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage2 message handlers

BOOL CMyPage2::OnInitDialog()

{

CPropertyPage::OnInitDialog();

//그림 1-5-31의 ⓑ 항목

((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_BACK);

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

//EXCEPTION: OCX Property Pages should return FALSE

}

LRESULT CMyPage2::OnWizardBack()

{

((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_NEXT);

return CPropertyPage::OnWizardBack();

}

LRESULT CMyPage2::OnWizardNext()

{

((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_BACK);

return CPropertyPage::OnWizardNext();

}

void CMyPage2::OnChangeEdit1()

{

//그림 1-5-31의 ⑨

UpdateData(TRUE);

if(m_strSer1.GetLength()==8)

{

GetDlgItem(IDC_EDIT2)->SetFocus();

}

}

void CMyPage2::OnChangeEdit2()

{

//그림 1-5-31의 ⑩

UpdateData(TRUE);

if(m_strSer2.GetLength()==8)

GetDlgItem(IDC_EDIT3)->SetFocus();

}

void CMyPage2::OnChangeEdit3()

{

//그림 1-5-31의 ⓐ

UpdateData(TRUE);

if(m_strSer3.GetLength()==8)

((CPropertySheet *)GetParent())->

SetWizardButtons(PSWIZB_NEXT|PSWIZB_BACK);

}

(5) CMyPage3

#if !defined(AFX_MYPAGE3_H__7C2B6E25_284A_11D2_8F71_006097283F10__INCLUDED_)

#define AFX_MYPAGE3_H__7C2B6E25_284A_11D2_8F71_006097283F10__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif //_MSC_VER >= 1000

//MyPage3.h : header file

//

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

//CMyPage3 dialog

class CMyPage3 : public CPropertyPage

{

DECLARE_DYNCREATE(CMyPage3)

//Construction

public:

CMyPage3();

~CMyPage3();

//Dialog Data

//{{AFX_DATA(CMyPage3)

enum { IDD = IDD_PAGE3 };

CString m_strName;

CString m_strUser;

//}}AFX_DATA

//Overrides

//ClassWizard generate virtual function overrides

//{{AFX_VIRTUAL(CMyPage3)

public:

virtual LRESULT OnWizardBack();

protected:

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

//}}AFX_VIRTUAL

//Implementation

protected:

//Generated message map functions

//{{AFX_MSG(CMyPage3)

virtual BOOL OnInitDialog();

afx_msg void OnChangeEdit2();

afx_msg void OnChangeEdit1();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

//Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif //!defined(AFX_MYPAGE3_H__7C2B6E25_284A_11D2_8F71_006097283F10__INCLUDED_)

//MyPage3.cpp : implementation file

//

#include “stdafx.h”

#include “ExWizard.h”

#include “MyPage3.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

//CMyPage3 property page

IMPLEMENT_DYNCREATE(CMyPage3, CPropertyPage)

CMyPage3::CMyPage3() : CPropertyPage(CMyPage3::IDD)

{

//{{AFX_DATA_INIT(CMyPage3)

m_strName = _T(“”);

m_strUser = _T(“”);

//}}AFX_DATA_INIT

}

CMyPage3::~CMyPage3()

{

}

void CMyPage3::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMyPage3)

DDX_Text(pDX, IDC_EDIT1, m_strName);

DDX_Text(pDX, IDC_EDIT2, m_strUser);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMyPage3, CPropertyPage)

//{{AFX_MSG_MAP(CMyPage3)

ON_EN_CHANGE(IDC_EDIT2, OnChangeEdit2)

ON_EN_CHANGE(IDC_EDIT1, OnChangeEdit1)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

//CMyPage3 message handlers

BOOL CMyPage3::OnInitDialog()

{

CPropertyPage::OnInitDialog();

((CPropertySheet *)GetParent())->SetWizardButtons(PSWIZB_BACK);

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

//EXCEPTION: OCX Property Pages should return FALSE

}

LRESULT CMyPage3::OnWizardBack()

{

((CPropertySheet *)GetParent())->

SetWizardButtons(PSWIZB_NEXT|PSWIZB_BACK);

return CPropertyPage::OnWizardBack();

}

void CMyPage3::OnChangeEdit2()

{

//그림 1-5-31의 ⓔ 항목

UpdateData(TRUE);

((CPropertySheet *)GetParent())->

SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT|PSWIZB_FINISH);

}

void CMyPage3::OnChangeEdit1()

{

UpdateData(TRUE);

}

본 항목은 본 장의 마지막인 툴바 컨트롤에 대한 부분입니다. 툴바 컨트롤은 툴바를 사용하는 컨트롤이라는 의미입니다. 보통 메인프레임에 설정된 툴바들은 툴바 컨트롤이 아니라 CToolBar입니다. 툴바 컨트롤은 이 CToolBar가 조금 발전된 형태로 툴바에 문자열까지 삽입할 수 있는 형태를 의미합니다. 그림 1-5-34는 본 항목에서 제작하는 예제 ExToolBarCtrl의 화면입니다.

예제 ExToolBarCtrl은 드래깅과 드롭이 가능한 메뉴 툴바와 누르면 들어가 있고 다시 누르면 나오는 그리기 툴바로 설정되어 있습니다.

본 항목에서는 ExToolBarCtrl을 제작하면서 툴바 컨트롤을 사용하는 방법을 설명하고, 본 장 전체에 걸쳐서 메시지 이벤트가 발생되는 NOTIFY 메시지에 대해서 설명하겠습니다. 본 항목은 초급자나 중급자라면 그냥 읽어보기만 하고 넘어가도 됩니다. 툴바 컨트롤을 사용하지 않고도 다음 장에서 설명하는 CToolBar를 이용하면 툴바를 설정하는 데 어려움이 없으며, 다음 장의 CToolBar의 사용법을 이해하면 본 툴바 컨트롤을 사용하는 데 어려움이 없기 때문입니다.

프로젝트 만들기

본 장에서 제작하는 프로젝트는 Dialog based 형태입니다. 이렇게 프로젝트를 만든 다음 CToolBarCtrl을 상속받은 두 개의 클래스를 클래스 위저드를 이용하여 만듭니다. 이 두 개의 클래스명은 다음과 같습니다.

class CStandardBar : public CToolBarCtrl

class CPaletteBar : public CToolBarCtrl

클래스를 만들고 난 후에 그림 1-5-32의 형태로 대화 상자를 만듭니다. 본 항목은 주로 고급자를 위한 부분이기 때문에 세세한 내용은 생략하겠습니다.

이렇게 하면 프로젝트를 만드는 기본 형태는 끝납니다.

메뉴 툴바 만들기

CStandardBar의 Create 함수를 만듭니다. 이 함수의 형태는 다음과 같습니다.

BOOL CStandardBar::Create( DWORD dwStyle, const RECT& rect, CWnd*  pParentWnd, UINT nID)

위의 함수를 virtual 형태로 클래스 헤더에 설정합니다. CToolBarCtrl에는 기본적으로 위와 같은 형태의 Create 함수가 있습니다. 이 함수와 같은 형태로 CStandardBar를 만들 때의 함수를 만듭니다. 이 함수를 만들고 다음과 같이 코딩합니다.

BOOL CStandardBar::Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)

{

BOOL bRet = CToolBarCtrl::Create(dwStyle, rect, pParentWnd, nID);

//IDR_STANDARDBAR 안에 있는 버튼은 모두 아홉 개이다.

m_nButtonCount = ID_CONTEXTHELP - ID_NEW + 1;

//비트맵을 설정한다.

VERIFY(AddBitmap(m_nButtonCount,IDR_STANDARDBAR) != -1);

//툴바 안에 버튼을 m_nButtonCount만큼 만든다.

m_pTBButtons = new TBBUTTON[m_nButtonCount];

for (int nIndex = 0; nIndex < m_nButtonCount; nIndex++)

{

CString string;

//해당 ID STRING을 로드시킨다.

string.LoadString(nIndex + ID_NEW);

//Add second ‘\0’

//nStringLength는 string의 길이 +1로 설정하고

int nStringLength = string.GetLength() + 1;

//pString을 string에서 nStringLength만큼 넣고

TCHAR * pString = string.GetBufferSetLength(nStringLength);

//끝은 0x00을 넣는다.

pString[nStringLength] = 0;

//m_pTBButtions[index]의 스트링에 pString을 넣는다.

VERIFY((m_pTBButtons[nIndex].iString = AddStrings(pString)) != -1);

//string 버퍼 Release

string.ReleaseBuffer();

//스타일 넣기

m_pTBButtons[nIndex].fsState = TBSTATE_ENABLED;

m_pTBButtons[nIndex].fsStyle = TBSTYLE_BUTTON;

m_pTBButtons[nIndex].dwData = 0;

m_pTBButtons[nIndex].iBitmap = nIndex;

m_pTBButtons[nIndex].idCommand = nIndex + ID_NEW;

}

//버튼 다음에 한 칸을 띄우기 위한 공백 버튼을 만들어 놓고

TBBUTTON sepButton;

sepButton.idCommand = 0;

sepButton.fsStyle = TBSTYLE_SEP;

sepButton.fsState = TBSTATE_ENABLED;

sepButton.iString = 0;

sepButton.iBitmap = 0;

sepButton.dwData = 0;

//m_pTBButtons로 만든 것을 모두 툴바에 기입한다.

for (nIndex = 0; nIndex < m_nButtonCount; nIndex++)

{

VERIFY(AddButtons(1,&m_pTBButtons[nIndex]));

//세 번째마다 공백 버튼을 넣는다.

if (!((nIndex +1) % 3))

{

VERIFY(AddButtons(1,&sepButton));

}

}

return bRet;

}

위와 같이 하고 Create 함수를 호출하면 Resource에 있는 IDR_STANDARDBAR를 로드하여 툴바를 만들게 됩니다. 이렇게 툴바를 만들 때 세 번째마다 SPARATE 형태로 끊어집니다. Create 함수를 만든 후에 대화 상자에서 다음과 같이 호출하면 툴바가 상단에 붙습니다.

//메뉴바를 새로 만든다.

m_StandardBar.Create(WS_BORDER | WS_VISIBLE | WS_CHILD

| CCS_TOP | CCS_ADJUSTABLE | TBSTYLE_TOOLTIPS,

CRect(0,0,0,0),this, IDR_STANDARDBAR);

//크기를 자동적으로 설정

m_StandardBar.AutoSize();

위와 같이 하면 툴바 컨트롤이 만들어지며 Shift 키를 누른 채 마우스를 드래깅하면 툴바의 위치가 드래깅됩니다. 이렇게 툴바를 만드는 방법은 일반적으로 많이 사용하기 때문에 이해할 것입니다.

OnChildNotify

본 장에서 설명하고자 하는 것은 툴바 하나하나에 발생되는 이벤트 얻기입니다. 일반적으로 컨트롤들은 WM_NOTIFY 메시지가 발생됩니다. 이 메시지와 함께 넘겨주는 wParam에는 통지 메시지를 보내는 윈도 ID가, 그리고 lParam에는 기본적으로 NMHDR 구조체를 포함하는 여러 구조체의 정보를 넘겨줍니다. 일반적으로 컨트롤에서는 이런 메시지를 WM_NOTIFY에 의해 받을 수 있다는 것을 알 수는 있는데, 컨트롤 안에 있는 윈도들에서 발생되는 이벤트는 어떻게 얻을까 하는 것입니다. Spy++ 프로그램이나 윈도 메시지 훅킹 프로그램을 작성하여 보면 메뉴나 작은 윈도들마저(메시지 훅킹을 해보면 메뉴 자체도 윈도입니다.) 부모 윈도에게 어떤 통지 메시지를 계속 날리고 있는 것을 볼 수가 있습니다.

지금까지 컨트롤을 사용하면 우리는 단순하게 컨트롤 자체의 메시지와 컨트롤 자체에서 발생하는 이벤트 메시지만 얻었을 뿐이지 컨트롤 안에 있는 작은 윈도들이 보내는 메시지를 받은 적은 없습니다. 없다는 것보다 그 메시지들이 다시 현재 컨트롤에 합류되어 이벤트 메시지로 넘겨준다는 것입니다. MFC에서는 이런 자식 윈도들의 메시지 이벤트를 가로챌 수 있는 Notification 함수를 제공합니다. 이 함수가 OnChildNotify입니다. 이 함수는 CWnd 멤버로서 자신의 윈도 안에 있는 자식 윈도들의 Notification 메시지를 얻을 수 있습니다. 이 함수의 형태는 다음과 같습니다.

virtual BOOL OnChildNotify( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult

message는 Notification 메시지이며 wParam에는 일반적으로 자기 자신의 ID가, 그리고 lParam에는 NMHDR 구조체를 포함하고 있는 여러 정보 구조체의 포인터를 가지고 있습니다. 이렇게 보면 WM_NOTIFY 메시지와 같은 형태입니다. 조금 다른 면이 있다면 pLResult입니다. 이 값이 TRUE일 경우에는 현재 부모 윈도에서 통지 메시지를 받고 처리를 하지 않습니다. 그러나 이 값을 FALSE로 설정할 경우에는 위의 함수를 virtual로 사용하고 난 후에 그 통지 메시지가 부모 윈도로 넘어가서 처리하지 않은 부분들을 다시 처리하게 됩니다. 툴바 컨트롤을 그냥 하나의 윈도라고 생각할지 모르나 윈도 안에 작은 아이콘들이 있습니다. 이런 아이콘들이 자신의 아이콘에서 발생한 이벤트를 부모 윈도에게 넘겨줍니다. 이런 이벤트를 바로 얻고자 할 경우에 위의 함수를 사용할 수 있습니다. 다음은 OnChildNotify 함수를 이용하여 툴바 컨트롤의 각 버튼에서 발생하는 메시지를 가로채는 예입니다.

BOOL CStandardBar::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)

{

CString data;

if (message == WM_NOTIFY)

{

NMHDR *pNMHDR = (NMHDR *) lParam;

switch (pNMHDR->code)

{

//버튼을 눌렀을 경우

case TBN_BEGINDRAG:

data=”TBN_BEGINDRAG”;

GetParent()->SendMessage(WM_USER+10,0,(LPARAM)(LPCSTR)data);

* pLResult = FALSE;

return FALSE;

//버튼을 놓았을 경우

case TBN_ENDDRAG:

data=“TBN_ENDDRAG”;

GetParent()->SendMessage(WM_USER+10,0,(LPARAM)(LPCSTR)data);

* pLResult = FALSE;

return FALSE;

//툴바로부터 어떤 버튼이 지워졌을 때

case TBN_QUERYDELETE:

data=”TBN_QUERYDELETE”;

GetParent()->SendMessage(WM_USER+10,0,(LPARAM)(LPCSTR)data);

* pLResult = TRUE;

return TRUE;

//툴바에 어떤 버튼이 삽입되었을 때

case TBN_QUERYINSERT:

data=“TBN_QUERYINSERT”;

GetParent()->SendMessage(WM_USER+10,0,(LPARAM)(LPCSTR)data);

* pLResult = TRUE;

return TRUE;

//툴바가 바뀌었을 경우

case TBN_TOOLBARCHANGE:

data=”TBN_TOOLBARCHANGE”;

GetParent()->SendMessage(WM_USER+10,0,(LPARAM)(LPCSTR)data);

* pLResult = TRUE;

return TRUE;

}

}

return CToolBarCtrl::OnChildNotify(message, wParam, lParam, pLResult);

}

본 예제 ExToolBarCtrl에서는 WM_NOTIFY 메시지만 가로채고 이 메시지에 의해서 발생된 이벤트 코드, 즉 이벤트 메시지 문자열을 부모 윈도에게 보내는 형태를 취했습니다. 이렇게 하였을 때 이 메시지는 그림 1-5-32의 ③ 항목에 출력됩니다.

OnChildNotify 함수를 이용하면 자식 윈도의 여러 메시지를 받을 수 있습니다. 또한 이 메시지와 함께 들어오는 여러 정보를 얻어서 자식 윈도에서 발생하는 특정적인 이벤트를 갱신하거나 무시할 수가 있습니다. 본 예제에서는 WM_NOTIFY 메시지만 화면에 출력하였으나 그 밖의 어떤 메시지들이 날아오는가는 독자 여러분이 분석해 보기 바랍니다. 나머지 부분은 소스를 분석하면서 이해하기 바랍니다. 본 항목의 예제 ExToolBarCtrl의 소스는 생략합니다. 제공되는 CD-ROM을 참조하기 바랍니다.