Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

2019.2.19f1 : PropertyField changed event?

Discussion in 'UI Toolkit' started by WavyRancheros, Jun 16, 2020.

  1. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    This is probably a nobrainer. I am trying to set a target object, and access available properties on that object. I'm trying to use UIElements for this.

    Code (CSharp):
    1.         public override VisualElement CreatePropertyGUI(SerializedProperty property)
    2.         {
    3.             Debug.Log("CreatePropertyGUI(...)");
    4.  
    5.             container = new VisualElement();
    6.  
    7.             var targetItemProperty = property.FindPropertyRelative("targetItem");
    8.  
    9.             var targetItemField = new PropertyField(targetItemProperty);
    10.             container.Add(targetItemField);
    11.  
    12.             var selectedItem = targetItemProperty.objectReferenceValue as Item;
    13.  
    14.             if (selectedItem != null)
    15.             {
    16.                 var propertyList = selectedItem.SerializeProperties().Select(x => x.Name).ToList();
    17.  
    18.                 popupField = new PopupField<string>("label", propertyList, 0);
    19.                 popupField.formatSelectedValueCallback += Selected;
    20.  
    21.                 container.Add(popupField);
    22.             }
    23.  
    24.             return container;
    25.         }
    selectedItem is always null, no matter what I do.

    Do I need to add a listener to the PropertyField like I do for the PopupField? How?
     
  2. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    I'm probably too used to the IMGUI. CreatePropertyGUI is called only once when you switch to the corresponding Inspector. So how do I listen to a change in the property field correctly?


    targetItemField.RegisterCallback<ChangeEvent<Item>>(i => PropertyCallback(i.newValue));


    does not work.
     
  3. stan-osipov

    stan-osipov

    Unity Technologies

    Joined:
    Feb 24, 2020
    Posts:
    31
    Yes UTK is retained mode UI so CreatePropertyGUI is called once.
    The PropertyField itself does not have a ChangedEvent but it will create an actual field (IntengerFiled, FloatField, ObjectField, etc) based on the property type inside.
    So one possibility, if you know your property type, you can use UQuery to grab an actual filed inside the PropertyField and register change event.
     
  4. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    Thanks, I'll try that. I don't understand the logic behind this. A PropertyField (no matter what type of property it is) is going to be changed. That's the whole point of having a property exposed to the inspector. So why not give it a changed event that someone can listen to?
     
  5. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    Code (CSharp):
    1. targetItemField = new PropertyField(targetItemProperty);
    2. container.Add(targetItemField);
    3. objectField = container.Query<ObjectField>().First();
    or
    Code (CSharp):
    1.  
    2. targetItemField = new PropertyField(targetItemProperty);
    3. objectField = targetItemField.Query<ObjectField>().First();
    objectField is always null.
     
  6. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    I now resorted to adding an ObjectField like this. Then I can register my valuechangedcallback.

    But now the PopupField never shows up in the Inspector.

    Code (CSharp):
    1.   public override VisualElement CreatePropertyGUI(SerializedProperty property)
    2.         {
    3.             container = new VisualElement();
    4.  
    5.             var targetItemProperty = property.FindPropertyRelative("targetItem");
    6.  
    7.             var objectField = new ObjectField(targetItemProperty.displayName);
    8.             objectField.objectType = typeof(Item);
    9.             objectField.bindingPath = targetItemProperty.propertyPath;
    10.             objectField.BindProperty(property.serializedObject);
    11.  
    12.             objectField.RegisterValueChangedCallback(ObjectField_ValueChanged);
    13.  
    14.             container.Add(objectField);
    15.  
    16.             return container;
    17.         }
    18.  
    19.         private void ObjectField_ValueChanged(ChangeEvent<UnityEngine.Object> e)
    20.         {
    21.             var previousValue =  e.previousValue as Item;
    22.             var newValue = e.newValue as Item;
    23.  
    24.             if (newValue != null)
    25.             {
    26.                 var propertyList = newValue.SerializeProperties().Select(x => x.Name).ToList();
    27.  
    28.                 popupField = new PopupField<string>("Property", propertyList, 0);
    29.                 popupField.formatSelectedValueCallback += Selected;
    30.  
    31.                 container.Add(popupField);
    32.             }
    33.         }
     
  7. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    Ok with this code, the property popup list appears, but the ObjectField content is immediately reset to None.

    I set the binding path for my object field, I call Bind. So why is the item that I drag & drop into the field not stored?

    Plus: container.Remove(popupField); causes an exception: ArgumentException: This visualElement is not my child

    Code (CSharp):
    1. public override VisualElement CreatePropertyGUI(SerializedProperty property)
    2.         {
    3.             container = new VisualElement();
    4.  
    5.             var targetItemProperty = property.FindPropertyRelative("targetItem");
    6.  
    7.             var objectField = new ObjectField(targetItemProperty.displayName);
    8.             objectField.objectType = typeof(Item);
    9.             objectField.bindingPath = targetItemProperty.propertyPath;
    10.             objectField.Bind(property.serializedObject);
    11.             objectField.RegisterValueChangedCallback((e) => ObjectField_ValueChanged(e, property.serializedObject));
    12.  
    13.             container.Add(objectField);
    14.  
    15.             return container;
    16.         }
    17.  
    18.         private void ObjectField_ValueChanged(ChangeEvent<UnityEngine.Object> e, SerializedObject serializedObject)
    19.         {
    20.             var previousValue =  e.previousValue as Item;
    21.             var newValue = e.newValue as Item;
    22.  
    23.             if (newValue != null)
    24.             {
    25.                 var propertyList = newValue.SerializeProperties().Select(x => x.Name).ToList();
    26.  
    27.                 if(popupField != null)
    28.                 {
    29.                     container.Remove(popupField);
    30.                 }
    31.  
    32.                 popupField = new PopupField<string>("Property", propertyList, 0);
    33.                 popupField.formatSelectedValueCallback += Selected;
    34.  
    35.                 container.Add(popupField);
    36.                 container.Bind(serializedObject);
    37.             }
    38.         }
     
    Last edited: Jun 17, 2020
  8. WavyRancheros

    WavyRancheros

    Joined:
    Feb 5, 2013
    Posts:
    176
    I threw everything away and did it using IMGUI.
     
    Digika and MostHated like this.
  9. Emiles

    Emiles

    Joined:
    Jan 22, 2014
    Posts:
    61
    I too was wondering how to bind my custom list displayed in the PopupField to a propery.
    I'm displaying a simple string value. and wanted to populate it based on values written in other scripts.

    This displays the actionKey with standard layout, lable, value. When clicking the popup its value changes, which is good enough for me.


    Code (CSharp):
    1.  
    2.  
    3.             PropertyField actionKey = new PropertyField(property.FindPropertyRelative("actionKey"));
    4.              rootContainer.Add(actionKey); // basically string.
    5.  
    6.             List<string> popupFieldValues = new List<string>();
    7.             MissionEventMapper[] mappers = GameObject.FindObjectsOfType<MissionEventMapper>();
    8.             foreach (var missionEventMapper in mappers)
    9.             {
    10.                 popupFieldValues.Add(""); // hoping theres a prop on PopupField to allow custom strings.
    11.                 foreach (var missionEventItem in missionEventMapper.eventItems)
    12.                 {
    13.                     popupFieldValues.Add(missionEventItem.eventKey);
    14.                 }
    15.             }
    16.  
    17.             var popupField = new PopupField<string>(popupFieldValues, popupFieldValues[0]);
    18.             popupField.BindProperty(property.FindPropertyRelative("actionKey"));
    19.            rootContainer.Add(popupField);
    20.  
     
  10. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    I guess this is a good summary of current state of UTK as of now :D
     
  11. Emiles

    Emiles

    Joined:
    Jan 22, 2014
    Posts:
    61
    I think the hardest bit is finding examples and tutorials that do things people actually want to do. Like, reorderable list. Its insane that Unity expect us to use 3rd party solutions for something this simple. I want to keep those to a minimum thanks.

    Other than that, now that i've spent, admittedly, way too much time working this out, it's by far a much better solution than IMGUI. This is of course once you know things like, rootContainer.bind(serialisedObject) is key to getting things to display. And using the xml/uss to manage layout is super helpful, but you need to shift the way you think about structuring your code, for example building lists as a result of a changeevent rather than in OnEnable.
     
  12. Digika

    Digika

    Joined:
    Jan 7, 2018
    Posts:
    225
    Is this even available at runtime?
     
  13. Emiles

    Emiles

    Joined:
    Jan 22, 2014
    Posts:
    61
    Its in an editor script so no.

    I imagine your refering to the use of Toolkit for ingame UI. I haven't played with that yet. Mostly using Toolkit for custom property inspectors, and when adding properties by hand (i.e. list recursion) i've found that unless you call rootContainer.bind(serialisedObject), nothing will appear.