Develope/MFC2013. 3. 18. 16:10

MFC 트리 컨트롤 드래그 앤 드롭 기능 구현하기.


다른 라이브러리 사용하지 않고 MFC 기본으로 구현해보자.


일단, 트리 컨트롤의 속성에 다음 값이 FALSE로 드래그&드롭 기능이 되도록 해주어야 한다.





해당 트리 컨트롤의 멤버변수를


m_ctrlTree 로 선언 하였다는 가정하에 진행 해보자.


아이템 추가는 다음과 같이 이루어진다.


TVINSERTSTRUCT  TI;
TI.hParent	= TVI_ROOT;		// TVI_ROOT, NULL
					// HTREEITEM값을 사용하면 해당하는 아이템의 자식으로 아이템이 추가된다.
TI.hInsertAfter	= TVI_LAST;		// TVI_FIRST, TVI_LAST, TVI_SORT
TI.item.mask	= TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
TI.item.iImage	= 0;				// Tree가 선택안되었을때 표시될 아이콘
TI.item.iSelectedImage = 1;		// Tree가 선택되었을때 표시될 아이콘
TI.item.pszText = "root";

HTREEITEM hTreeItem = m_ctrTree.InsertItem(&TI); // 추가된 아이템의 HTREEITEM이 리턴된다.


아이템의 확장은 다음과 같다.

m_ctrTree.Expand(hTreeItem, TVE_EXPAND);



선택된 아이템 하이라이트


 + TVN_SELCHANGED메시지를 사용한다.
   NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
   HTREEITEM hTreeItem = pNMTreeView->itemNew.hItem;   // 이 값이 선택된 아이템의 핸들이다.

아이템 문자열 가져오기
   CString str = m_ctrTree.GetItemText(hTreeItem);
 
아이템 개수 알아내기
   int nCount = m_ctrTree.GetCount();
 
아이템 제거
   m_ctrTree.DeleteItem(hTreeItem);   // 핸들 아래단의 아이템들도 모두 제거된다.
 
현재 선택된 아이템 알아내기
   HTREEITEM hTreeItem = m_ctrTree.GetSelectedItem();
 
위치로 아이템 찾기
 CPoint  p;
   GetCursorPos(&p);
   ::ScreenToClient(m_ctrTree.m_hWnd, &p);
   HTREEITEM hItem = m_ctrTree.HitTest(p);
 
아이템 확장 축소 감지
   + TVN_ITEMEXPANDED메시지를 사용한다.
   NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
   TVITEM  item;
   item.mask = TVIF_HANDLE;
   item.hItem = pNMTreeView->itemNew.hItem;
   m_ctrTree.GetItem(&item);            // 아이템 정보를 알아낸다.

   if(item.state & TVIS_EXPANDED)
   {
      // 확장
   }
   else
   {
      // 축소
   }
 
아이템 아이콘 설정 변경
   m_ctrTree.SetItemImage(hTreeItem, 0, 1);
 
아이템 에디트 입력중 포커스가 나갈때 입력중인 값 아이템에 적용하기
   + TVN_ENDLABELEDIT메시지를 사용한다.
   TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
 
   CEdit *pEdit = m_ctrTree.GetEditControl();
   if(pEdit)
   {
      CString str;
      pEdit->GetWindowText(str);
      if(str.GetLength() > 0)
      {
         m_ctrTree.SetItemText(pTVDispInfo->item.hItem, str);
      }
   }
 
이미지 리스트 설정
CImageList  m_Image;      // 32 x 16 아이콘 BITMAP 16 x 16 2개 짜리

m_Image.m_hImageList = ImageList_LoadImage(
					(HINSTANCE) GetWindowLong(m_hWnd, GWL_HINSTANCE),
					MAKEINTRESOURCE(IDB_BITMAP_SMALL), 16, 2,
					RGB(255,255,255), IMAGE_BITMAP, LR_CREATEDIBSECTION);
m_ctrTree.SetImageList(&m_Image, TVSIL_NORMAL);


◎ Drag & Drop 사용 하기


1. 드래그 시작

   - 트리컨트롤의 TVN_BEGINDRAG메시지 사용


CImageList  *m_pTreeDragImage = NULL;		// 드래그시 생성된 이미지 사용
HTREEITEM  m_hDragItem = NULL;			// 드래그시 처음 선택된 아이템 핸들 기억용

void CXXXDlg::OnBegindragTree(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	// 드래그 이미지 생성
	if(m_pTreeDragImage) m_pTreeDragImage->DeleteImageList();
	m_pTreeDragImage = m_ctrTree.CreateDragImage(pNMTreeView->itemNew.hItem);

	// 드래그시 사용할 이미지 크기 계산
	RECT  rc;
	m_ctrTree.GetItemRect(pNMTreeView->itemNew.hItem, &rc, TRUE); // 아이콘을 포함하는 크기
	// 드래그를 시작
	m_pTreeDragImage->BeginDrag(0, CPoint(pNMTreeView->ptDrag.x-rc.left+16,
		pNMTreeView->ptDrag.y-rc.top));
	// 드래그 이미지 표시
	m_pTreeDragImage->DragEnter(&m_ctrTree, pNMTreeView->ptDrag);

	// 마우스 메시지를 잡아두고
	SetCapture();

	// 현재 선택된 아이템 핸들을 기억
	m_hDragItem = pNMTreeView->itemNew.hItem;

	*pResult = 0;
}


2. 이동

   - WM_MOUSEMOVE메시지 사용


void CXXXDlg::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	// 드래그 중이라면
	if(m_pTreeDragImage)
	{
		// 트리컨트롤 기준으로 마우스 좌표 계산
		CPoint  p = point;
		ClientToScreen(&p);
		::ScreenToClient(m_ctrTree.m_hWnd, &p);

		// 마우스가 위치한 아이템을 검사한다.항목이 트리 뷰 항목위에 있는지 확인하고 그렇다면 항목이 밝게 표시되도록한다.
		HTREEITEM hItem = m_ctrTree.HitTest(p);

		// 밝게 표시된 부분과 현재 선택된 아이템이 틀리다면
		if(hItem != m_ctrTree.GetDropHilightItem())
		{
			// 드래그 이미지 그리기 중지
			m_pTreeDragImage->DragLeave(&m_ctrTree);

			// 새로운 항목을 밝게 표시한다.
			m_ctrTree.SelectDropTarget(hItem);

			// 드래그 이미지를 다시 보여준다.
			m_pTreeDragImage->DragEnter(&m_ctrTree, p);
		}
		else
		{
			m_pTreeDragImage->DragMove(p);
		}
	}

	CDialog::OnMouseMove(nFlags, point);
}

3. 드롭

   - WM_LBUTTONUP메시지 사용


void CXXXDlg::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default

	// 드래그 중이 었다면
	if(m_pTreeDragImage)
	{
		// 마우스 메시지 캡쳐 기능을 제거한다.
		ReleaseCapture();

		// 드래그 과정을 중단한다.
		m_pTreeDragImage->DragLeave(&m_ctrTree);
		m_pTreeDragImage->EndDrag();
		m_pTreeDragImage->DeleteImageList();
		delete m_pTreeDragImage;
		m_pTreeDragImage = NULL;

		// 일단 마지막으로 밝게 표시되었던 항목을 찾는다.
		HTREEITEM hTargetItem = m_ctrTree.GetDropHilightItem();

		// 밝게 표시된 드롭 항목의 선택을 취소한다.
		m_ctrTree.SelectDropTarget(NULL);

		// 선택된 항목(아이템)이 있다면
		if(hTargetItem)
		{
			// 선택된 아이템과 이동될 곳의 아이템이 같다면 이동할 필요가 없다.
			if(m_hDragItem != hTargetItem)
			{
				// 현재 자식의 부모 아이템 핸들을 구한다.
				HTREEITEM hParentItem = m_ctrTree.GetNextItem(m_hDragItem,
					TVGN_PARENT);

				// 이동하려는 곳이 자신이 직접속한 항목 이라면 이동할 필요가 없다.
				if(hParentItem != hTargetItem)
				{
					// 트리의 내용을 이동하자.
					MoveTreeItem(&m_ctrTree, m_hDragItem, hTargetItem);

					// 이동된 곳의 트리를 확장하자.
					m_ctrTree.Expand(hTargetItem, TVE_EXPAND);

					// 이미지도 확장한걸로 바꾸자
					m_ctrTree.SetItemImage(hTargetItem, 1, 1);

					// 원본 트리의 모든 아이템이 사라졌다면 이미지 그림을 기본으로 바꾸자.
					HTREEITEM hItem = m_ctrTree.GetChildItem(hParentItem);
					if(!hItem)
					{
						m_ctrTree.SetItemImage(hParentItem, 0, 0);
					}
				}
			}
		}
		m_hDragItem = NULL;
	}

	CDialog::OnLButtonUp(nFlags, point);
}

4. 트리 항목(아이템) 이동 함수


// 아이템 데이터 이동
BOOL MoveTreeItem(CTreeCtrl *pTree, HTREEITEM hSrcItem, HTREEITEM hDestItem)
{
	// 이동할 아이템의 정보를 알아내자.
	TVITEM    TV;
	char    str[256];
	ZeroMemory(str, sizeof(str));
	TV.hItem = hSrcItem;
	TV.mask  = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	TV.pszText = str;
	TV.cchTextMax = sizeof(str);
	m_ctrTree.GetItem(&TV);
	DWORD dwData = pTree->GetItemData(hSrcItem);

	// 아이템을 추가 하자.
	TVINSERTSTRUCT  TI;
	TI.hParent        = hDestItem;
	TI.hInsertAfter   = TVI_LAST;
	TI.item.mask     = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	TI.item.iImage   = TV.iImage;
	TI.item.iSelectedImage = TV.iSelectedImage;
	TI.item.pszText   = TV.pszText;
	HTREEITEM hItem  = pTree->InsertItem(&TI);
	pTree->SetItemData(hItem, dwData);

	// 현재 아이템에 자식 아이템이 있다면
	HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);
	if(hChildItem)
	{
		// 자식 아이템이 있다면 같이 이동하자.
		MoveChildTreeItem(pTree, hChildItem, hItem);
	}
	// 확장 여부를 알아서 똑같이 하자.
	TVITEM  item;
	item.mask = TVIF_HANDLE;
	item.hItem = hSrcItem;
	pTree->GetItem(&item);
	if(item.state & TVIS_EXPANDED)
	{
		pTree->Expand(hItem, TVE_EXPAND);
	}

	// 아이템을 선택하자.
	pTree->SelectItem(hItem);

	// 기존 아이템을 제거한다.
	pTree->DeleteItem(hSrcItem);

	return TRUE;
}


// 현재 트리의 모든 아이템 데이터 이동
BOOL MoveChildTreeItem(CTreeCtrl *pTree, HTREEITEM hChildItem,
					   HTREEITEM hDestItem)
{
	HTREEITEM hSrcItem = hChildItem;

	while(hSrcItem)
	{
		// 이동할 아이템의 정보를 알아내자.
		TVITEM    TV;
		char    str[256];
		ZeroMemory(str, sizeof(str));
		TV.hItem     = hSrcItem;
		TV.mask     = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
		TV.pszText = str;
		TV.cchTextMax = sizeof(str);
		m_ctrTree.GetItem(&TV);
		DWORD dwData = pTree->GetItemData(hSrcItem);

		// 아이템을 추가 하자.
		TVINSERTSTRUCT  TI;
		TI.hParent       = hDestItem;
		TI.hInsertAfter  = TVI_LAST;
		TI.item.mask    = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
		TI.item.iImage   = TV.iImage;
		TI.item.iSelectedImage = TV.iSelectedImage;
		TI.item.pszText   = TV.pszText;
		HTREEITEM hItem  = pTree->InsertItem(&TI);
		pTree->SetItemData(hItem, dwData);

		// 현재 아이템에 자식 아이템이 있다면
		HTREEITEM hChildItem = pTree->GetChildItem(hSrcItem);
		// pTree->GetNextItem(hSrcItem, TVGN_CHILD);
		if(hChildItem)
		{
			MoveChildTreeItem(pTree, hChildItem, hItem);
		}

		// 확장 여부를 알아서 똑같이 하자.
		TVITEM  item;
		item.mask = TVIF_HANDLE;
		item.hItem = hSrcItem;
		pTree->GetItem(&item);
		if(item.state & TVIS_EXPANDED)
		{
			pTree->Expand(hItem, TVE_EXPAND);
		}

		// 다음 아이템을 알아보자.
		hSrcItem = pTree->GetNextItem(hSrcItem, TVGN_NEXT);
	}

	// 기존 아이템을 제거한다.
	pTree->DeleteItem(hChildItem);

	return TRUE;
}


Posted by AsCarion