본문 바로가기
컴퓨터/MFC_API

CTreeCtrl 에서 check box를 집어 넣고 다루기

by adnoctum 2009. 12. 27.



   VC++ 6.0 IDE로 CTreeCtrl control을 집어 넣은 후 property 대화 상자를 통해 tree control의 property를 설정하면 제대로 작동하지 않는 경우가 있으므로, 직접 코드로 집어 넣는다. 


VC++ 9.0 (Visual Studio 2008) 의 경우에는 CTreeCtrl이나 CListCtrl 의 경우 속성 대화 상자에 모든 속성이 나와 있지는 않다. 즉, 코드상으로 변경시켜 줄 수 있는 속성이 속성 대화 상자에 나와 있지 않는 것이 있으므로 직접 코드로 작성해 주는 편이 낫다.

// check box를 트리에 추가하기 위해서는 TVS_CHECKBOXES  속성을 setting 해야 한다. 
// _treeResult가 CTreeCtrl 변수의 instance 이름이다.
// TVS_CHECKBOXES 이외의 속성은 개인적인 취향.
long style = ::GetWindowLong(
	_treeResult.GetSafeHwnd(),GWL_STYLE) | TVS_CHECKBOXES
	| TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS 
	| TVS_LINESATROOT;
::SetWindowLong(_treeResult.GetSafeHwnd(),GWL_STYLE,style);

각 옵션에 대한 약간의 설명을 하자면 다음과 같다. 
  • TVS_HASBUTTONS : tree control에서, 부모 노드와 자식 노드를 연결시킬 때 + 버튼이 부모 노드 밑에 생기게 해준다. 
  • TVS_HASLINES : 부모 노드와 자식 노드를 연결하는 선을 나타나게 해준다. 
  • TVS_SHOWSELALWAYS : 트리 컨트롤이 아닌 윈도우가 포커스를 받았을 때, 트리 컨트롤에 선택되었던 아이템이 여전히 선택된 형태로 남도록 해준다. 이것은, 트리 컨트롤이 포커스를 잃어도 선택되었던 아이템이 흐린 회색으로 표시되어 그것이 선택된 상태라는 것을 알 수 있도록 해준다. 엑셀은 이런 옵션이 없어서 엑셀의 일정 영역을 선택한 후 포커스를 다른 윈도우즈로 옮기면 어느 영역이 선택되었는지 알 수 없는 불편함이 있다. 반면에 윈도우즈의 탐색기는 이 옵션이 있어서(물론 리스트 컨트롤이지만) 여러 파일이나 폴더를 선택한 후 포커스를 다른 윈도우로 옮겨도 어느 항목들을 선택했는지 알 수 있다. 
  • TVS_LINESATROOT : 최상위 노드 왼쪽에 선이 그려지도록 한다. 


GetWindowLong 함수는 윈도우의 속성값에 해당하는 값을 반환해 준다. 속성 값은 bit mask로 설정이 되는데, 여러 속성을 일일이 반환해 줄 수 없기 때문에 이렇게 하는 것으로 보인다. 즉, long 타입 하나가 반환되더라도 각 bit를 확인함으로 여러 속성 중 어느 속성이 켜져 있고 꺼져 있는지를 알 수 있는 것이다. 


SetWindowLong 함수는 속성에 해당하는 long 값을 설정해 주는 역할을 하는데, 쉽게, 윈도우의 속성을 변경시키는 역할을 한다고 생각하면 된다. SetWindowStyle 과 같은 함수 이름이 아니어서 좀 혼동이 되긴 한다.

위의 코드에서 눈여겨 볼 것은, 변수 [style] 은 GetWindowLong의 반환값에 내가 원하는 스타일을 OR 시킨 값이라는 것이다. 이것은, 즉, 내가 원하는 속성들을 '추가' 시키는 역할을 하는 것이다. 현재 어떤 속성을 갖고 있는지는 신경쓰고 싶지 않고, 단지 추가를 해주고 싶기 때문에 OR 을 한 것이다. (특정 속성을 지우고 싶을 때 어떻게 해야 하는지 쉽게 알 수 있을 것이다).


tree control의 check state가 변했을 때 발생하는 notification message가 없으므로 다음과 같은 방법을 사용한다.

+ tree control에서 mouse를 click했을 때 발생하는 NM_CLICK message를 가로채서 처리한다. 

+ tree control에서 keyboard가 눌렸을 때 발생하는 TVN_KEYDOWN message를 가로채서 처리한다.

위 의 두 메세지를 잡아서, check box의 state를 변경해야 하는 상황이라면, 사용자 메세지를 보내서 state를 변경하도록 한다. 즉, mouse를 click했을 경우, click한 위치가 check box였는지를 CTreeCtrl::HitTest 함수로 알아낸 후, 만약 check box에서 click한 것이었을 때, 혹은 tree control에서 space bar를 눌렀을 때 사용자 메세지를 보낸다.


// NM_CLICK message handler를 다음과 같이 작성한다. 
// mouse click이 일어났을 때의 mouse pointer의 위치를 가져 온다. 
DWORD cur_pos = ::GetMessagePos(); 
CPoint point(LOWORD(cur_pos), HIWORD(cur_pos)); 
// mouse pointer의 위치를 tree control의 좌표로 바꾸어 준다. 
_treeResult.ScreenToClient(&point); 
UINT nFlags = 0; 
// click이 일어난 위치가 tree control의 check box였는지를 확인한다.
// 만약 click이 tree control의 check box였다면 CTreeCtrl::HitTest의
// 두 번째 parameter에
// TVHT_ONITEMSTATEICON mask가 1로 setting 된다.
HTREEITEM hItem = _treeResult.HitTest(point, &nFlags); 
if(hItem != NULL && (nFlags & TVHT_ONITEMSTATEICON) != 0){
    // tree control의 check box의 state 변화를 처리하는 사용자 메세지,    
    // 지금의 경우 TREE_CHECK_STATE_CHANGE, 를 보낸다.
    PostMessage(TREE_CHECK_STATE_CHANGE, 0, (LPARAM)hItem); 
}
 
 
 
// TVN_KEYDOWN message handler를 다음과 같이 작성한다. 
LPNMTVKEYDOWN pTVKeyDown = (LPNMTVKEYDOWN)(pNMHDR); 
if(pTVKeyDown->wVKey == VK_SPACE){
    HTREEITEM hCurItem = _treeResult.GetSelectedItem(); 
    if(hCurItem != NULL){
       PostMessage(TREE_CHECK_STATE_CHANGE, 0, (LPARAM)hCurItem); 
    }
}
 
 
// tree control의 check box의 state를 다루는 
// message handler를 다음과 같이 작성한다. 
HTREEITEM hItem = (HTREEITEM)lParam; 
bool checkState = (bool)_treeResult.GetCheck(hItem); 
CString str = _treeResult.GetItemText(hItem); 
TRACE("%d: %s\n", checkState, str); 


tree control 의 check box의 check 여부는 GetCheck 함수로 얻어갈 수 있고, 또한 SetCheck 함수로 체크된 상태를 변경시킬 수 있다. 이 글에서 말하고 싶은 것은, check state 가 변하는 것을 어떻게 잡아 내느냐 하는 것이었다.



'컴퓨터 > MFC_API' 카테고리의 다른 글

MSFlexGrid 사용하기  (0) 2009.12.27
Resize ScrollView  (0) 2009.12.27
프로그램이 플로피 디스크를 읽는 것 방지  (0) 2009.12.27
Scrollbar message handler 처리하기  (0) 2009.12.27
저장하기 대화 상자 열기  (0) 2009.12.14