このデータを含む架空のツリービューがあります。
RootNode
Leaf
vein
SecondRoot
seeds
flowers
特定のテキストを含むノードのみを表示するために、ノードをフィルタリングしようとしています。 「L」を指定すると、ツリーがフィルタリングされ、RootNode-> LeafとSecondRoot-> flowersのみが表示されます(両方に文字Lが含まれているため)。
M-v-vmパターンに従って、次のような基本的なTreeViewViewModelクラスがあります。
public class ToolboxViewModel
{
...
readonly ObservableCollection<TreeViewItemViewModel> _treeViewItems = new ObservableCollection<TreeViewItemViewModel>();
public ObservableCollection<TreeViewItemViewModel> Headers
{
get { return _treeViewItems; }
}
private string _filterText;
public string FilterText
{
get { return _filterText; }
set
{
if (value == _filterText)
return;
_filterText = value;
ICollectionView view = CollectionViewSource.GetDefaultView(Headers);
view.Filter = obj => ((TreeViewItemViewModel)obj).ShowNode(_filterText);
}
}
...
}
そして基本的なTreeViewItemViewModel:
public class ToolboxItemViewModel
{
...
public string Name { get; private set; }
public ObservableCollection<TreeViewItemViewModel> Children { get; private set; }
public bool ShowNode(string filterText)
{
... return true if filterText is contained in Name or has children that contain filterText ...
}
...
}
すべてがxamlで設定されているため、ツリービューと検索ボックスが表示されます。
このコードが実行されると、フィルターは不十分なルートノードにのみ適用されます。述語がすべてのノードに対して呼び出されるように、フィルターをノードの階層内で細流化する方法はありますか?つまり、フィルターをTreeView全体に適用できますか?
残念ながら、同じフィルターをすべてのノードに自動的に適用する方法はありません。 Filterは、DependencyObjectではないItemsCollectionのプロパティ(DPではない)であるため、DP値の継承はありません。
ツリー内の各ノードには、独自のFilterを持つ独自のItemsCollectionがあります。それを機能させる唯一の方法は、同じデリゲートを呼び出すようにすべてを手動で設定することです。
最も簡単な方法は、ToolBoxViewModelでPredicate <object>タイプのFilterプロパティを公開し、そのセッターでイベントを発生させることです。次に、ToolboxItemViewModelは、このイベントの消費とそのフィルターの更新を担当します。
かわいくないので、ツリー内の大量のアイテムのパフォーマンスがどのようになるかわかりません。
これが、TreeView
のアイテムをフィルタリングする方法です。
私はクラスを持っています:
class Node
{
public string Name { get; set; }
public List<Node> Children { get; set; }
// this is the magic method!
public Node Search(Func<Node, bool> predicate)
{
// if node is a leaf
if(this.Children == null || this.Children.Count == 0)
{
if (predicate(this))
return this;
else
return null;
}
else // Otherwise if node is not a leaf
{
var results = Children
.Select(i => i.Search(predicate))
.Where(i => i != null).ToList();
if (results.Any()){
var result = (Node)MemberwiseClone();
result.Items = results;
return result;
}
return null;
}
}
}
次に、結果を次のようにフィルタリングできます。
// initialize Node root
// pretend root has some children and those children have more children
// then filter the results as:
var newRootNode = root.Search(x=>x.Name == "Foo");
これを行うために私が見つけた唯一の方法(これは少しハックです)は、IListからIEnumerableに変換するValueConverterを作成することです。 ConvertTo()で、渡されたIListから新しいCollectionViewSourceを返します。
もっと良い方法があれば、聞いてみたいです。ただし、これは機能するようです。
ここで言及されているPhilippSumiによるツリービューを使用することにしました: http://www.codeproject.com/KB/WPF/versatile_treeview.aspx
そして、ここに示すようにそれにフィルターを適用しました: http://www.hardcodet.net/2008/02/programmatically-filtering-the-wpf-treeview
私はそれを十分に推薦することができませんでした:)
ItemContainerGenerator
を使用して、ツリー内の特定の要素のTreeViewItemを取得できます。取得したら、フィルターを設定することができます。
なぜフィルターやCollectionSourceが必要なのですか? TreeViewアイテムを処理する簡単なMVVMの方法は次のとおりです。
DataTriggersを使用するだけで、アイテムを表示、折りたたみ、色の変更、ハイライト、フラッシュなどを行うことができます。
public class Item : INotifyPropertyChanged
{
public string Title { get; set; } // TODO: Notify on change
public bool VisibleSelf { get; set; } // TODO: Notify on change
public bool VisibleChildOrSelf { get; set; } // TODO: Notify on change
public ObservableCollection<Item> Items { get; set; } // TODO: Notify on change
public void CheckVisibility(string searchText)
{
VisibleSelf = // Title contains SearchText. You may use RegEx with wildcards
VisibleChildOrSelf = VisibleSelf;
foreach (var child in Items)
{
child.CheckVisibility(searchText);
VisibleChildOrSelf |= child.VisibleChildOrSelf;
}
}
}
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Item> Source { get; set; } // TODO: Notify on change
public string SearchText { get; set; } // TODO: Notify on change
private void OnSearchTextChanged() // TODO: Action should be delayed by 500 millisec
{
foreach (var item in Source) item.CheckVisibility(SearchText);
}
}
<StackPanel>
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth="200" Margin="5"/>
<TreeView ItemsSource="{Binding Source}" Margin="5">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding VisibleChildOrSelf}" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding VisibleSelf}" Value="false">
<Setter Property="Foreground" Value="Gray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<StackPanel>
完全な例をWPFライブラリに含めます。
https://www.codeproject.com/Articles/264955/WPF-MichaelAgroskin