Home Full Site
Drag & Drop 기초

윈도우 프로그래밍에서 드래그-앤-드롭은 소스컨트롤에 있는 데이타나 아이템을 타겟컨트롤로 복사(Copy) 혹은 이동(Move)하는 등의 작업을 수행하는 것이다. 사용자 입장에서는 마우스로 소스(이를 Drop Source라 부름) 에 있는 데이타를 끌어다 타겟(이를 Drop Target이라 부름)으로 이동시켜 Drop하게 된다.

드래그-앤-드롭을 윈폼에서 구현하기 위해서는 다음 2가지 작업을 해주어야 한다. 즉, (1) Drop Source에서의 구현과 (2) Drop Target 에서의 구현이 그것이다.

(1) Drop Source 에서 해주어야 하는 일

윈폼의 기본 Base 클래스인 Control 클래스는 DoDragDrop() 이라는 메서드를 가지고 있다. Drop Source 컨트롤에서 이 DoDragDrop 메서드를 호출하면 드래그-앤-드롭을 시작하게 된다. 이 DoDragDrop 메서드는 대부분 Drop Source 컨트롤의 MouseDown 이벤트 핸들러에서 실행하게 되지만, ListView나 TreeView 등 일부 컨트롤에서는 특별한 ItemDrag 라는 이벤트를 사용하기 때문에 ItemDrag 이벤트핸들러 안에서 실행된다.

가령 txtDropSource 라는 텍스트 박스가 Drop Source 라고 가정할 때, 다음과 같이 DoDragDrop 메서드를 호출하여 Drag and Drop를 시작할 수 있다.

예제

private void txtDropSource_MouseDown(object sender, MouseEventArgs e)
{
    DoDragDrop(txtDropSource.Text, DragDropEffects.Copy);
    //txtDropSource.Text = "";  // 만약 Move 이면 소스를 이렇게 지움
}

DoDragDrop 메서드의 첫번째 파라미터를 타겟에 전달할 데이타를 가리키고, 두번째 파라미터는 타겟에 Drop하는 방식을 가리킨다. 즉, 두번째 파라미터는 소스에서 전달하는 데이타를 타겟에 그대로 복사(Copy)할 지, 타겟에 복사하지만 소스에서는 지우는 이동(Move)을 할 지, 혹은 타겟에 링크(Link)만 할 지 등을 정하는 것이다.

(2) Drop Target 에서 해주어야 하는 일

드래그-앤-드롭을 위해 Drop Source에서는 한 가지 일만 해주면 되지만, Drop Target에서는 최소한 3가지 일을 해주어야 한다.
  • Drop Target 컨트롤에서 AllowDrop 속성을 true 로 설정한다. 이는 해당 컨트롤에 Drop하는 것을 허용하는 일을 한다.
  • Drop Target 컨트롤에서 DragEnter 이벤트 핸들러를 구현하여 e.Effect 속성을 지정한다. DragEnter는 마우스가 타겟 컨트롤 내로 들어 올 때 발생하는 이벤트로서 이 이벤트핸들러에서 드래그-앤-드롭 데이타를 받아들일지 아닐지를 결정하게 된다. (주: DragEnter 대신 DragEnter 바로 다음에 발생하는 DragOver 이벤트에서 Effect 속성을 지정할 수도 있다)
  • Drop Target 컨트롤에서 DragDrop 이벤트 핸들러를 구현한다. DragDrop은 마우스를 Release하여 실제 Drop이 이루어질 때 발생하는 이벤트로서 이 이벤트핸들러에서는 Drop된 데이타를 타겟에 복사하거나 이동하는 등의 일을 하게 된다.

다음 예제는 txtDropTarget 이라는 텍스트박스 타겟컨트롤에서 DragEnter 이벤트핸들러와 DragDrop 이벤트핸들러를 정의한 예이다. Drag* 이벤트핸들러는 DragEventArgs 라는 이벤트 아큐먼트를 파라미터로 전달받는데, DragEventArgs의 Data 속성에는 드래그-앤-드롭 데이타 객체가 들어 있다. 이 Data 객체의 GetDataPresent(타입) 메서드는 데이타가 지정된 타입인지 검사하는 메서드이고, Data.GetData(타입) 메서드는 지정된 타입의 데이타를 리턴하는 메서드이다.

아래 DragEnter 이벤트핸들러는 전달된 Drop 데이타가 문자열인지 체크해서 문자열이면 복사를 허용하고, 아니면 Drop하지 못하게 (Effect = None) 하고 있다. 또한 DragDrop 이벤트핸들러 안에서는 전달된 Drop 데이타를 타겟컨트롤(txtDropTarget)의 Text 속성에 할당하고 있다.

예제

private void txtDropTarget_DragEnter(object sender, DragEventArgs e)
{
    // 데이타가 문자열 타입이면 복사하고, 아니면 Drop 무효처리
    if (e.Data.GetDataPresent(typeof(string)))
    {
        // Drop하여 복사함
        e.Effect = DragDropEffects.Copy;
    }
    else
    {
        // Drop 할 수 없음
        e.Effect = DragDropEffects.None;
    }
}

private void txtDropTarget_DragDrop(object sender, DragEventArgs e)
{
    // e.Data.GetData() 메서드는 드래그-앤-드롭에서 전달된 데이타를 가져옴.
    // 타켓컨트롤(txtDropTarget)에 드랍 데이타 지정
    txtDropTarget.Text = (string) e.Data.GetData(DataFormats.StringFormat);
}


txtDropSource 텍스트박스 컨트롤에서 txtDropTarget 텍스트박스 컨트롤로 드래그-앤-드롭하는 간단한 예.

단순한 드래그-앤-드롭


Drag & Drop 동작 프로세스

드래그-앤-드롭은 DoDragDrop() 메서스가 호출되면서 시작된다. DoDragDrop() 메서스는 마우스가 이동함에 따라 마우스 위치의 컨트롤이 잠재적인 Drop 타겟인지 (즉 컨트롤이 AllowDrop = true 인지) 체크하여 만약 잠재적 Drop 타겟이면 DragEnter 이벤트를 발생(Fire)시킨다. 이어 타켓 컨트롤에서 Effect 속성을 지정하면, Effect 속성에 따라 타겟컨트롤 위에 다른 아이콘을 표시한다. 예를 들어, Effect가 복사(Copy)인 경우 + 사인이 있는 사각형 아이콘을 타겟컨트롤 위 커서 위치에 표시하고, 이동(Move)인 경우 + 사인 없이 사각형 아이콘을 표시하며, None 이면 금지 아이콘을 표시한다. 사용자가 마우스버튼을 Release하면, 데이타를 Drop하게 되고, DragDrop 이벤트를 발생(Fire)하게 되며, 타겟컨트롤은 데이타를 받아들이게 된다.



ListView에서의 드래그-앤-드롭

ListView 나 TreeView 등의 일부 컨트롤은 ItemDrag 라는 특별한 커스텀 이벤트를 가지고 있는데, 이 이벤트의 핸들러에서 DoDragDrop() 메서드를 호출하여 드래그-앤-드롭을 시작한다.

타겟컨트롤의 DragDrop 이벤트핸들러에서는 e.Data.GetData(typeof(ListViewItem)) 와 같이 하여 ListViewItem을 얻어낼 수 있다.

아래는 2개의 ListView 간에 드래그-앤-드롭을 실행하는 예제로서, 소스 Listview (lvwSrc) 에서 드래그하여 타겟 ListView (lvwTgt)로 리스트 아이템을 복사하는 예제이다. 특히, DragDrop 이벤트핸들러 안에서 보면, lvwTgt 리스트뷰에 드래그된 소스 아이템을 추가할 때 Clone을 사용했음을 볼 수 있다. 이는 하나의 ListViewItem은 복수개의 ListView에 동시에 추가될 수 없으므로, ListViewItem을 Clone해서 추가한 것이다.


예제

// Drop Source: ItemDrag 이벤트 핸들러에서 DoDragDrop 호출
private void lvwSrc_ItemDrag(object sender, ItemDragEventArgs e)
{
    DoDragDrop(e.Item, DragDropEffects.Copy);
}

// Drop Target: DragEnter 핸들러에서 해당 소스가 ListViewItem 인지 체크
private void lvwTgt_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(ListViewItem)))
    {
        e.Effect = DragDropEffects.Copy;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

// Drop Target: DragDrop 핸들러에서 복사 실행
private void lvwTgt_DragDrop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(ListViewItem)))
    {
        // 드래그된 소스 ListViewItem
        var item = e.Data.GetData(typeof(ListViewItem)) as ListViewItem;        

        // ListViewItem를 Clone하여 추가
        lvwTgt.Items.Add(item.Clone() as ListViewItem);
    }        
}



© csharpstudy.com