Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

CustomPropertyDrawer for a Class with a Generic Type

Discussion in 'Extensions & OnGUI' started by SHiLLySiT, Feb 26, 2013.

  1. SHiLLySiT

    SHiLLySiT

    Joined:
    Feb 26, 2013
    Posts:
    11
    I have a class which acts like a dictionary but uses two lists instead:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class ListDictionary<T>
    7. {
    8.     private List<string> _keys;
    9.     private List<T> _values;
    10.    
    11.     public ListDictionary()
    12.     {
    13.         _keys = new List<string>();
    14.         _values = new List<T>();
    15.     }
    16.        
    17.         // ... etc
    18. }
    19.  
    I want to write a CustomPropertyDrawer for it but I'm getting the following error:
    Here's my property drawer:
    Code (csharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. [CustomPropertyDrawer(ListDictionary<T>)]
    8. public class ListDictionaryDrawer : PropertyDrawer
    9. {
    10.     public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    11.     {
    12.         EditorGUI.BeginProperty (position, label, property);
    13.        
    14.        
    15.         EditorGUI.EndProperty ();
    16.     }
    17. }
    18.  
    I tried variations of the class name (ListDictionary, ListDictionary<>, and etc) but I haven't gotten it to work. How do I go about fixing this? On a related note, are there any tutorials on creating a custom property drawer for C#? I'm currently going off one that is for javascript.
     
  2. aalmada

    aalmada

    Joined:
    Apr 29, 2013
    Posts:
    12
    I would also like to know if this is possible.
    Thanks!
     
  3. CHaNGeTe

    CHaNGeTe

    Joined:
    Nov 16, 2012
    Posts:
    1
    You are on c#, aren't you missing the "typeof" part?

    [CustomPropertyDrawer(typeof(List<ScriptableAction>))]

    For example
     
  4. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    548
    I tried this before and decided it didn't work. The only thing that I could do to make it work was to derive a non-generic class from the generic class, fixing its type parameters, and make the MonoBehaviours use that instead of using the generic type directly. You do need to proxy constructors, but most other things work fine. You also need the middle-man class to be [Serializable].

    It also means that your PropertyDrawer can only deal with one type - it can't be generic - though that's not quite the case either, as you can pull the same trick, defining a non-generic derived class and doing the [CustomPropertyDrawer] markup on that instead.

    So you end up with engine code:

    Code (csharp):
    1.  
    2. [Serializable]
    3. public class ListDictionary_int : ListDictionary<int>
    4. {
    5.     // ... constructors, at least ...
    6. }
    7.  
    And editor code:

    Code (csharp):
    1.  
    2. // No attributes needed
    3. public class ListDictionaryPropertyDrawer<T> : PropertyDrawer
    4. {
    5.     public override void OnGui(...)
    6.     // ... etc ...
    7. }
    8.  
    9. [CustomPropertyDrawer(typeof(ListDictionary_int))]
    10. public class ListDictionaryPropertyDrawer_int : ListDictionaryPropertyDrawer<int>
    11. {
    12.     // nothing needed here
    13. }
    14.  
     
    IsaiahKelly likes this.
  5. Xappek

    Xappek

    Joined:
    Oct 13, 2013
    Posts:
    1
    Try this [CustomPropertyDrawer(typeof(ListDictionary<>))]
     
    Last edited: Oct 13, 2013
  6. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    144
    @Xappek: I can't believe you even tested that, since it has never worked, and still (over a year later) doesn't work.
     
  7. BMayne

    BMayne

    Joined:
    Aug 4, 2014
    Posts:
    186
    Hey there,

    Unfortunately (from my experience) you are not able to do this. It has to do with how the inspector works and serialization works.

    Generic types are not serialized by Unity. When an inspector goes to preview an object it does not look at the class. It tells the class to serialize itself. It will then go ahead and show the results. Since Generics are not serialized there are no results to display.

    In short you can't :(
     
  8. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,216
    Too bad, I was also just trying this...

    Unity does serialize and display T[] or List<T> for example.

    Support for this would really be appreciated, because the alternative doesn't look very pretty. My goal is simply to replace the current editor for arrays with a version that is expanding automatically to reduce the need to set the size up front. This works perfectly fine for specific value types like int[], float[] and string[], but for any array of Component instances it would be nice to be able to define a generic property drawer.

    (The property drawer is not set for int[] directly, because that's also not possible. If have an ArrayInt class that can be implicitly cast to int[]. The expanding int array property drawer is placed on this class.)
     
    Last edited: Apr 18, 2017
    AdmiralThrawn likes this.
  9. mathewp

    mathewp

    Joined:
    Sep 11, 2013
    Posts:
    49
    So it is somewhat possible. Maybe not directly but there is a workaround for it. And I know I'm reviving old thread, but it was first answer in Google.

    PropertyDrawers does not support generic types. It is because Unity does not serialize generic types different than List<T>.

    So if you have:

    Code (CSharp):
    1. [Serializable]
    2. public class GenericClass<T>
    3. {
    4.     [SerializeField]
    5.     private T GenericVariable;
    6. }
    And MonoBehaviour with this class as a field:

    Code (CSharp):
    1. public class GenericPropertyTestController : MonoBehaviour
    2. {
    3.     public GenericClass<int> GenericField;
    4. }
    Unity will not serialize it. Period. Maybe someday we'll get such support but it would require changing such a base thing as Unity Serializer so we might never have it.

    But one can do a workaround.

    Basically you need to declare not generic class and your generic class will need to inherit from it. Then you can create your CustomPropertyDrawer for this class, but you need to set useForChildren as true in CustomPropertyDrawer attribute:

    Code (CSharp):
    1. [Serializable]
    2. public class GenericClassParent
    3. {
    4.  
    5. }
    6.  
    7. public class GenericClass<T> : GenericClassParent
    8. {
    9.     [SerializeField]
    10.     private T GenericVariable;
    11. }
    12.  
    13. [CustomPropertyDrawer(typeof(GenericClassParent), true)]
    14. public class GenericClassDrawer : PropertyDrawer
    15. {
    16.     //Property drawer code here.
    17. }
    But it still won't show in Inspector, because we're still trying to serialize the GenericClass<int> in our MonoBehaviour. Unity allow us to serialize types derived from generic classes though. So we can use this feature like this:

    Code (CSharp):
    1. [Serializable]
    2. public class IntGenericClass : GenericClass<int>
    3. { }
    4.  
    5. public class GenericPropertyTestController : MonoBehaviour
    6. {
    7.     public IntGenericClass GenericField;
    8. }
    This will allow Unity to serialize GenericField in GenericPropertyTestController and for drawing it'll use GenericClassDrawer.
     
    Last edited: Feb 27, 2018
    phobos2077, XiaGu and jvo3dc like this.
  10. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,216
    Yes, that is also how I do things now. So for the automatically expanding int array, I have the following classes:
    - Array<T> (abstract, Serializable, provides implicit casting to T[])
    - ArrayInt : Array<int> (really just an empty class, but Serializable)
    - ArrayDrawer<T> : PropertyDrawer (abstract, provides a general automatically expanding array drawer)
    - ArrayIntDrawer : ArrayDrawer<int> (CustomPropertyDrawer(typeof(ArrayInt)))
     
  11. mathewp

    mathewp

    Joined:
    Sep 11, 2013
    Posts:
    49
    If you use my method and do it like this:

    Code (CSharp):
    1. class Array{}
    2.  
    3. class Array<T> : Array {}
    4.  
    5. [CustomPropertyDrawer(typeof(Array), true)]
    6.  
    7. ArrayDrawer : PropertyDrawer {}
    8.  
    Then, with a bit of help from reflections, you can create one property drawer for all types of Array.
     
    phobos2077 and jvo3dc like this.
  12. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,216
    That is an interesting approach. It won't work out for this specific Array class, because it needs type specific handling, but I'll check to see whether I can apply this in other places. I didn't know this was possible.

    Actually, somewhere further along the array line I have classes to handle standard types of Unity:
    ArrayObject<T> : Array<T> where T : Object (also implements an interface to prevent circular references.)
    ArrayComponent<T> : ArrayObject<T> where T : Component
    ArrayAudioSource : ArrayComponent<AudioSource>

    It would be nice to be able to handle ArrayAudioSource with a PropertyDrawer that is general for ArrayComponent<T>, because in this case there is nothing type specific beyond Component. That won't be possible however, because ArrayComponent<T> does have to inherit from Array<T> in some way.

    I might split up Array into type specific variants for things like Array<int> and for object references like Array<AudioSource> that can have a single PropertyDrawer.
     
    Last edited: Feb 28, 2018
  13. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,216
    I'm actually running into a scenario where this is very useful, but it does not seem to work. The custom property drawer is simply not called for the children. It's called for the base class, but not for the child class.

    Both the base class and the generic class are serializable and the fields are too.

    Edit: If I make a non generic subclass of the base class, it does work. So this way also simply doesn't work for generic classes.
     
    Last edited: Apr 20, 2018
  14. mathewp

    mathewp

    Joined:
    Sep 11, 2013
    Posts:
    49
    Hi, sorry for late answer.

    Yes, this will only work if you have class derived from the genric one. It doesn't work for generic class probably because of the same reasons why generic classes are not serialized.

    My solution just allows to have one Property Drawer for all of these classes. But you still need to create them manually i.e. my example with IntGenericClass.

    So if you wanted to create ArrayDrawer for all Array<T> types you can do this. You'll need to use reflections to get the type of T inside your drawer. But if you'll work through it then you can have one ArrayDrawer for all classes that derive from Array<T> e.g: IntArray : Array<int> or FloatArray : Array<float>.