Runtime Bindings not working on style properties

I’ve been attempting to use the new runtime bindings to bind a mono behaviour type datasource to elements of my UI (all in code not the UI builder or uxml) and everything is working when changing value type stuff. For example I have custom element inheriting from Label and am able to have bindings updating things like the text of the label and a Vector3 as well.

However, I cannot seem to get the style opacity to bind in the same manner. My suspicion is that it is due to the style.opacity needing to be expressed as a StyleFloat whereas I am of course simply using a normal float in my datasource. I see that the documentation states that there are all sorts of global converters that are supposed to automatically handle this conversion… but at least for me this is not the case. Is this possibly because the converters are only working when this is all setup through UI Builder / UXML???

When setting up bindings completely in C# do I need to call these converters somehow explicitly?

I notice its possible to enable logging of bindings on the panel settings and when I do I do get this warning for the opacity binding (although as I say all other non-style related bindings are working fine):

[UI Toolkit] Could not set value for target of type 'WorldSpaceLabel' at path 'opacity': the path is either invalid or contains a null value. (PanelSettings)

What am I doing wrong here? The docs are pointing me in a certain direction but info on the C# world for this is scant. I can’t seem to find ANY example where a datasource path / binding for a style property is being set in C# rather than UI builder / UXML and it is a frustrating absence in the documentation.

Hi @NumT , to bind against style.opacity, you must use style.opacity as the binding id and not opacity.

You are correct that the property itself expects a StyleFloat, however, we have added converters for float and StyleKeyword already, so it should work out of the box.

One thing to be aware of is that the .style and .resolved properties will not currently report changes.

Hope this helps!

Ah I am already binding to style.opacity so that’s not the issue unfortunately. Hmmm I’ll post the code here a bit later. Thanks

So… I have a MonoBehaviour with a local class definition which I am using as my datasource(s):

public class WorldSpaceAnnotations : MonoBehaviour
{
    [System.Serializable]
    public class Annotation
    {
        [SerializeField]
        [DontCreateProperty]
        private Vector3 m_WorldPosition;
        [SerializeField]
        [DontCreateProperty]
        [TextArea(3, 10)]
        private string m_Text;
        [SerializeField]
        [DontCreateProperty]
        private float m_Opacity;

        [CreateProperty]
        public Vector3 worldPosition
        {
            get => m_WorldPosition;
            set => m_WorldPosition = value;
        }

        [CreateProperty]
        public string text
        {
            get => m_Text;
            set => m_Text = value;
        }

        [CreateProperty]
        public float opacity
        {
            get => m_Opacity;
            set => m_Opacity = value;
        }
    }

    public Annotation[] annotations;
    public UIDocument root;
    public VisualTreeAsset worldSpaceLabelAsset;

    
    private void OnEnable()
    {
        root = GetComponent<UIDocument>();
        foreach (Annotation annotation in annotations)
        {
            for (int i = 0; i < 2; i++)
            {
                // instantiate from template and set some values
                TemplateContainer container = worldSpaceLabelAsset.Instantiate();
                // do container related value setting
                //
                // retrieve world space label
                WorldSpaceLabel label = container.Q<WorldSpaceLabel>();
                // sets initial values
                label.text = annotation.text;
                label.worldPosition = annotation.worldPosition;
                label.style.opacity = annotation.opacity;
                // setup bindings
                label.dataSource = annotation;
                // style.opacity binding - this is the only one not working
                DataBinding opacityDataBinding = new DataBinding
                {
                    dataSourcePath = new PropertyPath(nameof(Annotation.opacity)),
                    bindingMode = BindingMode.ToTarget,
                };
                label.SetBinding(nameof(WorldSpaceLabel.style.opacity), opacityDataBinding);
                // bindings for text - this works fine
                DataBinding textDataBinding = new DataBinding
                {
                    dataSourcePath = new PropertyPath(nameof(Annotation.text)),
                    bindingMode = BindingMode.ToTarget
                };
                label.SetBinding(nameof(WorldSpaceLabel.text), textDataBinding);
                // just testing converter
                textDataBinding.sourceToUiConverters.AddConverter((ref string text) => text + " Added text");
                // binding for world position - this also works fine
                label.SetBinding(nameof(WorldSpaceLabel.worldPosition), new DataBinding
                {
                    dataSourcePath = new PropertyPath(nameof(Annotation.worldPosition)),
                    bindingMode = BindingMode.TwoWay
                });
                // need to add container to both sides as we can't do any heierachy changes
                if (i == 0)
                {
                    label.AddToClassList("left-side-label");
                    root.rootVisualElement.Q("LeftSide").Add(container); 
                }
                else
                {
                    label.AddToClassList("right-side-label");
                    root.rootVisualElement.Q("RightSide").Add(container);
                }
            }            
        }
    }
}

My WorldSpaceLabel class is inheriting from Label, then has a Vector3 and does some drawing in “OnGenerateVisualContent”:

[UxmlElement]
public partial class WorldSpaceLabel : Label
{
    [UxmlAttribute]
    public Vector3 worldPosition;

    public WorldSpaceLabel()
    {
        generateVisualContent += OnGenerateVisualContent;
    }

    private void OnGenerateVisualContent(MeshGenerationContext mgc)
    {
        if (Application.isPlaying)
        {
            Rect r = paddingRect;
            float right = r.width;
            float left = r.x;
            // get start and end points
            // Vector2 startPoint = new Vector2 (mgc.visualElement.layout.xMax, mgc.visualElement.layout.center.y);
            Vector3 viewPos = Camera.main.WorldToViewportPoint(worldPosition);
            Vector2 startPoint = new Vector2(right, r.center.y);
            if (viewPos.x > 0.5f)
            {
                startPoint = new Vector2(left, r.center.y);
            }
            Vector2 endPointPanelPos = RuntimePanelUtils.CameraTransformWorldToPanel(mgc.visualElement.panel, worldPosition, Camera.main);
            Vector2 endPointLocalPos = mgc.visualElement.WorldToLocal(endPointPanelPos);
            //
            if (mgc.visualElement.worldBound.center.x > endPointPanelPos.x)
            {
                // we are to the right of the point
                startPoint = new Vector2(left, r.center.y);
            }
            // do drawing
            Painter2D paint2D = mgc.painter2D;
            // draw outlines first
            paint2D.strokeColor = Color.black;
            paint2D.fillColor = Color.black;
            paint2D.BeginPath();
            paint2D.MoveTo(startPoint);
            paint2D.LineTo(endPointLocalPos);
            paint2D.ClosePath();
            paint2D.Stroke();
            //
            //paint2D.BeginPath();
            //paint2D.MoveTo(startPoint);
            //paint2D.LineTo(endPointLocalPos);
            //paint2D.ClosePath();
            //paint2D.Stroke();
            paint2D.BeginPath();
            paint2D.Arc(endPointLocalPos, 4f, 0, 360);
            paint2D.ClosePath();
            paint2D.Fill();
        }
    }
}

That’s about it really (I’ll upload the full code in case that’s handy). I am now wondering if the way I am specifying the binding path i.e. nameof(WorldSpaceLabel.style.opacity) is the problem - is this not providing a full path as it were to the binding? Does it somehow resolve to just “opacity” instead of “style.opacity”? If so what is the correct way to provide this full path to a binding???

9857127–1419708–WorldSpaceAnnotations.cs.zip (2.98 KB)

Ah actually am I trying to set these style bindings to soon maybe? Is it possible set style type bindings in OnEnable or not? In the debugger I’m getting a message on label.style.opacity of Could not find a member 'opacity' for 'label.style'. Is it not possible to setup bindings for these type of properties in OnEnable as you can for the more simple value types?

Hi @NumT ,

nameof(WorldSpaceLabel.style.opacity) will return opacity, which is not the correct binding id to bind against the style property. The correct id is style.opacity, so you can use "style."+ nameof(WorldSpaceLabel.style.opacity) instead.

Hope this helps!

1 Like

Yes! That’s the information I needed. Great it’s working now many thanks for the assistance.

1 Like