Заполнение TreeView из списка путей в WPF

Необходимо заполнить TreeView из некоего списка:

var paths = new List<string>
            {
                @"DD\flora\green_tree\tree_0",
                @"DD\flora\green_tree\tree_1",
                @"DD\flora\dead_tree\Dtree_2",
                @"DD\flora\green_tree\tree_3",
                @"DD\flora\green_tree\tree_4",
                @"DD\wall\wooden_wall\wall_0",
                @"DD\window\wooden_window\window_0",
                @"DD\wall\wooden_wall\wall_1",
                @"DD\wall\wooden_wall\wall_2",
                @"DD\flora\green_bush\bush_0",
                @"BD\flora\dead_bush\Dbush_1",
                @"BD\flora\green_bush\bush_1"
            };

Сейчас использую следующий код:

FillingTreeView(MyTreeView, paths, '\\');
private static void FillingTreeView(TreeView treeView, IEnumerable<string> paths, char separator)
{
    TreeViewItem? lastNode = null;
    string subPathAgg;
    foreach (string path in paths)
    {
        lastNode = null;
        subPathAgg = "";
        foreach (string subPath in path.Split(separator))
        {
            subPathAgg += subPath + separator;
            TreeViewItem? nodes = SearchTreeView(subPathAgg, treeView.Items);
            if (nodes == null)
            {
                if (lastNode == null)
                {
                    treeView.Items.Add(new TreeViewItem() { Header = subPath, Tag = subPathAgg });
                    lastNode = (TreeViewItem)treeView.Items[^1];
                }
                else
                {
                    lastNode.Items.Add(new TreeViewItem() { Header = subPath, Tag = subPathAgg });
                    lastNode = (TreeViewItem)lastNode.Items[^1];
                }
            }
            else
            {
                lastNode = nodes;
            }
        }
    }
}
private static TreeViewItem? SearchTreeView(string subPathAgg, ItemCollection Nodes)
{
    TreeViewItem? returnValue = null;
    foreach (TreeViewItem node in Nodes)
    {
        if (string.Equals(((HeaderedItemsControl)node).Tag.ToString(), subPathAgg) == true)
        {
            returnValue = node;
            return returnValue;
        }
        if (node.Items.Count > 0) returnValue = SearchTreeView(subPathAgg, node.Items);
    }
    return returnValue;
}

Все работает почти как нужно, но иногда получается вот так:

введите сюда описание изображения

Как можно увидеть, есть повторяющиеся пути "wooden_wall", которые должны быть одним узлом. Я с WPF работаю впервые, надеюсь на вашу помощь.


Ответы (1 шт):

Автор решения: aepot

Много лишнего делаете. Всё может стать проще, если использовать привязки.

Добавлю класс для поддержки динамических привязок

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null!)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Сделаю вот такую ноду как модель данных

public class TreeNode<T> : NotifyPropertyChanged
{
    private T _value;

    public T Value
    {
        get => _value;
        set
        {
            _value = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<TreeNode<T>> Children { get; }

    public TreeNode(T value)
    {
        _value = value;
        Children = new();
    }
}

Ну и дальше очень простой код, вот вам окно целиком

public partial class MainWindow : Window
{
    public ObservableCollection<TreeNode<string>> Nodes { get; } = new();
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var paths = new List<string>
        {
            @"DD\flora\green_tree\tree_0",
            @"DD\flora\green_tree\tree_1",
            @"DD\flora\dead_tree\Dtree_2",
            @"DD\flora\green_tree\tree_3",
            @"DD\flora\green_tree\tree_4",
            @"DD\wall\wooden_wall\wall_0",
            @"DD\window\wooden_window\window_0",
            @"DD\wall\wooden_wall\wall_1",
            @"DD\wall\wooden_wall\wall_2",
            @"DD\flora\green_bush\bush_0",
            @"BD\flora\dead_bush\Dbush_1",
            @"BD\flora\green_bush\bush_1"
        };
        FillNodes(paths);
    }

    private void FillNodes(List<string> paths)
    {
        foreach (var path in paths)
        {
            string[] tokens = path.Split('\\');
            var children = Nodes;
            foreach (string token in tokens)
            {
                // ищем, есть ли уже такая нода
                TreeNode<string> node = children.FirstOrDefault(x => x.Value == token);
                if (node is null)
                {
                    // если нет, то добавляем новую
                    node = new(token);
                    children.Add(node);
                }
                children = node.Children;
            }
        }
    }
}

Вот XAML целиком

<Window x:Class="WpfAppTreeView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppTreeView"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded"
        d:DataContext="{d:DesignInstance Type={x:Type local:MainWindow}, IsDesignTimeCreatable=False}">
    <Grid>
        <TreeView ItemsSource="{Binding Nodes}">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="True"/>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding Value}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>
</Window>

Вот результат

введите сюда описание изображения

Соответственно string можно поменять на что угодно, на любую модель данных и сверстать внутри HierarchicalDataTemplate любую XAML разметку для отображения этих данных.

Дополнительная прелесть этих привязок и такой реализации в том, что вы в процессе работы приложения можете в коллекцию с нодами вносить любые изменения, и они сразу же отобразятся в режиме реального времени в TreeView.

→ Ссылка