๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
C#

WPF TreeView ContextMenu ๋…ธ๋“œ ์ถ”๊ฐ€/์‚ญ์ œ

by samie 2025. 4. 10.
WPF TreeView ContextMenu ๋…ธ๋“œ ์ถ”๊ฐ€/์‚ญ์ œ

๐ŸŒณ WPF TreeView์—์„œ ContextMenu๋กœ ๋…ธ๋“œ ์ถ”๊ฐ€ ๋ฐ ์‚ญ์ œ ๊ตฌํ˜„ํ•˜๊ธฐ

TreeView๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋…ธ๋“œ๋ฅผ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋”์šฑ ์œ ์—ฐํ•œ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” MVVM ํŒจํ„ด์„ ์œ ์ง€ํ•˜๋ฉด์„œ ContextMenu(์šฐํด๋ฆญ ๋ฉ”๋‰ด)๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€/์‚ญ์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

1. Category ๋ชจ๋ธ ํด๋ž˜์Šค ๊ตฌ์„ฑ


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

2. ViewModel์—์„œ ๋ช…๋ น ์ •์˜


public class MainViewModel
{
    public ObservableCollection<Category> Categories { get; set; }
    public ICommand AddCommand { get; }
    public ICommand RemoveCommand { get; }

    public MainViewModel()
    {
        Categories = new ObservableCollection<Category>
        {
            new Category
            {
                Name = "ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด",
                Children = new ObservableCollection<Category>
                {
                    new Category { Name = "C#" },
                    new Category { Name = "Python" }
                }
            }
        };

        AddCommand = new RelayCommand(AddNode);
        RemoveCommand = new RelayCommand(RemoveNode);
    }

    private void AddNode(object? parameter)
    {
        if (parameter is Category category)
        {
            category.Children.Add(new Category { Name = "์ƒˆ ํ•˜์œ„ ํ•ญ๋ชฉ" });
        }
    }

    private void RemoveNode(object? parameter)
    {
        if (parameter is Category category)
        {
            foreach (var parent in Categories.SelectMany(GetAllCategories))
            {
                if (parent.Children.Contains(category))
                {
                    parent.Children.Remove(category);
                    return;
                }
            }
        }
    }

    private IEnumerable<Category> GetAllCategories(Category category)
    {
        yield return category;
        foreach (var child in category.Children.SelectMany(GetAllCategories))
            yield return child;
    }
}

3. TreeView์™€ ContextMenu XAML ๊ตฌ์„ฑ


<TreeView ItemsSource="{Binding Categories}" Margin="10">
  <TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Name}" />
        <StackPanel.ContextMenu>
          <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€"
                      Command="{Binding DataContext.AddCommand, RelativeSource={RelativeSource AncestorType=Window}}"
                      CommandParameter="{Binding}" />
            <MenuItem Header="์ด ํ•ญ๋ชฉ ์‚ญ์ œ"
                      Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=Window}}"
                      CommandParameter="{Binding}" />
          </ContextMenu>
        </StackPanel.ContextMenu>
      </StackPanel>
    </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

์œ„ ์ฝ”๋“œ์—์„œ DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"๋Š”
๋ฉ”๋‰ด๊ฐ€ ๋…ธ๋“œ ์ž์ฒด๋ฅผ ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

4. ๋…ธ๋“œ ์ถ”๊ฐ€/์‚ญ์ œ ๋™์ž‘ ํ™•์ธ

  • ๋…ธ๋“œ๋ฅผ ์šฐํด๋ฆญํ•˜๋ฉด ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ / ํ•ญ๋ชฉ ์‚ญ์ œ ๋ฉ”๋‰ด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ์‹œ ํ•ด๋‹น ๋…ธ๋“œ์˜ ์ž์‹์œผ๋กœ ์ƒˆ๋กœ์šด ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.
  • ์‚ญ์ œ ์‹œ ์ƒ์œ„ ์ปฌ๋ ‰์…˜์—์„œ ํ•ด๋‹น ๋…ธ๋“œ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“  ๋กœ์ง์€ MVVM ๊ตฌ์กฐ ์•ˆ์—์„œ Command + ViewModel ์ฒ˜๋ฆฌ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

5. ๋งˆ๋ฌด๋ฆฌ ๋ฐ ๋‹ค์Œ ํŽธ ์˜ˆ๊ณ 

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” WPF TreeView์—์„œ ์šฐํด๋ฆญ ๋ฉ”๋‰ด(ContextMenu)๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋…ธ๋“œ๋ฅผ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ MVVM ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ContextMenu์—์„œ๋„ CommandParameter๋ฅผ ํ†ตํ•ด ๋ฐ”์ธ๋”ฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค!
  • ์‚ญ์ œ ์‹œ์—๋Š” ์žฌ๊ท€์ ์œผ๋กœ ๋ถ€๋ชจ๋ฅผ ์ฐพ์•„์„œ ์ œ๊ฑฐ
  • ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ViewModel์— ์žˆ๊ณ  View๋Š” Command์—๋งŒ ์—ฐ๊ฒฐ!

๋‹ค์Œ ์‹œ๋ฆฌ์ฆˆ์—์„œ๋Š” TreeView ๋…ธ๋“œ์˜ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๊ธฐ๋Šฅ(DnD)์„ ๋‹ค๋ฃฐ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
๋” ํ’์„ฑํ•œ TreeView UI๋ฅผ ๋งŒ๋“ค ์ค€๋น„๋˜์…จ๋‚˜์š”? ๐Ÿ˜‰