,Unity Custom Inspector CreateInspectorGUI redraw on change

I am working with the new UIElements version of the inspector initialization routine, using CreateInspectorGUI() and I have it displaying the way I want. The problem comes in when I make a change to the object as a result of a button press in the inspector, I can’t get the inspector to repaint to reflect the changes no matter what I try.

I’ve tried Repaint(), I’ve tried overriding RequiresConstantRepaint() and returning true, I’ve tried calling MarkDirtyRepaint on my instance. I’m at my wits end as I can’t find anything on this problem

Can anyone more experienced in this please give me a hand please?

Unity Version is 2019.2.2f1

Here is my inspector

[CustomEditor(typeof(ActionListRunner))]
    public class GameActionListInspector : Editor
    {
        public override bool RequiresConstantRepaint()
        {
            return false;
        }
    
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
        }
    
        public override VisualElement CreateInspectorGUI()
        {
            ActionListRunner runner = (ActionListRunner) target;
    
            if (runner.ListActions == null)
            {
                runner.ListActions = new List<BaseGameAction>();
            }
    
            var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/GameActionListInspector.uxml");
            var uxmlVE = visualTree.CloneTree();
            uxmlVE.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/GameActionListInspector.uss"));
    
            EnumField typeToCreate = uxmlVE.Query<EnumField>("ActionType"); 
    
            Button addAction = new Button(()=>
            {
                ActionType type = (ActionType) typeToCreate.value;
    
                if (type == ActionType.MessageBox)
                {
    
                    runner.ListActions.Add(ScriptableObject.CreateInstance<ShowMessageBox>());
                    uxmlVE.MarkDirtyRepaint();
                }
            });
            addAction.text = "Test";
            uxmlVE.Add(addAction);
            //addAction.clickable = new Clickable(btnAddAction);
    
            VisualElement actionsElement = uxmlVE.Query<VisualElement>("actions");
            int i = 0;
            foreach (var gameAction in runner.ListActions)
            {
                Label label = new Label($"{i+1} - {gameAction.Type.ToString()}");
                actionsElement.Add(label);
                if (gameAction is ShowMessageBox)
                {
                    InspectorElement element = new InspectorElement((ShowMessageBox)gameAction);
                    actionsElement.Add(element);
                }
                Button btnDelete = new Button(() =>
                {
                    runner.ListActions.Remove(gameAction);
                    uxmlVE.MarkDirtyRepaint();
                });
                btnDelete.text = $"Remove {label.text}";
                actionsElement.Add(btnDelete);
                i++;
            }
    
            return uxmlVE;
        }
    }

Here is my UXML

<?xml version="1.0" encoding="utf-8"?>
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ue="UnityEditor.UIElements">
  <ui:VisualElement class="gameActionList">
    <ui:Label text="Action List Editor" />

    <ui:ScrollView name="actions" class="cmd" mode="Vertical">

    </ui:ScrollView>

    <ue:EnumField class="menu__item" name="ActionType" type="Assets.ActionType, Assembly-CSharp" value="SaveGame"  />
  </ui:VisualElement>
</UXML>

2 Answers

2

Custom inspector using Visual Element will instantiate only once, so it will not get refreshed when a field changed. That’s why Repaint() doesn’t work.
So you need to manually add callback when a value changed in inspector.

See this image below

On the image above, I create custom inspector with Visual Element.
The green highlighter on UI Toolkit Debugger is my custom visual element. Did you notice that my Root visual element is inside unity default property?

Here is preview part of code how I created Root.

var root = new VisualElement();
root.name = "Root";
UnityEditor.UIElements.InspectorElement.FillDefaultInspector(root, serializedObject, this);
...
return root;

Because my Root visual element is inside unity default property, I can get property field using my Root visual element by Linq.

...
var children = root.Children()
                   .Where(x => x is UnityEditor.UIElements.PropertyField prop && prop != null)
                   .Select(x => x as UnityEditor.UIElements.PropertyField);
...

After I get properties, I add them a callback on property changed event.

...
foreach (var item in children)
{
    item.RegisterValueChangeCallback(
        (propertyChanged) =>
        {
               /// Write callback codes here
        }
    );
}
...

Because the callback I could change my custom InfoBox. Here is my full script looks like.

public override VisualElement CreateInspectorGUI()
{
    var state = target as T;
    var root = new VisualElement();
    root.name = "Root";
    UnityEditor.UIElements.InspectorElement.FillDefaultInspector(root, serializedObject, this);
    GetScriptAndLabel(state, out var scriptName, out var labelName);

    var helpBox = CreateHelpBox(scriptName, labelName);
    root.Add(helpBox);
    var labelElement = helpBox.Q<Label>();
    labelElement.style.fontSize = 15;

    var buttonContainer = new VisualElement();
    buttonContainer.name = "ButtonContainer";
    buttonContainer.style.flexDirection = FlexDirection.Row;

    var copyTitle = new Button(() => GUIUtility.systemCopyBuffer = scriptName);
    copyTitle.text = "Copy Title";
    var copyLabel = new Button(() => GUIUtility.systemCopyBuffer = labelName);
    copyLabel.text = "Copy Label";

    buttonContainer.Add(copyTitle);
    buttonContainer.Add(copyLabel);
    root.Add(buttonContainer);
    var children = root.Children()
        .Where(x => x is UnityEditor.UIElements.PropertyField prop && prop != null)
        .Select(x => x as UnityEditor.UIElements.PropertyField);
    foreach (var item in children)
    {
        item.RegisterValueChangeCallback(
            (valueChanged) =>
            {
                GetScriptAndLabel(state, out scriptName, out labelName);
                helpBox.text = $"Script: {scriptName}\nLabel: {labelName}";
            }
        );
    }
    return root;

    static HelpBox CreateHelpBox(string script, string label)
    {
        return new HelpBox($"Script: {script}\nLabel: {label}", HelpBoxMessageType.Info);
    }
}

I’ve found how to work with custom List element in custom inspector with updating it.
https://forum.unity.com/threads/uielements-listview-with-serializedproperty-of-an-array.719570/#post-4824431