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

WPF TreeView ์„ ํƒ ์ƒํƒœ ์ €์žฅ ๋ฐ ๋ณต์›

by samie 2025. 4. 13.
WPF TreeView ์„ ํƒ ์ƒํƒœ ์ €์žฅ ๋ฐ ๋ณต์›

๐Ÿ’พ WPF TreeView ์„ ํƒ ์ƒํƒœ ์ €์žฅ ๋ฐ ๋ณต์›

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” WPF์˜ TreeView์—์„œ ์„ ํƒ๋œ ๋…ธ๋“œ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
MVVM ๊ตฌ์กฐ๋กœ IsSelected ๋ฐ”์ธ๋”ฉ์„ ๊ตฌํ˜„ํ•˜๊ณ , ์„ ํƒ๋œ ํ•ญ๋ชฉ์„ JSON์œผ๋กœ ์ €์žฅํ•ด ์•ฑ ์žฌ์‹คํ–‰ ์‹œ ์ž๋™ ๋ณต์›ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

1. Category ๋ชจ๋ธ์— IsSelected ์ถ”๊ฐ€


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

    private bool _isSelected;
    public bool IsSelected
    {
        get => _isSelected;
        set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

2. ViewModel์—์„œ ์„ ํƒ ํ•ญ๋ชฉ ์ถ”์  ๋ฐ ์ €์žฅ


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

    public MainViewModel()
    {
        Categories = LoadFromJson();
    }

    public List<string> GetSelectedPaths()
    {
        List<string> selected = new();
        Traverse(Categories, "", selected);
        return selected;
    }

    private void Traverse(IEnumerable<Category> list, string path, List<string> selected)
    {
        foreach (var item in list)
        {
            string currentPath = string.IsNullOrEmpty(path) ? item.Name : $"{path}/{item.Name}";

            if (item.IsSelected)
                selected.Add(currentPath);

            Traverse(item.Children, currentPath, selected);
        }
    }

    public void SaveSelection()
    {
        var selected = GetSelectedPaths();
        File.WriteAllText("selection.json", JsonSerializer.Serialize(selected));
    }

    public ObservableCollection<Category> LoadFromJson()
    {
        var categories = new ObservableCollection<Category>
        {
            new Category
            {
                Name = "ํ”„๋กœ๊ทธ๋ž˜๋ฐ",
                Children = new ObservableCollection<Category>
                {
                    new Category { Name = "C#" },
                    new Category { Name = "Java" }
                }
            },
            new Category
            {
                Name = "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค",
                Children = new ObservableCollection<Category>
                {
                    new Category { Name = "Oracle" },
                    new Category { Name = "MySQL" }
                }
            }
        };

        if (File.Exists("selection.json"))
        {
            var saved = JsonSerializer.Deserialize<List<string>>(File.ReadAllText("selection.json"));
            ApplySelection(categories, "", saved);
        }

        return categories;
    }

    private void ApplySelection(IEnumerable<Category> list, string path, List<string>? selected)
    {
        foreach (var item in list)
        {
            string currentPath = string.IsNullOrEmpty(path) ? item.Name : $"{path}/{item.Name}";
            if (selected?.Contains(currentPath) == true)
                item.IsSelected = true;

            ApplySelection(item.Children, currentPath, selected);
        }
    }
}

3. XAML์—์„œ TreeViewItem ์Šคํƒ€์ผ ์ •์˜


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

  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>

<Button Content="์„ ํƒ ์ €์žฅ" Command="{Binding SaveCommand}" Margin="10" />

TreeViewItem์˜ IsSelected ์†์„ฑ์„ ๋ฐ”์ธ๋”ฉํ•˜๋ฉด MVVM ๊ตฌ์กฐ์—์„œ๋„ ์„ ํƒ ์ƒํƒœ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ์„ ํƒ ํ•ญ๋ชฉ JSON ์ €์žฅ ๋ฐ ๋ณต์›

  • ์„ ํƒ ํ•ญ๋ชฉ์€ ๊ฒฝ๋กœ(path) ํ˜•ํƒœ๋กœ ์ €์žฅ๋จ
  • ์˜ˆ์‹œ: "ํ”„๋กœ๊ทธ๋ž˜๋ฐ/C#", "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค/Oracle"
  • ์ €์žฅ๋œ JSON ํŒŒ์ผ์„ ํ†ตํ•ด ์•ฑ ์žฌ์‹คํ–‰ ์‹œ ์ƒํƒœ ๋ณต์›

์‹ค์ œ ์ €์žฅ ์˜ˆ์‹œ (selection.json):


[
  "ํ”„๋กœ๊ทธ๋ž˜๋ฐ/C#",
  "๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค/Oracle"
]

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

TreeView์˜ ์„ ํƒ ์ƒํƒœ ์ €์žฅ ๊ธฐ๋Šฅ์€ UX ์ธก๋ฉด์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž์˜ ์„ ํƒ์„ ๊ธฐ์–ตํ•˜๊ณ  ๋ณต์›ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์•ฑ์˜ ์™„์„ฑ๋„๋ฅผ ๋†’์—ฌ์ค๋‹ˆ๋‹ค.

  • MVVM ๊ตฌ์กฐ๋กœ๋„ IsSelected ๋ฐ”์ธ๋”ฉ ๊ฐ€๋Šฅ
  • ๊ฒฝ๋กœ ๊ธฐ๋ฐ˜ ์ €์žฅ์œผ๋กœ ํŠธ๋ฆฌ ๊ตฌ์กฐ ์ „์ฒด ์ง€์›
  • JSON ํŒŒ์ผ์„ ํ†ตํ•ด ๊ฐ„ํŽธํ•œ ๋ณต์› ์ฒ˜๋ฆฌ

๋‹ค์Œ ํŽธ์—์„œ๋Š” TreeView ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ(Drag & Drop) ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค! ๐Ÿ–ฑ๏ธ ๋…ธ๋“œ ์œ„์น˜๋ฅผ ์ง์ ‘ ๋ฐ”๊พธ๋Š” ์œ ์ € ์ธํ„ฐ๋ž™์…˜์„ ๋‹ค๋ค„๋ณด์•„์š”.