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

WPF TreeView ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„

by samie 2025. 4. 16.
WPF TreeView ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„

๐Ÿ” WPF TreeView ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„

TreeView๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์›ํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ์ฐพ๊ธฐ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” TreeView์— ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ด€๋ จ ๋…ธ๋“œ๋งŒ ํ‘œ์‹œ๋˜๋„๋ก ๋งŒ๋“ค์–ด๋ด…๋‹ˆ๋‹ค.

1. ๊ฒ€์ƒ‰ ์ž…๋ ฅ UI ๋งŒ๋“ค๊ธฐ


<StackPanel>
  <TextBox Width="300" Margin="10"
           Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
           PlaceholderText="๋…ธ๋“œ ๊ฒ€์ƒ‰..." />

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

์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ViewModel๋กœ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.

2. ViewModel์—์„œ ํ•„ํ„ฐ๋ง ์ฒ˜๋ฆฌ


private ObservableCollection<Category> _allCategories;
public ObservableCollection<Category> FilteredCategories { get; set; } = new();

private string _searchText = "";
public string SearchText
{
    get => _searchText;
    set
    {
        _searchText = value;
        OnPropertyChanged();
        FilterTree();
    }
}

private void FilterTree()
{
    FilteredCategories.Clear();
    foreach (var cat in _allCategories)
    {
        var copy = FilterCategory(cat, _searchText);
        if (copy != null)
            FilteredCategories.Add(copy);
    }
}

private Category? FilterCategory(Category cat, string keyword)
{
    var matchedChildren = cat.Children
        .Select(child => FilterCategory(child, keyword))
        .Where(c => c != null)
        .ToList();

    bool isMatch = cat.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase);
    if (isMatch || matchedChildren.Count > 0)
    {
        return new Category
        {
            Name = cat.Name,
            Children = new ObservableCollection<Category>(matchedChildren)
        };
    }
    return null;
}

ํŠธ๋ฆฌ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํƒ์ƒ‰ํ•˜๋ฉด์„œ ํ‚ค์›Œ๋“œ์™€ ์ผ์น˜ํ•˜๊ฑฐ๋‚˜ ์ž์‹ ์ค‘์— ์ผ์น˜ํ•˜๋Š” ๋…ธ๋“œ๋ฅผ ๋ณต์‚ฌํ•˜์—ฌ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

3. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ž๋™ ํ™•์žฅ

ํ•„ํ„ฐ๋ง๋œ ํŠธ๋ฆฌ ํ•ญ๋ชฉ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์—ด๋ ค ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด ๋…ธ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ํ™•์žฅ๋˜๋„๋ก ์ถ”๊ฐ€ ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


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

    private bool _isExpanded = false;
    public bool IsExpanded
    {
        get => _isExpanded;
        set { _isExpanded = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string? name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

๊ฒ€์ƒ‰ ์‹œ ๊ฒฐ๊ณผ๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ IsExpanded = true๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ์ž๋™ ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

4. ์ •๋ฆฌ ๋ฐ ํ™•์žฅ ์•„์ด๋””์–ด

  • TreeView์—์„œ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์„ ํ†ตํ•ด UX๋ฅผ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ๊ฒ€์ƒ‰์–ด ํ•˜์ด๋ผ์ดํŠธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ (TextBlock ์Šคํƒ€์ผ ๋ณ€๊ฒฝ)
  • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์„ ํƒ ์‹œ ํ•ด๋‹น ๋…ธ๋“œ๋ฅผ ๊ฐ•์กฐํ•˜๊ฑฐ๋‚˜ ์ž๋™ ์Šคํฌ๋กค ๊ธฐ๋Šฅ๋„ ๊ตฌํ˜„ ๊ฐ€๋Šฅ

๋‹ค์Œ ํŽธ์—์„œ๋Š” TreeView์—์„œ ์ฒดํฌ๋ฐ•์Šค ๋…ธ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์ƒ์œ„/ํ•˜์œ„ ๋…ธ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ฒดํฌ๋˜๋„๋ก ์—ฐ๊ฒฐํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค! โœ