Hi,
I would like to have a view list of items in the Editor.
These items are all in one file scriptableObject ItemDatabase in order to save them.
In the editor, I want to edit, move, remove and add items.
I taked the sample UIElements ListView with serializedProperty of an array and I am trying to replace the existing struct CustomStruct by my scriptableObject.
I don’t know how to use the binding path with scriptableObject.
Maybe It’s a bad idea and I should use more SerializedObject, like say here :
functions changed for me and important row :
bindingPath - row 86
bindingPath - row 143
FindOptions() - row 182
[System.Serializable]
public class Item : ScriptableObject
{
public int Id = default;
public string name = default;
public float size = default;
}
[System.Serializable]
public class ItemDatabase : ScriptableObject
{
public List<Item> items = new List<Item>();
}
public class ListViewWithItems : EditorWindow
{
[SerializeField]
private List<Item> m_CustomStructs;
private ListView m_ListView;
private SerializedObject serializedObject;
private SerializedProperty m_ArraySizeProperty;
private SerializedProperty m_ArrayProperty;
private int m_ListViewInsertIndex = -1; // To make sure we insert the ListView at the right place in our visualTree
VisualElement root;
ItemDatabase ItemDatabase;
string path = "Assets/Editor/";
string objectName = "yoMan.asset";
public void CreateGUI()
{
FindOptions();
serializedObject = new SerializedObject(this);
root = rootVisualElement;
var rowContainer = new VisualElement();
rowContainer.style.flexDirection = FlexDirection.Row;
rowContainer.style.justifyContent = Justify.FlexStart;
root.Add(rowContainer);
CreateListView();
root.Bind(serializedObject);
}
void AddButton(VisualElement container, string label, Action onClick)
{
container.Add(new Button(onClick) { text = label });
}
void CreateListView()
{
if (m_ListView != null)
{
//We clean ourselves up
m_ListView.Unbind();
m_ListView?.RemoveFromHierarchy();
}
m_ListView = new ListView();
m_ListView.showBoundCollectionSize = false;
m_ListView.name = "OneList";
m_ListView.bindingPath = nameof(m_CustomStructs);
m_ListView.itemHeight = 60;
m_ArrayProperty = serializedObject.FindProperty(m_ListView.bindingPath);
m_ArraySizeProperty = serializedObject.FindProperty(m_ListView.bindingPath + ".Array.size");
m_ListView.style.flexGrow = 1;
m_ListView.name = m_ListView.name + "-custom-item";
m_ListView.makeItem = () => CreateCustomStructListItem();
m_ListView.name += "+bind";
m_ListView.bindItem = ListViewBindItem;
if (m_ListViewInsertIndex < 0)
{
m_ListViewInsertIndex = rootVisualElement.childCount;
rootVisualElement.Add(m_ListView);
}
rootVisualElement.Insert(m_ListViewInsertIndex, m_ListView);
m_ListView.Bind(serializedObject);
}
private void AddRemoveItemButton(VisualElement row)
{
var button = new Button() { text = "-" };
button.RegisterCallback<ClickEvent>((evt) =>
{
var clickedElement = evt.target as VisualElement;
if (clickedElement != null && clickedElement.userData is int index)
{
m_ArrayProperty.DeleteArrayElementAtIndex(index);
serializedObject.ApplyModifiedProperties();
}
});
button.tooltip = "Remove this item from the list";
row.Add(button);
}
VisualElement CreateCustomStructListItem()
{
var keyFrameContainer = new BindableElement(); //BindableElement so the default bind can assign the item's root property
var lbl = new Label("Custom Item UI");
lbl.AddToClassList("custom-label");
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.justifyContent = Justify.SpaceBetween;
row.Add(lbl);
AddRemoveItemButton(row);
keyFrameContainer.Add(row);
keyFrameContainer.Add(new TextField( { } ));
//keyFrameContainer.Add(new TextField() { bindingPath = nameof(CustomStruct.StringValue });
//keyFrameContainer.Add(new FloatField() { bindingPath = nameof(CustomStruct.FloatValue) });
return keyFrameContainer;
}
void ListViewBindItem(VisualElement element, int index)
{
var label = element.Q<Label>(className: "custom-label");
if (label != null)
{
label.text = "Custom Item UI (Custom Bound)";
}
var button = element.Q<Button>();
if (button != null)
{
button.userData = index;
}
//we find the first Bindable
var field = element as IBindable;
if (field == null)
{
//we dig through children
field = element.Query().Where(x => x is IBindable).First() as IBindable;
}
// Bound ListView.itemsSource is a IList of SerializedProperty
var itemProp = m_ListView.itemsSource[index] as SerializedProperty;
field.bindingPath = itemProp.propertyPath;
element.Bind(itemProp.serializedObject);
}
private void FindOptions()
{
ItemDatabase = AssetDatabase.LoadAssetAtPath<ItemDatabase>(path + objectName);
if (ItemDatabase == null)
{
ItemDatabase = CreateInstance<ItemDatabase>();
m_CustomStructs = ItemDatabase.items;
AssetDatabase.CreateAsset(ItemDatabase, path + objectName);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Item item = CreateInstance<Item>();
item.name = "toto";
m_CustomStructs.Add(item);
item = CreateInstance<Item>();
item.name = "titi";
m_CustomStructs.Add(item);
}
}
[MenuItem("testo/ListViewWithItems")]
public static void ShowExample()
{
ListViewWithItems wnd = GetWindow<ListViewWithItems>();
wnd.titleContent = new GUIContent("ListViewWithItems");
}
}