ArgumentNullException when adding PropertyField to any VisualElement.

I’m creating an “Expose” field attribute using UI Toolkit (CreatePropertyGUI from PropertyDrawer). Its purpose is to expose an object’s field (serialized property) in the inspector without having to travel to the object to edit it. I will mainly use this attribute to directly edit ScriptableObjects while a MonoBehaviour is selected.

I’ve managed to successfully code this using IMGUI - the older editor UI system; but, I’m reaching an unexpected issue with UI Toolkit.

Basic code explanation:

  1. Create root VIsualElement;
  2. Create, setup and add a normal PropertyField;
  3. Get an UXML file for the custom Foldout;
  4. Query and set up the Foldout;
  5. Create a SerializedObject based on the PropertyDrawer’s property object reference value;
  6. Iterate through every SerializedObject’s properties and copy them to an array (ignores some properties);
  7. Use this array to create and add PropertyFields; ← The add is where the error is coming from!

The PropertyDrawer class:

```csharp
*using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(ExposeAttribute))]
public class ExposeAttributePropertyDrawer : PropertyDrawer
{
private VisualElement _root;
private SerializedProperty _serializedProperties;

private const string k_foldoutUxmlGuid = "c370dc7d5a39e92428503c5c195c612b";

private static string s_foldoutUxmlPath;
private static string FoldoutUxmlPath
{
    get
    {
        if (string.IsNullOrEmpty(s_foldoutUxmlPath))
            s_foldoutUxmlPath = AssetDatabase.GUIDToAssetPath(k_foldoutUxmlGuid);

        return s_foldoutUxmlPath;
    }
}

public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
    _root = new VisualElement();

    var propField = new PropertyField(property);
    propField.BindProperty(property);

    _root.Add(propField);

    if (property.objectReferenceValue == null)
        return _root;

    var foldoutUxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(FoldoutUxmlPath);

    var foldoutRoot = foldoutUxml.Instantiate();
    var foldout = foldoutRoot.Q<Foldout>();
    foldout.text = $"Edit \"{property.objectReferenceValue.name}\"";

    using var serializedObject = new SerializedObject(property.objectReferenceValue);

    GetProperties(serializedObject);

    if (foldout.value)
    {
        for (int i = 0; i < _serializedProperties.Length; i++)
        {
            var prop = _serializedProperties[i];

            var propPropField = new PropertyField(prop);
            propPropField.BindProperty(prop);

            // The next line of code doesn't work.
            // If I comment the next line of code, the error goes away.
            foldoutRoot.Add(propPropField);
        }
    }

    _root.Add(foldoutRoot);

    serializedObject.ApplyModifiedProperties();

    return _root;
}

private void GetProperties(SerializedObject serializedObject)
{
    serializedObject.UpdateIfRequiredOrScript();

    var prop = serializedObject.GetIterator();

    if (!prop.Next(true))
        return;

    int currentPropIndex = 0;

    List<SerializedProperty> serializedProperties = new();

    do
    {
        if (currentPropIndex > 9)
            serializedProperties.Add(prop.Copy());

        currentPropIndex++;
    }
    while (prop.Next(false));

    _serializedProperties = serializedProperties.ToArray();
}

}*
```

The error:

The error doesn’t directly point to line 60, but when removing it, the error gets never printed. I tried to add the PropertyFields to other VisualElements and the error gets printed anyway. The IMGUI version does literally the same thing, but it works (take this into account; I may post the IMGUI code if necessary).

Am I missing something? Thank you.

There’s a problem that repeats all over that code - you’re using UI Toolkit as if it was an immediate mode UI framework. It’s not, CreatePropertyGUI is called once
So when you do this:

        if (property.objectReferenceValue == null)
            return _root;

That means that if you open the editor with the object reference not set to anything, and then update the object reference to something else, the UI won’t change.

Similarly, here:

       if (foldout.value)
        {
            for (int i = 0; i < _serializedProperties.Length; i++)

That will only run once, so depending on what setting the foldout has in the default when you load it from the uxml, you will generate or not generate the fields.

What you need to do in order to work with UI Toolkit is to set up callbacks when UI elements change, and then update the data based on that.

You’ll need to eg. register a value changed callback in order to edit what’s in the UI when the property field changes:

var propField = new PropertyField(property);
propField.RegisterValueChangeCallback(changeEvent =>
{
    CreateUIFor(changeEvent.changedProperty);

});

if (property.objectReferenceValue != null)
    CreateUIFor(property);

void CreateUIFor(SerializedProperty prop)
{
    ...
}

In CreateUIFor, you’ll want to delete any old UI you have, and create completely new UI.

Your foldout should similarly not do if (foldout.value) { ... }. Instead, it should always create the UI for the child SerializedProperties, and then set the display of the child elements to Display.Flex if the foldout is folded out and Display.None if it’s not. You probably want to create a root for the content so you don’t have to iterate all of them to set the display, but can just set it on the root instead.

1 Like

Thanks a ton!

Your post was the push I needed to finally start creating tools with UI Toolkit.
I now have a reference to an UXML UI Document that has everything that I need, minus the code generated PropertyFields.

If you or someone reading this is interested in how everything is working, I’ll leave a pastebin link to the code and a screenshot of the final result.

Pastebin link:
https://pastebin.com/v49H5GSh

Screenshot:
9417374--1319357--upload_2023-10-18_16-31-38.png