Обход файловой системы WPF

Я пытаюсь реализовать аналог программы TreeSize на WPF. Основная проблема - не могу заставить свой код обновлять интерфейс по мере обхода дерева (файловой системы). В оригинале это выглядит вот так: https://ibb.co/Mg30Pk4

Мой код сначала обходит всю файловую систему и только потом отрисовывает дерево. Ниже какая-то по счету попытка добиться результата (неудачная).

Model

public class TreeNode : INotifyPropertyChanged
    {
        private string _name;
        private long _size;
        private ObservableCollection<TreeNode> _nodes;

        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                OnPropertyChanged("Size");
            }
        }

        public long Size { get => _size;
            set
            {
                _size = value;
                OnPropertyChanged("Size");
            }

        }

        public ObservableCollection<TreeNode> Nodes
        {
            get => _nodes;
            set
            {
                _nodes = value;
                OnPropertyChanged("Nodes");
            }

        }
        public event PropertyChangedEventHandler PropertyChanged;

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

ViewModel + обход файловой системы

    public class TreeNodeViewModel : INotifyPropertyChanged
    {
        private TreeNode _treeNode;

        public TreeNode TreeNode
        {
            get => _treeNode;
            set
            {
                _treeNode = value;
                OnPropertyChanged(nameof(TreeNode));
            }
        }
        public TreeNodeViewModel()
        {
            _treeNode = new TreeNode{Name = @"C:\"};
        }

        public string Name
        {
            get => _treeNode.Name;
            set
            {
                _treeNode.Name = value;
                OnPropertyChanged("Name");
            }
        }

        public long Size
        {
            get => _treeNode.Nodes.Sum(x => x.Size);
            set
            {
                _treeNode.Size = value;
                OnPropertyChanged("Size");
            }

        }

        public ObservableCollection<TreeNode> Nodes
        {
            get => _treeNode.Nodes;
            set
            {
                _treeNode.Nodes = value;
                OnPropertyChanged("Nodes");
            }

        }
        public event PropertyChangedEventHandler PropertyChanged;

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

        public void EnumerateFs()
        {
            TreeNode.Nodes = new ObservableCollection<TreeNode>();
            EnumerationOptions options = new EnumerationOptions { AttributesToSkip = FileAttributes.ReparsePoint, IgnoreInaccessible = true };
            var entries = (new DirectoryInfo(TreeNode.Name)).EnumerateFileSystemInfos("*", options);
            foreach (var nodeEntry in entries)
            {
                if (nodeEntry is FileInfo fi)
                {
                    TreeNode.Nodes.Add(new TreeNode { Name = fi.Name, Size = fi.Length });
                    OnPropertyChanged("Nodes");
                }
                else
                {
                    var newNode = new TreeNode { Name = nodeEntry.FullName, Nodes = new ObservableCollection<TreeNode>() };
                    TreeNode.Nodes.Add(newNode);
                    newNode.Nodes.Add(EnumerateFs(newNode));
                    OnPropertyChanged("Nodes");
                }
            }
        }

        public TreeNode EnumerateFs(TreeNode node)
        {
            node.Nodes = new ObservableCollection<TreeNode>();
            EnumerationOptions options = new EnumerationOptions { AttributesToSkip = FileAttributes.ReparsePoint, IgnoreInaccessible = true };
            var entries = (new DirectoryInfo(node.Name)).EnumerateFileSystemInfos("*", options);
            foreach (var nodeEntry in entries)
            {
                if (nodeEntry is FileInfo fi)
                {
                    node.Nodes.Add(new TreeNode { Name = fi.Name, Size = fi.Length });
                }
                else
                {
                    var newNode = new TreeNode { Name = nodeEntry.FullName, Nodes = new ObservableCollection<TreeNode>() };
                    node.Nodes.Add(newNode);
                    newNode.Nodes.Add(EnumerateFs(newNode));
                }
            }
            return node;
        }
    }

View

        <TreeView Grid.Row="1" x:Name="TreeView">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:TreeNodeViewModel}" ItemsSource="{Binding IsAsync=True, Path=Nodes, UpdateSourceTrigger=PropertyChanged}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Width="200" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
                        <TextBlock Width="100" Text="{Binding Size, Converter={StaticResource FileSizeConverter}, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>

MainWindow

 public partial class MainWindow : Window
    {
        TreeNodeViewModel vm; 
        public MainWindow()
        {
            InitializeComponent();
            vm = new TreeNodeViewModel();
            TreeView.ItemsSource = new ObservableCollection<TreeNodeViewModel> {vm};
            Task.Run(() => vm.EnumerateFs());
        }
}

Реально ли вообще добиться перерисовки во время рекурсивного обхода? Если да, то как? Не осталось никаких идей уже.


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