Force an inspector repaint on custom property drawer

Hello !
I created a custom property drawer for one of my scriptable objects, so I can display them how they want.

However, when I perform the undo/redo actions, the inspector doesn’t update (event though the undo/redo did in fact occur)

Does anyone know how I can force a repaint of the inspector ?

I tried this but it didn’t work:

  private void OnEnable() {
    Undo.undoRedoPerformed += OnUndoRedo;
  }

  private void OnDisable() {
    Undo.undoRedoPerformed -= OnUndoRedo;
  }

  private void OnUndoRedo() {
    EditorUtility.SetDirty(target);
    Repaint();
  }

Thank you ! :slight_smile:

Hmm, kinda strange. Can you show us the code for the custom property drawer?
Do you use serializedObject.ApplyModifiedProperties()? If not, that might be the case why the inspector doesn’t refresh.

A PropertyDrawer or Editor class? Worth noting that property drawers are for non-Unity objects, though the code you showed implies a UnityEditor.Editor class.

In any case, the inspector not repainting during an undo would imply that values are perhaps not being dirtied or bound correctly, but we would need to see the actual inspector code to determine why.

I needed that to draw a ScriptableObject how I wanted:


using UnityEditor;
using UnityEngine;
using static Helpers.Graphics;


[CustomEditor(typeof(NPCData))]
public sealed class NPCDataInspector : Editor {

  private SerializedProperty npcName;
  private SerializedProperty country;
  private SerializedProperty mesh;
  private SerializedProperty voices;

  private SerializedProperty infosDicoSonic;
  private SerializedProperty infosDicoShadow;
  private SerializedProperty infosDicoMetalSonic;
  private SerializedProperty infosDicoLast;

  private void OnEnable() {
    npcName = serializedObject.FindProperty("npcName");
    country = serializedObject.FindProperty("country");
    mesh = serializedObject.FindProperty("mesh");
    voices = serializedObject.FindProperty("voices");

    infosDicoSonic = serializedObject.FindProperty("infosDicoSonic");
    infosDicoShadow = serializedObject.FindProperty("infosDicoShadow");
    infosDicoMetalSonic = serializedObject.FindProperty("infosDicoMetalSonic");
    infosDicoLast = serializedObject.FindProperty("infosDicoLast");
  }

  public override void OnInspectorGUI() {
    EditorGUILayout.PropertyField(npcName);
    EditorGUILayout.PropertyField(country);
    EditorGUILayout.PropertyField(mesh);
    EditorGUILayout.PropertyField(voices);

    EditorGUILayout.Space();

    EditorGUILayout.PropertyField(infosDicoSonic);
    EditorGUILayout.PropertyField(infosDicoShadow);
    EditorGUILayout.PropertyField(infosDicoMetalSonic);
    EditorGUILayout.PropertyField(infosDicoLast);

    serializedObject.ApplyModifiedProperties();
  }

}

[CustomPropertyDrawer(typeof(NPCLine<,>), true)]
public class NPCLineDrawer : PropertyDrawer {
  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    EditorGUI.BeginProperty(position, label, property);

    float lineHeight = EditorGUIUtility.singleLineHeight;

    SerializedProperty text = property.FindPropertyRelative("text");
    SerializedProperty eventFlag = property.FindPropertyRelative("eventFlag");
    SerializedProperty emotion = property.FindPropertyRelative("emotion");
    SerializedProperty useHub = property.FindPropertyRelative("useHub");
    SerializedProperty hub = property.FindPropertyRelative("hub");
    SerializedProperty useMission = property.FindPropertyRelative("useMission");
    SerializedProperty townMission = property.FindPropertyRelative("townMission");
    SerializedProperty hasChoices = property.FindPropertyRelative("hasChoices");
    SerializedProperty choices = property.FindPropertyRelative("choices");

    string foldoutLabel = string.IsNullOrEmpty(text.stringValue) ? $"Element {property.propertyPath.Substring(property.propertyPath.LastIndexOf('[') + 1, property.propertyPath.LastIndexOf(']') - property.propertyPath.LastIndexOf('[') - 1)}" : text.stringValue;

    // Calculate rect for foldout label
    Rect foldoutLabelRect = new(position.x, position.y, position.width, lineHeight);

    // Change foldout label color based on hasChoices
    GUIStyle foldoutLabelStyle = new(EditorStyles.foldout);
    if (useMission.boolValue) {
      foldoutLabelStyle.normal.textColor = HexToColor("#00AAFF");
    }
    else if (eventFlag.objectReferenceValue) {
      foldoutLabelStyle.normal.textColor = Color.green;
    }
    else if (hasChoices.boolValue) {
      foldoutLabelStyle.normal.textColor = Color.yellow;
    }

    // Draw foldout
    property.isExpanded = EditorGUI.Foldout(
        foldoutLabelRect,
        property.isExpanded,
        foldoutLabel,
        true,
        foldoutLabelStyle
    );

    // Draw other fields if expanded
    if (property.isExpanded) {
      // Indent the child properties
      EditorGUI.indentLevel++;

      float emotionHeightBonus = emotion.isExpanded ? 6.5f : 0.5f;
      float useHubHeightBonus = useHub.boolValue ? 1f : 0f;
      float hubHeightBonus = hub.isExpanded ? 3.5f : 0.5f;
      float missionHeightBonus = useMission.boolValue ? 1f : 0f;
      float emotionHubHeightBonus = emotionHeightBonus + useHubHeightBonus + hubHeightBonus;
      float foldablesHeightBonus = emotionHubHeightBonus + missionHeightBonus;

      if (!useHub.boolValue) hub.isExpanded = false;
      if (!hasChoices.boolValue) choices.isExpanded = false;

      // Calculate rects
      Rect textRect = new(position.x, position.y + lineHeight + 2f, position.width, lineHeight);
      Rect eventFlagRect = new(position.x, position.y + 2f * (lineHeight + 2f), position.width, lineHeight);
      Rect emotionRect = new(position.x, position.y + 3f * (lineHeight + 2f), position.width, lineHeight);
      Rect useHubRect = new(position.x, position.y + (emotionHeightBonus + 4f) * (lineHeight + 2f), position.width, lineHeight);
      Rect hubRect = new(position.x, position.y + (emotionHeightBonus + 5f) * (lineHeight + 2f), position.width, lineHeight);
      Rect useMissionRect = new(position.x, position.y + (emotionHubHeightBonus + 5f) * (lineHeight + 2f), position.width, lineHeight);
      Rect townMissionRect = new(position.x, position.y + (emotionHubHeightBonus + 6f) * (lineHeight + 2f), position.width, lineHeight);
      Rect hasChoicesRect = new(position.x, position.y + (foldablesHeightBonus + 6.5f) * (lineHeight + 2f), position.width, lineHeight);

      // Draw fields
      EditorGUI.PropertyField(textRect, text);
      EditorGUI.PropertyField(eventFlagRect, eventFlag);

      // Draw emotion properties
      if (emotion != null) {
        // Draw emotion foldout
        EditorGUI.PropertyField(emotionRect, emotion);

        // If emotion is expanded, draw its properties
        if (emotion.isExpanded) {
          SerializedProperty faceAnimation = emotion.FindPropertyRelative("faceAnimation");
          SerializedProperty bodyAnimation = emotion.FindPropertyRelative("bodyAnimation");
          SerializedProperty emotionFX = emotion.FindPropertyRelative("emotionFX");
          SerializedProperty textBubble = emotion.FindPropertyRelative("textBubble");
          SerializedProperty textSize = emotion.FindPropertyRelative("textSize");
          SerializedProperty voice = emotion.FindPropertyRelative("voice");

          // Indent for emotion properties
          EditorGUI.indentLevel++;

          // Calculate rects for emotion fields
          Rect faceAnimationRect = new(position.x, position.y + 4f * (lineHeight + 2f), position.width, lineHeight);
          Rect bodyAnimationRect = new(position.x, position.y + 5f * (lineHeight + 2f), position.width, lineHeight);
          Rect emotionFXRect = new(position.x, position.y + 6f * (lineHeight + 2f), position.width, lineHeight);
          Rect textBubbleRect = new(position.x, position.y + 7f * (lineHeight + 2f), position.width, lineHeight);
          Rect textSizeRect = new(position.x, position.y + 8f * (lineHeight + 2f), position.width, lineHeight);
          Rect voiceRect = new(position.x, position.y + 9f * (lineHeight + 2f), position.width, lineHeight);

          // Draw emotion fields
          EditorGUI.PropertyField(faceAnimationRect, faceAnimation);
          EditorGUI.PropertyField(bodyAnimationRect, bodyAnimation);
          EditorGUI.PropertyField(emotionFXRect, emotionFX);
          EditorGUI.PropertyField(textBubbleRect, textBubble);
          EditorGUI.PropertyField(textSizeRect, textSize);
          EditorGUI.PropertyField(voiceRect, voice);

          // Un-indent emotion properties
          EditorGUI.indentLevel--;
        }
      }

      EditorGUI.PropertyField(useHubRect, useHub);

      // Draw hub properties
      if (useHub.boolValue && hub != null) {
        // Draw hub foldout
        if (useHub.boolValue) EditorGUI.PropertyField(hubRect, hub);

        // If hub is expanded, draw its properties
        if (hub.isExpanded) {
          SerializedProperty hubName = hub.FindPropertyRelative("name");
          SerializedProperty startPos = hub.FindPropertyRelative("startPos");
          SerializedProperty startRot = hub.FindPropertyRelative("startRot");

          // Indent for emotion properties
          EditorGUI.indentLevel++;

          // Calculate rects for emotion fields
          Rect hubNameRect = new(position.x, position.y + (emotionHeightBonus + 6f) * (lineHeight + 2f), position.width, lineHeight);
          Rect startPosRect = new(position.x, position.y + (emotionHeightBonus + 7f) * (lineHeight + 2f), position.width, lineHeight);
          Rect startRotRect = new(position.x, position.y + (emotionHeightBonus + 8f) * (lineHeight + 2f), position.width, lineHeight);

          // Draw emotion fields
          EditorGUI.PropertyField(hubNameRect, hubName);
          EditorGUI.PropertyField(startPosRect, startPos);
          EditorGUI.PropertyField(startRotRect, startRot);

          // Un-indent emotion properties
          EditorGUI.indentLevel--;
        }
      }

      EditorGUI.PropertyField(useMissionRect, useMission);
      if (useMission.boolValue) EditorGUI.PropertyField(townMissionRect, townMission);

      EditorGUI.PropertyField(hasChoicesRect, hasChoices);

      // Draw choices if hasChoices is true
      if (hasChoices.boolValue) {
        Rect choicesRect = new(position.x, position.y + (foldablesHeightBonus + 7.5f) * (lineHeight + 2f), position.width, lineHeight);
        EditorGUI.PropertyField(choicesRect, choices, true);
      }

      // Un-indent the child properties
      EditorGUI.indentLevel--;
    }

    EditorGUI.EndProperty();
  }

  public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    SerializedProperty emotion = property.FindPropertyRelative("emotion");
    SerializedProperty useHub = property.FindPropertyRelative("useHub");
    SerializedProperty hub = property.FindPropertyRelative("hub");
    SerializedProperty useMission = property.FindPropertyRelative("useMission");
    SerializedProperty townMission = property.FindPropertyRelative("townMission");
    SerializedProperty hasChoices = property.FindPropertyRelative("hasChoices");
    SerializedProperty choices = property.FindPropertyRelative("choices");

    float emotionHeightBonus = emotion.isExpanded ? 6.5f : 0.5f;

    float lineHeight = EditorGUIUtility.singleLineHeight;
    float height = lineHeight;

    if (property.isExpanded) {
      height += (emotionHeightBonus + 6f) * (lineHeight + 2f); // Text, eventFlag, emotion
      height += EditorGUI.GetPropertyHeight(hasChoices, true) + lineHeight * 0.25f; // hasChoices

      if (useHub.boolValue) {
        height += EditorGUI.GetPropertyHeight(hub, true) + lineHeight * 0.25f; // Choices
      }

      if (useMission.boolValue) {
        height += EditorGUI.GetPropertyHeight(townMission, true) + lineHeight * 0.25f; // Choices
      }

      if (hasChoices.boolValue) {
        height += EditorGUI.GetPropertyHeight(choices, true) + lineHeight * 0.25f; // Choices
      }
    }

    return height;
  }
}





I can see you call serializedObject.ApplyModifiedProperties(); at the end of OnInspectorGUI, but you don’t call serializedObject.Update(); at the beginning of it. Maybe that’s the case?

Yes that was it !

Thank you ! :slight_smile:

1 Like