ItemSource ( observablecollection ) をconverterにバインドしている場合、再帰的なツリービューが子を更新しないという大きな問題があります。イライラする動作を説明するために、小さなサンプル WPF アプリを作成しました。
まず、Folder という名前のクラスがあります。フォルダーのコレクションは、実際のアプリの DataLayer から配信されます。簡単なクラスの例を次に示します。
// 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>();
}
}
プレゼンテーション層では、ツリービューで表示するためにさらにプロパティが必要です。Folder クラスを変更できません。
// Hold additional data to a Folder
public class TreeViewFolder
{
public Folder Folder { get; set; }
public bool IsExpanded { get; set; }
}
この例では、ルートレベルのテスト データを生成する ViewModel クラスを使用しています。タスクを介して、3 秒後にテスト データの変化をシミュレートします。1 つのルート フォルダーと 1 つの子フォルダーを追加します。
// 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"});
}));
}
}
コンバーターは、既に TreeViewFolder であるすべてのフォルダーを追跡するための辞書を実装し、1 つのフォルダーまたは ObservableCollection を TreeViewFolder または ObservableCollection に変換できます。逆変換は実装されていません。
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;
}
MainWindow.xaml には、2 つの TreeView しかありません。最初のものは、元のフォルダー (TreeViewFolder ではなく) を使用するとすべてが機能することを示すためのものです。2 番目の TreeView は、必要な方法です。
<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>
さらに、特別な TextBlock をフォルダーの子のカウントにバインドします。
ここでの問題は、2 番目の TreeView で、TextBlock がエントリ "2" の 3 秒後に Count=1 を表示することです。ただし、TreeView には [+] は表示されません。[+] はエントリ「3」にあります。けれど。上部の TreeView はうまく機能します。
ここで何が問題なのか教えてください。一番下の TreeView の 2 番目のエントリに [+] を表示するにはどうすればよいですか?
TreeViewFolder のプロパティ (継承、リフレクションなど) を使用してフォルダーを展開する他の方法があることは知っていますが、実際のアプリには適していません。ここでの主な問題は、対応する TreeViewFolder が異なるプロパティで n 回存在するアプリケーション全体に対して、1 つのフォルダーが 1 回だけ存在することです...
前もってありがとうソコ