Search Unity

Resolved UI Elements Inspector ApplyModifiedProperties / Refresh Inspector View ?

Discussion in 'UI Toolkit' started by toomasio, Apr 19, 2022.

  1. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    199
    Hello,

    Trying to figure out the proper way to "refresh" the inspector after a value is changed using this new system. The inspector does not seem to refresh... so my fields are not changing to the desired look.

    Code (CSharp):
    1. public override VisualElement CreateInspectorGUI()
    2.         {
    3.             var tar = (BlobAnimationPlayerAuthoring)target;
    4.             var ser = serializedObject;
    5.             var storage = ser.FindProperty("storage");
    6.             var clip = ser.FindProperty("clip");
    7.             var inspector = new VisualElement();
    8.             var storageField = new PropertyField(storage);
    9.             storageField.RegisterValueChangeCallback(_ => ser.Update());
    10.             inspector.Add(storageField);
    11.  
    12.             if (storage.objectReferenceValue == null)
    13.             {
    14.                 inspector.Add(new Label("Please Assign Storage"));
    15.                 return inspector;
    16.             }
    17.             var pop = new PopupField<string>("Clip", tar.storage.GetNames(), clip.intValue);
    18.             pop.RegisterValueChangedCallback(_ => { clip.intValue = pop.index; ser.ApplyModifiedProperties(); });
    19.             inspector.Add(pop);
    20.             return inspector;
    21.         }
    Any help appreciated. Thanks,
     
  2. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    220
    Not sure if this is the only way here, but you could do

    inspector.schedule.Execute(_ => RefreshContent(inspector)).Every(100);


    and then have a RefreshContent method that takes in the inspector and populates it from scratch again. If you want to do a bit better for performance, you could have a quick check that the underlying storage has changed, and return early if it hasn't.
     
    toomasio likes this.
  3. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    199
    Thanks for the reply. Is there any specific methods that draws the inspector elements? Since CreateInspectorGUI only gets called once...I do not know what to call to "refresh" the elements. I tried using your inspector.schedule and I couldnt get it to redraw anything....I got it to debug console commands so it does loop though. Is there some sort of VisualElement inspector variable that I can "Reset" or anything like that?

    I tried Initialize() but it throws an error.
     
  4. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    220
    Here's an example of what I mean by using schedule.Execute to modify some variable content in your inspector:

    Code (CSharp):
    1. // Editor/MyItemEditor.cs
    2. using UnityEditor;
    3. using UnityEditor.UIElements;
    4. using UnityEngine.UIElements;
    5.  
    6. [CustomEditor(typeof(MyItem))]
    7. public class MyItemEditor : Editor
    8. {
    9.     public override VisualElement CreateInspectorGUI()
    10.     {
    11.         var root = new VisualElement();
    12.  
    13.         var fixedContent = new VisualElement();
    14.         fixedContent.Add(new PropertyField(serializedObject.FindProperty(nameof(MyItem.description))));
    15.         root.Add(fixedContent);
    16.      
    17.         var variableContent = new VisualElement();
    18.         RefreshContent(variableContent);
    19.         root.Add(variableContent);
    20.      
    21.         root.schedule.Execute(() => RefreshContent(variableContent)).Every(100);
    22.         return root;
    23.     }
    24.  
    25.     private string oldDescription;
    26.     void RefreshContent(VisualElement variableContentRoot)
    27.     {
    28.         var item = (MyItem) target;
    29.  
    30.         // It would have been simpler to use RegisterValueChangeCallback, but for sake of demonstration,
    31.         // this shows that it's also possible to use schedule.Execute and look for changes manually.
    32.         if (oldDescription != item.description)
    33.         {
    34.             oldDescription = item.description;
    35.  
    36.             // Clear visual tree from any previous content before adding to it.
    37.             variableContentRoot.Clear();
    38.  
    39.             // Then add some optional content that changes depending on the description.
    40.             if (string.IsNullOrEmpty(item.description))
    41.                 variableContentRoot.Add(new HelpBox("This item has no description. Please provide a description!", HelpBoxMessageType.Warning));
    42.         }
    43.     }
    44. }
    45.  
    Code (CSharp):
    1. // MyItem.cs:
    2. using UnityEngine;
    3. [CreateAssetMenu]
    4. public class MyItem : ScriptableObject
    5. {
    6.     public string description;
    7. }
    8.  
    Note that in this example, all the elements under the variableContent get completely removed and created from scratch when the values of the object change, which means if they were focused or if they had any persistent state, it would be lost. In your case, you probably want to conserve the same PopupField instance but change its content in your equivalent of my RefreshContent method, for example.
     
    toomasio likes this.
  5. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    199
    @uBenoitA I am running into another issue when tracking property values and trying to add elements in the inspector. Using attributes, I am trying to do a ShowIfAttribute, which only displays a property field if another property field is true.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6. using UnityEditor.UIElements;
    7. #endif
    8.  
    9. public class ShowIfAttribute : PropertyAttribute
    10. {
    11.     public string property;
    12.  
    13.     public ShowIfAttribute(string property)
    14.     {
    15.         this.property = property;
    16.     }
    17. }
    18.  
    19. #if UNITY_EDITOR
    20. [CustomPropertyDrawer(typeof(ShowIfAttribute))]
    21. public class ShowIfAttributeDrawer : PropertyDrawer
    22. {
    23.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    24.     {
    25.         var root = new VisualElement();
    26.         //get properties
    27.         var target = attribute as ShowIfAttribute;
    28.         var conditionProp = property.serializedObject.FindProperty(target.property);
    29.         // Create property container element.
    30.         var variableContent = new VisualElement();
    31.         RefreshContent(variableContent, property, conditionProp);
    32.         root.Add(variableContent);
    33.  
    34.         variableContent.TrackPropertyValue(conditionProp, (e) =>
    35.         {
    36.             RefreshContent(variableContent, property, e);
    37.         });
    38.  
    39.         return root;
    40.     }
    41.  
    42.     private void RefreshContent(VisualElement element, SerializedProperty property, SerializedProperty conditionProp)
    43.     {
    44.         element.Clear();
    45.         var propField = new PropertyField(property);
    46.         if (conditionProp.boolValue)
    47.         {
    48.             element.Add(propField);
    49.             Debug.Log($"{property.name} should be displayed!");
    50.         }  
    51.     }
    52. }
    53. #endif
    There is no problem clearing the property instantly on value change [false], but does not add the property field on [true] instantly. I need to deselect the object and reselect to update the inspector and show the property.

    Any ideas?
     
  6. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    Hi @toomasio,

    I believe what you are missing is a call to
    Bind
    the
    PropertyField
    in your
    RefreshContent
    method. This
    Bind
    operation is done automatically once for you for the element returned by
    CreatePropertyGUI
    , but subsequent binding operations must be done manually.

    Hope this helps!
     
    toomasio likes this.
  7. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    199

    That did it! Thanks!

    Code (CSharp):
    1. private void RefreshContent(VisualElement element, SerializedProperty property, SerializedProperty conditionProp)
    2. {
    3.     element.Clear();
    4.     var propField = new PropertyField(property);
    5.     if (conditionProp.boolValue)
    6.         element.Add(propField);
    7.     propField.Bind(property.serializedObject);
    8. }
     
    martinpa_unity likes this.