Search Unity

[UIElements] ListView derived class won't show selection highlight

Discussion in 'UI Toolkit' started by jwvanderbeck, Mar 21, 2019.

  1. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Hi,

    I have a rather simple class that I have derived from ListView. It does very little. Its only purpose is to make the ListView reactive so that if its data source is changed the ListView automatically refreshes.

    However with this derived class, the ListView does not show the selection highlight when an item is clicked on like the raw ListView does, and for the life of me I can't figure out why. The items DO register the click properly, and the result from the onSelectionChanged action is correct.

    My derived class is very simple, and is posted here in its entirety. The ReactiveCollection here comes from UniRx and implements IList.

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UniRx;
    6. using UnityEngine.Experimental.UIElements;
    7. using UnityEngine;
    8.  
    9. namespace Genesis.Unity.Tessa
    10. {
    11.     public class ReactiveListView : ListView
    12.     {
    13.         private ReactiveCollection<string> m_collection;
    14.         public ReactiveCollection<string> SourceCollection
    15.         {
    16.             get { return m_collection; }
    17.             set
    18.             {
    19.                 if (m_collection == value) return;
    20.              
    21.                 m_collection = value;
    22.                 m_collection.ObserveReset().Subscribe(OnReset);
    23.                 m_collection.ObserveCountChanged().Buffer(TimeSpan.FromSeconds(1)).Subscribe(OnCountChanged);
    24.             }
    25.         }
    26.  
    27.         public bool SortItems = true;
    28.  
    29.         public ReactiveListView(ReactiveCollection<string> itemsSource, int itemHeight, Func<VisualElement> makeItem, Action<VisualElement, int> bindItem) : base(itemsSource, itemHeight, makeItem, bindItem)
    30.         {
    31.             SourceCollection = itemsSource;
    32.         }
    33.  
    34.         private void OnCountChanged(IList<int> obj)
    35.         {
    36.             if (obj.Count > 0)
    37.             {
    38.                 Debug.Log($"OnCountChanged {obj.Count}");
    39.                 if (SortItems)
    40.                 {
    41.                     ArrayList.Adapter(m_collection).Sort();
    42.                 }
    43.                 Refresh();
    44.             }
    45.         }
    46.      
    47.         private void OnReset(Unit obj)
    48.         {
    49.             Debug.Log("OnReset");
    50.         }
    51.     }
    52. }
    53.  
     
  2. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    Hello,

    The problem is that in 2018.3 the default style sheets matches against the ListView type to apply the "selected" background color therefore inheriting from ListView breaks this selector.

    This is the reason why we have completely moved away from type based selectors in our standard USS file for the release in 2019.1. I ported your ReactiveListView to use the non-experimental namespace and it works perfectly.

    In the mean time, one solution is to use the ListView by composition :

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.Experimental.UIElements;
    4. using UnityEditor.Experimental.UIElements;
    5. using UniRx;
    6. using System;
    7. using System.Collections;
    8. using System.Collections.Generic;
    9.  
    10. public class Test : EditorWindow
    11. {
    12.     [MenuItem("Window/UIElements/Test")]
    13.     public static void ShowExample()
    14.     {
    15.         Test wnd = GetWindow<Test>();
    16.         wnd.titleContent = new GUIContent("Test");
    17.     }
    18.  
    19.  
    20.     private ReactiveCollection<string> m_collection;
    21.     public ReactiveCollection<string> SourceCollection
    22.     {
    23.         get { return m_collection; }
    24.         set
    25.         {
    26.             if (m_collection == value) return;
    27.        
    28.             m_collection = value;
    29.             m_collection.ObserveReset().Subscribe(OnReset);
    30.             m_collection.ObserveCountChanged().Buffer(TimeSpan.FromSeconds(1)).Subscribe(OnCountChanged);
    31.         }
    32.     }
    33.  
    34.     ListView m_ListView;
    35.  
    36.     public void OnEnable()
    37.     {
    38.         SourceCollection = new ReactiveCollection<string>();
    39.         m_ListView = new ListView(SourceCollection, 20,
    40.             () => new Label(),
    41.             (elem, index) => (elem as Label).text = SourceCollection[index]
    42.         );
    43.         SortItems = false;
    44.         m_ListView.style.height = 300;
    45.        
    46.         this.GetRootVisualContainer().Add(m_ListView);
    47.  
    48.         while (SourceCollection.Count < 100)
    49.         {
    50.             SourceCollection.Add("Test" + SourceCollection.Count + 1);
    51.         }
    52.     }
    53.     public bool SortItems = false;
    54.  
    55.     private void OnCountChanged(IList<int> obj)
    56.     {
    57.         if (obj.Count > 0)
    58.         {
    59.             Debug.Log($"OnCountChanged {obj.Count}");
    60.             if (SortItems)
    61.             {
    62.                 ArrayList.Adapter(m_collection).Sort();
    63.             }
    64.             m_ListView.Refresh();
    65.         }
    66.     }
    67.     private void OnReset(Unit obj)
    68.     {
    69.         Debug.Log("OnReset");
    70.     }
    71. }
     
  3. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Thank you for the reply Antoine. I never would have figured out that problem on my own! We are on 2018.3 at the moment, and not sure when 2019.1 will happen sadly.

    As for composition, wrapping it up in a new class is fine but we want something that can be dropped inline where a normal ListView would go, in another class that handles the UI. So a VisualElement that just handles itself. I'll have to think about the best way to handle this, or implemented highlighting manually as a fall back option.

    Thanks again for helping me understand what the root problem is.
     
  4. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Are the default stylesheets available somewhere where I could copy the styling used for ListView?

    EDITL Ah nevermind I can just grab it from the UIElements Inspector window.
     
    Last edited: Mar 22, 2019
  5. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Ok so in case I (or others) run into this again and come here via Google..

    This was easily fixed by adding my own stylesheet that picked up ReactiveListView :selected.

    ReactiveListView :selected {
    background-color: rgba(61, 96, 145, 1.0);
    }

    This reproduces the exact same behaviour as ListView.

    SIDE NOTE: Deconstructing Style rules from the UIElements Inspector is fine, but bear in mind the information shown in that window isn't necessarily exactly what is in the actual rule :( In my case, I had two small roadbumps in that regard:

    1) The UIElements Inspector lists the style as RGBA() when in reality it needs to be lowercase rgba()
    2) The UIElements Inspector lists color values in floating point 0-1, but they must be specified in integer 0-255 in the USS file. So if you are copying directly from you inspector and seeing black and white results this is why.

    It would be nice if the inspector more precisely showed the value as it is in the USS file.
     
  6. benoitd_unity

    benoitd_unity

    Unity Technologies

    Joined:
    Jan 2, 2018
    Posts:
    331
    Taking note of that, thanks for reporting.