[퍼옴]MFC04
(그림 27) 비트맵을 만든 화면
리스트 컨트롤에 설정할 그림들은 큰 아이콘일경우 보통 크기가 32x32입니다. 새로운 비트맵을 만든후에 속성설정 대화상자를 실행하고 그림 27의 하단부에 있는 대화상자가 출력될때 Width 를 32 Height를 32로 설정하시면 됩니다. 만일 Color를 256으로 할경우 Colors항목선택하여 색상 빨레트 수를 바꾸시면 됩니다. 본예제에서는 16칼라를 사용하였습니다. 이와 같이 하여 비트맵을 3개를 만들었습니다.이 비트맵 ID는 IDB_BITMAP1,IDB_BITMAP2,IDB_BITMAP3입니다.
이 3개의 비트맵은 큰 아이콘 즉 리스트 컨트롤에서 ICON 스타일 이 설정될때 사용할 그림입니다. 이제 작은 아이콘 (리스트 컨트롤에서 SMALLICON,LIST,REPORT 스타일 이 설정될때 사용할 비트맵을 만들겠습니다. IDB_BITMAP1을 만들때 와 같은 방법인데 작은 아이콘 용이기 때문에 크기를 16x16으로 설정합니다. 즉 그림 36에서 Width를 32 Height를 32로 설정하는 것입니다. 이렇게 하여 작은 크기의 그림을 IDB_BITMAP4,IDB_BITMAP5,IDB_BITMAP6 으로 설정하였습니다.
지금까지로 리스트 컨트롤을 출력하기 위한 기본작업이 끝난것입니다.
리스트 컨트롤 전처리 과정
리스트 컨트롤에 데이터를 입력하기 위해서는 3가지 스텝의 전처리 과정이 있습니다. 이스템은 다음과 같습니다.
1.먼저 그림을 그림 리스트 저장 클래스에 등록을 합니다. 리스트 박스에 필요한 그림 리스트는 두개입니다. 하나은 큰아이콘용 그림 리스트이며 두번째는 작은 아이이콘용 그림 리스트입니다. 우리가 만든 IDB_BITMAP1-IDB_BITMAP3까지는 큰아이콘용 그림 리스트에 저장하고 IDB_BITMAP4-IDB_BITMAP6까지를 작은 아이콘용 그림 리스트에 저장합니다.
2.그림 리스트 두개를 리스트 컨트롤에 등록합니다.
3.컬럼 제목을 기록함니다.그림 33에서 Colors 라고 설정된 것이 바로 컬럼입니다. 이컬럼 제목을 기록하는 것입니다.
전처리 과정은 보통 대화상자가 초기화 되었을때 합니다. 대화상자가 초기화 되었을때의 함수는 OnInitDialog입니다. 대화상자 형태의 프로젝트를 만들면 OnInitDialog가 자동적으로 설정됩니다. 그리고 대화상자에 시스템 메뉴를 설정하고 아이콘을 등록하기 위해서 몇가지 전처리 작업을 해놓았습니다. 다음은 CMExListCtrlDlg에 자동적으로 설정된 OnInitDialog의 내용입니다.
BOOL CMExListCtrlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here <--이부분다음부터 기록
return TRUE; // return TRUE unless you set the focus to a control
}
기록된 내용을 이해할 필요없이 위의 소스에서 "<-- 이부분 다음부터 기록“ 이라라인 다음부터 우리가 필요한 데이터를 설정하면 됩니다. OnInitDialog함수에 전처리 과정 3스텝을 항목별로 나누어서 설명하겟습니다.
1.그림리스트 저장클래스 만들기
전처리 과정 첫번째 스텝인 그림 리스트 저장클래스를 만들기 위해서 사용되는 클래스가 CImageList입니다. 이클래스는 그림리스트를 저장하는 용도의 클래스입니다. 이클래스이 변수가 두개 필요합니다. 하나는 큰아이콘용 저장 클래스이고 또하나은 작은 아이콘용 저장 클래스입니다. CImageList형을 가진 두개의 변수를 CMExListCtrl 헤더에 다음과 같이 설정합니다.
CImageList *m_largeImage; //큰아이콘을 넣을 이미지 구조체
CImageList *m_smallImage; //작은 아이콘을 넣을 이미지 구조체
헤더에 맴버를 설정하는 방법은 MexListCtrlDlg.h에 직접 위와 같은 내용을 코딩하거나 또는 Project Workspace윈도우에서 ClassView항목을 선택하고 우측 마우스 버튼을 클릭하여 Add Member Variable를 항목을 선택하여 기입할수 있습니다.
위와 같이 이미지 리스트를 설정한후에 OnInitDialog에 디 두개의 이미지 리스틀 클래스크기만큼 메모리에 할당합니다.
//메모리에 할당
m_largeImage= new CImageList;
m_smallImage= new CImageList;
위와같이 한후에 CImageList의 맴버함수인 Create함수를 이용하여 저장통을 만듭니다. CImageList 의 Create함수는 다음과 같습니다.
BOOL Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow );
cx: 저장될 이미지의 가로크기
cy: 저장될 이미지의 세로크기
nFlags : 이미지의 칼라수 ILC_COLOR4 이면 16칼라이며 ILC_COLOR8이면 256칼 라
nInitial: 처음에 만들어지는 이미지 저장버퍼 갯수
nGrow : 이미지가 버퍼에 저장되고 늘어날때 늘어나는 버퍼의 갯수
Create함수를 이용하여 m_largeImage와 m_smallImage의 클래스에 저장통을 만듭니다. 저장통을 만드는 내용은 다음과 같습니다.
m_largeImage->Create(32,32,ILC_COLOR4,3,3);//큰이미지 저장통 만들기
m_smallImage->Create(16,16,ILC_COLOR4,3,3);//작은 이미지 저장통 만들기
이제 이이미지 리스트에 비트맵을 설정합니다. 비트맵을 설정하는 맴버함수는 Add입니다. 이함수의 형태는 다음과 같습니다.
int Add( CBitmap* pbmImage, COLORREF crMask );
pbmImage: 비트맵 이미지 클래스
crMask: 비트맵 마스크 칼라 포인터
Add안에 또하나의 클래스 CBitmap가 인자로 넘어옵니다. 이것은 비트맵을 설정시키는 클래스입니다. CBitmap 클래스에 자원에 등록한 ID를 설정하고자 한다면 CBitmap 의 맴버 함수인 LoadBitmap함수를 이용하여 다음과 같이 할수 있습니다.
CBitmap bit;
bit.LoadBitmap(IDB_BITMAP1);
위와 같이 하여 bit에 자원의 비트맵 ID인 IDB_BITMA1를 설정하고 이것을 CImageList의 맴버함수 Add에 넘겨주어서 리스트 컨트롤에 데이터를 설정합니다.
m_largeImage->Add(&bit,RGB(255,255,255));
위의 Add함수에 RGB(255,255,255)는 마스크 칼라값입니다. 흰색으로 설정하였으므로 흰색은 화면에 투명하게 설정됩니다. 이와같은 방법으로 m_largeImage에 이미지를 등록합니다. 처음에 IDB_BITMAP1 을 등록하는 예는 다음과 같습니다.
CBitmap cBit;
cBit.LoadBitmap(IDB_BITMAP1);
m_largeImage->Add(&cBit,RGB(255,255,255));
cBit.DeleteObject();
등록을하고 CBitmap맴버함수인 DeleteObject를 이용하여 설정된 비트맵을 해제 합니다. 이렇게 하는 이유는 cBit를 이용하여 또다른 자원을 로드하고 이것을 이미지 리스트 에 설정하기 위해서 입니다. IDB_BITMAP1~IDB_BITMAP3까지의 3개의 비트맵을 m_largeImage리스트 구조체에 저장하는 방법은 다음과 같습니다.
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();
CBitmap의 cBit하는 IDB_BITMAP1부터 IDB_BITMAP3까지 3개를 연속적으로 로드하여 이미지 리스트에 저장하고 삭제하는 것을 반복하였습니다. 이렇게 하여 3개의 비트맵이 m_largeImage에 등록됩니다. 같은 방법으로 m_smallImage에 IDB_BITMAP4 ~ IDB_BITMAP6까지를 설정합니다. 설정하는 소스는 다음과 같습니다.
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();
2.그림리스트 클래스를 리스트 컨트롤에 등록
위와 같이 작은 아이콘 이미지 리스트와 큰아이콘 이미지 리스트를 만든다음에 이것을 리스트 컨트롤에 설정합니다. 설정할때는 CListCtrl의 맴버함수인 SetImageList 함수를 사용합니다. SetImageList의 함수형은 다음과 같습니다.
CImageList* SetImageList( CImageList* pImageList, int nImageList );
pImageList: 설정할 CImageList 클래스 포인터
nImageList: 이미지 리스트의 형태 다음값을 가진다.
LVSIL_NORMAL : 큰아이콘 형태의 이미지 리스트
LVSIL_SMALL : 작은 아이콘 형태의 이미지 리스트
LVSIL_STATE : 상태를 알려주는 이미지 리스트
SetImageList함수를 이용하여 우리가 만든 m_largeImage와 m_smallImage 두개의 이미지 리스트를 리스트 컨트롤에 설정합니다. 리스트 컨트롤에 설정하는 내용은 다음과 같습니다.
m_listCtrl.SetImageList(m_largeImage,LVSIL_NORMAL);
m_listCtrl.SetImageList(m_smallImage,LVSIL_SMALL);
이와 같이 함으로써 리스트 컨트롤에 그림 이미지 리스틀 설정하는 첫번째 단계가 끝났습니다.
3.컬럼 만들기
리스트 컨트롤에 컬럼을 만들고자 할경우 LV_COLUMN 구조체의 변수를 만들고 이변수에 적당한 값을 설정한 다음 CListCtrl 클래스이 맴버함수 InsertColumn 함수를 이용하여 컬럼을 설정합니다.
LV_COLUMN은 컬럼을 설정하는 구조체로 다음과 같은 맴버들을 가지고 있습니다.
typedef struct _LV_COLUMN {
UINT mask; //맴버 변수들의 활성 비활성 마스크
int fmt; //정렬방식
int cx; //컬럼의 가로길이
LPTSTR pszText; //컬럼 제목 문자열
int cchTextMax; //컬럼의 문자열의 최대 크기
int iSubItem; //컬럼의 번호
} LV_COLUMN;
mask는 fmt,cx,pssText,등의 값들이 유효한가 아닌가를 설정합니다. 이플러그에 OR연산으로 설정할수 있는 값은 다음과 같습니다.
LVCF_FMT fmt 맴버 변수 유효
LVCF_SUBITEM iSubItem 맴버 변수 유효
LVCF_TEXT pszText 맴버변수 유효
LVCF_WIDTH cx 맴버 변수유효
예를 들어서 모든 맴버변수가 유효하게 설정하고자 한다면 다음과 같이 할수가 있습니다.
mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
fmt는 문자가 정렬되는 형태를 설정하는 플러그로 LVCFMT_CENTER 이면 중앙을 기점으로 정렬 LVCFMT_LEFT이면 좌측으로 정렬 LVCFMT_RIGHT이면 우측으로 컬럼 문자가 정렬됩니다.
예를 들어서 문자가 “테스트” 인 컬럼을 가로가 80인 크기로 자측으로 정렬하는 LV_COLUMN구조체를 만든다면 다음과 같습니다.
LV_COLUMN lvcolumn;
lvcolumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcolumn.fmt=LVCFMT_LEFT;
lvcolumn.pszText=_T("테스트“);
lvcolumn.cx=80;
lvcolumn.cchTextMax=512;
위와 같은 방식으로 컬럼을 만든후에 CListCtrl의 맴버함수이 InsertColumn을 이용하여 커럼을 설정할수가 있습니다. InsertColumn의 함수 는 다음과 같은 형입니다.
int InsertColumn( int nCol, const LV_COLUMN* pColumn );
nCol은 설정할 컬럼 번호입니다. 좌측에서 첫번째가 0으로 시작하는 zerobase 수입니다. pColumn은 위에서 설명한 LV_COLUMN의 구조체 포인터입니다.
위의 lvcolumn에 설정되어 있는 데이터를 리스트 컨트롤에 첫번째 컬럼 제목으로 설정하고자 한다면 다음과 같이 하면 됩니다.
m_listCtrl.InsertColumn(0,&lvcolumn);
MExListCtrl예제의 리스트 컨트롤에 “로보트” “설명” 이라는 컬럼을 설정해 보겠습니다. 위에서 설명한 방법을 그대로 이용한 것입니다. 다음은 리스트 컨트롤에 컬럼까지 설정한 OnInitDialog함수입니다.
BOOL CMExListCtrlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//메모리에 할당
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();
//이미지 리스트를 컨트롤 클래스에 등록
m_listCtrl.SetImageList(m_largeImage,LVSIL_NORMAL);
m_listCtrl.SetImageList(m_smallImage,LVSIL_SMALL);
//컬럼 만들기
LV_COLUMN lvcolumn;
lvcolumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcolumn.fmt=LVCFMT_LEFT;
lvcolumn.iSubItem=0;
lvcolumn.cx=80;
lvcolumn.pszText=_T("로보트");
m_listCtrl.InsertColumn(0,&lvcolumn);
lvcolumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcolumn.fmt=LVCFMT_LEFT;
lvcolumn.iSubItem=1;
lvcolumn.cx=280;
lvcolumn.pszText=_T("설명");
m_listCtrl.InsertColumn(1,&lvcolumn);
return TRUE; // return TRUE unless you set the focus to a control
}
위와 같이 OnInitDialog함수를 수정한뒤에 컴파일 하고 실행하면 그림 28과 같은 화면이 출력됩니다. 리스트 컨트롤에 아직 데이터는 들어가 있지는 않지만 컬럼이 만들어져 있는 형태이며 콤보박스를 클릭하면 4개의 리스트 컨트롤 데이터를 볼수 있을것입니다.
(그림 28)리스트 컨트롤에 컬럼을 설정한 화면
리스트 컨트롤에 데이터 입력하기
리스트 컨트롤에 데이터를 입력하고자 할때는 LV_ITEM 이라는 구조체를 사용합니다. 이구조체를 이용하여 CListCtrl클래스의 맴버 함수 InsertItem과 SetItem 함수에 LV_ITEM구조체를 인자로 넘겨주어서 데이터를 입력할수 있습니다. InsertItem은 레코드의 첫번째 데이터를 입력할때 사용하는 함수이며 SetItem은 그이후에 같은 레코드에 컬럼 데이터를 추가할때 사용합니다. 예를 들어서 “태권브이” “지구를 지키는 용사” 라는 하나의 레코드를 그림 37과 같은 리스트 컨트롤에 넣고자 할때 “태권브이”라는 항목을 넣을때는 InsertItem을 사용하고 "지구를 지키는 용사“는 SetItem을 사용하는 것입니다.
먼저 LV_ITEM 구조체의 맴버들을 보겠습니다.
typedef struct _LV_ITEM {
UINT mask; //설정할 아이템 스타일 마스크
int iItem; //아이템 번호
int iSubItem; //서브아이템 번호
UINT state; //현재 상태
UINT stateMask; //상태 마스크
LPTSTR pszText; //현재 필드의 문자열
int cchTextMax; //문자열의 최대 필드
int iImage; // 그림 번호
LPARAM lParam; // 아이템의 lParam 포인터
} LV_ITEM;
mask에 LVIF_TEXT를 설정하면 텍스트라는 것이며 LVIF_IMAGE이면 그림 이라는 의미입니다. 만일 LVIF_TEXT|LVIF_IMAGE이면 그림과 글자가 함께 설정되는 것을 의미합니다. iItem은 아이템번호로써 쉽게 생각하면 레코드 번호라고 생각하면 됩니다. iSubItem은 서브아이템 번호입니다. 이것은 가로열로의 번호입니다. MExListCtrl에서 만든 컬럼중 "로보트“는 서브아이템 번호가 0이며 "설명”은 서브아이템 번호가 1이됩니다. pszText는 현재 설정된 문자열을 의미하며 cchTextMax는 문자열의 최대길이입니다. iImage는 이미지 리스트의 번호를 이야기 합니다. 처음에 Add에 의해서 설정된 이미지번호가 0이되면서 이미지 리스트 번호는 증가됩니다.
LV_ITEM을 이용하여 “태권브이” “우주를 지키는 용사”라는 항목을 그림 번호 0번과 함께 설정한다면 다음과 같습니다.
//데이터 넣기
LV_ITEM lvitem;
lvitem.mask=LVIF_TEXT | LVIF_IMAGE;
lvitem.iItem=0;
lvitem.iSubItem=0;
lvitem.pszText=_T("태권브이");
lvitem.iImage=0;
m_listCtrl.InsertItem(&lvitem);
lvitem.mask=LVIF_TEXT;
lvitem.iItem=0;
lvitem.iSubItem=1;
lvitem.pszText=_T("우주를 지키는 용사");
m_listCtrl.SetItem(&lvitem);
처음에서는 iItem번호가 0 그리고 iSubItem번호가 0으로 “태권브이”를 넣고 그다음에는 iItem번호가 0 iSubItem번호가 1로 가로로 증가되어 “우주를 지키는 용사”라는 데이터를 넣습니다. 이때는 InsertItem을 사용하지 않고 SetItem을 사용하는 것을 유념하시기 바랍니다. 같은 방법으로 위의 데이터와 2개의 데이터를 더추가한 OnInitDialog는 다음과 같습니다.
BOOL CMExListCtrlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//메모리에 할당
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();
//이미지 리스트를 컨트롤 클래스에 등록
m_listCtrl.SetImageList(m_largeImage,LVSIL_NORMAL);
m_listCtrl.SetImageList(m_smallImage,LVSIL_SMALL);
//컬럼 만들기
LV_COLUMN lvcolumn;
lvcolumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcolumn.fmt=LVCFMT_LEFT;
lvcolumn.iSubItem=0;
lvcolumn.cx=80;
lvcolumn.pszText=_T("로보트");
m_listCtrl.InsertColumn(0,&lvcolumn);
lvcolumn.mask=LVCF_FMT|LVCF_SUBITEM|LVCF_TEXT|LVCF_WIDTH;
lvcolumn.fmt=LVCFMT_LEFT;
lvcolumn.iSubItem=1;
lvcolumn.cx=280;
lvcolumn.pszText=_T("설명");
m_listCtrl.InsertColumn(1,&lvcolumn);
//데이터 넣기
LV_ITEM lvitem;
lvitem.mask=LVIF_TEXT | LVIF_IMAGE;
lvitem.iItem=0;
lvitem.iSubItem=0;
lvitem.pszText=_T("태권브이");
lvitem.iImage=0;
m_listCtrl.InsertItem(&lvitem);
lvitem.mask=LVIF_TEXT;
lvitem.iItem=0;
lvitem.iSubItem=1;
lvitem.pszText=_T("우주를 지키는 용사");
m_listCtrl.SetItem(&lvitem);
lvitem.mask=LVIF_TEXT | LVIF_IMAGE;
lvitem.iSubItem=0;
lvitem.iItem=1;
lvitem.pszText=_T("맥칸더브이");
lvitem.iImage=1;
m_listCtrl.InsertItem(&lvitem);
lvitem.mask=LVIF_TEXT;
lvitem.iItem=1;
lvitem.iSubItem=1;
lvitem.pszText=_T("지구를 지키는 용사");
m_listCtrl.SetItem(&lvitem);
lvitem.mask=LVIF_TEXT | LVIF_IMAGE;
lvitem.iItem=2;
lvitem.iSubItem=0;
lvitem.pszText=_T("깡통 로보트");
lvitem.iImage=2;
m_listCtrl.InsertItem(&lvitem);
lvitem.mask=LVIF_TEXT;
lvitem.iItem=2;
lvitem.iSubItem=1;
lvitem.pszText=_T("가정을 지키는 용사");
m_listCtrl.SetItem(&lvitem);
return TRUE; // return TRUE unless you set the focus to a control
}
위와 같이 한후에 컴파일하고 실행시키면 그림 29과 같은 화면이 출력됩니다.
리스트 컨트롤에 데이터를 입력할대는 LV_ITEM 을 사용한다는 것을 그리고 새로운 데이터를 입력할때는 InsertItem 을 그리고 현재 레코드에 서브아이템을 추가할경우에는 SetItem을 사용한다는 것이 요점입니다. 리스트 컨트롤에 그림 데이터를 넣지 않을경우에는 이미지 리스트를 등록할 필요가 없습니다. 그리고 LV_ITEM구조체의 mask에 서브아이템을 설정하는 것과 같이 LV_TEXT만 설정하면 됩니다.
(그림 29)리스트컨트롤에 데이터를 입력한 상태
리스트 컨트롤에 스타일 바꾸기
리스트 컨트롤의 스타일을 바꾼다는것은 윈도우 스타일을 바꾼다는 의미입니다. 일반적인 윈도우 스타일에 리스트컨트롤에 해당되는 스타일을 추가시키면서 리스트 컨트롤을 만들수 있습니다. CListCtrl클래의 Create함수에 윈도우 스타일과 함께 리스트 컨트롤 스타일을 설정하여 리스트 컨트롤을 만들고 이것을 ShowWindow함수를 이용하여 화면에 출력하여도 리스트 컨트롤을 출력할수 있습니다. 중요하게 생각해야할 점은 리스트 컨트롤도 윈도우라는 것입니다. 리스트컨트롤 스타일은 LVS_ 계열로 정의 되어 있습니다. LVS_ICON이면 아이콘 스타일이고 LVS_SMALLICON이면 Small Icon스타일이며 LVS_REPROT이면 리포트 형태 그리고 리스트 형태일경우에는 LVS_LIST로 설정하면 됩니다. 윈도우 스타일을 바꾸는 함수는 GetWindowLong라는 SDK함수를 이용하는 것이 편리합니다. 이함수를 이용하여 윈도우 스타일을 먼저 스타일을 얻습니다. 다음과 같이 하여 현재 리스트 컨트롤의 윈도우 스타일을 얻습니다.
DWORD style=::GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);
C++에서 SDK 즉 C언어 함수를 사용할때는 앞에 ::를 사용합니다. 위의 함수는 안해도 컴파일에 지장이 없으나 프로그램을 보면서 클래스 맴버 함수인지 아니면 SDK함수인지 분간을 하기 위해서입니다. GetWindowLong에 두번째 인자에 GWL_STYLE값을 설정하면 style 에 리스트 컨트롤의 스타일이 설정됩니다. 이스타일에는 LVS_ICON,LVS_SMALLICON,LVS_REPORT,LVS_LIST 중 한개의 값이 이미 설정되어 있습니다. 이값을 제거하고 새로운 설정값을 넣기 위해서는 현재 설정되어 있는 LVS_계열의 스타일을 삭제해야 합니다. 이때는 각계열의 TYPEMASK를 인버스하여 AND연산을 하면 됩니다. 다음과 같이 하면 현재 받은 style에서 설정된 LVS_계열의 스타일이 삭제됩니다.
style &=~(LVS_TYPEMASK);
위와 같이 style 에 기존의 스타일을 제거한후 새로운 스타일을 OR연산을 이용하여 넣으며 됩니다. 예를 들어서 LV_ICON으로 새로운 스타일을 정의하고자 한다면 다음과 같이 하면 됩니다.
style |= LVS_ICON;
위와 같이 한 style를 GetWindowLong와 쌍을 이루고 있는 SetWindowLong함수를이용하여 설정을 합니다.
::SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,(LONG)style);
위와같이하면 리스트 컨트롤의 스타일을 바꿀수 있습니다. 모든 윈도우 스타일은 이와 비슷한 방식으로 처리됩니다.
CMExListCtrlDlg 에서 콤보박스에서 새로운 스타일을 선택하면 이스타일로 리스트 컨트롤을 변경해야 합니다. 여기에서 기억해야 할것은 콤보박스 (ID:IDC_COMBO1)은 CMExListCtrlDlg의 클래스의 맴버와 연결되어 있지 않습니다. 즉 Add member Variable를 이용하여 자원과 클래스 맴버함수를 연결하지 않았다는 뜻입니다. DoDataExchange를 이용하여 자원과 클래스를 연결하지 않아도 자원을 컨트롤 할수 있습니다. 이때 사용하는 함수가 GetDlgItem입니다. 이함수에 ID를 설정하면 CWnd클래스로 ID의 윈도우 포인터를 날려줍니다. 즉다음과 같이 하면 콤보박스의 윈도우 클래스를 넘겨 받을수 있습니다.
CWnd *pcombo=GetDlgItem(IDC_COMBO1);
헌재 여기서 중요한 점은 우리가 원하는 것은 CWnd가 아니라 CComboBox형태의 윈도우 클래스를 원하는 것입니다. 이때 기억해야 할것은 모든 윈도우는 CWnd를 상속받았으며 CComboBox또한 CWnd를 상속받았다는 것입니다. 따라서 다음과 같이 형전환을 하여 CComboBox형태의 윈도우 클래스를 얻을수 있습니다.
CComboBox *pcombo=(CComboBox *)GetDlgItem(IDC_COMBO1);
GetDlgItem을 이용하여 자원의 윈도우를 받는 방법으로 사용할대는 단순한 기능만 얻을경우가 좋습니다. 예를 들어 콤보박스에 데이터를 넣고 빼고 그리고 검색하고 등등의 많은 기능을 하고자 한다면 맴버변수와 연결시키는 것이 좋으나 단한번 현재 어떤 항목을 선택하였나 하는 것만 알고자 한다면 GetDlgItem을 사용하는 것이 좋습니다. MExListCtrl에서 콤보박스에는 이미 데이터가 설정되어 있고 우리가 원하는 것은 어느 항목을 설정하였는가? 하는 번호 뿐입니다. 이번호 알자고 맴버와 연결시킬필요는 없는 것입니다. 이때는 GetDlgItem을 이용하여 다음과 같이 하면 됩니다.
CComboBox *pcombo=(CComboBox *)GetDlgItem(IDC_COMBO1);
int num=pcombo->GetCurSel();
GetDlgItem을 이용하여 pcombo가 IDC_COMBO1을 컨트롤 할수 있게 한후 GetCurSel함수를 이용하여 현재 선택한 번호를 얻습니다. 그리고 이번에 해당되는 스타일로 리스트 컨트롤 스타일을 바꾸면 되는 것입니다.
클래스 위자드에서 Class name가 CMExListCtrlDlg인 상태에서 Object IDs가 IDC_COMBO1 을 선택하고 messages가 CBN_SELCHANGE를 선택하여 콤보박스에서 새로운 항목을 선택하였을때 실행하는 함수 OnSelchangeCombo1() 를 만들고 이함수에서 콤보박스이 선택번호를 알고 그번호대로 스타일을 바꾼 내용을 설정한것은 다음과 같습니다.
void CMExListCtrlDlg::OnSelchangeCombo1()
{
// TODO: Add your control notification handler code here
CComboBox *pcombo=(CComboBox *)GetDlgItem(IDC_COMBO1);
int num=pcombo->GetCurSel();
DWORD style=::GetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE);
style &=~(LVS_TYPEMASK);
switch(num)
{
case 0:
style|=LVS_REPORT;
break;
case 1:
style|=LVS_ICON;
break;
case 2:
style|=LVS_SMALLICON;
break;
default:
style|=LVS_LIST;
break;
}
::SetWindowLong(m_listCtrl.m_hWnd,GWL_STYLE,(LONG)style);
}
지금까지의 설명으로 위의 소스 내용이 어떤 내용인지 이해하실수가 있을겁니다. 위와같이 프로그램을 수정한뒤 컴파일하고 실행시킨뒤 콤보박스에서 스타일을 선택하여 보세요. 선태된 형태대로 스타일이 바뀔것입니다. 그림30는 MExListCtrl에서 콤보박스에서 ICON항목을 선택하였을때 윈도우 스타일이 바뀐화면입니다.
(그림 30) ICON스타일로 설정된 리스트 컨트롤의 모습
리스트 컨트롤에서 이벤트 가로채기
리스트컨트롤에서 마우스를 클릭한다든지 또는 새로운 항목을 설정한다는지 할경우 이것은 실제 메시지로는 WM_NOTIFY 로 설정됩니다. 그리고 lParam으로 넘어오는 데이터를 다시 분석해서 이벤트 메지지를 얻게 됩니다. 그러나 MFC에서는 클래스 위저드의 기능으로 단지 messages 리스트 항목에서 메시지를 선택할수 있습니다. 리스트 컨트롤에서 발생되는 WM_NOTIFY에 대해서 지금 모르시는 분은 2편 8장의 리스트 컨트롤을 보아주시기 바랍니다. 본장에서는 클래스위저드에서 리스트 컨트롤 ID를 선택했을때 출력되는 리스트 컨트롤 이벤트 메시지중 중요 메시지만 설명하겟습니다.
LVN_BEGINDRAG :드래깅이 시작되었다.
LVN_ITEMCHANGED :선택된 아이템이 변경되었다.
LVN_ITEMCHANGING : 선택된 아이템이 변경중이다.
LVN_KEYDOWN: 키가 눌려졌다.
LVN_DELETEITEM: 현재 아이템이 지워졌다.
LVN_INSERTITEM: 새로운 아이템이 추가되었다.
LVN_게열의 메시지가 발생되면 함수가 만들어 지면서 NM_LISTVIEW 구조체의 변수에 정보를 설정해 줍니다. 보통 다음과 비슷한 형태로 넘어오게 되는 것입니다.
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
NM_LISTVIEW는 현재 리스트 컨트롤에 대한 모든 정보를 설정해 놓은 상태 구조체입니다. 이구조체는 다음과 같습니다.
typedef struct tagNM_LISTVIEW {
NMHDR hdr; //NMHDR 헤더 WM_NOTIFY에서 얻을수있는 정보구조체
int iItem; //아이템 번호
int iSubItem; //서브아이템 번호
UINT uNewState; //새로운 상태
UINT uOldState; //이전 상태
UINT uChanged; //변경되었음 값
POINT ptAction; // 이벤트가 발생한 위치정보 포인터
LPARAM lParam; //구조체 정보 포인터 lParam
} NM_LISTVIEW;
리스트 컨트롤에서 이벤트가 발생할경우에는 WM_NOTIFY 메시지가 발생됩니다. 이메시지와 함께 어떤 이벤트가 발생되었는가를 lParam에 설정됩니다. 여기에서 최초의 구조체 블럭이 설정되는데 이것이 NMHDR입니다. 따라서 리스트 컨트롤은 이구조체를 포함한 구조체가 되는 것입니다. 리스트 컨트롤 메시지 리스트에서 NM_ 계열 메시지는 바로 NMHDR로 받은 정보에의해서 얻어지는 포인터입니다.
NM_에 의해서 얻는 메시지에서 NM_LISTVIEW의 메시지로 전환하고자 한다면 바로 형전환을 하면 이루어집니다.
예를 들어 NM_ 계열의 메시지에 의해서 NMHDR* pNMHDR 을 받았다고 할경우 다음과 같이 변경하여 NM_LISTVIEW를 얻을수 있습니다.
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
이런 의미에서 LVN_ 메시지 계열들이 위와 같은 코드가 있는 것입니다.
리스트 컨트롤에서 새로운 아이템이 설정되었을때 어떤 데이터가 설정되었다는 것을 출력하기 위한 방법을 프로그래밍 해보겠습니다. 클래스위저드를 이용하여 리스트 컨트롤에서 LVN_ITEMCHANGED 이벤트 메시지가 발생되었을때 실행하는 함수 OnItemchangedList1 을 만듭니다. 이함수에 다음과 같이 코딩을 합니다.
void CMExListCtrlDlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CString message;
int num=pNMListView->iItem;
static int oldnum=0;
if(num>-1 && oldnum!=num)
{
message=m_listCtrl.GetItemText(num,0);
AfxMessageBox(message);
oldnum=num;
}
*pResult = 0;
}
리스트 컨트롤에서 새로운 아이템이 선택되면 선택된 아이템의 번호가 pNMListView의 맴버 iItem에 설정됩니다.
위의 함수의 내용은 pNMLIstView의 맴버 iItem을 num으로 설정하고 이값이 -1보다크고 이전값과 같지 않으면 리스트 컨트롤 맴버 함수인 GetItemText함수를 이용하여 첫번째 서브아이템 문자열을 얻습니다. 이 문자열을 메시지 박스로 출력하는 것입니다. 그리고 oldnum에 num의 값을 설정합니다. 아이템이 바뀌면 최소 두번의 OnItemchangedList1 함수가 실행됩니다. 이중 최후에 실행된것이 새로 바뀐 아이템입니다. 따라서 oldnum을 이용하여 같은항목이 계속나오면 실행이 안되도록 하였습니다.
이제 컴파일하고 실행하고 리스트 컨트롤에서 아이템을 선택하면 그림 31과 같은 화면이 출력됩니다.
이제 리스트 컨트롤을 이용하는 전반적인 방법을 설명하였습니다. 지금까지 제작한 방법을 이해하시고 좀더 발전적으로 리스트 컨트롤을 사용하고자 한다면 CListCtrl클래스의 맴버 함수를 도움말에서 찾아보시기 바랍니다. 드래깅 드롭및 리스트 컨트롤에서 데이터를 수정삭제하는 방법등 다양한 함수가 내장되어 있습니다.
(그림 31) 완성된 MExListCtrl
3.트리 컨트롤
이번항목에서는 트리컨트롤 클래스를 이용하여 트리 리스트를 구현하는 방법에 대해서 설명하겠습니다. 트리컨트롤을 담당하는 클래스는 CTreeCtrl입니다. 이클래스를 이용하여 트리구조리스트를 만들수가 있습니다. 윈도우 탐색기에 보여지는 트리리스트같은 것을 제작할수 있는 것이 CTreeCtrl입니다.
트리컨트롤 에제 프로젝트 만들기
트리컨트롤 예제는 MExTreeCtrl로 제작합니다. 프로젝트를 만들때 Dialog based로 제작합니다. 프로젝트를 만드는 방법은 리스트컨트롤에서 프로젝트를 만드는 방법과 같습니다. MExTreeCtrl로 프로젝트를 만들면 IDD_MEXTREECTRL_DIALOG의 대화상자가 만들어 집니다. 또한 이대화상자를 컨트롤하는 클래스는 CDialog클래스를 상속받은 CMExTreeCtrlDlg입니다. IDD_MEXTREECTRL_DIALOG 에 컨트롤 도구바에서 버튼을 설정하여 트리를 설정합니다.
그림 32는 IDD_MEXTREECTRL_DIALOG에 트리컨트롤을 설정한 화면입니다.
(그림 32) IDD_MEXTREECTRL_DIALOG에 트리컨트롤 설정화면
트리컨트롤을 설정할대 트리의 속성을 변경합니다. 기본적인 트리는 트리에 라인이트려지지 않고 트리의 루트에 현재 부디렉토리가 있는가 없는가를 표시나는 buttons가 없는 형태입니다. 보통 일반적인 트리구조는 Has buttons,Has lines,Line at root,Edit labels,Border이설정된 형태입니다. 그림 32는 트리컨트롤을 설정하고 속성을 바꾸어서 라인과 버튼이 설정된 형태로 바꾼것입니다. 트리컨트롤을 설정한후에 Add Member Variabel를 이용하여 ( 마우스로 트리 컨트롤을 선택한후에 Ctrl키를 누르고 있는 상태에서 좌측 마우스 버튼 더블클릭) 트리컨트롤 자원과 CMexTreeCtrlDlg의 맴버를 연결합니다.연결되는 맴버는 CTreeCtrl 형을 가진 m_treeCtrl입니다. 헤더를 보시면 다음과 같이 설정되어 있을겁니다.
CTreeCtrl m_treeCtrl;
리스트 컨트롤과 같이 트리컨트롤 또한 그림을 도시할수 있습니다. 그림을 설정하기 위해서 CImageList를 이용하는 것은 리스트 컨트롤과 같습니다. 이미지 리스트에 그림을 설정할때 리스트 컨트롤에서는 비트맵을 이용하였습니다. 이미지 리스트에는 비트맵 뿐만 아니라 아이콘 또한 등록할수있습니다. 본 트리 리스트 컨트롤에서느는 아이콘의 그림을 등록하는 방법으로 예제를 작성해 보겟습니다. 새로운 아이콘을 만들고자 한다면 Ctrl+4키늘 누르거나 또는 Resource도구바에서 아이콘을 클릭하여 만들수 있습니다. 이렇게 하여 총 6개의 아이콘을 만듭니다. 아이콘 ID는 IDI_ICON1 ~ IDI_ICON6까지 만들면 됩니다. 이아이콘을 이미지 리스트에 등록하고 이미지 리스트를 트리에 설정하여 트리컨트롤에서 출력하기 위해서입니다.
트리컨트롤은 리스트 컨트롤과는 달리 이미지 리스트가 1개가 필요합니다. 허나 이미지 리스트에서 두개의 아이콘이 쌍으로 이루져야 합니다. 하나는 현재 트리가 선택되었을대 나타나는 아이콘과 선택되 되지 않았을때 나타나는 아이콘 두개가 쌍으로 이루어 져야 한다는 것입니다. 윈도우 탐색기에서는 서류철 모양의 아이콤이 있다가 해당 디렉토리를 선택하면 서류철이 열려져 있는 형태의 아이콘으로 변화됩니다. 즉 한개의 항목에 설정되는 그림이 하나는 보통모양이고 하나는 선택된 모양이라는 것입니다. 본예제는 단순히 배경색이 다른 원모양의 아이콘 을 쌍으로 하여 6개의 아이콘을 만들었습니다.
트리컨트롤 전처리 과정
트리컨트롤의 전처리 과정이라고 하면 간단하게 말해서 이미지리스트를 만들어 트리에 설정하는 것 뿐입니다. 이방법은 리스트 컨트롤과 비슷합니다.
CMExTreeCtrlDlg의 헤더에 다음과 같이 이미지 리스트 클래스를 설정합니다.
CImageList *pimagelist;
pimagelist에 아이콘을 로드하는 방법은 비트맵을 로드하는 방법과 비슷합니다. 먼저 소스 부터 보시기 바랍니다.
pimagelist= new CImageList;
pimagelist->Create(32,32,ILC_COLOR4,3,3);
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON1));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON2));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON3));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON4));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON5));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON6));
m_treeCtrl.SetImageList(pimagelist,TVSIL_NORMAL);
pimagelist에 메모리를 할당한다음 Create함수를 이요하여 32x32형태의 이미지를 저장하는 리스트를 만듭니다. 이곳에 Add함수를 이용하여 아이콘을 설정하는데 AfxGetApp()->LoadIcon함수에 인자를 아이콘 아이디를 설정하여 이미지 리스트 박스에 설정을 하였습니다. AfxGetApp()라는 함수의 의미는 현재 프로그램은 CWinApp를 상속받은 클래스를 얻고자 하는것입니다. MExtreeCtrl 프로젝트에서 CWinApp를 상속받은 클래스는 CMExTreeCtrlApp 입니다. 이클래스의 맴버함수 (쉽게 보면 CWinApp멤버함수) 중에 LoadIcon이라는 함수를 이용하여 아이콘을 로드한것입니다. AfxGetApp()함수에 대해서는 7장에서 자세히 설명할것입니다. 우선 아이콘을 로드하고자 한다면 AfxGetApp()->LoadIcon()함수를 이용하고 이곳 인자에 ID를 넣어주면 된다고 생각하시기 바랍니다. 이렇게 이미지 리스트를 만든다음 CTreeCtrl의 형을 가진 m_treeCtrl에 SetImageList함수를 이용하여 본 이미지 리스트를 등록합니다. 이와 같이하면 트리를 설정하기 위한 전처리과정이 끝나게 됩니다. 위의 소스는 CMExTreeCtrlDlg에 OnInitDialog함수에 기록합니다.
트리컨트롤에 데이터 입력
트리컨트롤에 데이터를 입력하기 위해서는 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 맴버 유효화
위에 설명한 내용외에 나먼지는 2편 8장을 참조하시기 바랍니다.
위의 구조체에 정보를 넣고 CTreeCtrl의 맴버함수인 InsertItem 함수를 이용하면 데이터를 설정할수가 있습니다. 이제 실제로 데이터를 설정해보겠습니다. 먼저 루트로 “동물” 이라는 항목을 설정해 보겠습니다. 이내용은 다음과같습니다.
TV_INSERTSTRUCT tvstruct;
HTREEITEM item;
tvstruct.hParent=0;//루트로 설정한다.
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="동물";
//그림 선택그림 문자형태의 아이템
tvstruct.item.mask=TVIF_TEXT | TVIF_IMAGE |TVIF_SELECTEDIMAGE ;
//이미지 번호설정
tvstruct.item.iImage =0;
//선택된 이미지 번호설정
tvstruct.item.iSelectedImage=3;
//트리에 설정한다.
item=m_treeCtrl.InsertItem(&tvstruct);
위와 같이 하면 루트로 “동물” 이 설정됩니다. InsertItem함수에는 HTREEITEM을 리턴합니다. 이리턴값을 item에 받아 두었습니다. 이아이템 포인터 값을 받아두는 이유는 이 아이템의 자식 아이템으로 차일드를 설정하기 위해서입니다. “동물” 이라는 트리에 “강아지”라는 차일드 아이템을 삽입하고자 한다면 다음과 같습니다.
tvstruct.hParent=item;
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="강아지";
tvstruct.item.mask=TVIF_TEXT | TVIF_IMAGE |TVIF_SELECTEDIMAGE ;
tvstruct.item.iImage =0;
tvstruct.item.iSelectedImage=3;
m_treeCtrl.InsertItem(&tvstruct);
hParent가 루트일경우에는 0이렀으나 이번에는 루트를 설정할때 받아두었던 item을 설정하였습니다. 이렇게함으로써 “강아지” 는 “동물”의 자식 아이템으로 들어가게 됩니다.
이와 같은 방법으로 MExtreeCtrl에서 데이터를 “강아지”,“사자”,“호랑이”를 입력시켜 트리구조를 만들었습니다. CMExtreeCtrlDlg의 OnInitDialog함수에 트리에 데이터를 이력하는 내용을 넣은 소스는 다음과 같습니다.
BOOL CMExTreeCtrlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//이미지 리스트 설정
pimagelist= new CImageList;
pimagelist->Create(32,32,ILC_COLOR4,3,3);
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON1));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON2));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON3));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON4));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON5));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON6));
//트리에 설정
m_treeCtrl.SetImageList(pimagelist,TVSIL_NORMAL);
TV_INSERTSTRUCT tvstruct;
HTREEITEM item;
//루트 데이터 넣기
tvstruct.hParent=0;
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="동물";
tvstruct.item.mask=TVIF_TEXT |
TVIF_IMAGE |TVIF_SELECTEDIMAGE ;
tvstruct.item.iImage =0;
tvstruct.item.iSelectedImage=3;
item=m_treeCtrl.InsertItem(&tvstruct);
//자식 데이터 넣기
tvstruct.hParent=item;
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="강아지";
tvstruct.item.mask=TVIF_TEXT |
TVIF_IMAGE |TVIF_SELECTEDIMAGE ;
tvstruct.item.iImage =0;
tvstruct.item.iSelectedImage=3;
m_treeCtrl.InsertItem(&tvstruct);
tvstruct.hParent=item;
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="사자";
tvstruct.item.mask=TVIF_TEXT |
TVIF_IMAGE|TVIF_SELECTEDIMAGE ;
tvstruct.item.iImage =0;
tvstruct.item.iSelectedImage=3;
m_treeCtrl.InsertItem(&tvstruct);
tvstruct.hParent=item;
tvstruct.hInsertAfter=TVI_LAST;
tvstruct.item.pszText="호랑이";
tvstruct.item.mask=TVIF_TEXT |
TVIF_IMAGE|TVIF_SELECTEDIMAGE ;
tvstruct.item.iImage =0;
tvstruct.item.iSelectedImage=3;
m_treeCtrl.InsertItem(&tvstruct);
return TRUE; // return TRUE unless you set the focus to a control
}
위와 같이 한후에 컴파일하고 실행시키면 그림 33과 같은 화면이 출력됩니다.
(그림 33) MExTreeCtrl 출력 결과
트리컨트롤에 이벤트 얻기
트리컨트롤의 이벤트를 얻는 방식은 리스트 컨트롤과 같습니다. 리스트컨트롤에서는 LVN_계열이 들어오는것과 같은 방법으로 트리 컨트롤에서는 TVN_ 계열이 들어옵니다. 이메시지에 대해서는 2편 8장을 참조하시기 바랍니다.
트리컨트롤에서 특정항목을 선택하였을때 특정항목 문자열을 얻고자 할때는 리스트 컨트롤과 다릅니다. CMExTreeCtrlDlg에서 TVN_SELCHANGED 이멘트 메시지를 이용하여 만든 함수 OnSelchangedTree1 는 다음과 같습니다.
void CMExTreeCtrlDlg::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
UINT flags;
CPoint point;
CString message;
//마우스 위치를 얻고
GetCursorPos(&point);
//마우스 위치의 좌푤을 트리의 클라이언트 영역 좌표로 전환
m_treeCtrl.ScreenToClient(&point);
//HitTest함수를 이용하여 아이템을 얻고
HTREEITEM nitem=m_treeCtrl.HitTest(point,&flags);
//아이템의 문자열을 얻는다.
message=m_treeCtrl.GetItemText(nitem);
if(message.GetLength()>0)
AfxMessageBox(message);
*pResult = 0;
}
트리는 리스트 컨트롤과는 달리 순서적이지 않습니다. 루트에 차일드 또다시 차일드의 차일드 등으로 설정되어 있기 때문에 마우스 포인터가 어느곳에 위치했는가를 알아내서 HitTest함수를 이용하여 HTREEITEM형을 얻은다음 이 아이템 값을 GetItemText함수의 인자로 넘겨주면서 아이템의 문자열을 얻을수 있습니다.
위의 OnSelchangedTree1 함수는 바로 이방식에 의해서 트리에서 특정 아이템을 얻는 예입니다.
위의 함수를 코딩한후 컴파일하고 실행하면 완결된 MExTreeCtrl프로그램이 생성됩니다. 그림 34는 완결된 MExTreeCtrl의 출력결과입니다.
(그림 34) MExTreeCtrl 의 완결 형태
4.FormView
SDI 나 또는 MDI로 작성할때 클라이언트 영역 즉 실제 그래픽을 출력하는 부분은 CView 였습니다. CView를 상속받은 FormView 라는 새로운 View가 있습니다. 이View는 Resource의 대화상자 자원을 컨트롤하는 View입니다. 쉽게보면 View와 대화상자가 결합된 형태입니다. 어플리케이션 프로그램을 작성할경우 에디터 박스나 또는 버튼 리스트 박스를 설정하는 View가 필요할경우 FormView를 사용합니다. 본항목에서 FormView를 이용하는 예를 설명하겠습니다.
FormView용 프로젝트 만들기
View 가 FormView로 설정되는 프로젝트를 만들고자 할경우 AppWizard Step에서 설정을 해야 합니다. 하나의 예를 만들어 가면서 설명을 해보겠습니다. 프로젝트를 만들때 MExFormView로 설정합니다. File New항목을 이용하여 프로젝트명을 설정하면 AppWizard Step1이 실행될것입니다. 이항목에서 Single document를 선택하여 SDI 의 형태로 설정합니다. 지금까지는 이형태에서 Finish버튼을 클릭하여 프로젝트를 만들었습니다. FormView를 만들경우에는 Next버튼을 클릭합니다. 계속 Next 를 클릭하여 AppWizard Step6 of 6의 위치까지 옵니다. 그림 35은 MFC AppWizard Step6의 화면입니다.
(그림 35)AppWizard Step 6 of 6의 화면
Step6에서 View를 담당하는 클래스 즉 CMExFormViewView 클래스를 선택하고 Base class를 CFormView로 설정하면 View가 CFormView로 설정되는 프로젝트가 만들어집니다. 그림 33은 AppWizard Step6에서 View를 FormView로 설정된 화면입니다. 그림 33과 같은 상태에서 Finish를 클릭하면 프로젝트가 만들어 질것입니다. 프로젝트를 만들고 난후에 컴파일 하고 실행하면 그림 34와 같은 화면이 출력됩니다.
(그림 35) MExFormView출력결과
CView형태와 달리 회색바탕의 화면이 View형태가 되고 화면에는 “이대화상자에 컨트롤을 설정하시오!” 라는 내용이 출력됩니다. 대화상자 형태의 프로젝트를 만들었을때화 비슷한 화면이 나올것입니다. 이제 ResourceView에서 대화상자 IDD_MEXFORMVIEW_FORM 을 클릭하면 대화상자 자원이 그림 36와 같은 대화상자 자원이 화면에 출력될것입니다.
(그림 36) IDD_MEXFORMVIEW_FORM 출력화면
그림 36에 보이는 IDD_MEXFORMVIEW_FORM 대화상자 자원이 바로 본프로젝트에서 CMExFormViewView 클래스가 로드하는 대화상자 자원입니다. FormView는 Dialog와는 스타일이 다릅니다. 대화상자에는 타이틀바가 있으며 때로는 시스템 메뉴와 대화상자 종료 버튼이 있습니다. 그러나 FromView에서는 타이틀 바가 없으며 시스템 메뉴 또한 없습니다. 그냥 단순하게 외곽만 설정된 형태입니다. 이 이유는 대화상자는 독립적인 윈도우로 출력되지만 FormView는 차일드 윈도우 형태로 부모윈도우에 삽입되기 때문입니다. 부모 윈도우에 삽입되기 때문에 타이틀 바나 시스템 메뉴가 없는 것입니다. 이것들은 모두 부모 윈도우가 가지고 있기 때문입니다. 따라서 대화상자를 가지고 있다하여소 FormView와 Dialog는 윈도우 스타일이 다릅니다. Dialog WM_POPUP를 기본 골격으로 유지하고 FormView는 WM_CHILD를 기본 골격으로 유지합니다. 그렇기 때문에 속정 대화상자에 Style에 Style에 Child로 설정해 주어야 합니다. 프로젝트에서 FormView를 만들게 되면 자동적으로 그림 36처럼 Style가 Child이고 Border이 None로 설정되지만 프로젝트가 아닌 다른 방법으로 (뒷장에서 설명을 합니다) FormView를 만들고자 한다면 대화상자 자원을 만들때 그림 36와 같은 형태의 style를 설정해 주어야 합니다.
그림 36와 같은 대화상자에 간단하게 에디터 박스와 버튼을 도시 해보겠습니다.
그림 37은 그림36와 같은 대화상자에 에디터 박스와 버튼을 도시한 형태입니다.
(그림 37) IDD_MEXFORMVIEW_FORM 수정화면
에디터 박스의 ID는 IDC_EDIT1 이며 이것과 연결된 CMExFormViewView클래스의 맴버는 CString형으로 m_strEdit 입니다. (이제 이부분에 대해서는 설명을 생략합니다.)
이제 버튼을 누르면 실행되는 함수를 만들겠습니다. 클래스 위저드로 이함수를 만들면 CMexFormViewView의 OnButton1() 함수가 만들어 집니다. 이함수에서 에디터 박스에 입력한 데이터가 버튼을 클릭하면 출력되도록 하는 소스는 다음과 같습니다.
void CMExFormViewView::OnButton1()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
AfxMessageBox(m_strEdit);
}
위와 같이 수정을 한후에 프로그램을 실행시켜서 에디터 박스에 데이터를 입력하고 “내용보기” 버튼을 클릭하면 에디터박스에 입력한 데이터를 대화상자에 출력될것입니다.
FormView에서 OnInitDialog 대응 되는 OnInitialUpdate
FormView에서는 OnInitDialog를 설정할수 없습니다. 이와 비슷한 기능을 가진 OnInitialUpdate함수를 이용해야 합니다. OnInitialUpdate함수는 클래스위저드의 messages에서 OnInitialUpdate 를 선택하시면 됩니다. MExFormView에서 에디터박스에 “안녕하세요”라는 문자열을 넣어보겠습니다. 대화상자라면 이것을 넣을때 OnInitDialog에 넣을것입니다. MExFormView에서는 FormView를 사용하기 때문에 OnInitialUpdate함수를 만들고 이곳에 다음과 같이 에디터박스에 들어갈 데이터를 기록합니다.
void CMExFormViewView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
m_strEdit="안녕하세요";
UpdateData(FALSE);
}
OnInitalUpdate함수는 윈도우가 만들어지고 여러 정보를 처음에 초기화 할때 사용하는 함수입니다. 이함수가 발생하기 이전에 윈도우가 출력할 모든것의 준비가 끝나게 됩니다. FormView에 데이터를 설정하기 위해서는 윈도우가 만들어지고 윈도우안에 차일드 윈도우들이 모두 설정이되어야 합니다. MExFormView에서는 에디터 박스와 버튼등이 자신의 윈도우 형태를 갖춘 다음이라고 생각하시면 됩니다. 이렇게 설정이 되어 있어야 차일드윈도우에 데이터를 전송할수 있기 때문입니다.
CFormView 에서는 WM_INITDIALOG라는 메시지로 함수는 만들수 있습니다. 그러나 주의하실점은 컴파일이 안된다는 것입니다.그이유는 WM_INITDIALOG에 의해서 발생되는 OnInitDialog함수는 CFormView의 맴버함수가 아니기 때문입니다.
5.탭컨트롤
Visual Studio에서 Project Workspace 윈도우는 탭컨트롤과 비슷한 형태가 설정된 윈도우입니다. 탭컨트롤이란 윈도우에 카드철 같은 버튼이 설정되어 버튼을 클릭하면 화면이 바뀌는 형태입니다. 탭컨트롤과 비슷한것이 프로퍼티 쉬트입니다. 프로퍼티 쉬트는 카드철 같은 형태로 될수도 있고 또한 AppWizard처럼 Next 와 Prev버튼이 설정되어 대화상자에서 앞뒤로 버튼을 누르면서 여러 옵션을 선택하는 형태도 될수 있습니다. 프로퍼티 쉬트는 본장 마지막에서 자세히 설명합니다. 탭컨트롤과 프로퍼티 쉬트의 차이점은 탭컨트롤은 단순히 선택옵션을 보여주는 윈도우이고 프로퍼티 쉬트는 프로퍼티 쉬트안에 프로퍼티페이지라는 여러개의 대화상자를 가지고 있는 페이지를 포함하는 것입니다. 탭컨트롤은 이런 여러개의 대화상자를 포함하고 있는 것처럼 보이나 그렇게 만드는 것일뿐 다만 탭컨트롤은 버튼만 설정될뿐입니다. 그리고 여러개의 자원을 설정하고 탭버튼이 선택될때마다 필요한 윈도우만 화면에 보이고 나머지 윈도우는 화면에서 감추는 형태를 취합니다. 결국 탭컨트롤 자체도 리스트컨트롤이나 또는 트리컨트롤과 비슷한 형태입니다. 예제를 작성해 나가면서 어떤 형태인지 이해가 되실것입니다.
예제는 지금까지 만든 MExFormView에 탭컨트롤을 삽입시키는 방법으로 해보겠습니다.
탭컨트롤 만들기
ResoruceView에서서 IDD_MEXFORMVIEW_FORM 를 선택하고 컨트롤 도구바에서 버튼을 선택한후에 대화상자에 설정합니다. 그림 38은 탭컨트롤이 설정된 IDD_MEXFORMVIEW_FORM 입니다.
(그림 38)IDD_MEXFORMVIEW_FORM에 탭컨트롤을 설정한 화면
그림 38과 같이 탭컨트롤을 설정하고 이탭컨트롤과 연결되는 변수를 설정합니다. 컨트롤로 설정하게 되면 CMExFormViewView의 헤더에 다음과 같이 설정됩니다.
CTabCtrl m_tabCtrl;
m_tabCtrl은 그림 38의 탭컨트롤과 연결되는 변수이며 이것은 CTabCtrl클래스 형을 가진것입니다. 즉 탭컨트롤 클래스는 CTabCtrl이라는 의미입니다. 리스트 컨트롤이나 트리컨트롤과 같이 탭컨트롤은 카드철 항목버튼에 그림을 설정할수 있습니다. 즉 탭컨트롤또한 이미지리스트 박스를 설정할수 있다는 것입니다. 다음은 CMExFormViewView의 OnInitialUpdate함수에서 이미지 리스트를 만들고 이것을 탭컨트롤에 설정한 내용입니다.
pimagelist= new CImageList;
pimagelist->Create(32,32,ILC_COLOR4,3,3);
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON1));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON2));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON3));
m_tabCtrl.SetImageList(pimagelist);
위의 내용은 리스트컨트롤과 트리컨트롤에서 계속설명하였기에 여기에서는 생략합니다. 다음은 카트철 버튼을 하나 만드는 내용입니다.
TC_ITEM item;//카드철 아이템 구조체
//구조체에 정보넣기
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭1";
item.iImage=0;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(0,&item);
위의 소스내용을 설명하기 보다 여러분이 알아내는 방법을 설명하겟습니다. 먼저 대화상자에서 탭컨트롤을 설정하고 Ctrl키를 누른상태에서 마우스 버튼을 더블클릭하여 컨트롤로 연결시키면 헤더에 CTabCtrl로 설정되기 때문에 탭컨트롤을 컨트롤하는 클래스를 알수 있습니다. 여기에서 도움말로 CTabCtrl를 찾고 Class Member항목을 보시기 바랍니다. 이항목에 새로운 아이템을 삽입하는 함수라는 내용과 파랑색의 글자로 InsertItem이라는 함수가 있을것입니다.
(그림 39)도움말에서 찾은 InsertItem
이함수를 클릭하면 InsertItem의 형을 알수 있습니다. 이형은 다음과 같이 도움말에 출력될것입니다.
BOOL InsertItem( int nItem, TC_ITEM* pTabCtrlItem );
그리고 nItem은 삽입할 번호이며 pTabCtrlItem은 설정할 아이템이라는 내용이 나올것입니다. 번호는 우리가 보아도 어떤 수를 넣는다는 것을 이해할수 있습니다. 첫번째 삽입하면 0 두번째 삽입하면 1 이런식으로 설정하면 되기 때문입니다. 그러나 TC_ITEM이라는 구조체는 처음보는 내용입니다. 이때 다시 도움말에서 TC_ITEM을 찾고 구조체를 확인합니다. 이구조체에 대한 정보가 도움말에 출력되는 화면이 그림 40입니다.
(그림 40) TC_ITEM에 대한 정보가 도움말에 출력되는 화면
이도움말에는 mask는 현재 설정할 아이템의 스타일에 대한 마스크이며 그림에 나와 있지는 않지만 이플러그값이 그림 40이 보이는 도움말 밑에 출력되어 있습니다. 그림과 글자를 함께쓰고자 할때는 TCIF_TEXT|TCIF_IMAGE 을 설정하라는 내용이 써있습니다. 그리고 pszText는 문자열이며 iImage는 그림 번호라는 것이 설명되어 있습니다. 이렇게 추적하여 내용을 알고 이것을 코딩하는 것입니다.
필자는 Visual C++를 배울때 이런 방법으로 배웠습니다. 이방법 보다 더빠르게 프로그램을 배우는 방법이 없기 때문입니다. 어떤 Visual C++책도 모든 함수를 다 자세히 설명하고 있지는 않습니다. 본책또한 마찬가지 입니다. 만약에 본책에 탭 컨트롤에 대한 내용이 빠졌다고 해도 이미 리스트컨트롤과 트리컨트롤을 본책에서 배웠다면 유추해석을 할수 있는 겁니다. Visual C++은 사실 어느면에서 기본서를 읽고 기본을 이해한다음에는 도움말을 이용하는 것이 최상입니다. 샘플프로그램 또한 Visual C++를 공부하는데 매우 좋은 자료입니다. 샘플프로그램을 컴파일하여서 어떻게 돌아가는가 보고 핵심 클래스를 확인한다음 이클래스이 맴버 함수를 찾아보면서 이해를 하는 것입니다. 이것은 몇번을 강조해도 아깝지 않은 말입니다.
탭컨트롤에 그림을 설정하고 아이템 하나를 등록한 OnInitialUpdate의 전체 소스는 다음과 같습니다.
void CMExFormViewView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
m_strEdit="안녕하세요";
UpdateData(FALSE);
pimagelist= new CImageList;
pimagelist->Create(32,32,ILC_COLOR4,3,3);
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON1));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON2));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON3));
m_tabCtrl.SetImageList(pimagelist);
TC_ITEM item;//카드철 아이템 구조체
//구조체에 정보넣기
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭1";
item.iImage=0;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(0,&item);
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭2";
item.iImage=1;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(1,&item);
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭3";
item.iImage=2;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(2,&item);
}
탭컨트롤에 3개의 아이템을 설정하였습니다. 각각의 아이템 문자는 “탭1”,“탭2”,“탭3”이며 그림 번호는 0,1,2로 설정하였습니다. 위와 같이 OnInitialUpdate함수를 수정한후에 컴파일하고 실행하면 그림 41과 같은 화면이 출력됩니다.
(그림 41) MExFormView에 탭컨트롤 설정화면
탭컨트롤 선택시 화면 바꾸기
지금까지 제작한 MExFormView는 탭버튼을 클릭하여도 화면의 변화가 없습니다. 이제 탭버튼을 클릭하여 화면의 변화가 생기도록 해보겠습니다.
화면의 변화이전에 우선적으로 탭컨트롤을 선택했을때 어느 항목을 선택했는가를 알아야 합니다.
클래스 위저드에서 탭컨트롤 ID를 선택하고 메시지 이벤트가 TCN_SELCHANGE 함수 OnSelchangeTab1 함수를 생성합니다. 이함수는 탭컨트롤의 항목을 변경하였을때 발생하는 함수입니다. 이함수에서 다음과 같이 하면 현재의 탭번호가 리턴됩니다.
int num=m_tabCtrl.GetCurSel();
위와 같이 하고 num이 0이면 즉 첫번째 탭버튼이 선택되어 있으면 에디터 박스와 버튼을 화면에 보이게 합니다. 화면에 윈도우가 나타나게 하는 것은 CWnd의 맴버 함수인 ShowWindow를 사용하면 됩니다.
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_SHOW);
GetDlgItem이란 인자로 설정한 ID의 CWnd를 리턴합니다. GetDlgItem(IDC_EDIT1) 이란 의미는 IDC_EDIT1의 CWnd라는 뜻입니다. 전에 설명한 내용이나 다시한번 설명드립니다. CWnd의 맴버함수중 ShowWindow(SW_SHOW)라고 하면 윈도우가 화면에 보이고 ShowWindow(SW_HIDE)라고 하면 윈도우가 감쳐집니다. num이 0이면 윈도우를 화면에 보이게 하는 것입니다. 그리고 num이 1이거나 또는 2이면 즉 탭버튼중 두번째 나 또는 세번째 버튼을 클릭하게 되면 이때는 에디터 박스와 버튼을 화면에서 감춥니다. 감추는 방법은 다음과 같습니다.
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
즉 화면에 보이는 것과 역으로 SW_HIDE인자를 사용한 것입니다. num이 0일경우에만 두개의 윈도우가 화면에 보이기 때문에 다른 항목을 선택하면 화면에 윈도우가 나타나지 않을 것입니다. 이렇게 수정한 OnSelchangeTab1 함수는 다음과 같습니다.
void CMExFormViewView::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
int num=m_tabCtrl.GetCurSel();
switch(num)
{
case 0:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_SHOW);
break;
case 1:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
break;
case 2:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
break;
}
*pResult = 0;
}
위와 같이 한후 컴파일하고 실행시킨후 탭버튼을 클릭하여 보세요 “탭1”이라는 항목을 클릭했을때만 에디터 박스와 버튼이 화면에 출력될것입니다. “탭2”와 “탭3”항목을 선택하였을때는 화면에 아무것도 나타나지 않습니다. “탭2”와 “탭3” 때 새로운 화면이 나오게 할려면 출력할 자원을 대화상자에 설정한후 해당 버튼을 눌렀을때만 화면에 보이고 다른때는 감추게 하면 됩니다. 다음항목 비트맵 버튼과 비트맵을 설명하면서 계속적으로 이탭컨트롤을 사용해 보겠습니다.
6.비트맵과 비트맵 버튼
비트맵 출력하기
비트맵을 화면에 출력시킬때 사용하는 함수는 CBitmap입니다. 이함수는 리스트 컨트롤을 작성할때 사용해 본 클래스입니다. 이클래스와 함께 자원에 설정된 비트맵 ID 를 인자로 설정한 LoadBitmap가 비트맵을 클래스에 로드하는 함수입니다.(본 내용 또한 리스트 컨트롤에서 배웠을겁니다.)
이 비트맵을 그대로 DC에 출력하고자 한다면 이때 사용하는 함수가 BitBlt입니다.
이함수는 CDC의 맴버함수이며 다음과 같은 형식을 가지고 있습니다.
BOOL BitBlt(
int x, //출력할 x좌표
int y, //출력할 y좌표
int nWidth, //가로 크기
int nHeight, //세로 크기
CDC* pSrcDC, //소스 DC
int xSrc, //소스의 x좌표
int ySrc, //소스의 y좌표
DWORD dwRop //ROP코드
);
이함수의 각인자값은 도움말을 참조하시기 바랍니다. 위의 함수를 보면 CBitmap의 인자가 하나도 없습니다. 결국 CBitmap데이터를 BitBlt 함수에 의해서 설정할수 없다는 의미입니다. 이때 사용하는 방법은 CDC를 하나 만들고 이DC는 출력할 CDC와 호환성이 있는 DC로 Create한다음에 이 DC에 비트맵을 설정하고 그리고 BitBlt를 사용하는 방법입니다. 다음과 같은 방법을 이용합니다.
CBitmap m_bit;
m_bit.LoadBitmap(IDB_BITMAP1);//비트맵을 로드하고
CDC memdc;
memdc.CreateCompatibleDC(pDC);//출력할 pDC와 호환성 있는 DC를 만든다.
memdc.SelectObject(&m_bit);//DC에 비트맵 설정
pDC->BitBlt(0,0,320,320,&memdc,0,0,SRCCOPY);
위와 같이 하면 비트맵이 화면에 출력됩니다.
탭컨트롤에 비트맵 출력하기-버튼 사용자그리기
탭컨트롤에서 두번째 항목을 선택하면 비트맵이 출력하도록 하고자 합니다. 위에서 설명한 비트맵 출력의 방법을 FormView의 OnDraw함수에 사용하면 출력될것으로 생각할수 있으나 불행히도 출력이 되지 않습니다. 아니 출력은 되나 탭컨트롤 윈도우가 위에 다시 출력되기 때문에 가려지게 됩니다. 해서 위에서 설명한 비트맵 추력 방법으로는 탭에서 출력을 할수 없습니다. 비트맵 뿐만아니라 탭컨트롤 위에 어떤 문자를 출력할려고 하여도 탭컨트롤 윈도우가 가리고 있기 때문에 어렵습니다. 필자가 탭컨트롤에서 윈도우를 나타났다 사라지는 방식으로 출력을 하라고 답변을 했을때 또다시 들어 오는 질문이 자원은 가능하나 글자나 그래픽을 출력할수 없다는 질문이 었습니다. 이에 대한 답변을 본책에도 기록하고자 합니다.
탭컨트롤위에 그래픽을 출력하고자 한다면 출력하고자 하는 영역에 버튼을 설정합니다. 탭컨트롤 전체에 그래픽을 출력하고자 한다면 탭윈도우 만한 버튼을 만들면 됩니다. 이렇게 만든다음 이버튼의 속성에서 Style에서 Owner draw를 선택합니다. 이렇게 하면 사용자가 버튼영역에 그림이나 문자를 출력할수가 있습니다.
MExFormView에서 두번째 탭버튼 즉 “탭2”를 클릭했을때 비트맵이 출력하도록 해보겠습니다. IDD_MEXFORMVIEW_FORM 대화상자 자원의 적당한 영역에 버튼을 설정합니다. 그리고 이버튼의 속서을 Owner draw가 체크 되어 있도록 하고 (Style 카드철 항목) 그리고 General 항목에서 Visible가 체크되어 있지 않도록 합니다. Visible가 체크 되어 있지 않도록 하는 이유는 프로그램이 실행되면 탭컨트롤은 “탭1” 이설정되어 있는 형태이기 때문에 이때에는 버튼이 보여지지 않게 하기 위해서 입니다. 이와 같이 한다음 클래스위저드에서 Object IDs를 CMExFormViewView로 선택하고 Messages에서 WM_DRAWITEM 메시지를 선택하여 OnDrawItem 이라는 함수를 만듭니다. 이렇게 만든 OnDrawItem 는 다음과 같습니다.
void CMExFormViewView::OnDrawItem(int nIDCtl,
LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CFormView::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
버튼이 화면에 출력될때 이버튼을 사용자 그리기로 설정했기 때문에 버튼은 자신이 그림을 그리지 않고 부모 윈도우에게 WM_DRAWITEM을 보냅니다. 이메시지를 받고 설정된 함수가 OnDrawItem입니다. 이함수의 첫번째 인자 nIDCtrl은 자원의 ID를 의미하고 뒤의 lpDrawItemStruct는 아이템의 그리기 정보를 가지고 있는 구조체입니다. 구조체의 모든 맴버는 도움말을 참조하시기 바랍니다. DRAWITEMSTRUCT 구조체중에 hDC가 그래픽 디바이스 핸들러 입니다. 이핸들러를 이용하여 CDC를 얻을수 있습니다. 이때 사용하는 함수가 CDC::FromHandle 입니다. 이함수를 사용하여 CDC 를 얻는 방법은 다음과 같습니다.
CDC pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
이렇게 버튼의 DC를 얻은다음 이 DC에 비트맵을 출력합니다. 출력하는 방법은 바로 전항목에서 설명하였습니다. 비트맵을 출력하는내용을 설정한 OnDrawItem은 다음과 같습니다.
void CMExFormViewView::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
CBitmap bit;
bit.LoadBitmap(IDB_BITMAP1);
SelectObject(MemDC,bit);
pDC->BitBlt(0,0,202,172,&MemDC,0,0,SRCCOPY);
CFormView::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
이제 탭컨트롤에서 두번째 버튼을 클릭했을때만 비트맵이 보여야 합니다. OnSelchangeTab1 함수를 다음과 같이 수정하여 2번째 탭버튼을 클릭하면 버튼만 보이고 나머지 윈도우는 감추게 합니다.
void CMExFormViewView::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
int num=m_tabCtrl.GetCurSel();
switch(num)
{
case 0:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_SHOW);
//비트맵 출력 버튼감춤
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE);
break;
case 1:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
//비트맵 출력 버튼 보임
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW);
break;
case 2:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
//비트맵 출력 버튼감춤
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE);
break;
}
*pResult = 0;
}
위와 같이 하면 탭버튼이 두번째가 선택되었을때 즉 num이 1일경우에는 비트맵을 출력하는 버튼이 화면에 보이고 다른때는 화면에 보이지 않습니다. 그림42는 두번째 버튼을 클릭했을때 비트맵이 보여지는 화면입니다.
(그림 42)“탭2” 항목을 선택했을때의 출력화면
비트맵 버튼 출력하기
비트맵 버튼을 출력하고자 할경우 버튼을 만들고 Style속성을 Owner draw 설정하여 줍니다. 또한 이 버튼에 설정할 비트맵을 만듭니다. 그리고 버튼의 캡션 이름과 (버튼에 출력되는 문자열)과 비트맵의 이름을 일치 시켜 줍니다. 예를 들어서 버튼의 문자열이 "BUTTON"이라면 들어간 그림이 있는 비트맵은 “BUTTOND" 그리고 나온 그림이 있는 비트맵은 "BUTTONU"로 설정해 줍니다.
헤더에 CBitmapButton크래스 형의 맴버를 하나 설정한후에 이클래스이 맴버함수중 AutoLoad함수를 이용하여 비트맵버튼을 만들어 주면 됩니다.
다음은 AutoLoad함수를 사용항 예입니다.
CBitmapButton m_pbit;//클래스 헤더에 설정
m_pbit.AutoLoad(IDC_BUTTON3,this);
위와 같이 하면 자동적으로 비트맵 버튼이 됩니다.
이방법 외에 버튼과 CBitmapButton의 맴버를 DoDataExchange함수에서 DDX_Control로 연결시키고 LoadBitmaps를 이용하여도 됩니다. 안타까운점은 자원에서 비트맵을 선택하고 Ctrl키를 누르고 있는 상태에서 좌측마우스 버튼을 더블클릭하여 연결되지 않기 때문에 수동으로 기입해야 합니다.
예를 들어 헤더에 CBitmapButton이 m_pbit로 연결되어 있다면 DoDataExchange함수에 다음과 같이 설정을 해줍니다.
DDX_Control(pDX,IDC_BUTTON3,m_pbit);
위와 같이 설정한 다음 LoadBitmaps함수를 이용하여 비트맵을 로드하면 됩니다.
m_pbit.LoadBitmap("BUTTONU',"BUTTOND","BUTTONU","BUTTONE");
첫번째 인자는 버튼이 나와있을대이며 두번째 인자는 버튼이 눌린상태 그리고 세번째 인자는 버튼에 포커스가 주어졌을때 그리고 4번째 인자는 버튼이 비활성화되었을 때입니다.
CMExFormViewView의 전체 소스
MExFormView에서 탭컨트롤에서 3번째 탭버튼을 클릭하면 비트맵 버튼이 나오는 내용을 기록하였습니다. 본책에서는 MFC에서는 가능하면 소스를 배제할려고 했습니다.(Bible 5.x에서 소스가 많아서 두꺼워졌고 그래서 가지고 다니기 힘들다는 불만이 있어서...) 허나 MExFormViewView는 탭컨트롤,비트맵,폼뷰,비트맵 버튼 4가지를 모두 포함하고 있는 소스이기 때문에 볼필요가 있다고 보아 소스를 기록합니다. 소스는 다음과 같습니다.
(프로그램 소스)
// MExFormViewView.h : interface of the CMExFormViewView class
//
/////////////////////////////////////////////////////////////////////////////
#if !defined(AFX_MEXFORMVIEWVIEW_H__68AC8EEE_C3F8_11D1_AC4E_006097AEB8A7__INCLUDED_)
#define AFX_MEXFORMVIEWVIEW_H__68AC8EEE_C3F8_11D1_AC4E_006097AEB8A7__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class CMExFormViewView : public CFormView
{
protected: // create from serialization only
CMExFormViewView();
DECLARE_DYNCREATE(CMExFormViewView)
public:
//{{AFX_DATA(CMExFormViewView)
enum { IDD = IDD_MEXFORMVIEW_FORM };
CTabCtrl m_tabCtrl;
CString m_strEdit;
CImageList *pimagelist;//이미지 리스트
//}}AFX_DATA
// Attributes
public:
CMExFormViewDoc* GetDocument();
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMExFormViewView)
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void OnInitialUpdate();
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnPrint(CDC* pDC, CPrintInfo*);
//}}AFX_VIRTUAL
// Implementation
public:
CBitmapButton bitbut;//비트맵 버튼 클래스
int m_nTabNum;
virtual ~CMExFormViewView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CMExFormViewView)
afx_msg void OnButton1();
afx_msg void OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#ifndef _DEBUG // debug version in MExFormViewView.cpp
inline CMExFormViewDoc* CMExFormViewView::GetDocument()
{ return (CMExFormViewDoc*)m_pDocument; }
#endif
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_MEXFORMVIEWVIEW_H__68AC8EEE_C3F8_11D1_AC4E_006097AEB8A7__INCLUDED_)
// MExFormViewView.cpp : implementation of the CMExFormViewView class
//
#include "stdafx.h"
#include "MExFormView.h"
#include "MExFormViewDoc.h"
#include "MExFormViewView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CMExFormViewView
IMPLEMENT_DYNCREATE(CMExFormViewView, CFormView)
BEGIN_MESSAGE_MAP(CMExFormViewView, CFormView)
//{{AFX_MSG_MAP(CMExFormViewView)
ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB1, OnSelchangeTab1)
ON_WM_DRAWITEM()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CMExFormViewView construction/destruction
CMExFormViewView::CMExFormViewView()
: CFormView(CMExFormViewView::IDD)
{
//{{AFX_DATA_INIT(CMExFormViewView)
m_strEdit = _T("");
//}}AFX_DATA_INIT
// TODO: add construction code here
}
CMExFormViewView::~CMExFormViewView()
{
}
void CMExFormViewView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMExFormViewView)
DDX_Control(pDX, IDC_TAB1, m_tabCtrl);
DDX_Text(pDX, IDC_EDIT1, m_strEdit);
//}}AFX_DATA_MAP
}
BOOL CMExFormViewView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CFormView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CMExFormViewView printing
BOOL CMExFormViewView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}
void CMExFormViewView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CMExFormViewView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
void CMExFormViewView::OnPrint(CDC* pDC, CPrintInfo*)
{
// TODO: add code to print the controls
}
/////////////////////////////////////////////////////////////////////////////
// CMExFormViewView diagnostics
#ifdef _DEBUG
void CMExFormViewView::AssertValid() const
{
CFormView::AssertValid();
}
void CMExFormViewView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
CMExFormViewDoc* CMExFormViewView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMExFormViewDoc)));
return (CMExFormViewDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CMExFormViewView message handlers
void CMExFormViewView::OnButton1()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
AfxMessageBox(m_strEdit);
}
void CMExFormViewView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
m_strEdit="안녕하세요";
UpdateData(FALSE);
pimagelist= new CImageList;
pimagelist->Create(32,32,ILC_COLOR4,3,3);
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON1));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON2));
pimagelist->Add(AfxGetApp()->LoadIcon(IDI_ICON3));
m_tabCtrl.SetImageList(pimagelist);
TC_ITEM item;//카드철 아이템 구조체
//구조체에 정보넣기
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭1";
item.iImage=0;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(0,&item);
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭2";
item.iImage=1;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(1,&item);
item.mask=TCIF_TEXT|TCIF_IMAGE;
item.pszText="탭3";
item.iImage=2;
//IntertItem함수를 이용하여 아이템 설정
m_tabCtrl.InsertItem(2,&item);
//비트맵 버튼 자동 로드
bitbut.AutoLoad(IDC_BUTTON3,this);
}
void CMExFormViewView::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
int num=m_tabCtrl.GetCurSel();
switch(num)
{
case 0:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_SHOW);
//비트맵 출력 버튼감춤
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_HIDE);
break;
case 1:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
//비트맵 출력 버튼 보임
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_HIDE);
break;
case 2:
GetDlgItem(IDC_EDIT1)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON1)->ShowWindow(SW_HIDE);
//비트맵 출력 버튼감춤
GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE);
GetDlgItem(IDC_BUTTON3)->ShowWindow(SW_SHOW);
break;
}
*pResult = 0;
}
void CMExFormViewView::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
//nIDCtl이 IDC_BUTTON3일경우에도 이함수가 실행된다.
//따라서 IDC_BUTTON3일경우에는 자동로드가 되어야 함
//버튼이 IDC_BUTTON2이면
if(nIDCtl==IDC_BUTTON2)
{
CDC *pDC=CDC::FromHandle(lpDrawItemStruct->hDC);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
CBitmap bit;
bit.LoadBitmap(IDB_BITMAP1);
SelectObject(MemDC,bit);
pDC->BitBlt(0,0,202,172,&MemDC,0,0,SRCCOPY);
}
CFormView::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
(프로그램 소스끝)
'WindowsPrograming' 카테고리의 다른 글
[퍼옴]MFC03 (0) | 2007.03.11 |
---|---|
[퍼옴]MFC05 (0) | 2007.03.11 |
[퍼옴]MFC06 (0) | 2007.03.11 |