Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feature Request MultiColumnListView with a ToolbarSearchField

Discussion in 'UI Toolkit' started by skjalgsm, Apr 15, 2023.

  1. skjalgsm

    skjalgsm

    Joined:
    Aug 5, 2021
    Posts:
    2
    Hello!

    I am currently rewriting an existing editor tool for our game from IMGUI controls to using the new UI Toolkit. One thing that was already implemented was a ListView with multiple colums using the built in IMGUI TreeView.

    And to my surprise that already existed as MultiColumnListView in UI Toolkit. So far so great. Problem I encountered pretty quickly though, is that TreeView has a field called searchString. When you set that string it automatically filters the list for you and keeps the current indexes up to date with the serialized objects array, which is very handy when you try to search in the list.

    MultiColumnListView does not have that feature. And even worse, it needs a itemsSource IList to be passed into it to work instead of a serialized property, which means that if I try to write my own search functionality by manipulating that list I am digging my hole pretty deep because the cells in the view are all individually bound by a serializedProperty, as opposed to the entire list being bound by a serializedProperty.

    That means if I have a list with 10 entries and I set that as the sourceItems IList, and then manipulate that list using a search field like ToolbarSearchField by removing items, the actual items that will be displayed in the MultiColumnListView are the wrong ones because the indexes don't match up any more with the serializedProperty that is bound to each individual cell. What's worse is, I now have to manually deal with replacing the correct items in the source list from my manipulated list and I have to store my own indexes and its pretty much all a mess when I all I really want is the data to stay the same, but the view to filter the search results.

    I find it really strange that having this whole setup with view, controller and data being separate that the equivalent IMGUI control actually have a better data / view arrangement that allows for filtering.

    Is this something Unity is aware of and will fix/introduce?
     
  2. noirb

    noirb

    Joined:
    Apr 10, 2014
    Posts:
    84
    I had to do something similar recently, and wound up writing a wrapper around ListView which maintained two ILists: the "true" list of all elements, and the list given to the ListView for rendering. Given a search string, we run the query on the true list and use the results to update the ListView's itemsSource.
     
  3. skjalgsm

    skjalgsm

    Joined:
    Aug 5, 2021
    Posts:
    2
    That's very interesting. Mind sharing the code?

    I've been trying to do something similar using MultiColumnListView, but it just flat out crashes Unity
    Code (CSharp):
    1. public class Pita<T> : MultiColumnListView
    2.     {
    3.         public Func<T, bool> filterItems { get; }
    4.  
    5.         public Pita(Columns columns, Func<T, bool> filterItems) : base(columns)
    6.         {
    7.             this.filterItems = filterItems;
    8.         }
    9.  
    10.         protected override CollectionViewController CreateViewController()
    11.         {
    12.             return new Pitac<T>(this.columns, this.sortColumnDescriptions, this.sortedColumns as List<SortColumnDescription>, filterItems);
    13.         }
    14.     }
    15.  
    16.     public class Pitac<T> : MultiColumnListViewController
    17.     {
    18.         private readonly Func<T, bool> m_filterItems;
    19.  
    20.         public Pitac(Columns columns, SortColumnDescriptions sortDescriptions, List<SortColumnDescription> sortedColumns, Func<T, bool> filterItems) : base(columns, sortDescriptions, sortedColumns)
    21.         {
    22.             m_filterItems = filterItems;
    23.         }
    24.  
    25.         private List<T> m_items;
    26.  
    27.         public List<T> items
    28.         {
    29.             get => m_items;
    30.             set
    31.             {
    32.                 m_items = value;
    33.                 itemsSource = value;
    34.             }
    35.         }
    36.  
    37.         public override IList itemsSource
    38.         {
    39.             get
    40.             {
    41.                 if (m_filterItems != null)
    42.                 {
    43.                     var filtered = items.FindAll(i => m_filterItems.Invoke(i));
    44.                     //base.itemsSource = filtered;
    45.                     return filtered;
    46.                 }
    47.  
    48.                 return items;
    49.             }
    50.             set
    51.             {
    52.                 if (value is List<T> l)
    53.                 {
    54.                     items = l;
    55.                 }
    56.             }
    57.         }
    58.     }
    And then I instantiate the list by

    Code (CSharp):
    1. m_listView = new Pita<AbilityData>(columns, OnFilterItems);
    2.  
    3.     private bool OnFilterItems(AbilityData data)
    4.     {
    5.         return m_abilities.Any(a => a.name.StartsWith(m_searchString, StringComparison.CurrentCulture));
    6.     }
     
  4. Skjalg

    Skjalg

    Joined:
    May 25, 2009
    Posts:
    208
    I figured it out. The problem I initially had with the serialized property being bound to each individual cell was that the index was wrong when setting the itemsSource with a filtered list so I had to do a reverse lookup when binding the cell.