Hey folks,
I have a UI element which represents the current assignment of the room.
The data source looks like this:
public sealed class RoomAssignment : ScriptableObject {
[SerializeField]
public Student Student;
}
…and Student class for reference:
public sealed class Student : ScriptableObject {
[SerializeField]
public string StudentName;
}
When nobody is assigned to the room, the Student field becomes null.
The UI element has a Label which binds to StudentName.
This works as expected, but not when the Student class is null, which gives me a warning message:
[UI Toolkit] Could not retrieve the value at path ‘Student.StudentName’ for source of type ‘RoomAssignment’: the path from the source to the target is either invalid or contains a null value. (PanelSettings)
UnityEngine.UIElements.UIElementsRuntimeUtilityNative:RepaintPanels (bool)
Is there a way to have a fallback value for bindings in case they couldn’t be resolved? My UI Element is partially setup in C#, but I want to keep as much binding logic as possible on UXML side of things.
Thanks!
As far I know, there is no fallback. However, you could make custom binding type to handle when it’s null.
Thanks for that! I either don’t understand the purpose of custom binding, or it’s not the right thing to use here. I wish there were more examples in the documentation.
The purpose of a custom binding is to define your own binding logic, if you wanted to do something that we do not support currently (i.e. binding to a uss style class, making event bindings, etc.)
For your case, I would probably have a binding deriving from DataBinding and extending it to include a fallback value.
Thanks! This is what I got, but it doesn’t seem to be hit:
using UnityEngine.UIElements;
[UxmlObject]
public sealed partial class DataBindingWithFallback : DataBinding {
[UxmlAttribute]
public string FallbackValue = "";
protected override BindingResult UpdateUI<TValue>(in BindingContext context, ref TValue value) {
var res = base.UpdateUI(in context, ref value);
if (res.status is not BindingStatus.Failure) {
return new BindingResult(BindingStatus.Success);
}
var element = context.targetElement;
base.UpdateUI(in context, ref FallbackValue);
return new BindingResult(BindingStatus.Success);
}
}
I did set the binding in UXML.
I feel like I am wrestling with the API here.
Thanks! This is what I got, but it doesn’t seem to be hit:
Oh, sorry, the DataBinding instance will only have the UpdateUI called if we can extract the value, so in your case, since there is a null value along the path itself, the value can’t be extracted.
You could get away with it by targeting Student instead of Student.studentName and register a local converter for the binding that will either return studentName or a fallback value.
I see. That sounds like too much boilerplate for something that should’ve been part of the core data binding. I think my way to go would be to do everything in C#.
It’s a shame because UI Toolkit is definitely step in the right direction, but it still lacks some essential and you have to do a lot of workarounds and hack about.
I appreciate the help, but I hope this is something that will be added in the future.
I appreciate the help, but I hope this is something that will be added in the future.
How do you envision that being offered?
Good question!
Having a “Fallback value” field in binding window feels the most intuitive to me:
And the “Fallback value” would accept input based on the property type (e.g. String for Label’s text, Sprite for VisualElement’s background image etc.)
In UXML, it could be set through the fallback-value attribute like so:
<engine:Label>
<Bindings>
<engine:DataBinding property="text" fallback-value="EMPTY" data-source-path="Name" data-source-type="Item, Namespace.Data" binding-mode="ToTarget" />
</Bindings>
</engine:Label>
Just my two pence on this.
The main issue with this is that the targeted property can be any arbitrary type, which might or might not be serializable in UXML. This is less of an issue when working within the UI Builder, but it’s not something that can be easily generalized.
The value would also need to be interpreted at runtime, because the binding will only know the type of the targeted property once it has been registered to an element. This would mean probably storing it in a string format and converting it at runtime, which would still require to use converters for not-known-ahead-of-time types.
c# + ui, is studenmt name, how? never be boolean
From what I gathered during my short experience with UI Toolkit, you folks have already solved this problem!
I know for a fact that complex types such as references to asset files (like Sprite) can be saved within the UXML.
For example, this is considered valid in UXML:
<engine:VisualElement name="icon" style="background-image: url("project://database/Assets/Icons/icon_iron_ore.png?fileID=2800000&guid=f5af81c0d0793fe4f991b4c1de991760&type=3#icon_iron_ore");">
There is always going to be an edge case out there where a certain data type cannot be handled by UXML, but I don’t see average user trying to bind complex data types to something like labels (that’s what data sources are for I believe).
I’m sorry, I don’t follow.
I know for a fact that complex types such as references to asset files (like Sprite ) can be saved within the UXML.
Asset references are serializable by Unity and are using dedicated paths. We also have dedicated paths inside UI Toolkit to be able to have a background-image take a Sprite or a Texture2D or a RenderTexture or a VectorImage. It is definitely not something that is generalizable to complex types in general.
There is always going to be an edge case out there where a certain data type cannot be handled by UXML, but I don’t see average user trying to bind complex data types to something like labels (that’s what data sources are for I believe).
No, in general, the average users will not try to bind a Texture2D to a FloatField. However, I would argue that a lot of users will end up using dedicated UI data types for their views because it will allow them:
- To control how and when the view is updated.
- To facilitate change tracking of the view.
- To optimize the binding operation itself by reducing the length of the property paths (i.e.
studentName will take less time to process than Student.studentName)
- To decouple code changes from the UI and making refactors more resilient.
Making a dedicated UI data type where the bound properties are at the root of the object gets rid of the fallback issue, because that fallback issue is baked in the data type itself.
I see, a bit tricky. Maybe an alternative approach would be to expand CustomBinding or open up DataBinding class a bit might help a lot.
You folks have clearly more knowledgeable about the ins and outs of UI Toolkit so I am likely talking hot air here 
It’s a fair request to make, only it’s one that’s tricky to implement without creating garbage.