The previous posts are completely right about the design with data binding. Additionally, if you want to solve the issue to fill the tree asynchronously, you have to do this in another thread/task. With "ObservableCollection", you can also keep your tree up-to-date while this task is running and changing the tree. But be careful - ObservableCollections don't like it when they are being changed from other thread then the Ui thread. This issue was discussed here and here.
In the following example that I wrote, I simply call the Dispatcher for each modification of the ObservableCollection, but it's not the best solution.
In the example, you can call the tree creation synchronously or asynchronously - so you can understand the difference in implementation and behaviour of the Ui.
namespace TreeViewExample
{
public class MyNode
{
public ObservableCollection<MyNode> ChildNodes { get; set; }
public int Number { get; set; }
public MyNode()
{
ChildNodes = new ObservableCollection<MyNode>();
}
}
public class SimpleCommand : ICommand
{
private readonly Action _action;
public SimpleCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter is string && (string) parameter == "async")
{
new TaskFactory().StartNew(_action);
}
else _action();
}
public event EventHandler CanExecuteChanged;
}
public class NodeViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyNode> _rootNodes;
public ObservableCollection<MyNode> RootNodes
{
set
{
_rootNodes = value;
OnPropertyChanged();
}
get { return _rootNodes; }
}
private readonly ICommand _populateCommand;
private readonly Random _random;
private Dispatcher _dispatcher;
public ICommand PopulateCommand { get { return _populateCommand; } }
public NodeViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
_random = new Random();
_populateCommand = new SimpleCommand(PopulateTree);
RootNodes = new ObservableCollection<MyNode>();
}
private void PopulateTree()
{
try
{
var node = new MyNode {Number = 0};
if (_dispatcher.CheckAccess())
RootNodes.Add(node);
else _dispatcher.Invoke(() => RootNodes.Add(node));
FillNodeRecursively(node, 1);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private void FillNodeRecursively(MyNode rootNode, int level)
{
int rand = _random.Next(0, 4);
for (var i = 0; i <= rand; i++)
{
var subNode = new MyNode {Number = rand + i};
Thread.Sleep(50); //simulating some workload
if (_dispatcher.CheckAccess())
rootNode.ChildNodes.Add(subNode);
else _dispatcher.Invoke(() => rootNode.ChildNodes.Add(subNode));
if (level < 4)
FillNodeRecursively(subNode, level + 1);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the xaml for a WPF window:
<Window x:Class="TreeViewExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate x:Key="NodesTemplate" ItemsSource="{Binding Path=ChildNodes}" >
<TextBlock Text="{Binding Path=Number}" />
</HierarchicalDataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Bottom" >
<Button Command="{Binding PopulateCommand}" CommandParameter="sync">Sync</Button>
<Button Command="{Binding PopulateCommand}" CommandParameter="async">Async</Button>
</StackPanel>
<TreeView DockPanel.Dock="Top" ItemsSource="{Binding RootNodes}" ItemTemplate="{StaticResource NodesTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>