Binding a C# Property to a PropertyField without bypassing the getter and setter

I’ve been working on an editor tool recently, but I still struggle to use the binding with properties.

From what I can tell, it’s currently impossible to do something like this:

public class DataClass : MonoBehaviour
{
    [field: SerializeField, DontCreateProperty]
    [CreateProperty]
    public int Data {get; set;}
}

[CustomEditor(typeof(DataClass))]
public class Inspector : Editor
{
    public override VisualElement CreateInspectorGUI()
    {
        VisualElement myInspector = new();
        PropertyField field = new(serializedObject.FindProperty("Data"));
        myInspector.Add(field);
        return myInspector;
    }
}

Maybe I’m misunderstanding this, but I think it should work.

It’s not only simpler (I don’t have to make the backing field explicit or use <Data>k__BackingField), but it could also allow for stuff like this:

public class DataClass : MonoBehaviour
{
    [field: SerializeField, DontCreateProperty]
    private int m_data;

    [CreateProperty]
    public int Data
    {
        get { return m_data; }
        set { m_data = value >= 0 ? value : 0; }
    }
}

So that you don’t have to do the same check twice for the ui and the api.

The first part is more of a QoL, but it would still be very useful.
While the second one would be a life saver, but there’s probably limitations that I’m not aware of.

Maybe there’s a good reason not to do it, or maybe I just missed the right page on the documentation :sweat_smile:

(english is not my first language so sorry if the wording is awkward)

You cannot do this through PropertyField nor anything related to SerializedObject / SerializezProperty.

You can do it through the Runtime Binding System. But there are some major pain points:

  • You lose the default Undo + SetDirty behaviour, which you would have to implement through a custom DataBinding type.
  • You lose the abiility to have multiple editor targets. This is the hasMixedValue property, and it would be harder to implement.
  • You lose the automatic visual that PropertyField provides, and you have to manually specify which VisualElement to create.
  • Now your code executes constantly in the Inspector, so you should make sure that your getters/setters dont trigger exceptions in case something is missconfigured.
  • You could potentially modify the field of another Component / Scriptable inside your property, and thus there is no way to call Undo & SetDirty for the “other” target, so you should only modify you own instance properties from those properties.

You can kinda get to a point where its all automated though, but it requires some work, and might not be worth it unless you have a specific goal in mind (other than simple validation).

Quick & dirty would be like this:

The binding

public class UndoDataBinding : DataBinding
{
    protected override BindingResult UpdateSource<TValue>(in BindingContext context, ref TValue value)
    {
        var obj = context.dataSource as UnityEngine.Object;
        if (obj)
        {
            // this should normally be RecordObject, but I believe that it only records 1 property modification,
            // and now that you are using setters, what prevents you to modifying multiple properties?
            Undo.RegisterCompleteObjectUndo(obj, $"Modify {context.dataSourcePath}.");
        }
        
        var result = base.UpdateSource(in context, ref value);
        if (obj)
        {
            // many other things should be done here, like MarkSceneDirty it 'obj' belongs to a scene,
            // or PrefabUtility.RecordPrefabInstancePropertyModifications if 'obj' is a prefabInstance,
            // or PrefabUtility.SavePrefabAsset if you are modifying a prefab from the ProjectWindow without
            // opening it, etc, so it gets tricky
            EditorUtility.SetDirty(obj);
        }
        return result;
    }
}

Then on your Editor like:

VisualElement myInspector = new()
{
    dataSource = target
};
var path = PropertyPath.FromName("Data");
// now you cannot rely on "PropertyField" and you should decide your VisualElement manually.
// you could get a system to automate this, but if not, there is not current workaround AFAIK
var field = new IntegerField("Data")
{
    dataSourcePath = path
};
field.SetBinding(new BindingId(nameof(IntegerField.value)), new UndoDataBinding());
        
myInspector.Add(field);
return myInspector;
1 Like

Unity.Properties.CreateProperty and such aren’t related to editor SerializedProperty’s at all. It’s an API used by runtime bindings, and is available to us to implement our own systems that might want to use it. Looking at the docs, it seems fairly powerful, though no use case immediately pops to mind: Unity - Manual: Use `PropertyVisitor` to create a property visitor

It would be more useful it we could implement visitors that run during general asset re-serialisation.

In any case, I wouldn’t use runtime bindings for editor stuff. This is where you’d write a property drawer, or for your specific example, use the existing [MinAttribute]: Unity - Scripting API: MinAttribute

MinValue on the serialized field which handles the same validation as your property.

1 Like