I have a big problem that a recursive treeview doesn't update any childs if I'm binding the ItemSource (observablecollection) with a converter. I made up a small example WPF app to explain the irritating behaviour.
First I have a class named Folder. A collection of Folders is delivered from the DataLayer in the real app. Here's the simple example class:
// Represents a Folder from the database
public class Folder
{
public string Label { get; set; }
// Data recursion
public ObservableCollection<Folder> Children { get; set; }
public Folder()
{
Children = new ObservableCollection<Folder>();
}
}
In the presentation layer I need more properties to view it in a treeview. I'm not able to alter the Folder class.
// Hold additional data to a Folder
public class TreeViewFolder
{
public Folder Folder { get; set; }
public bool IsExpanded { get; set; }
}
I hava a ViewModel class in my example that generates some root-level test data. Via a task I simulate a change in the test data after 3 seconds: Adding one root and one child Folder.
// ViewModel to MainWindow.xaml
public class ViewModel
{
public ObservableCollection<Folder> Folders { get; set; }
public ObservableCollection<TreeViewFolder> TreeViewFolders { get; set; }
public ViewModel()
{
// childfolder
var childs = new ObservableCollection<Folder>();
childs.Add(new Folder{Label="3.1"});
// Generate some test data
Folders = new ObservableCollection<Folder>
{
new Folder {Label = "1."},
new Folder {Label = "2."},
new Folder {Label = "3.", Children = childs},
new Folder {Label = "4."}
};
// Add the test data to new TreeViewFolders
TreeViewFolders = new ObservableCollection<TreeViewFolder>
{
new TreeViewFolder{Folder = Folders[0]},
new TreeViewFolder{Folder = Folders[1]},
new TreeViewFolder{Folder = Folders[2]},
new TreeViewFolder{Folder = Folders[3]}
};
// This is the UI Thread
Execute.InitializeWithDispatcher();
// Wait for 3 seconds...
Task.Factory.StartNew(() =>
System.Threading.Thread.Sleep(3000))
// ...and then add a new main folder and one child folder
.ContinueWith((pre) =>
Execute.InvokeOnUIThread(() => {
Folders.Add( new Folder() {Label = "5."});
TreeViewFolders.Add(new TreeViewFolder{Folder = Folders[4]});
Folders[1].Children.Add(new Folder {Label = "2.1"});
}));
}
}
The converter implements a dictionary to keep track of all Folders that are allready a TreeViewFolder and is able to convert one Folder or a ObservableCollection into TreeViewFolder or ObservableCollection. The back-conversion is not implemented.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//DictionaryLookUp just holds a Folder <-> TreeViewFolder relation in a Dictionary<Folder, TreeViewFolder> for performance issues
// Single object
if (targetType == typeof(TreeViewFolder)) {
var inputFolder = value as Folder;
if (inputFolder == null) return null;
return DictionaryLookUp(inputFolder);
}
// Collection of Folders
// WPF SAYS IT WANTS AN IENUMERABLE??? I GIVE AN OBSERVABLECOLLECTION
if (targetType == typeof(IEnumerable)) {
var inputFolders = value as IEnumerable<Folder>;
if (inputFolders == null) return null;
var outputFolders = new ObservableCollection<TreeViewFolder>();
foreach (var folder in inputFolders) {
outputFolders.Add(DictionaryLookUp(folder));
}
return outputFolders;
}
//Error!
return null;
}
In the MainWindow.xaml I have just two TreeViews. The first one is again just to demonstrate that everything works when I use the original Folder (not the TreeViewFolder). The second TreeView is how I need it.
<Window x:Class="TreeViewConverterBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewConverterBinding"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<local:FolderToTreeViewFolderConverter x:Key="FtTVFConverter" />
</Window.Resources>
<StackPanel >
<Label Content="TreeView for Folders: Binding without Converter" />
<TreeView ItemsSource="{Binding Folders}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:Folder}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Label Content="TreeView for TreeViewFolders: Binding to same Source with Converter" />
<TreeView ItemsSource="{Binding TreeViewFolders}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:TreeViewFolder}"
ItemsSource="{Binding Folder.Children, Converter={StaticResource FtTVFConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Folder.Label}"/>
<TextBlock Text=" Folder.Children.Count=" />
<TextBlock Text="{Binding Folder.Children.Count}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
Furthermore I bind a special TextBlock to the Count of the Children of a Folder.
The issue here is, that in the second TreeView the TextBlock displays a Count=1 after the 3 seconds at entry "2." but no [+] appears in the TreeView. The [+] is there in entry "3." though. The upper TreeView works great!
Can anyone please tell me whats the problem here?? How do I make the [+] appear in the 2nd entry of the bottom TreeView?
I know that there are other methods to expand the Folder with the properties in the TreeViewFolder (inheritance, reflection, ...) but that are not suitable for real app. The main issue here is, that one Folder only exists one time for the whole application whereat the corresponding TreeViewFolder exists n times with different properties...
Thanks in advance Soko