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.
public override VisualElement CreateInspectorGUI()
{
var tar = (BlobAnimationPlayerAuthoring)target;
var ser = serializedObject;
var storage = ser.FindProperty("storage");
var clip = ser.FindProperty("clip");
var inspector = new VisualElement();
var storageField = new PropertyField(storage);
storageField.RegisterValueChangeCallback(_ => ser.Update());
inspector.Add(storageField);
if (storage.objectReferenceValue == null)
{
inspector.Add(new Label("Please Assign Storage"));
return inspector;
}
var pop = new PopupField<string>("Clip", tar.storage.GetNames(), clip.intValue);
pop.RegisterValueChangedCallback(_ => { clip.intValue = pop.index; ser.ApplyModifiedProperties(); });
inspector.Add(pop);
return inspector;
}
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.
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?
Here’s an example of what I mean by using schedule.Execute to modify some variable content in your inspector:
// Editor/MyItemEditor.cs
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomEditor(typeof(MyItem))]
public class MyItemEditor : Editor
{
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
var fixedContent = new VisualElement();
fixedContent.Add(new PropertyField(serializedObject.FindProperty(nameof(MyItem.description))));
root.Add(fixedContent);
var variableContent = new VisualElement();
RefreshContent(variableContent);
root.Add(variableContent);
root.schedule.Execute(() => RefreshContent(variableContent)).Every(100);
return root;
}
private string oldDescription;
void RefreshContent(VisualElement variableContentRoot)
{
var item = (MyItem) target;
// It would have been simpler to use RegisterValueChangeCallback, but for sake of demonstration,
// this shows that it's also possible to use schedule.Execute and look for changes manually.
if (oldDescription != item.description)
{
oldDescription = item.description;
// Clear visual tree from any previous content before adding to it.
variableContentRoot.Clear();
// Then add some optional content that changes depending on the description.
if (string.IsNullOrEmpty(item.description))
variableContentRoot.Add(new HelpBox("This item has no description. Please provide a description!", HelpBoxMessageType.Warning));
}
}
}
// MyItem.cs:
using UnityEngine;
[CreateAssetMenu]
public class MyItem : ScriptableObject
{
public string description;
}
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.
@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.
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.UIElements;
#endif
public class ShowIfAttribute : PropertyAttribute
{
public string property;
public ShowIfAttribute(string property)
{
this.property = property;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ShowIfAttribute))]
public class ShowIfAttributeDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var root = new VisualElement();
//get properties
var target = attribute as ShowIfAttribute;
var conditionProp = property.serializedObject.FindProperty(target.property);
// Create property container element.
var variableContent = new VisualElement();
RefreshContent(variableContent, property, conditionProp);
root.Add(variableContent);
variableContent.TrackPropertyValue(conditionProp, (e) =>
{
RefreshContent(variableContent, property, e);
});
return root;
}
private void RefreshContent(VisualElement element, SerializedProperty property, SerializedProperty conditionProp)
{
element.Clear();
var propField = new PropertyField(property);
if (conditionProp.boolValue)
{
element.Add(propField);
Debug.Log($"{property.name} should be displayed!");
}
}
}
#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.
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.