본문 바로가기
C#

WPF TreeView 드래그 앤 드롭 기능 구현

by samie 2025. 4. 14.
WPF TreeView 드래그 앤 드롭 기능 구현

🧲 WPF TreeView 드래그 앤 드롭 기능 구현

TreeView를 사용하다 보면 노드를 끌어서 순서를 바꾸거나 다른 부모 노드 아래로 이동시키고 싶은 경우가 많습니다.
이번 포스트에서는 WPF TreeView에서 드래그 앤 드롭(Drag & Drop) 기능을 구현하는 방법을 설명합니다.

1. Category 모델 구성


public class Category
{
    public string Name { get; set; }
    public ObservableCollection<Category> Children { get; set; } = new();
    public Category? Parent { get; set; }
}

Parent 프로퍼티를 활용해 트리 구조에서 상위 노드를 추적할 수 있게 합니다.

2. TreeView 이벤트 처리 (Drag & Drop)


private Point _startPoint;

private void TreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _startPoint = e.GetPosition(null);
}

private void TreeView_MouseMove(object sender, MouseEventArgs e)
{
    Point mousePos = e.GetPosition(null);
    Vector diff = _startPoint - mousePos;

    if (e.LeftButton == MouseButtonState.Pressed &&
        (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
         Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
    {
        TreeView treeView = sender as TreeView;
        TreeViewItem? item = FindAncestor<TreeViewItem>((DependencyObject)e.OriginalSource);

        if (item != null && item.DataContext is Category category)
        {
            DragDrop.DoDragDrop(item, category, DragDropEffects.Move);
        }
    }
}

private void TreeView_Drop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(Category)))
    {
        Category draggedItem = (Category)e.Data.GetData(typeof(Category))!;
        TreeViewItem? targetItem = FindAncestor<TreeViewItem>((DependencyObject)e.OriginalSource);

        if (targetItem != null && targetItem.DataContext is Category targetCategory)
        {
            MoveCategory(draggedItem, targetCategory);
        }
    }
}

마우스를 움직이는 거리로 Drag 시작을 감지하고, Drop 시 대상 아이템에 맞춰 트리를 업데이트합니다.

3. 아이템 이동 로직 구현


private void MoveCategory(Category source, Category destination)
{
    if (source == destination || IsChildOf(source, destination))
        return;

    source.Parent?.Children.Remove(source);

    source.Parent = destination;
    destination.Children.Add(source);
}

private bool IsChildOf(Category parent, Category child)
{
    if (child.Parent == null) return false;
    if (child.Parent == parent) return true;
    return IsChildOf(parent, child.Parent);
}

이동 시 순환 참조를 방지하고, 기존 부모에서 제거한 후 새 부모에 추가하는 방식입니다.

4. 사용자 피드백 효과 (Highlight 등)

드롭 가능한 위치에만 효과를 주고 싶다면 XAML에 이벤트와 스타일을 추가해보세요.


<TreeView
    PreviewMouseLeftButtonDown="TreeView_PreviewMouseLeftButtonDown"
    MouseMove="TreeView_MouseMove"
    AllowDrop="True"
    Drop="TreeView_Drop">

    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}" />
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

5. 마무리 및 다음 편 예고

  • WPF TreeView에서도 드래그 앤 드롭이 가능하며
  • 노드 구조를 동적으로 업데이트할 수 있습니다
  • MVVM 구조와도 함께 사용할 수 있도록 구조 설계가 중요합니다

다음 편에서는 TreeView와 ContextMenu(오른쪽 클릭 메뉴)를 연동해서 노드를 삭제하거나 이름을 변경하는 기능을 구현해보겠습니다! ✂️