다음 소스는 Windows API 정복에 나와있는 드래그 앤 드롭 소스을 바탕으로,
MFC 버전으로 변경한 것이다.
리스트뷰 자체는 드래그에 관련된 기능을 가지고 있지 않으며, 이미지 리스트의 드래그 기능을 빌려서 사용하므로 먼저 이미지 리스트에 충분히 익숙해 있어야 한다.
시작하기 앞서, 사용 멤버 변수
bool m_bDrag; /**< 현재드래그중인지체크플래그*/
INT m_nOldTarget; /**< 이전드래그선택항목 */
INT m_nSource; /**< 드래그중인항목*/
CListCtrl m_ctrlList; /**< 리스트컨트롤*/
CImageList *m_pDragImage; /**< 드래그중나타낼이미지리스트*/
1. 드래그 시작
void CListDragDropDlg::OnLvnBegindragListMain(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
*pResult = 0;
if (0 >= m_ctrlList.GetSelectedCount())
return ;
m_nSource = pNMLV->iItem;
//드래그이미지리스트생성
POINT ptImg;
m_pDragImage = m_ctrlList.CreateDragImage(m_nSource, &ptImg);
if (NULL == m_pDragImage)
return ;
//핫스팟
CPoint ptSpot;
ptSpot.x = pNMLV->ptAction.x - ptImg.x;
ptSpot.y = pNMLV->ptAction.y - ptImg.y;
m_pDragImage->BeginDrag(0, ptSpot);
//현재마우스커서위치에드래그이미지그림
ptImg.x = pNMLV->ptAction.x;
ptImg.y = pNMLV->ptAction.y;
ClientToScreen(&ptImg);
m_pDragImage->DragEnter(NULL, ptImg);
m_nOldTarget = m_nSource;
m_bDrag = true;
SetCapture();
}
사용자가 리스트의 항목을 마우스 왼쪽 버튼으로 누른 후 조금이라도 움직일 때, 리스트는 부모 윈도우에게 LVN_BEGINDRAG 통지 메시지를 보내주며 lParam으로 NMLISTVIEW 구조체의 포인터가 전달된다.
드래그 기능은 이미지 리스트가 제공해 주므로 드래그를 하기 위해서 드래그 이미지 리스트를 먼저 만들어야 한다.
m_ctrlList.CreateDragImage(m_nSource, &ptImg);
리스트 컨트롤의 멤버 함수로, m_nSource 항목의 드래그 이미지를 포함하는 이미지 리스트를 만들고 그 핸들을 리턴해 준다.
m_pDragImage->BeginDrag(0, ptSpot);
표현할 대상 이미지 이며, dtSpot은 이미지중 드래그되는 대표점이다. 이미지는 넓은데 비해 드래그는 한 점을 대상으로 이루어 지므로 핫스팟이 필요하다. 핫 스팟은 보통 이미지의 좌상단을 기준으로 마우스가 눌러진 이미지 내의 좌표로 지정한다.
m_pDragImage->DragEnter(NULL, ptImg);
드래그 중에 특정 윈도우의 모든 갱신을 금지 시키며 ptImg 좌표(윈도우 좌표)에 드래그 이미지를 최초로 지정해 준다. 드래그 이미지는 원본 이미지 보다 약간 흐리게 표현된다.
SetCapture();
드래그 시작 후 마우스 이동 메시지를 계속적으로 받기 위해 마우스 커서를 캡쳐
2. 드래그 이미지 이동
void CListDragDropDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 여기에메시지처리기코드를추가및/또는기본값을호출합니다.
if (m_bDrag)
{
if (NULL == m_pDragImage)
return ;
// 드래그이미지이동
CPoint ptCursor(point);
ClientToScreen(&ptCursor);
m_pDragImage->DragMove(ptCursor);
// 커서위치의리스트아이템찾아서선택표시를해준다.
INT nTarget = GetHitIndex(point);
if (-1 != nTarget)
{
if (m_nOldTarget != nTarget)
{
m_pDragImage->DragLeave(NULL);
m_ctrlList.SetItemState(nTarget,
LVIS_DROPHILITED, LVIS_DROPHILITED);
m_ctrlList.SetItemState(m_nOldTarget, 0,
LVIS_DROPHILITED);
m_nOldTarget = nTarget;
m_pDragImage->DragEnter(NULL, ptCursor);
m_ctrlList.UpdateWindow();
}
}
}
CDialog::OnMouseMove(nFlags, point);
}
// 커서 위치의 리스트아이템 인덱스를 찾는다.
INT CListDragDropDlg::GetHitIndex(CPoint point)
{
CRect rcList;
m_ctrlList.GetWindowRect(&rcList);
ScreenToClient(reinterpret_cast<LPPOINT>(&rcList));
LVHITTESTINFO HitInfo;
HitInfo.pt.x = point.x - rcList.left;
HitInfo.pt.y = point.y - rcList.top;
return m_ctrlList.HitTest(&HitInfo);
}
마우스 이동시 WM_MOUSEMOVE 메시지가 전달 된다. 이 메시지에서 이동시점의 동작을 구현한다.
m_pDragImage->DragMove(ptCursor);
ptCursor는 마우스 커서의 좌표(윈도우 좌표)로서, 드래그 중 이미지 이동에 대한 모든 처리를 다해준다.
GetHitIndex(point);
이것은 사용자 정의 함수로서, 내가 임의로 구현한 함수이다.
해당 함수를 이용하여 현재 마우스 커서의 위치에 해당하는 리스트 아이템의 인덱스를 구해,
해당 인덱스의 아이템에 LVIS_DROPHILITED 설정을 해주어, 현재 이동할 위치를 인지하게 쉽게 해준다.
3. 드래그 종료
void CListDragDropDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bDrag)
{
if (NULL == m_pDragImage)
return ;
m_ctrlList.SetItemState(m_nOldTarget, 0, LVIS_DROPHILITED);
//드래그종료
m_pDragImage->DragLeave(NULL);