Search code examples
c#treeviewwinui-3winuicommunity-toolkit-mvvm

How to Search in a Nested TreeView with ItemsSource?


i have a treeView which i loaded data with ItemsSource like this:

[ObservableProperty]
private ObservableCollection<FileSystemItem> items = new ObservableCollection<FileSystemItem>();

private async Task LoadFolder(string folderPath, ObservableCollection<FileSystemItem> parentCollection)
{
    foreach (string directoryPath in Directory.EnumerateDirectories(folderPath))
    {
        var directoryInfo = new DirectoryInfo(directoryPath);
        
        var directoryNode = new FileSystemItem(directoryInfo);
        await LoadFolder(directoryPath, directoryNode.Children);
        parentCollection.Add(directoryNode);
    }

    foreach (string filePath in Directory.EnumerateFiles(folderPath))
    {
        var fileInfo = new FileInfo(filePath);
        var fileNode = new FileSystemItem(fileInfo);
            parentCollection.Add(fileNode);
    }
}

now, i want to search in my treeview, i am using following code:

Items = new(Search(query, _backupItems));

public IEnumerable<FileSystemItem> Search(string query, ObservableCollection<FileSystemItem> nodes)
{
    foreach (var node in nodes)
    {
        if (node.DisplayName.Contains(query, StringComparison.OrdinalIgnoreCase))
        {
            yield return node;
        }

        foreach (var result in Search(query, node.Children))
        {
            yield return result;
        }
    }
}

but there is 2 issues: 1.after searching and removing text from textbox ItemTemplaceSelector uses a wrong template! 2.i want to show every searched item in its parent folder, currently all searched items added into treeview root level.


UPDATE 1:

This is a sample repo App5.zip

First of all change folder path (please create a folder with some sub folders which contains some ttf,otf or woff files. )

await LoadFilesAndFolders(@"D:\Applications\Font");

UPDATE 2:

i used following codes and it seems that everything is working fine.

public void OnAutoSuggestBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
    var query = sender.Text.ToLower();

    // Apply filter to the main collection
    advancedCollectionView.Filter = item =>
    {
        var fileSystemItem = item as FileSystemItem;
        if (fileSystemItem == null) return false;
        return FilterItems(fileSystemItem, query);
    };

    // Apply filter to children of each item in the main collection
    foreach (var item in Items)
    {
        ApplyFilterToItem(item, query);
    }
}

private void ApplyFilterToItem(FileSystemItem item, string query)
{
    item.MyChildren.Filter = childItem =>
    {
        var childFileSystemItem = childItem as FileSystemItem;
        if (childFileSystemItem == null) return false;
        return FilterItems(childFileSystemItem, query);
    };

    // Recursively apply the filter to each child
    foreach (var child in item.Children)
    {
        ApplyFilterToItem(child, query);
    }
}
bool FilterItems(FileSystemItem item, string query)
{
    // Check if the item matches the query
    if (item.DisplayName.ToLower().Contains(query))
    {
        return true;
    }

    // Recursively check if any child item matches the query
    foreach (var child in item.Children)
    {
        if (FilterItems(child, query))
        {
            return true;
        }
    }

    return false;
}

Solution

  • Did you mean this?

    enter image description here

    I make a little change on your code but note OnAutoSuggestBoxTextChanged need rewriting for the general condition.

    I use one more AdvancedCollectionView for children based on your application design.

    public class FileSystemItem
    {
        ...
        public ObservableCollection<FileSystemItem> Children { get; } = new ObservableCollection<FileSystemItem>();
        public AdvancedCollectionView MyChildren { get; } = new AdvancedCollectionView();
      
        public FileSystemItem(FileSystemInfo fileSystemInfo)
        {
            ...
            MyChildren = new AdvancedCollectionView(Children, true);
        }
    }
    

    XAML

    <TreeView x:Name="treeView"
              ItemsSource="{x:Bind ViewModel.advancedCollectionView, Mode=OneWay}"
              SelectionMode="Multiple">
        <TreeView.ItemTemplate>
            <DataTemplate x:DataType="local:FileSystemItem">
                <TreeViewItem ItemsSource="{x:Bind MyChildren}">
                    <TextBlock Text="{x:Bind DisplayName}" />
                </TreeViewItem>
            </DataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    

    The Code behind

        public void OnAutoSuggestBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
    {
        advancedCollectionView.Filter = _ => true;
        advancedCollectionView.Filter = item =>
        {
            var node = (FileSystemItem)item;
            var name = node.DisplayName ?? "";
    
            // Check if the current node matches the search text
            if (name.Contains(sender.Text, StringComparison.OrdinalIgnoreCase))
                return true;
    
            if (node.HasChildren)
            {
                node.MyChildren.Filter = _ => true;
                node.MyChildren.Filter = item =>
                {
                    var node = (FileSystemItem)item;
                    var name = node.DisplayName ?? "";
    
                    // Check if the current node matches the search text
                    if (name.Contains(sender.Text, StringComparison.OrdinalIgnoreCase))
                        return true;
                    else
                        return false;
    
                    //TODO rewrite the code. This is Just for example.
                };
                if (node.MyChildren.Count > 0)
                {
                    return true;
                }
            }
    
            // Check if any child node matches the search text
            //foreach (var child in node.Children)
            //{
            //    if (FilterRecursive(child, sender.Text))
            //        return true;
            //}
    
            // If none of the current node or its children match, return false
            return false;
    
        };
    }
    

    Update with the more general code example.

    private string _theFilterString;
    public void OnAutoSuggestBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
    {
        _theFilterString = sender.Text;
        advancedCollectionView.Filter = _ => true;
        advancedCollectionView.Filter = MyFilter;
    }
    
    bool MyFilter(object item)
    {
        var node = (FileSystemItem)item;
        var name = node.DisplayName ?? "";
    
        if (name.Contains(_theFilterString, StringComparison.OrdinalIgnoreCase))
            return true;
    
        if (node.HasChildren)
        {
            node.MyChildren.Filter = _ => true;
            node.MyChildren.Filter = MyFilter;
            if (node.MyChildren.Count > 0)
            {
                return true;
            }
        }
        return false;
    }