ReorderableList in the custom EditorWindow

Hello!
I’m trying to write a simple Inventory System, but I have a problem with ReorderableList which I want to implement in EditorWindow (not in the Editor).
I read the article aboud ReorderableLists from this article: Unity: make your lists functional with ReorderableList

and I know that instead of OnInspectorGUI function, I have OnGUI method, but when I’m implementing this, I’m able to see only the empty ReorderableList in my window (even if data source for list isn’t empty),when I click this, the list disappears and I’m getting the hundreds of NullReferenceExceptions.
I would be very grateful if someone could give me a simple code snippet about using those lists in the EditorWindows (Additionaly I need the drawElementcallback)

That article has a complete, working example. There’s a pastebin link at the bottom with the full source code. In the article, and in the source code, you’ll see that you need to initialize the list first, usually in an OnEnable method.

@TonyLi
Yes yes,but as I said earlier, this article is only about the Editor, but I need an example on EditorWindow.
I have solved a half of problem,because Now I’m displaying in my window the ReorderableList properly, I can even add and remove Items, but When I will close and open again my window, I’m getting NullReferenceException, and the list disappears, but when I reload the Unity, the list (and the Items) looks just like I want.

The NullReferenceException is at this command:

list.DoLayoutList();

In your OnEnable method, you still need to initialize your list and assign callback methods. The article author uses anonymous methods, but you can just as easily assign named methods. You can use a static variable to keep track of the current selection, or check Selection.

For example, say your editor window works on the currently-selected GameObject, and it edits a list inside a script named Inventory:

public class Inventory : MonoBehaviour {
    public List<Item> items;

You need an OnEnable like this example:

ReorderableList list = null;

void OnEnable() {
    var inventory = (Selection.activeGameObject != null) ? Selection.activeGameObject.GetComponent<Inventory>() : null;
    if (inventory != null) {
        list = new ReorderableList(inventory.items, typeof(item), true, true, true, true);
        // (Assign your callbacks here if needed.)
    }
}

void OnGUI() {
    if (list != null) {
        list.DoLayoutList();
    } else {
        EditorGUILayout.Label("Select a GameObject with an Inventory component.");
    }
}

@TonyLi
It doesn’t work.
Here is my complete source code of the Window:

using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System.Collections;
using System;


public class InventoryEditorWindow : EditorWindow
{
    ItemDatabaseObject _DatabaseObject;
    SerializedObject so;
    public ReorderableList list = null;

    public static InventoryEditorWindow Instance { get;set; }
    public static bool IsOpened
    {
        get { return Instance != null; }
    }
    public static void ShowWindow(ItemDatabaseObject _itemDatabaseObject)
    {
        GetWindow<InventoryEditorWindow>("InventoryEditor");
        Instance._DatabaseObject = _itemDatabaseObject;
    }

    void OnEnable()
    {
        Instance = this;
            so = new SerializedObject(_DatabaseObject);
            list = new ReorderableList(so, so.FindProperty("ItemList"));
            list.drawElementCallback =
            (Rect rect, int index, bool isActive, bool isFocused) =>
            {
                var element = list.serializedProperty.GetArrayElementAtIndex(index);
                    rect.y += 2;
                    EditorGUI.PropertyField(
                        new Rect(rect.x, rect.y, 60, EditorGUIUtility.singleLineHeight),
                        element.FindPropertyRelative("Name"), GUIContent.none);
            };
    }


    void OnDisable()
    {
    }

    void OnGUI()
    {
            so.Update();
            list.DoLayoutList();
            so.ApplyModifiedProperties();
    }
}

The ItemDatabaseObject is just an ScriptableObject which is holding the Items.
As I mentioned, the ReorderableList is looking fine, but when I close it,and then open again, It disappears until I will reload the script, but the Items of the List are saved just like I want.

1 Like

It’s just an execution order problem. You shouldn’t reference Instance in ShowWindow because it won’t have been set correctly yet. It gets set afterward in OnEnable. Try something like this, which makes DatabaseObject static and sets it in ShowWindow:

using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System.Collections;
using System;


public class InventoryEditorWindow : EditorWindow
{
    public static ItemDatabaseObject DatabaseObject { get; private set; }
    SerializedObject so;
    public ReorderableList list = null;

    public static InventoryEditorWindow Instance { get;set; }
    public static bool IsOpened
    {
        get { return Instance != null; }
    }
    public static void ShowWindow(ItemDatabaseObject _itemDatabaseObject)
    {
        DatabaseObject = _itemDatabaseObject;
        GetWindow<InventoryEditorWindow>("InventoryEditor");
        //Instance._DatabaseObject = _itemDatabaseObject;
    }

    void OnEnable()
    {
        Instance = this;
            so = new SerializedObject(DatabaseObject);
            list = new ReorderableList(so, so.FindProperty("ItemList"));
            list.drawElementCallback =
            (Rect rect, int index, bool isActive, bool isFocused) =>
            {
                var element = list.serializedProperty.GetArrayElementAtIndex(index);
                    rect.y += 2;
                    EditorGUI.PropertyField(
                        new Rect(rect.x, rect.y, 60, EditorGUIUtility.singleLineHeight),
                        element.FindPropertyRelative("Name"), GUIContent.none);
            };
    }


    void OnDisable()
    {
    }

    void OnGUI()
    {
            so.Update();
            list.DoLayoutList();
            so.ApplyModifiedProperties();
    }
}

@TonyLi
At first, your solution wasn’t helpful, because after saving the code, and reloading the Unity, everything were the same, I don’t know, maybe It’s Visual Studio’s fault, but now everything works and I hope It will work forever.
Thank you for your help :slight_smile: .

Happy to help! One last tip: Before using a variable, make sure it’s not null. For example:

void OnGUI()
{
    if (so == null)
    {
        Debug.LogError("ScriptableObject is null");
    }
    else if (list == null)
    {
        Debug.LogError("ReorderableList is null");
    }
    else if (list.list == null)
    {
        Debug.LogError("ReorderableList points to a null list");
    }
    else
    {
        so.Update();
        list.DoLayoutList();
        so.ApplyModifiedProperties();
    }
}
1 Like

@TonyLi
I’m sorry but I have to ask another question, but also about ReorderableLists.
I want to display in ReorderableList fields of ItemProperty, which has the Item Selected in the another ReorderableList.

I’m initializing the new ReorderableList like this:

        itemDataPropertyList = new ReorderableList(EditedItem.Properties, typeof(ItemProperty), true, true, true, true);

But I’m getting the NullReferenceExceptions (again, I really hate them), and the ReorderableList doesn’t display.
I also need to Reinitialize the ReorderableList, but you said that I have to do this in OnEnable();
(EditedItem is null at the beginning, but It’s getting a value when I will select some item from another ReorderableList)

How about if, in OnGUI, you check if EditedItems.Properties has changed. If so, reinitialize itemDataPropertyList. Something like this:

void OnGUI()
{
    // (Omitting null checks from my previous post for easier reading)
    if (itemDataPropertyList == null || itemDataPropertyList.list != EditedItem.Properties)
    {
        itemDataPropertyList = new ReorderableList(EditedItem.Properties, typeof(ItemProperty), true, true, true, true);
    }
    itemDataPropertyList.DoLayoutList();
}

@TonyLi
Ok, and the callbacks as usual in the OnEnable ?

I usually bundle them together in a method. So it might look something like this:

void OnGUI()
{
    PrepareList();
    itemDataPropertyList.DoLayoutList();
}

void PrepareList()
{
    if (itemDataPropertyList == null || itemDataPropertyList.list != EditedItem.Properties)
    {
        itemDataPropertyList = new ReorderableList(EditedItem.Properties, typeof(ItemProperty), true, true, true, true);
        itemDataPropertList.OnWhateverCallback = blahblahblah; //etc.
    }
}
1 Like

@TonyLi thanks,
I’m very grateful.

Glad I could help!

1 Like

@TonyLi
Ummm…I don’t know why but your last sample isn’t working properly.
Here:

void OnGUI()
{
    PrepareList();
    itemDataPropertyList.DoLayoutList();
}
void PrepareList()
{
    if (itemDataPropertyList == null || itemDataPropertyList.list != EditedItem.Properties)
    {
        itemDataPropertyList = new ReorderableList(EditedItem.Properties, typeof(ItemProperty), true, true, true, true);
        itemDataPropertList.OnWhateverCallback = blahblahblah; //etc.
    }
}

itemDataPropertyList.serializedProperty is null :frowning:

Try using an additional variable to keep track of which list is assigned to itemDataPropertyList. For example:

private List<ItemProperty> currentList = null;

void OnGUI()
{
    PrepareList();
    itemDataPropertyList.DoLayoutList();
}

void PrepareList()
{
    if (EditedItem.Properties != currentList)
    {
        currentList = EditedItem.Properties;
        itemDataPropertyList = new ReorderableList(currentList, typeof(ItemProperty), true, true, true, true);
        itemDataPropertList.OnWhateverCallback = blahblahblah; //etc.
    }
}

@TonyLi
Nothing changes :frowning:
It’s throwing an error on this line:

var element = itemDataPropertyList.serializedProperty.GetArrayElementAtIndex(index);

Maybe check that the index is valid first:

if (0 <= itemDataPropertyList.index && itemDataPropertyList.index < itemDataPropertyList.count)
{
    var element = itemDataPropertyList.serializedProperty.GetArrayElementAtIndex(index);
}

@TonyLi
Now List is displaying but I can’t see the Fields, which I’m drawing in the drawElementCallback

It sounds like itemDataPropertyList isn’t getting set properly then.