Develope/MFC2013. 3. 18. 16:10

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


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


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





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


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


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


1
2
3
4
5
6
7
8
9
10
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);



선택된 아이템 하이라이트


1
2
3
+ 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();
 
위치로 아이템 찾기
1
2
3
4
CPoint  p;
  GetCursorPos(&p);
  ::ScreenToClient(m_ctrTree.m_hWnd, &p);
  HTREEITEM hItem = m_ctrTree.HitTest(p);
 
아이템 확장 축소 감지
   + TVN_ITEMEXPANDED메시지를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
      }
   }
 
이미지 리스트 설정
1
2
3
4
5
6
7
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메시지 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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메시지 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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메시지 사용


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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. 트리 항목(아이템) 이동 함수


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 아이템 데이터 이동
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