0

I am populating a treeview using a recursive function as shown below. The treeview gets populated properly, but i am not able to get it updated in UI during recursion. I am still new to threading ,but i tried to use the "updateTreeView" function given below...within the recursive function , and was not able to implement it properly. How can i achieve this functionality?? Please share some code...since i am not aware of threading.

For Simplicity, i have modified the codes. But, the recursive function is very complex and dealing with COM objects.

private void CreateMyTree(List<string> RootNodes,  TreeViewItem ParentNode)
    {
        if(mycheck here....)
         {
            for (int i = 1; i <= RootNodes.Count; i++)
            {
               TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
               ParentNode.Items.Add(NewTreeItem);    
            } 
         }
        else
        {            
           ///here some checks again and recursion again
           CreateMyTree(RootNodes, ParentNode)

        }
}

private void button1_Click(object sender, RoutedEventArgs e)
{
     //Create RootNode in TreeView
     TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };

     //Recursively add items to TreeView
     CreateMyTree(RootNode, ParentNode);

     //update TreeView GUI
     treeView1.Items.Add(ParentNode);

}

private void updateTreeView(TreeViewItem TreeItem)
{
  this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate()
   {
        treeView1.Items.Add(TreeItem);
   }));
}
911Rapid
  • 199
  • 3
  • 13
  • 3
    delete all that and use proper XAML and databinding. – Federico Berasategui Aug 31 '15 at 17:42
  • I think the ui redraw is event driven for most MS packaged controls. But @HighCore is right, if you want to avoid code debt, abstract your complicated COM objects and their data behind a data source that you can plug into a XAML defined control. Resist the temptation to rebuild the wheel. – J E Carter II Aug 31 '15 at 17:49
  • http://www.codemag.com/article/1401031 – eran otzap Aug 31 '15 at 18:08

3 Answers3

1

I think you should take HighCore advice and use proper XAML databinding, if you insist on going "old school" you can do this :

private static Action EmptyDelegate = delegate() { };
private void CreateMyTree(List<string> RootNodes,  TreeViewItem ParentNode)
    {
        if(mycheck here....)
         {
            for (int i = 1; i <= RootNodes.Count; i++)
            {
               TreeViewItem NewTreeItem = new TreeViewItem() { Header = RootNodes[i], IsExpanded = false };
               ParentNode.Items.Add(NewTreeItem);  
               updateTreeView();
            } 
         }
        else
        {            
           ///here some checks again and recursion again
           CreateMyTree(RootNodes, ParentNode)

        }
}

private void button1_Click(object sender, RoutedEventArgs e)
{
     //Create RootNode in TreeView
     TreeViewItem ParentNode = new TreeViewItem() { Header = "TopNode", IsExpanded = true };

      //update TreeView GUI
     treeView1.Items.Add(ParentNode);

     //Recursively add items to TreeView
     CreateMyTree(RootNode, ParentNode);



}

private void updateTreeView()
{
  treeView1.Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
}
Black0ut
  • 1,642
  • 14
  • 28
  • I know , the current code i have is not a better solution and it could be optimised with xaml and databinding. But, i am choosing this as answer because its almost a one-liner code solution for my current situation. Thankyou – 911Rapid Sep 01 '15 at 07:03
1

You should just follow along. This have nothing to do with threading. And there still needs to be a better implementation using a DataTemplateSelector if you want multiple levels.

The following is a sample for an hierarchy which spans 3 levels:

CS:

     public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private Entity _entity;
    public Entity Entity
    {
        get
        {
            if(_entity == null)
                _entity = new Entity();
            return _entity;
        }
    }

    public List<Entity> Entities
    {
        get { return CreateMyTree(); }
    }

    private List<Entity> CreateMyTree()
    {
        var list = new List<Entity>();

        var p1 = new Entity {Title = "Parent 1"};
        p1.Children.Add(new Entity{ Title = "Child 1"});
        p1.Children.Add(new Entity { Title = "Child 2" });

        var p2 = new Entity { Title = "Parent 2" };
        var c1 = new Entity { Title = "Child 1"};

        var g1 = new Entity {Title = "GrandChild 1"};
        c1.Children.Add(g1);

        var c2 = new Entity { Title = "Child 2" };
        p2.Children.Add(c1);
        p2.Children.Add(c2);

        list.Add(p1);
        list.Add(p2);

        return list;
    }

}

public class Entity : INotifyPropertyChanged
{
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;
        }
    }

    private List<Entity> _children;
    public List<Entity> Children
    {
        get
        {
            if(_children == null)
                _children = new List<Entity>();
            return _children;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

}

xaml :

 <Window x:Class="WpfApplication7.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:wpfApplication7="clr-namespace:WpfApplication7"
      Title="MainWindow" Height="350" Width="525">
   <Window.Resources>
      <DataTemplate x:Key="level3">
          <TextBlock Text="{Binding Title}" Foreground="Green" />
      </DataTemplate>

      <HierarchicalDataTemplate x:Key="rootTemplate" ItemsSource="{Binding Children}" >
           <TextBlock Text="{Binding Title}" Foreground="Red"  />
           <HierarchicalDataTemplate.ItemTemplate>
               <HierarchicalDataTemplate ItemsSource="{Binding Children}" ItemTemplate="{StaticResource level3}">
                   <TextBlock Text="{Binding Title}" Foreground="Blue" />
               </HierarchicalDataTemplate>
           </HierarchicalDataTemplate.ItemTemplate>

      </HierarchicalDataTemplate>

 </Window.Resources>
   <Grid>
       <TreeView ItemsSource="{Binding Entities}" ItemTemplate="{StaticResource rootTemplate}"/>
   </Grid>
</Window>
eran otzap
  • 12,293
  • 20
  • 84
  • 139
0

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>

Community
  • 1
  • 1
Stefan R.
  • 438
  • 1
  • 5
  • 22
  • Sorry, forgot to post the code to create the ViewModel and put it into the DataContext of the Window. This can happen in the Constructor of MainWindow: DataContext = new NodeViewModel(); – Stefan R. Aug 31 '15 at 20:48