Search Unity

How to use make EditorWindow use CustomPropertyDrawer

Discussion in 'Immediate Mode GUI (IMGUI)' started by Vipsu, Jul 18, 2017.

  1. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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

    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class HelloWorldObject
    4. {
    5.     //Hello property drawer should not show this value
    6.     public int someValue = 0;
    7. }
    8.  
    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

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. [CustomPropertyDrawer(typeof(HelloWorldObject))]
    6. public class HelloPropertyDrawer : PropertyDrawer
    7. {
    8.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    9.     {
    10.         position.width = 200;
    11.         position.height = 100;
    12.         EditorGUI.LabelField(position, "Hello World!!!!!");
    13.     }
    14. }
    15.  
    ScriptableObject that contains the data as constructor for SerializedObject only accepts Unity Objects.

    Code (CSharp):
    1.  
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class HelloDataForEditor : ScriptableObject
    6. {
    7.     public HelloWorldObject NonListed;
    8.     public List<HelloWorldObject> List;
    9.  
    10.     public HelloDataForEditor()
    11.     {
    12.         NonListed = new HelloWorldObject();
    13.         List = new List<HelloWorldObject>();
    14.     }
    15. }
    16.  
    Finally the EditorWindow.
    (Also tried looping through List with GetIterator and while loop but that didn't work either.)

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEditor;
    6.  
    7. public class HelloEditorWindow : EditorWindow
    8. {
    9.     HelloDataForEditor data;
    10.  
    11.     [MenuItem("Window/Hello Window")]
    12.     static void Init()
    13.     {
    14.         // Get existing open window or if none, make a new one:
    15.         HelloEditorWindow window = (HelloEditorWindow)EditorWindow.GetWindow(typeof(HelloEditorWindow));
    16.         window.Show();
    17.     }
    18.  
    19.     void OnGUI()
    20.     {
    21.         if (data == null)
    22.         {
    23.             data = new HelloDataForEditor();
    24.         }
    25.  
    26.         if (GUILayout.Button("Add Hello"))
    27.         {
    28.             data.List.Add(new HelloWorldObject());
    29.         }
    30.      
    31.         SerializedObject o = new SerializedObject(data);
    32.  
    33.         SerializedProperty single = o.FindProperty("NonListed");
    34.         EditorGUILayout.PropertyField(single, true);
    35.      
    36.         SerializedProperty list = o.FindProperty("List");
    37.         for (int i = 0; i < list.arraySize; i++)
    38.         {
    39.             EditorGUILayout.LabelField(list.GetArrayElementAtIndex(i).type);
    40.             EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
    41.         }
    42.     }
    43. }
    44.  
    What am I doing wrong?

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

    Using Unity 2017.1.0f3
     
  2. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @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.
     
    Last edited: Jul 19, 2017
    Vipsu likes this.
  3. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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.
     

    Attached Files:

    Last edited: Jul 20, 2017
  4. PsyKaw

    PsyKaw

    Joined:
    Aug 16, 2012
    Posts:
    102
    If I have understand, you can use Editor.CreateEditor instead of SerializedObject.

    Code (csharp):
    1.  
    2. void OnGUI()
    3. {
    4.     if (data == null)
    5.     {
    6.         data = new HelloDataForEditor();
    7.     }
    8.  
    9.     if (GUILayout.Button("Add Hello"))
    10.     {
    11.         data.List.Add(new HelloWorldObject());
    12.     }
    13.  
    14.     var editor = Editor.CreateEditor(data);
    15.     if (editor != null)
    16.     {
    17.         editor.OnInspectorGUI();
    18.     }
    19. }
    20.  
    Done! :)
     
    OxDEADFACE and _slash_ like this.
  5. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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.
     
    Last edited: Jul 21, 2017
  6. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    Made these changes and the EditorWindow still fails to use the custom property drawer for HelloWorldObject.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. [CreateAssetMenu]
    7. public class HelloDataForEditor : ScriptableObject
    8. {
    9.     public HelloWorldObject NonListed;
    10.     public List<HelloWorldObject> List;
    11.  
    12.     public static HelloDataForEditor CreateAsset()
    13.     {
    14.         HelloDataForEditor asset = ScriptableObject.CreateInstance<HelloDataForEditor>();
    15.  
    16.         UnityEditor.AssetDatabase.CreateAsset(asset, "Assets/HelloData.asset");
    17.         UnityEditor.AssetDatabase.SaveAssets();
    18.         return asset;
    19.     }
    20. }
    21.  
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEditor;
    6.  
    7. public class HelloEditorWindow : EditorWindow
    8. {
    9.     HelloDataForEditor data;
    10.  
    11.     [MenuItem("Window/Hello Window")]
    12.     static void Init()
    13.     {
    14.         // Get existing open window or if none, make a new one:
    15.         HelloEditorWindow window = (HelloEditorWindow)EditorWindow.GetWindow(typeof(HelloEditorWindow));
    16.         window.Show();
    17.     }
    18.  
    19.     bool tryOpenFile = true;
    20.  
    21.     void OnGUI()
    22.     {
    23.         if (data == null)
    24.         {
    25.             if(GUILayout.Button("open file"))
    26.             {
    27.                 string path = EditorUtility.OpenFilePanel("Open hello data", "Assets", "asset");
    28.  
    29.                 if (path.StartsWith(Application.dataPath))
    30.                     path = "Assets" + path.Substring(Application.dataPath.Length);
    31.            
    32.                 if (path != null)
    33.                 {
    34.                     data = AssetDatabase.LoadAssetAtPath<HelloDataForEditor>(path);
    35.                 }
    36.             }
    37.         }
    38.         else
    39.         {
    40.             SerializedObject o = new SerializedObject(data);
    41.  
    42.             if (GUILayout.Button("Add item to list"))
    43.             {
    44.                 Undo.RegisterCreatedObjectUndo(data, "HelloList Add");
    45.                 if (data.List == null)
    46.                     data.List = new List<HelloWorldObject>();
    47.  
    48.                 data.List.Add(new HelloWorldObject());
    49.             }
    50.  
    51.             SerializedProperty single = o.FindProperty("NonListed");
    52.             EditorGUILayout.PropertyField(single, true);
    53.  
    54.             SerializedProperty list = o.FindProperty("List");
    55.             for (int i = 0; i < list.arraySize; i++)
    56.             {
    57.                 EditorGUILayout.LabelField(list.GetArrayElementAtIndex(i).type);
    58.                 EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
    59.             }
    60.         }
    61.     }
    62. }
    63.  
    Also tried to make CustomEditor for the data but that doesn't use CustomPropertyDrawer either.

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3.  
    4. [CustomEditor(typeof(HelloDataForEditor))]
    5. public class HelloEditor : Editor
    6. {
    7.     SerializedProperty list;
    8.     SerializedProperty single;
    9.     HelloDataForEditor _data;
    10.     Editor _editor;
    11.  
    12.     void OnEnable()
    13.     {
    14.         list = serializedObject.FindProperty("List");
    15.         single = serializedObject.FindProperty("NonListed");
    16.         _data = (HelloDataForEditor)serializedObject.targetObject;
    17.     }
    18.  
    19.  
    20.     public override void OnInspectorGUI()
    21.     {
    22.         serializedObject.Update();
    23.  
    24.         if (_data && _editor == null)
    25.             _editor = CreateEditor(_data);
    26.         else if (_data)
    27.             _editor.DrawDefaultInspector();
    28.  
    29.         for (int i = 0; i < 5; i++)
    30.             EditorGUILayout.Space();
    31.  
    32.         EditorGUILayout.PropertyField(single, true);
    33.         for (int i = 0; i < list.arraySize; i++)
    34.             EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i), true);
    35.      
    36.         serializedObject.ApplyModifiedProperties();
    37.     }
    38. }
    39.  
    Below are pictures on how it looks on EditorWindow and Editor compared to how it looks on MonoBehavior.

    What am I doing wrong?
     

    Attached Files:

    Last edited: Jul 21, 2017
  7. PsyKaw

    PsyKaw

    Joined:
    Aug 16, 2012
    Posts:
    102
    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.
     

    Attached Files:

    Vipsu likes this.
  8. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Last edited: Jul 21, 2017
    Vipsu likes this.
  9. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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.
     

    Attached Files:

  10. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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
    Code (CSharp):
    1.  
    2.         long Before = Profiler.GetTotalAllocatedMemoryLong();
    3.         var editor = Editor.CreateEditor(data);
    4.  
    5.         long after = Profiler.GetTotalAllocatedMemoryLong();
    6.  
    7.         Debug.Log("Memory use  "+ Before + " -> " + after );
    8.     }
    Cashed.
    Code (CSharp):
    1.  
    2.         long Before = Profiler.GetTotalAllocatedMemoryLong();
    3.  
    4.         if (_editor == null)
    5.             _editor = Editor.CreateEditor(data);
    6.  
    7.         long after = Profiler.GetTotalAllocatedMemoryLong();
    8.  
    9.         Debug.Log("Memory use  "+ Before + " -> " + after );
    10.     }
    11.  
    This test only shows that it takes memory to CreateEditor so I went and made simple OnGUI script that I attached to a GameObject.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Profiling;
    3.  
    4. [ExecuteInEditMode]
    5. public class DebugMemoryUse : MonoBehaviour
    6. {
    7.  
    8.     private long allocated;
    9.     private long reserved;
    10.     private long reserved_unused;
    11.     private long heap;
    12.     private long mono;
    13.  
    14.     public void Update()
    15.     {
    16.         allocated = Profiler.GetTotalAllocatedMemoryLong();
    17.         reserved = Profiler.GetTotalReservedMemoryLong();
    18.         reserved_unused = Profiler.GetTotalUnusedReservedMemoryLong();
    19.         heap = Profiler.GetMonoHeapSizeLong();
    20.         mono = Profiler.GetMonoUsedSizeLong();
    21.     }
    22.  
    23.     private void OnGUI()
    24.     {
    25.         GUI.BeginGroup(new Rect(10, 10, 300, 300));
    26.         GUILayout.Label("Allocated " + allocated);
    27.         GUILayout.Label("reserved " + reserved);
    28.         GUILayout.Label("reserved_unused " + reserved_unused);
    29.         GUILayout.Label("heap " + heap);
    30.         GUILayout.Label("mono " + mono);
    31.  
    32.         GUI.EndGroup();
    33.     }
    34. }
    35.  
    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.
     

    Attached Files:

    Last edited: Jul 22, 2017
  11. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
  12. Vipsu

    Vipsu

    Joined:
    Oct 8, 2012
    Posts:
    88
    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.
     
  13. zzgrzyt

    zzgrzyt

    Joined:
    Jun 13, 2017
    Posts:
    8
    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:
    Code (CSharp):
    1. [System.Serializable]
    2. public class TestObject
    3. {
    4.     // This MyCustomClass class is serializable and has a CustomPropertyDrawer
    5.     public MyCustomClass Member = new MyCustomClass();
    6. }
    7.  
    8. [System.Serializable]
    9. public class FakeContainer : ScriptableObject
    10. {
    11.     public MyCustomClass Member = new MyCustomClass();
    12. }
    13.  
    14. public class MyEditor : EditorWindow
    15. {
    16.     // Custom object to be edited.
    17.     private TestObject Target;
    18.  
    19.     [SerializeField]
    20.     private MyCustomClass Test = new MyCustomClass();
    21.     private FakeContainer Container;
    22.  
    23.     private void OnEnable()
    24.     {
    25.         // Fake container for testing purposes.
    26.         Container = ScriptableObject.CreateInstance<FakeContainer>();
    27.  
    28.         // Load the custom object.
    29.         Target = LoadMyTestObjectFromCustomAsset();
    30.     }
    31.  
    32.     private void OnGUI()
    33.     {
    34.         // Attempt 1 - using fake container, does NOT work.
    35.         // Uses the default base editor, no custom drawers.
    36.         Container.Member = Target.Member;
    37.         SerializedObject so = new SerializedObject(Container);
    38.         EditorGUILayout.PropertyField(so.FindProperty("Member"), true);
    39.         Target.Member = Container.Member;
    40.  
    41.         // Attempt 2 - using the instance in the editor window itself.
    42.         // This WORKS! Uses my CustomPropertyDrawer!
    43.         Test = Target.Member;
    44.         SerializedObject so = new SerializedObject(this);
    45.         EditorGUILayout.PropertyField(so.FindProperty("Test"));
    46.         Target.Member = Test;
    47.     }
    48. }
    49.  
     
    WojtekR, lijun_unity926 and Mikael-H like this.
  14. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    When I tried this it indeed did use the property drawer, but I could not set the value. Did you get that to work?
     
    WojtekR and hamza_unity995 like this.
  15. WojtekR

    WojtekR

    Joined:
    May 3, 2019
    Posts:
    7
    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();
    }
     
    Last edited: Mar 23, 2023