How to change default values for custom UI element base attributes

Greetings!

I’ve been playing around with creating custom UI elements and I don’t quite understand how to change the default values of attributes that come from the element’s base class. For example, I created a simple TestField that inherits from BaseField:

public class TestField : BaseField<string>
{
    public new class UxmlFactory : UxmlFactory<TestField, UxmlTraits> { }
    public new class UxmlTraits : BaseFieldTraits<string, UxmlStringAttributeDescription>
    {
        private readonly UxmlBoolAttributeDescription extraAttribute = new UxmlBoolAttributeDescription { name = "extra-attribute", defaultValue = true };
        
        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);
            // [...]
        }
    }

    public TestField() : this(null, new TextField())
    { }
    
    public TestField(string label, VisualElement visualInput) : base(label, visualInput)
    {
        // [...]
    }
}

The BaseFieldTraits<> class I’m using includes several attributes we will have access to in UIBuilder through base.Init().
I would like to set different default values for some attributes (namely label and value), but I can’t figure out how to do it.

Unity’s own TextField, IntegerField and many others derive (directly or indirectly) from BaseField<>, and they have default values for their labels that are different than each other. I’ve peeked inside those components and couldn’t figure out how they do it.

I’ve tried setting the default label as a hardcoded value in the empty constructor call (line 16), but the UXMLTraits base overrides the label value with it’s own attribute.

If you have access to the UXML attribute description of the parent class (it’s not always the case, though), you can change the default value for it through att.defaultValue in the constructor of your UXML traits class.

Looking at the BaseField code the label attribute description seems to be private.

/// <summary>
///        <para>
/// Defines UxmlTraits for the BaseField.
/// </para>
///      </summary>
public new class UxmlTraits : BindableElement.UxmlTraits
{
    private UxmlStringAttributeDescription m_Label;

    public UxmlTraits()
    {
    UxmlStringAttributeDescription attributeDescription = new UxmlStringAttributeDescription();
    attributeDescription.name = "label";
    this.m_Label = attributeDescription;
    // ISSUE: explicit constructor call
    base.\u002Ector();
    this.focusIndex.defaultValue = 0;
    this.focusable.defaultValue = true;
    }

So specifically for the label this does not seem to be a solution. Same case for the value attribute description, which I also checked.
At least for the labels, Unity’s included components did it somehow…

One thing you can try is overridding the attribute in the child class. If you create an attribute with the same uxml name then this default can be applied after the base class.

For example

using UnityEngine.UIElements;

public class MyElement : TextField
{
    public new class UxmlFactory : UxmlFactory<MyElement, UxmlTraits> { }

    public new class UxmlTraits : TextField.UxmlTraits
    {
        private UxmlStringAttributeDescription value = new UxmlStringAttributeDescription { name = "value", defaultValue = "New default" };

        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            // Applies original default
            base.Init(ve, bag, cc);

            var myElement = ve as MyElement;

            // Apply new default
            myElement.value = value.GetValueFromBag(bag, cc);
        }
    }
}

Hi, Karl, unfortunately this doesn’t work, it only creates a second “Value” attribute that affects the same variable.

Technically it does solve the issue, but I don’t think it’s an acceptable solution to have a duplicate of all the attributes I want a different default to. In theory I could just not use the base Init() and recreate all the base attributes myself, but I shouldn’t need to do that to get the result I want.

Unfortunatly the UI Builder is not equipped for this when using UxmlTraits. This is supported in the new UxmlAttribute system in Unity 6.
Sadly theres no simple way to solve this. Another thing you could try is using refection to access the private traits to change the default value in them.

Thanks anyways. What bothers me is that TextField and IntegerField and many other Unity fields do it. How does it work for those classes that I cannot do in my example?

Unfortunately there is no supported mechanism for changing the default values of attributes on child elements using UxmlTraits. While workarounds like reflection might work in some cases, they are not ideal. This is one of the problems we solved with the new UxmlElement system, which allows you to override child attributes and replace or update the UI Builder property fields.
If you are able to use Unity 6 we highly recommend transitioning to the UxmlElement system. We have deprecated UxmlTraits and UxmlFactory in Unity 6, and they will be removed in the next major release.

For example, to change a default value using the new UxmlElement system, you would do the following:


[UxmlElement]
public partial class MyElement : TextField 
{
    public MyElement()
    {
        label = "New default label";
        value = "New default value"
    }
}

One workaround that I found on accident for the value attribute for BaseField inheritors specifically is to have the UxmlTraits inherit from BaseField<>.UxmlTraits instead of BaseFieldTraits<>. The difference between both is the value attribute only, which makes it possible for us to define the value attribute ourselves.
So, picking up from the first code example:

public class TestField : BaseField<string>
{
    public new class UxmlFactory : UxmlFactory<TestField, UxmlTraits> { }
    // parent class used here is important
    public new class UxmlTraits : BaseField<string>.UxmlTraits
    {
        private readonly UxmlStringAttributeDescription value = new UxmlStringAttributeDescription { name = "value", defaultValue = "hello world!" };

        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);
            TestField field = ve as TestField;
            field.value = value.GetValueFromBag(bag, cc);
        }
    }
    public TestField() : this(null, new VisualElement()) { }
    public TestField(string label, VisualElement visualInput) : base(label, visualInput)
    {
     // [...]
    }
}

This specific approach doesn’t work for any other attributes, but it gives us the idea that we can set the parent of UxmlTraits further up the inheritance chain and only reimplement the attributes up to where we need it. So to be able to set the default attribute label we would inherit from BindableElement.UxmlTraits and implement the label and value attributes ourselves.

Not simple for all situations (TextFields, for example would be more complicated), but a useful trick to have anyways.

1 Like