Search Unity

Question Simple *Get-Started on Scrollview*?

Discussion in 'UI Toolkit' started by Fritsl, Aug 17, 2020.

  1. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    I understand UI Toolkit is in development, but I am getting quite frustrated, as I can see all sorts of complex stuff, but not get a head start with some of the simplest things, and Google is NOT my friend, as there's pollution from ages of Unity naming things more or less the same..

    I am doing an Editor project
    I have a simple List in C#
    I just want to get a start with showing this C# List by using the new UI toolkit

    So.. thought "List view" would be a good start.. OK, that's apparently not related:
    The use of "keyword" "List" is just random, it's _not_ a smart viewer for (order-able) list's. (IMHO it should really not have that name then, not use "List", just as the word "Array" should not be used)

    So, asking Unity Dev, I realize I have to use ScrollView if my List is small.

    After realizing I have to expand the "Window -> UI Toolkit -> Samples" to actually _see the examples refereed to_, I find that all there is an example for is "Scroller"..not ScrollView Argh ! :D

    Is it possible for a friendly Unity Dev or other smart person to post just the beginners guide to:

    Use UI Toolkit to make UXML that can be called from C# to display a simple C# List?

    I'm sure I can figure out how to edit and expand from thereon, but the overall flush of name conflicts and cross-outdated-for-something-else-documentation is killing me coming from outside :D

    Thanks!
     
  2. etienne_unity

    etienne_unity

    Unity Technologies

    Joined:
    Aug 31, 2017
    Posts:
    102
    Hi @Fritsl

    For a simple
    Editor
    , I'd suggest to use
    PropertyField
    for lists.

    Something like this:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class SimpleComponent : MonoBehaviour
    5. {
    6.     public List<int> listOfInts;
    7.     public string stringPrimitive;
    8. }
    9.  
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine.UIElements;
    3.  
    4. [CustomEditor(typeof(SimpleComponent))]
    5. public class SimpleComponentEditor : Editor
    6. {
    7.     public override VisualElement CreateInspectorGUI()
    8.     {
    9.         var viewAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/SimpleComponentView.uxml");
    10.         var view = viewAsset.CloneTree();
    11.         return view;
    12.     }
    13. }
    Code (uxml):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
    2.     <uie:PropertyField binding-path="listOfInts" />
    3.     <uie:PropertyField binding-path="stringPrimitive" />
    4. </ui:UXML>
     
  3. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    Oh, thank you very much @etienne_unity
    , that's much simpler than anticipated, almost cheating ;)

    If fancy pants wants to use UI Builder -> your fine class would contain IntegerField (not just an Int), and it should all be wrapped in a Scroll view, that is really my problem here:

    I wrote
    Use UI Toolkit to make UXML that can be called from C# to display a simple C# List?
    I meant
    Use UI Builder to make UXML that can be called from C# to display a simple C# List?
    the name changing all over the material available is killing me, sorry.

    So, end goal should be
    Use UI Builder (or at least have code that UI Builder can read)
    Insert a form of scrollable "list" (ListView, ScrollView, Scroller, or??!? -> part of the confusion)
    C# Editor window or Extension populates this "List"

    Now UI Builder can be used to make the "List" appear nicer. I hope you follow: I am totally capable of hacking any old system up with Unity, display a an Array or List.. but how would one use the UI Builders "scroll-window-displays-of-any-kind" to display even a simple list that is populated not via the UI builder, but via C#?
     
  4. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    PS: I DID set this up using the example for ListView, but then I was not able to make ListView update from code, and one of your nice colleagues told me to not use ListView, but ScrollView, but I can not find code examples on that, and the in-editor-included examples only mention "Scroller", adding to lack of understanding for how to do this simple task..
     
  5. etienne_unity

    etienne_unity

    Unity Technologies

    Joined:
    Aug 31, 2017
    Posts:
    102
    I'll try to clarify things a bit.

    ListView
    creates a
    ScrollView
    internally, so no need to create one manually.

    To update a
    ListView
    from code, modify its backing itemsSource, and then call Refresh. Just like in the ListView example, make sure to set the
    makeItem
    and
    bindItem
    callbacks.
     
  6. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    You can treat
    ScrollView
    like a regular
    VisualElement
    that just happens to handle overflow using a scroll bar (which is actually the
    Scroller
    control you saw in the Samples).

    So you can put down a
    ScrollView
    in the Builder, give it a size (or make it stretch to take up all available space), and give it a _name_. In C#, you can the query for this _name_ (and/or by type
    ScrollView
    ), and then just
    Add()
    your list item elements to it from your data, like you would with a vanilla
    VisualElement
    .
     
  7. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    Thanks both :)
     
  8. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    @etienne_unity no matter how much I Quote: "modify its backing itemsSource, and then call Refresh", including if I mark as dirty, serialize all sorts of things, anything I do, I just cannot make a list refresh properly: Am left with a window that has some entries spread, some empty fields, strange leftovers.. only when I reload the window can I make it refresh.

    Since there's also no examples on this simple task, I am starting to thingk there's some bug somewhere?

    Please, is it not possible for you to post fully working, simple code, on how to have a List of labels with clickable callback, including for example code-delete-one-item in the list, and how the list then should be refreshed?
     
    VizworxInc likes this.
  9. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    @uDamian Thanks, everything you write makes total sense.. But without any examples / lack of "ever seeing it work in any way", I still cannot get my head around this part:

    "just Add() your list item elements to it from your data, like you would with a vanilla VisualElement"

    I presume then that "my list item elements" should be a C# List of Labels for example?

    But without too much insane hacking, how would one go about Producing this C# List (or Array) from for example a C# List (or Array) of Strings, let alone classes with for example 2 strings, to produce a simple visual scrollable list of such "buttons" (Labels with callback, I presume)?

    And since the mode no longer is Immediate, we do not want to redraw everything, so how would this code be structured?

    Again, I am stuck on what I would presume would be a UI systems 101, a really simple task: Produce a scrollable list of "buttons" from for example a C# array or C# List of a Class -> Populate list with things to click on, have easy lookup of what to do from code, when user press button.. and how to REFRESH this scrollable UI from code..

    It would not not be a problem in IMGU: Just throw out them buttons with all the parameters one can wish for ;)

    I would really appreciate a fully working example of this, it appears to be found nowhere, only "how to show a list", never how to refresh, which should be a key point for a Retained mode system, so it would be really nice to see how you go about this, in a working example.

    Thanks :)
     
    VizworxInc likes this.
  10. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,532
    UI Builder doesn't hook up events. It only builds the structure of the UI. After you load the UXML template from UI Builder as mentioned in the first reply then you can Query the hierarchy to find items you are interested in and hook them up to whatever code you want.

    https://docs.unity3d.com/Manual/UIE-UQuery.html

    As far as a 'list of items' there are two ways to go about it.

    1) Use a ScrollView. All you have to do is
    MyScrollView.Add(MyElement);
    and it will automatically handle overflow for you. To refresh you can either manually track adding/removing specific elements from the hierarchy or redraw and add them again when necessary. Its up to you, this is not automatic.

    2) Use a ListView. This is the hard way and probably not necessary unless you have > 1000 elements. All elements are required to be the same height. This is bound to an
    IList<T>
    object and it automatically draws the elements for you and includes a
    ScrollView
    . You simply have to change the target list and call
    Refresh()
    on the
    ListView
    when you do. There are callbacks provided on the ListView such as
    onSelectionChanged
    or
    onItemChosen
    . Use those to handle click events. Dealing with interaction is your job, it's not automatic because no one knows what you want to do with the data but you.
     
  11. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    Thank you @LaneFox for chiming in, that was a lot of great info.

    I am however STILL stuck with what I really think should be a simple task, but there must be some fundamental understanding that I miss, I would really appreciate help on this.

    To clarify, here is pseudo code with 3 problems that I cannot solve:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.UIElements;
    4. using UnityEditor.UIElements;
    5. using System.Collections.Generic;
    6. using System;
    7. public class ItemLibrary : EditorWindow
    8. {
    9.     List<string> items; // This would be my data source that I would like to have shown as a scrollable UI
    10.     ScrollView MyScrollView;
    11.  
    12.     [MenuItem("Testing/A simple scrollable item library")]
    13.     public static void ShowExample()
    14.     {
    15.         ItemLibrary wnd = GetWindow<ItemLibrary>();
    16.         wnd.titleContent = new GUIContent("ItemLibrary");
    17.     }
    18.  
    19.     public void OnEnable()
    20.     {
    21.         VisualElement root = rootVisualElement;
    22.         VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Testing/Editor/ItemLibrary.uxml");
    23.         VisualElement visualElementFromUXML = visualTree.Instantiate();
    24.         root.Add(visualElementFromUXML);
    25.         MyScrollView = root.Query<ScrollView>("MyScrollView").First();
    26.         MakeInitialListOfLabels();
    27.     }
    28.  
    29.     void MakeInitialListOfLabels() // Trying to populate MyScrollView with demo labels..
    30.     {
    31.         int itemCount = 100;
    32.         var items = new List<string>(itemCount);
    33.         for (int i = 1; i <= itemCount; i++)
    34.         {
    35.             items.Add(i.ToString() + " as an example");
    36.         }
    37.  
    38.         Func<VisualElement> makeItem = () => new Label();
    39.         Action<VisualElement, int> bindItem = (e, i) => (e as Label).text = items[i];
    40.  
    41.         // Non-working pseudo code 1: Add labels to ScrollView
    42.         // MyScrollView.Add("AddTheLabel?!");
    43.         // MyScrollView.Label("GiveItSomeCallback");
    44.     }
    45.  
    46.     void DeleteASingleEntryFromList(int ListItemToDelete) // Called from elsewhere
    47.     {
    48.         // Non-working pseudo code 2: Remove Item from Scrollview
    49.         // items.Remove(ListItemToDelete);
    50.         // MyScrollView.refresh the list of labels and redraw only the Scrollview, not the entire UI
    51.     }
    52.  
    53.     void AddASingleEntryToList(VisualElement v)
    54.     {
    55.         // Non-working pseudo code 3: Add a single Item to Scrollview
    56.         // MyScrollView.Add(v with callback);
    57.         // MyScrollView refresh, but only the Scrollview, not the entire UI
    58.     }
    59. }
    - And for good measure, the UXML as well:
    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
    2.     <ui:ScrollView name="MyScrollView" />
    3. </ui:UXML>
    4.  
    Thank you so very much in advance :)
     
  12. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,532
    So you've chosen a ScrollView which means you have to ask yourself why you're trying to make it behave like a ListView.

    Recall:
    ScrollView is just a regular hierarchy that automatically handles children overflowing out of it with scrolling. Choose which one you want and code accordingly.

    If you want a ListView - which is a totally different thing and not to be mixed with ScrollView - then you have to do much more work and use the whole MakeItem / BindItem approach with an
    IList
    data source. You don't have to do anything other than change the source
    IList
    and
    Refresh()
    the
    ListView
    .

    MakeItem and BindItem in the examples are [inappropriately] using inline functions which is very confusing for people trying to read the code who aren't wizards with UI Toolkit. Don't do that. If you are using a ListView then you need gobs of functionality and more space to write it so just make them regular functions.

    Personally my current preference is to make them from scratch in a VisualElement wrapper. This is definitely preference and I'll probably do it differently later.

    Code (csharp):
    1.  
    2. [...]
    3.         public VaultAssetColumn()
    4.         {
    5.             Rebuild();
    6.         }
    7.  
    8.         public void Rebuild()
    9.         {
    10.             Clear();
    11.             AllAssetsOfFilteredType = new List<DataEntity>();
    12.  
    13.             this.style.flexGrow = 1;
    14.             this.name = "Asset List Wrapper";
    15.             this.viewDataKey = "asset_list_wrapper";
    16.  
    17.             ListElement = new ListView(AllAssetsOfFilteredType, 16, ListMakeItem, ListBindItem);
    18.             ListElement.name = "Asset List View";
    19.             ListElement.viewDataKey = "asset_list";
    20.             ListElement.style.flexGrow = 1;
    21.             ListElement.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
    22.             ListElement.selectionType = SelectionType.Multiple;
    23.             ListElement.onSelectionChanged += PickAssetsToInspect;
    24.             ListElement.onItemChosen += PickAssetToInspect;
    25.  
    26. [...]
    27.         }
    28. [...]
    29.         private void ListBindItem(VisualElement element, int listIndex)
    30.         {
    31.             // find the serialized property
    32.             Editor ed = Editor.CreateEditor(m_isSearchFiltering ? m_searchFilteredList[listIndex] : AllAssetsOfFilteredType[listIndex]);
    33.             SerializedObject so = ed.serializedObject;
    34.             SerializedProperty prop = so.FindProperty("Title");
    35.  
    36.             // build a prefix
    37.             ((Label) element.ElementAt(0)).text = listIndex.ToString(" ▪ ");
    38.  
    39.             // bind the label to the serialized target target property title
    40.             ((Label) element.ElementAt(1)).BindProperty(prop);
    41.         }
    42.         private static VisualElement ListMakeItem()
    43.         {
    44.             VisualElement selectableItem = new VisualElement
    45.             {
    46.                 style =
    47.                 {
    48.                     flexDirection = FlexDirection.Row,
    49.                     flexGrow = 1f,
    50.                     flexBasis = 1,
    51.                     flexShrink = 1,
    52.                     flexWrap = new StyleEnum<Wrap>(Wrap.NoWrap)
    53.                 }
    54.             };
    55.             selectableItem.Add(new Label {name = "Prefix", text = "error", style = {unityFontStyleAndWeight = FontStyle.Bold}});
    56.             selectableItem.Add(new Label {name = "DB Title", text = "unknown"});
    57.             return selectableItem;
    58.         }
    59. [...]
    60.  
    Understand that...

    MakeItem is a factory for creating each selectable entity on the fly.
    BindItem is a process to bind each selectable entity to specific data.

    These are ListView concepts. Not ScrollView.

    Again, if you're using a ScrollView, you have to manage the hierarchy yourself. If you're using a ListView then (after creating it properly) you have to manage only the source list data and never touch the actual elements inside its hierarchy.

    Here is an example of it working. Left is a ScrollView, the Center is a ListView and the Right is a Custom Inspector.

    output1.gif
     
    Last edited: Aug 18, 2020
  13. Fritsl

    Fritsl

    Joined:
    Mar 10, 2013
    Posts:
    211
    @LaneFox Wow, thank you again for all your info, it is very valuable. And yes, as you write:

    ..You then go on with info of how not to use the UI Builder, no UXML.. But the UI Toolkit is supposed to lean towards the UI Builder producing UXML, and the UI Toolkit is is what this tread is about, and I never wanted to decide which way to do it (ScrollView or ListView), I just want to learn how to make a "list of clickable items in an editor window".

    @uDamian @etienne_unity
    Is it not possible for you to show a simple UMXL & C# / UI Builder compatible code (no inline tricks) fully working, zero assumptions, that will make a "list" of "labels" (or what would be best) ?

    UXML create all objects

    C# create "items" on "list"
    C# delete "items" on "list"
    C# refresh "the list" (but not the entire UI, well structured for retained mode)
    C# easy reference to what happens if user press one of the "items" on the "list"

    I presume it should only be a few lines of code: One C# and one UXML file, but getting those right without ANY examples (all there is is hacks and inline tricks) is close to impossible ;)
     
  14. etienne_unity

    etienne_unity

    Unity Technologies

    Joined:
    Aug 31, 2017
    Posts:
    102
    Here's a functional
    EditorWindow
    using a data-bound
    ListView
    with customized items (simple labels) and buttons to create / delete / clear the list.

    Code (uxml):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
    2.     <ui:ListView focusable="True" selection-type="Multiple" reorderable="true" item-height="16" name="items" show-bound-collection-size="false" style="flex-grow: 1;" />
    3.     <ui:VisualElement style="flex-direction: row; justify-content: flex-end;">
    4.         <ui:Button text="+" name="items-add" tooltip="Add new item." />
    5.         <ui:Button text="-" name="items-delete" tooltip="Delete selected items." />
    6.         <ui:Button text="Clear" name="items-clear" tooltip="Clear all items." />
    7.     </ui:VisualElement>
    8. </ui:UXML>
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEditor.UIElements;
    5. using UnityEngine;
    6. using UnityEngine.UIElements;
    7.  
    8. [EditorWindowTitle(title = "Item Library")]
    9. public class ItemLibrary : EditorWindow
    10. {
    11.     // using the window itself as an example container
    12.     [SerializeField] private List<string> m_Items = new List<string>();
    13.    
    14.     private ListView m_ListView;
    15.     private SerializedProperty m_ListProperty;
    16.     [MenuItem("Testing/Item Library")]
    17.     public static void ShowExample()
    18.     {
    19.         GetWindow<ItemLibrary>().Show();
    20.     }
    21.     public void OnEnable()
    22.     {
    23.         var root = rootVisualElement;
    24.        
    25.         // instantiate the window UI
    26.         var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/ItemLibrary/ItemLibrary.uxml");
    27.         visualTree.CloneTree(root);
    28.  
    29.         var listView = root.Q<ListView>("items");
    30.  
    31.         var dataContext = new SerializedObject(this);
    32.         var listProperty = dataContext.FindProperty(nameof(m_Items));
    33.        
    34.         // bind this ListView to the m_Items list
    35.         listView.BindProperty(listProperty);
    36.        
    37.         // important, otherwise the item at index 0 is expected to bind to the Array.size property
    38.         listView.showBoundCollectionSize = false;
    39.        
    40.         // customize the list items look and feel
    41.         // note: if you don't set these callbacks, PropertyField will be used to bind each list item
    42.         listView.makeItem = MakeItem;
    43.         listView.bindItem = BindItem;
    44.  
    45.         m_ListView = listView;
    46.         m_ListProperty = listProperty;
    47.  
    48.         var addButton = root.Q<Button>("items-add");
    49.         addButton.clicked += AddItem;
    50.        
    51.         var deleteButton = root.Q<Button>("items-delete");
    52.         deleteButton.clicked += DeleteSelectedItems;
    53.        
    54.         var clearButton = root.Q<Button>("items-clear");
    55.         clearButton.clicked += ClearItems;
    56.     }
    57.  
    58.     private void ClearItems()
    59.     {
    60.         Undo.RecordObject(this, "Clear items");
    61.         m_Items.Clear();
    62.     }
    63.  
    64.     private void DeleteSelectedItems()
    65.     {
    66.         if (!m_ListView.selectedIndices.Any())
    67.             return;
    68.        
    69.         Undo.RecordObject(this, "Remove selected items");
    70.         foreach (var selectedIndex in m_ListView.selectedIndices.OrderByDescending(i => i))
    71.         {
    72.             m_Items.RemoveAt(selectedIndex);
    73.         }
    74.     }
    75.  
    76.     private void AddItem()
    77.     {
    78.         Undo.RecordObject(this, "Add new item");
    79.         m_Items.Add("Item " + m_Items.Count);
    80.     }
    81.  
    82.     private VisualElement MakeItem()
    83.     {
    84.         // TODO: instantiate some UXML template here
    85.         return new Label();
    86.     }
    87.  
    88.     private void BindItem(VisualElement target, int index)
    89.     {
    90.         var bindable = (BindableElement)target;
    91.         bindable.BindProperty(m_ListProperty.GetArrayElementAtIndex(index));
    92.     }
    93. }
     
  15. RamType0

    RamType0

    Joined:
    Sep 11, 2018
    Posts:
    67
    Is there any way to create scroll view with wrap around?
     
    VizworxInc likes this.
  16. Cyrille-Paulhiac

    Cyrille-Paulhiac

    Joined:
    Jul 9, 2015
    Posts:
    20
    @etienne_unity @uDamian Hi, I'm also struggling with the same issue RamType0 mentioned: I'm trying to get a button array into a scrollview with a wrap-around layout.
    The issue is that the buttons don't wrap-around and instead only show up as a very tall column of single buttons.