- Home /
How does one access the Label argument of a PropertyField within a PropretyDrawer using UIToolkit/UIElements?
Title. I want to be able to create my own label for a PropertyField, but when using a custom PropertyDrawer the "label" argument of the PropertyField constructor seems to be entirely ignored. Further I can't seem to find any "label" or "text" fields or the like.
Answer by exploringunity · May 27, 2020 at 04:03 AM
Hey @craftsmanbeck,
It does appear that the label
argument to the PropertyField
constructor is ignored during CreateInspectorGUI
when using a custom property drawer with UIElements. However, I have found a bit of a hack workaround using EditorCoroutineUtility.StartCoroutine
. You can install the EditorCoroutine package via Window -> Package Manager. Be sure to check Advanced -> Show Preview Packages.
Below is an example that demonstrates the problem of not being able to set the labels as well as a solution. It uses the Recipe/Ingredient data structure from https://docs.unity3d.com/ScriptReference/PropertyDrawer.html. Here's a screenshot of the resulting UI (note the custom labels):
Assets/Recipe.cs
// From the example at https://docs.unity3d.com/ScriptReference/PropertyDrawer.html
using System;
using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
[Serializable]
public class Ingredient
{
public string name;
public int amount = 1;
public IngredientUnit unit;
}
public class Recipe : MonoBehaviour
{
public Ingredient potionResult;
public Ingredient[] potionIngredients;
}
Assets/Editor/IngredientDrawer.cs
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// NOTE: This gets called when you call `ui.Bind()` in `RecipeEditor.cs`,
// but also gets called again automatically afterwards, which seems
// to be what's blowing away the changes to the labels.
//Debug.Log($"[IngredientDrawer.CreatePropertyGUI] - {property.displayName}"); // For tracing
var container = new VisualElement();
// NOTE: These 3 `label` arguments work fine
var amountField = new PropertyField(property.FindPropertyRelative("amount"), "Amount (arg)");
var unitField = new PropertyField(property.FindPropertyRelative("unit"), "Unit (arg)");
var nameField = new PropertyField(property.FindPropertyRelative("name"), "Name (arg)");
container.Add(amountField);
container.Add(unitField);
container.Add(nameField);
return container;
}
}
Assets/Editor/IngredientDrawer.cs
using System.Collections;
using Unity.EditorCoroutines.Editor;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
[CustomEditor(typeof(Recipe))]
public class RecipeEditor : Editor
{
PropertyField resultField;
PropertyField ingredientsField;
public override VisualElement CreateInspectorGUI()
{
//Debug.Log($"[RecipeEditor.CreateInspectorGUI] - BEGIN"); // For tracing
var ui = new VisualElement();
// NOTE: The `label` arguments, "Result (arg)" and "Ingredients (arg)", seem to be ignored.
//resultField = new PropertyField(serializedObject.FindProperty("potionResult"), "Result (arg)");
//ingredientsField = new PropertyField(serializedObject.FindProperty("potionIngredients"), "Ingredients (arg)");
// NOTE: Setting the `label` property after creating the fields also doesn't work.
//resultField.label = "Result (set prop)";
//ingredientsField.label = "Test Ingredients (set prop)";
resultField = new PropertyField(serializedObject.FindProperty("potionResult"));
ingredientsField = new PropertyField(serializedObject.FindProperty("potionIngredients"));
ui.Add(resultField);
ui.Add(ingredientsField);
// Calling `Bind` actually fleshes out the PropertyFields above, but it doesn't matter,
// since the fields get overwritten later, so setting the labels after this still won't work.
////Debug.Log(ingredientsField.childCount); // 0
//ui.Bind(serializedObject);
////Debug.Log(ingredientsField.childCount); // 1 -- doesn't matter
// HOWEVER!! Setting the labels in a coroutine later does work!!
EditorCoroutineUtility.StartCoroutine(InitializeUICoroutine(), this);
EditorCoroutineUtility.StartCoroutine(RefreshUICoroutine(), this);
//Debug.Log($"[RecipeEditor.CreateInspectorGUI] - END"); // For tracing
return ui;
}
void InitializeUI()
{
// Static labels (not dynamic list/array)
// OPTION: Create and insert a new label
resultField.Insert(0, new Label("Result (coroutine)"));
}
void RefreshUI()
{
//Debug.Log($"[RecipeEditor.UpdateLabels]"); // For tracing
// OPTION: Find and edit an existing label
var foldout = ingredientsField.Q<Foldout>("unity-foldout-potionIngredients");
foldout.text = "Potion Ingredients (coroutine)";
// Alternative quick-n-dirty way to do the above since we know it's the first label
//ingredientsField.Q<Label>().text = "Potion Ingredients (coroutine2)";
// Another example of finding and editing an existing label
var sizeField = ingredientsField.Q<IntegerField>("unity-input-potionIngredients.Array.size");
sizeField.label = "Size (coroutine)";
// Really hacky way to set a label's text, if you know the exact child layout (FRAGILE!)
//((Label)ingredientsField[0][0][0][0]).text = "Size (coroutine2)";
// New size field needs a new event handler
sizeField.RegisterCallback<BlurEvent>(HandleSizeFieldBlur);
}
IEnumerator InitializeUICoroutine()
{
yield return new WaitForSecondsRealtime(0.067f);
InitializeUI();
}
IEnumerator RefreshUICoroutine()
{
yield return new WaitForSecondsRealtime(0.067f);
RefreshUI();
}
void HandleSizeFieldBlur(BlurEvent evt)
{
EditorCoroutineUtility.StartCoroutine(RefreshUICoroutine(), this);
}
}
And here's a screenshot of when I was using the UIElements debugger to find the structure of the UI:
Note: tested with Unity version 2019.3.13f1 and 2020.1.0b3
Hope this helps!
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Custom attribute not respecting property's PropertyDrawer 0 Answers
Custom PropertyDrawer causes NullReferenceException 1 Answer
Sort a GenericMenu alphabetically. 1 Answer