- Home /
Get the instance the SerializedProperty belongs to in a CustomPropertyDrawer
Here's what my PropertyDrawer class looks like.
[CustomPropertyDrawer (typeof(OnChangeAttribute))]
public class OnChangePropertyDrawer : PropertyDrawer
{
public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label)
{
EditorGUI.BeginChangeCheck ();
string val = EditorGUI.TextField (position, label, prop.stringValue);
if (EditorGUI.EndChangeCheck ()) {
// ... get instance of object that I'm changing (an instance of ObjectB)
// ... call a method on that instance
prop.stringValue = val;
}
}
}
When there's a change to a property in the GUI, I'd like to be able to get the instance of the object that has changed.
Is there any way to get the reference to that instance?
EDIT:
The problem seems to be that the property my PropertyDrawer is drawing is part of an array of objects in another class.
public class ObjectA : MonoBehaviour {
public ObjectB[] objects;
}
[System.Serializable]
public class ObjectB {
[OnChange ("StringValue")] public int myProperty
}
So when I change myProperty
in the inspector, the value of prop.serializedObject.targetObject
in the PropertyDrawer class is an instance of type ObjectA
. I want the instance of ObjectB
that I've changed.
EDIT:
ObjectA
is a MonoBehaviour class that I've added as a component to a GameObject. ObjectB
is a generic, serializable class I defined, the instances of which are created in the inspector. I set the size
of the array, and enter the properties of each instance shown in the inspector.
I haven't tried - but can't you use the reference to the SerializedObject and that path, parsing out the parent's path value?
Probably better to keep going here rather that that other Q you commented on...
@whydoidoit I'm pretty new to Unity, and I'm not quite sure what you mean. Would you be able to provide a code (or pseudo code) example of what you mean?
@whydoidoit I think you're on to something. prop.propertyPath
outputs objects.Array.data[0].myProperty
. However, I'm not really sure what to do with that information. I know I want to get to the property at objects.Array.data[0]
(which would be an instance of ObjectB
), but prop.FindPropertyRelative ("objects.Array.data[0]")
returns Null
.
So the serialized property has a reference to its serializedObject and then you'd use FindProperty on that, with the parsed out parent name, to get the value.
Answer by whydoidoit · Mar 27, 2013 at 01:19 AM
Here you go - this will use reflection to descend to the parent of the object you are looking with a SerializedProperty
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Linq;
using System;
using System.Reflection;
...
public object GetParent(SerializedProperty prop)
{
var path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
var elements = path.Split('.');
foreach(var element in elements.Take(elements.Length-1))
{
if(element.Contains("["))
{
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[","").Replace("]",""));
obj = GetValue(obj, elementName, index);
}
else
{
obj = GetValue(obj, element);
}
}
return obj;
}
public object GetValue(object source, string name)
{
if(source == null)
return null;
var type = source.GetType();
var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if(f == null)
{
var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if(p == null)
return null;
return p.GetValue(source, null);
}
return f.GetValue(source);
}
public object GetValue(object source, string name, int index)
{
var enumerable = GetValue(source, name) as IEnumerable;
var enm = enumerable.GetEnumerator();
while(index-- >= 0)
enm.MoveNext();
return enm.Current;
}
Here's a test project showing how it works by just inspecting all of the properties of a particular type and checking their parents: Example
Sorry I dumbly had it finding the actual object the first time I posted :S Fixed it now.
Thanks, I really appreciate it. No, I don't work for Unity - I'm just a user :)
@whydoidoit there's actually a FieldInfo
instance in PropertyDrawer
that gives you reflection access to the property being drawn ;) - so just fieldInfo.GetValue(property.serializedObject);
- from the 4.3 notes:
Editor: PropertyDrawers now have a fieldInfo property that can be used to obtain reflection data about the member that the property represents.
Omg you saved my life. I was looking for a solution to my problem for an hour now, when I saw this. Thank you so much!
Just FYI, property.serializedObject.targetObject returns the Component being inspected. Which means fieldInfo.GetValue(property.serializedObject.targetObject) will not work for any nested fields that is not declared on the component.
If you have a field in Serializable Class C, inside Serializable Class B, defined inside $$anonymous$$onoBehaviour Class A, you will end up needing whydoidoit's GetParent function as well in order to call fieldInfo Get/Set functions.
@vexe thank you for that hint. This is really awesome. Just a small correction though: it should read
fieldInfo.GetValue(property.serializedObject.targetObject)
Best answer that works for me. Thank you so much! Based upon this, another functionality of GetValue() was what I desired:
SomeSettings settings = (SomeSettings)PropertyDrawerUtility.GetValue(property);
It is just based on GetParent(), but does not skip the last element.
static public object GetValue(SerializedProperty prop)
{
string path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
string[] elements = path.Split('.');
foreach (string element in elements.Take(elements.Length))
{
if (element.Contains("["))
{
string elementName = element.Substring(0, element.IndexOf("["));
int index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue(obj, elementName, index);
}
else
{
obj = GetValue(obj, element);
}
}
return obj;
}
Thank you this was the only solution which worked correctly with lists!
Answer by idbrii · Feb 21, 2018 at 10:20 PM
If your class is a SerializedObject (or any child of Object), you can use property.objectReferenceValue
to get a reference to it.
GameObject ob = property.objectReferenceValue as GameObject;
As vexe & codingChris point out, you can use PropertyDrawer.fieldInfo for all Serializable types:
GameObject ob = fieldInfo.GetValue(property.serializedObject.targetObject) as GameObject;
ObjectB ob = fieldInfo.GetValue(property.serializedObject.targetObject) as ObjectB;
All of these methods work even if your object is inside an array.
Demonstration of using this code:
[Demo]
public GameObject thing;
[CustomPropertyDrawer(typeof(DemoAttribute))]
public class DemoDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
{
Debug.Log(string.Format("objectReferenceValue={0} fieldInfo={1}", property.objectReferenceValue, fieldInfo.GetValue(property.serializedObject.targetObject) as GameObject));
EditorGUI.PropertyField(position, property);
}
EditorGUI.EndProperty();
}
}
It works on the first level of the object, but no, it does NOT work inside arrays. Just tested it for 2018.3.All of these methods work even if your object is inside an array.
Answer by Bubsavvy · Oct 02, 2019 at 02:52 AM
Just to improve upon @whydoidoit's answer. If you want to find derived fields and properties when fetching the value, you can do the following.
public static object GetValue(object source, string name)
{
if (source == null)
return null;
var type = source.GetType();
var f = FindFieldInTypeHierarchy(type, name);
if (f == null)
{
var p = FindPropertyInTypeHierarchy(type, name);
if (p == null)
return null;
return p.GetValue(source, null);
}
return f.GetValue(source);
}
public static FieldInfo FindFieldInTypeHierarchy(Type providedType, string fieldName)
{
FieldInfo field = providedType.GetField(fieldName, (BindingFlags)(-1));
while (field == null && providedType.BaseType != null)
{
providedType = providedType.BaseType;
field = providedType.GetField(fieldName, (BindingFlags)(-1));
}
return field;
}
public static PropertyInfo FindPropertyInTypeHierarchy(Type providedType, string propertyName)
{
PropertyInfo property = providedType.GetProperty(propertyName, (BindingFlags)(-1));
while (property == null && providedType.BaseType != null)
{
providedType = providedType.BaseType;
property = providedType.GetProperty(propertyName, (BindingFlags)(-1));
}
return property;
}
Your answer
Follow this Question
Related Questions
Statefull PropertyDrawer 0 Answers
Caching data for a PropertyDrawer 1 Answer
How to handle this case: elements inside an array has a CustomPropertyDrawer? 1 Answer
How to find the first child serializedProperty inside a PropertyDrawer 1 Answer
How To Draw a PropertyDrawer within a PropertyDrawer 0 Answers