How to use make EditorWindow use CustomPropertyDrawer

I've been playing around with the Editor a bit, but currently having problems figuring out how to display custom data in the editor using it's CustomPropertyDrawer.

The Class for CustomPropertyDrawer to draw

[System.Serializable]
public class HelloWorldObject
{
    //Hello property drawer should not show this value
    public int someValue = 0;
}

Here's the property drawer for the class above.
As you can see it should only draw Label with Text "Hello World!!!!!"
This actually works in Inspector but for some reason cannot get it to work in EditorWindow

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(HelloWorldObject))]
public class HelloPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        position.width = 200;
        position.height = 100;
        EditorGUI.LabelField(position, "Hello World!!!!!");
    }
}

ScriptableObject that contains the data as constructor for SerializedObject only accepts Unity Objects.

using System.Collections.Generic;
using UnityEngine;

public class HelloDataForEditor : ScriptableObject
{
    public HelloWorldObject NonListed;
    public List<HelloWorldObject> List;

    public HelloDataForEditor()
    {
        NonListed = new HelloWorldObject();
        List = new List<HelloWorldObject>();
    }
}

Finally the EditorWindow.
(Also tried looping through List with GetIterator and while loop but that didn't work either.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class HelloEditorWindow : EditorWindow
{
    HelloDataForEditor data;

    [MenuItem("Window/Hello Window")]
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        HelloEditorWindow window = (HelloEditorWindow)EditorWindow.GetWindow(typeof(HelloEditorWindow));
        window.Show();
    }

    void OnGUI()
    {
        if (data == null)
        {
            data = new HelloDataForEditor();
        }

        if (GUILayout.Button("Add Hello"))
        {
            data.List.Add(new HelloWorldObject());
        }

        SerializedObject o = new SerializedObject(data);

        SerializedProperty single = o.FindProperty("NonListed");
        EditorGUILayout.PropertyField(single, true);

        SerializedProperty list = o.FindProperty("List");
        for (int i = 0; i < list.arraySize; i++)
        {
            EditorGUILayout.LabelField(list.GetArrayElementAtIndex(i).type);
            EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
        }
    }
}

What am I doing wrong?

Have I perhaps misunderstood something fundamental on how CustomPropertyDrawers should be used?

Using Unity 2017.1.0f3

@Vipsu

Hi, I'm pretty noob with programming I guess, but I've dabbled quite a bit with editor scripting, so I can try to help.

I think screenshots of the end results would have helped.

I tried your code with a few modifications, and it seemed to work OK both in Inspector and Editor Window...

Although I'm using Unity 5.6.2f1 - not 2017

What I did:
I added [CreateAssetMenu] Attribute for HelloDataForEditor class.
I created an asset version of HelloDataForEditor class / Scriptable Object using above created menu item.
I put HelloEditorWindow.cs and HelloPropertyDrawer.cs into Editor folder.

Both Editor window and Scriptable Object Asset seem to render fields formatted by Custom Property Drawer's styling correctly for your serialized class, unless I missed something in your explanation.

1 Like

Interesting.
Did you also reference the asset version of the data in the EditorWindow instead of creating a new object?

What I am trying to do is to create EditorWindow for handling serializeable data structures in Unity that I might want to serialize myself to json or whatever without having to rely on scribtableObject assets (basically without Unity’s own serialization).

What I’d really like would be to just give the PropertyField a class like HelloWorldObject to draw and for it to use the CustomPropertyDrawer assigned for class of that type. The whole HelloDataForEditor is there to just get around the fact that PropertyField only accepts type of SerializedProperty and Unity can’t convert a serializedObject in to one.

Here’s a picture of what happens at least by without creating an actual asset. I’ll test with the asset version later to see if that’s the problem here.

3152129--239601--CustomPropertyDrawerProblem.jpg

If I have understand, you can use Editor.CreateEditor instead of SerializedObject.

void OnGUI()
{
    if (data == null)
    {
        data = new HelloDataForEditor();
    }

    if (GUILayout.Button("Add Hello"))
    {
        data.List.Add(new HelloWorldObject());
    }

    var editor = Editor.CreateEditor(data);
    if (editor != null)
    {
        editor.OnInspectorGUI();
    }
}

Done! :)

2 Likes

No difference, it still does not use the CustomPropertyDrawer instead of the default one.
Note: there’s no Editor for HelloDataForEditor objects only for HelloWorldObject.

Made these changes and the EditorWindow still fails to use the custom property drawer for HelloWorldObject.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class HelloDataForEditor : ScriptableObject
{
    public HelloWorldObject NonListed;
    public List<HelloWorldObject> List;
 
    public static HelloDataForEditor CreateAsset()
    {
        HelloDataForEditor asset = ScriptableObject.CreateInstance<HelloDataForEditor>();

        UnityEditor.AssetDatabase.CreateAsset(asset, "Assets/HelloData.asset");
        UnityEditor.AssetDatabase.SaveAssets();
        return asset;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class HelloEditorWindow : EditorWindow
{
    HelloDataForEditor data;

    [MenuItem("Window/Hello Window")]
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        HelloEditorWindow window = (HelloEditorWindow)EditorWindow.GetWindow(typeof(HelloEditorWindow));
        window.Show();
    }

    bool tryOpenFile = true;

    void OnGUI()
    {
        if (data == null)
        {
            if(GUILayout.Button("open file"))
            {
                string path = EditorUtility.OpenFilePanel("Open hello data", "Assets", "asset");

                if (path.StartsWith(Application.dataPath))
                    path = "Assets" + path.Substring(Application.dataPath.Length);
            
                if (path != null)
                {
                    data = AssetDatabase.LoadAssetAtPath<HelloDataForEditor>(path);
                }
            }
        }
        else
        {
            SerializedObject o = new SerializedObject(data);

            if (GUILayout.Button("Add item to list"))
            {
                Undo.RegisterCreatedObjectUndo(data, "HelloList Add");
                if (data.List == null)
                    data.List = new List<HelloWorldObject>();

                data.List.Add(new HelloWorldObject());
            }

            SerializedProperty single = o.FindProperty("NonListed");
            EditorGUILayout.PropertyField(single, true);

            SerializedProperty list = o.FindProperty("List");
            for (int i = 0; i < list.arraySize; i++)
            {
                EditorGUILayout.LabelField(list.GetArrayElementAtIndex(i).type);
                EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
            }
        }
    }
}

Also tried to make CustomEditor for the data but that doesn’t use CustomPropertyDrawer either.

using UnityEditor;

[CustomEditor(typeof(HelloDataForEditor))]
public class HelloEditor : Editor
{
    SerializedProperty list;
    SerializedProperty single;
    HelloDataForEditor _data;
    Editor _editor;

    void OnEnable()
    {
        list = serializedObject.FindProperty("List");
        single = serializedObject.FindProperty("NonListed");
        _data = (HelloDataForEditor)serializedObject.targetObject;
    }

 
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        if (_data && _editor == null)
            _editor = CreateEditor(_data);
        else if (_data)
            _editor.DrawDefaultInspector();

        for (int i = 0; i < 5; i++)
            EditorGUILayout.Space();

        EditorGUILayout.PropertyField(single, true);
        for (int i = 0; i < list.arraySize; i++)
            EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
     
        serializedObject.ApplyModifiedProperties();
    }
}

Below are pictures on how it looks on EditorWindow and Editor compared to how it looks on MonoBehavior.

What am I doing wrong?

3153470--239738--Problem persists.png
3153470--239744--WorksCorrectlyInMonoBehavior.png

My solution works very well, I had not tried yet. But if i use your code with my OnGUI code in HelloEditorWindow, see the result in attached file.
I attach a project which works. Tell me if something is wrong.

3153580--239758--Screenshot_1.png
3153580–239759–TestHelloEditor.zip (17.6 KB)

1 Like

@Vipsu and @PsyKaw

I'm not sure exactly what is going on, didn't quite have effort to read code again, but this is what I got when I first tried the code, I don't think I made too many changes... I can see serialized class HelloWorldObject styled with CustomProperty drawer in Editor window, and in the Asset file, in the Inspector - is this what was supposed to happen?

https://www.dropbox.com/s/ouv6qses06vtfw5/20170721_EditorWindow_and_scriptableObjectAsset.PNG

1 Like

Thanks for the help both of you.

Seems the issue was a typo in one of the filenames which I noticed after I opened SpyKaw’s project and saw that the scripts where pretty much identical but yet it was working on in the attached project but not in my own. Editor window was showing that there was no attached script (picture below).

I had typed HelloDataForEditor.cs as HellowDataForEditor.cs in my own project and Unity is quite picky when it comes to file names.

3155061--239926--missingScript.png

As additional note I should mention one thing I’ve noticed with Editor.CreateEditor method is that it seems to “leak” memory so it should always be cashed instead of creating a new one every update.

Was profiling this at work as Unity was hogging more and more memory and had to be restarted every couple to make it less sluggish.

Non-cashed

        long Before = Profiler.GetTotalAllocatedMemoryLong();
        var editor = Editor.CreateEditor(data);

        long after = Profiler.GetTotalAllocatedMemoryLong();

        Debug.Log("Memory use  "+ Before + " -> " + after );
    }

Cashed.

        long Before = Profiler.GetTotalAllocatedMemoryLong();

        if (_editor == null)
            _editor = Editor.CreateEditor(data);

        long after = Profiler.GetTotalAllocatedMemoryLong();

        Debug.Log("Memory use  "+ Before + " -> " + after );
    }

This test only shows that it takes memory to CreateEditor so I went and made simple OnGUI script that I attached to a GameObject.

using UnityEngine;
using UnityEngine.Profiling;

[ExecuteInEditMode]
public class DebugMemoryUse : MonoBehaviour
{

    private long allocated;
    private long reserved;
    private long reserved_unused;
    private long heap;
    private long mono;

    public void Update()
    {
        allocated = Profiler.GetTotalAllocatedMemoryLong();
        reserved = Profiler.GetTotalReservedMemoryLong();
        reserved_unused = Profiler.GetTotalUnusedReservedMemoryLong();
        heap = Profiler.GetMonoHeapSizeLong();
        mono = Profiler.GetMonoUsedSizeLong();
    }

    private void OnGUI()
    {
        GUI.BeginGroup(new Rect(10, 10, 300, 300));
        GUILayout.Label("Allocated " + allocated);
        GUILayout.Label("reserved " + reserved);
        GUILayout.Label("reserved_unused " + reserved_unused);
        GUILayout.Label("heap " + heap);
        GUILayout.Label("mono " + mono);

        GUI.EndGroup();
    }
}

Attached this to a GameObject, opened up the Editor window with non-cashed CreateEditor in it. Also added 1000 HelloWorldObjects to the list. Commented the debug.logs and started resizing the gameview (to get make the ExecuteInEditMode to update).

Noticed that Unity steadily was is using more and more allocated memory when the window with non-cashed CreateEditor(s) was just open. Even Windows task manager showed the UnityEditor memory use steadily rise.



@Vipsu - OK, good that things worked out for you...

I wouldn't know too much about leaking memory issue, but I remember reading about not using Editor.CreateEditor constantly and caching it, I had this link in my notes:

http://www.gamasutra.com/blogs/MarkWahnish/20150904/252962/Unity_Trick_1__Make_an_inspector_for_any_ScriptableObject.php

Yeah cashing it is definately way to go with these but even so these Created Editors should be caught by garbage collection like everything else.

Hi,

I'd like to supplement the answer, as I found a very simple workaround for the issue in subject (EditorWindow not using CustomPropertyDrawers). All you need to do is to put a temporary object of the edited type in the editor instance and edit that one instead.
This is especially surprising, since creating a fake ScriptableObject container for the type does NOT work. For some reason the EditorWindow gets a different treatment as a host, even though it also inherits from ScriptableObject.

Example code:

[System.Serializable]
public class TestObject
{
    // This MyCustomClass class is serializable and has a CustomPropertyDrawer
    public MyCustomClass Member = new MyCustomClass();
}

[System.Serializable]
public class FakeContainer : ScriptableObject
{
    public MyCustomClass Member = new MyCustomClass();
}

public class MyEditor : EditorWindow
{
    // Custom object to be edited.
    private TestObject Target;

    [SerializeField]
    private MyCustomClass Test = new MyCustomClass();
    private FakeContainer Container;

    private void OnEnable()
    {
        // Fake container for testing purposes.
        Container = ScriptableObject.CreateInstance<FakeContainer>();

        // Load the custom object.
        Target = LoadMyTestObjectFromCustomAsset();
    }

    private void OnGUI()
    {
        // Attempt 1 - using fake container, does NOT work.
        // Uses the default base editor, no custom drawers.
        Container.Member = Target.Member;
        SerializedObject so = new SerializedObject(Container);
        EditorGUILayout.PropertyField(so.FindProperty("Member"), true);
        Target.Member = Container.Member;

        // Attempt 2 - using the instance in the editor window itself.
        // This WORKS! Uses my CustomPropertyDrawer!
        Test = Target.Member;
        SerializedObject so = new SerializedObject(this);
        EditorGUILayout.PropertyField(so.FindProperty("Test"));
        Target.Member = Test;
    }
}
3 Likes

When I tried this it indeed did use the property drawer, but I could not set the value. Did you get that to work?

2 Likes

to get value from EditorGUILayout.PropertyField you need to add serializedObject.ApplyModifiedProperties();

so it looks like this
private void OnGUI()
{
SerializedObject serializedObject = new SerializedObject(this);
EditorGUILayout.PropertyField(serializedObject.FindProperty("someField"));
serializedObject.ApplyModifiedProperties();
}