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 !
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
For a simple Editor, I’d suggest to use PropertyField for lists.
Something like this:
using System.Collections.Generic;
using UnityEngine;
public class SimpleComponent : MonoBehaviour
{
public List<int> listOfInts;
public string stringPrimitive;
}
using UnityEditor;
using UnityEngine.UIElements;
[CustomEditor(typeof(SimpleComponent))]
public class SimpleComponentEditor : Editor
{
public override VisualElement CreateInspectorGUI()
{
var viewAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/SimpleComponentView.uxml");
var view = viewAsset.CloneTree();
return view;
}
}
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#?
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…
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.
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.
@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?
@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.
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.
As far as a ‘list of items’ there are two ways to go about it.
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.
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.
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:
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
using System;
public class ItemLibrary : EditorWindow
{
List<string> items; // This would be my data source that I would like to have shown as a scrollable UI
ScrollView MyScrollView;
[MenuItem("Testing/A simple scrollable item library")]
public static void ShowExample()
{
ItemLibrary wnd = GetWindow<ItemLibrary>();
wnd.titleContent = new GUIContent("ItemLibrary");
}
public void OnEnable()
{
VisualElement root = rootVisualElement;
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Testing/Editor/ItemLibrary.uxml");
VisualElement visualElementFromUXML = visualTree.Instantiate();
root.Add(visualElementFromUXML);
MyScrollView = root.Query<ScrollView>("MyScrollView").First();
MakeInitialListOfLabels();
}
void MakeInitialListOfLabels() // Trying to populate MyScrollView with demo labels..
{
int itemCount = 100;
var items = new List<string>(itemCount);
for (int i = 1; i <= itemCount; i++)
{
items.Add(i.ToString() + " as an example");
}
Func<VisualElement> makeItem = () => new Label();
Action<VisualElement, int> bindItem = (e, i) => (e as Label).text = items[i];
// Non-working pseudo code 1: Add labels to ScrollView
// MyScrollView.Add("AddTheLabel?!");
// MyScrollView.Label("GiveItSomeCallback");
}
void DeleteASingleEntryFromList(int ListItemToDelete) // Called from elsewhere
{
// Non-working pseudo code 2: Remove Item from Scrollview
// items.Remove(ListItemToDelete);
// MyScrollView.refresh the list of labels and redraw only the Scrollview, not the entire UI
}
void AddASingleEntryToList(VisualElement v)
{
// Non-working pseudo code 3: Add a single Item to Scrollview
// MyScrollView.Add(v with callback);
// MyScrollView refresh, but only the Scrollview, not the entire UI
}
}
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.
[...]
public VaultAssetColumn()
{
Rebuild();
}
public void Rebuild()
{
Clear();
AllAssetsOfFilteredType = new List<DataEntity>();
this.style.flexGrow = 1;
this.name = "Asset List Wrapper";
this.viewDataKey = "asset_list_wrapper";
ListElement = new ListView(AllAssetsOfFilteredType, 16, ListMakeItem, ListBindItem);
ListElement.name = "Asset List View";
ListElement.viewDataKey = "asset_list";
ListElement.style.flexGrow = 1;
ListElement.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
ListElement.selectionType = SelectionType.Multiple;
ListElement.onSelectionChanged += PickAssetsToInspect;
ListElement.onItemChosen += PickAssetToInspect;
[...]
}
[...]
private void ListBindItem(VisualElement element, int listIndex)
{
// find the serialized property
Editor ed = Editor.CreateEditor(m_isSearchFiltering ? m_searchFilteredList[listIndex] : AllAssetsOfFilteredType[listIndex]);
SerializedObject so = ed.serializedObject;
SerializedProperty prop = so.FindProperty("Title");
// build a prefix
((Label) element.ElementAt(0)).text = listIndex.ToString(" ▪ ");
// bind the label to the serialized target target property title
((Label) element.ElementAt(1)).BindProperty(prop);
}
private static VisualElement ListMakeItem()
{
VisualElement selectableItem = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
flexGrow = 1f,
flexBasis = 1,
flexShrink = 1,
flexWrap = new StyleEnum<Wrap>(Wrap.NoWrap)
}
};
selectableItem.Add(new Label {name = "Prefix", text = "error", style = {unityFontStyleAndWeight = FontStyle.Bold}});
selectableItem.Add(new Label {name = "DB Title", text = "unknown"});
return selectableItem;
}
[...]
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.
@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
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
[EditorWindowTitle(title = "Item Library")]
public class ItemLibrary : EditorWindow
{
// using the window itself as an example container
[SerializeField] private List<string> m_Items = new List<string>();
private ListView m_ListView;
private SerializedProperty m_ListProperty;
[MenuItem("Testing/Item Library")]
public static void ShowExample()
{
GetWindow<ItemLibrary>().Show();
}
public void OnEnable()
{
var root = rootVisualElement;
// instantiate the window UI
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/ItemLibrary/ItemLibrary.uxml");
visualTree.CloneTree(root);
var listView = root.Q<ListView>("items");
var dataContext = new SerializedObject(this);
var listProperty = dataContext.FindProperty(nameof(m_Items));
// bind this ListView to the m_Items list
listView.BindProperty(listProperty);
// important, otherwise the item at index 0 is expected to bind to the Array.size property
listView.showBoundCollectionSize = false;
// customize the list items look and feel
// note: if you don't set these callbacks, PropertyField will be used to bind each list item
listView.makeItem = MakeItem;
listView.bindItem = BindItem;
m_ListView = listView;
m_ListProperty = listProperty;
var addButton = root.Q<Button>("items-add");
addButton.clicked += AddItem;
var deleteButton = root.Q<Button>("items-delete");
deleteButton.clicked += DeleteSelectedItems;
var clearButton = root.Q<Button>("items-clear");
clearButton.clicked += ClearItems;
}
private void ClearItems()
{
Undo.RecordObject(this, "Clear items");
m_Items.Clear();
}
private void DeleteSelectedItems()
{
if (!m_ListView.selectedIndices.Any())
return;
Undo.RecordObject(this, "Remove selected items");
foreach (var selectedIndex in m_ListView.selectedIndices.OrderByDescending(i => i))
{
m_Items.RemoveAt(selectedIndex);
}
}
private void AddItem()
{
Undo.RecordObject(this, "Add new item");
m_Items.Add("Item " + m_Items.Count);
}
private VisualElement MakeItem()
{
// TODO: instantiate some UXML template here
return new Label();
}
private void BindItem(VisualElement target, int index)
{
var bindable = (BindableElement)target;
bindable.BindProperty(m_ListProperty.GetArrayElementAtIndex(index));
}
}
@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.