web-dev-qa-db-ja.com

ICollectionViewを使用してwpfツリービュー階層をフィルタリングするにはどうすればよいですか?

このデータを含む架空のツリービューがあります。

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全体に適用できますか?

23
David

残念ながら、同じフィルターをすべてのノードに自動的に適用する方法はありません。 Filterは、DependencyObjectではないItemsCollectionのプロパティ(DPではない)であるため、DP値の継承はありません。

ツリー内の各ノードには、独自のFilterを持つ独自のItemsCollectionがあります。それを機能させる唯一の方法は、同じデリゲートを呼び出すようにすべてを手動で設定することです。

最も簡単な方法は、ToolBoxViewModelでPredicate <object>タイプのFilterプロパティを公開し、そのセッターでイベントを発生させることです。次に、ToolboxItemViewModelは、このイベントの消費とそのフィルターの更新を担当します。

かわいくないので、ツリー内の大量のアイテムのパフォーマンスがどのようになるかわかりません。

4
Alex_P

これが、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");
7
Tono Nam

これを行うために私が見つけた唯一の方法(これは少しハックです)は、IListからIEnumerableに変換するValueConverterを作成することです。 ConvertTo()で、渡されたIListから新しいCollectionViewSourceを返します。

もっと良い方法があれば、聞いてみたいです。ただし、これは機能するようです。

4
Andy

ここで言及されているPhilippSumiによるツリービューを使用することにしました: http://www.codeproject.com/KB/WPF/versatile_treeview.aspx

そして、ここに示すようにそれにフィルターを適用しました: http://www.hardcodet.net/2008/02/programmatically-filtering-the-wpf-treeview

私はそれを十分に推薦することができませんでした:)

2
basarat

ItemContainerGeneratorを使用して、ツリー内の特定の要素のTreeViewItemを取得できます。取得したら、フィルターを設定することができます。

0
Peter Wone

なぜフィルターや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

0
agroskin