I saw in the latest release (#18) that there is something about binding. It seems like it is the runtime binding that has been talked about for a while. So I checked it out, and the APIs look cool, but I couldn’t for the life of me figure out how to use it.
I tried to set the data source path to be a field and a property, and in C# setting the data source to a component, and also a POCO. But got nothing. Of course read what docs I could find. So before I dove too deep in to the source to figure it out. I thought I would ask here first, is it a runtime binding system? Does it work at runtime? And if so, is there some sort of code snippet example of how to use it in the most basic way?
Or is it best to just wait for now before trying it out?
We indeed shipped the runtime binding feature and are working on a general announcement. This should be available soon. Look for it in the UI Toolkit forum.
The basic idea behind the new binding system is that you get to configure the sources and the binding themselves. Because you can now bind to multiple properties of a given element, you need to create binding objects manually. For a simple example:
// Some data somewhere
public class MyBehaviour : MonoBehaviour
{
public float value;
}
// Elsewhere, set your mono behaviour as a data source
var field = new FloatField
{
dataSource = gameObject.GetComponent<MyBehaviour>();
};
// Create a binding between the value of the field and the value of your behaviour
field.SetBinding(nameof(FloatField.value), new DataBinding { dataSourcePath = PropertyPath.FromName(nameof(MyBehaviour.value)) });
Note that the registration of binding objects and their update is tied to the update mechanism of the UI panel, so the updated value will not be available immediately, but will be ready before rendering.
There are a lot of things to say about the new system, so I’ll keep it to the simple example I’ve provided, as it should get you started on using it. We’ll talk more about the capabilities it provides in the proper announcement.
Thanks for the reply! It is an interesting setup. Would it be better to wait for the official announcement thread to share feedback so it is all in one place?
If you have a element defined in UXML, the data-source-path that is set there looks like it takes precedence over SetBinding.
I tried setting the .dataSource property in C# and having a UXML element with the data-source-path set, but it doesn’t seem to bind. maybe I am doing something wrong.
It appears that using SetBinding, the DataBinding.dataSourcepath only supports fields. This is a pretty big issue that it doesn’t support C# properties, considering that means you can’t get raise events when values changes and having public fields is against C# guidelines. Please let me know if I am wrong and it does support C# properties!
Using SetBindings, and specifying the value on the float field feels really weird. I mean it makes sense, so you can support more advanced use cases. But feels really weird.
I would be interested in seeing a more complete sample later on, because right now it feels like setting up binding has a lot of boilerplate involved, especially when you consider having to do it for a multitude of properties.
That is all I have at the moment. Will probably have more once the thread is posted and I can read more complete info on it and maybe some more examples
A couple of things here. There is a dataSource and dataSourcePath on both VisualElement and DataBinding.
The dataSource and dataSourcePath on the binding object is local to that specific binding object and will not have any effect on other binding objects and/or element. The dataSource and dataSourcePath on the VisualElement will affect all the bindings of that element and child element, until they are overridden.
In the end, this is how we resolve which data source and path to use: starting from the binding object, we combine the dataSourcePath together until we find a dataSource that is set. if the dataSource is set on the binding itself, we use that one. Otherwise, we will look for the dataSource and dataSourcePath on the owning element and its ancestor, until we find a dataSource that is set.
This way, you could have a global data source that is set at the root your visual tree and use the dataSourcePath as filters in your hierarchy (i.e. under this root, the elements show only have access to ${datasource}@player.inventory), while still retaining the ability to have specific bindings that link to a different data source/path.
So, if you set the dataSourcePath of an element in uxml, it will be set on the element in code and the binding object should not repeat it.
Good news, it does support c# properties, but you need to instrument them to make them available. Here is an exemple:
// To support Unity serialization
[SerializeField]
// For ease of use, we automatically create a property when [SerializeField] is used, but you can opt-out using this if you do not want users to be able to bind against m_Value.
[DontCreateProperty]
private float m_Value;
// Expose the property to the binding system
[CreateProperty]
public float value
{
get => m_Value;
set => m_Value = value;
}
At the moment, you need to use a string of the nameof as I’ve done in the sample, however, the preferred way will be to do something like this: fieldField.SetBinding(FloatField.valueProperty, new DataBinding { ... });
The “properties” are there, but remained internal for now. We will expose them at a later time.
The dataSource affects the current element and its children, until it’s overridden. What this means is that you do not need to setup the data source on every element. Typically, you would set it on a root where multiple bindings in the sub-hierarchy can use it.
Ideally, the preferred way to setup the bindings would be to use the UI Builder to create the bindings directly in uxml. If your data source is a scriptable object, you can also that in your uxml file, which would allow you to use bindings with writing any code for it.
In a lot of cases, what I do is set the bindings in uxml and then assign the data source of my root element from code.
public class UITest : MonoBehaviour
{
[SerializeField] private float _foo;
private void Start()
{
var doc = GetComponent<UIDocument>();
doc.rootVisualElement.dataSource = this; // With only this line, and the UXML, nothing happens.
// If the `data-source-path` is set in UXML, this line does nothing either.
doc.rootVisualElement.Q<FloatField>().SetBinding(nameof(FloatField.value),
new DataBinding() { dataSourcePath = new PropertyPath("_foo") });
}
}
Oooh right, I remember reading the in the Property docs about that! I did think it was strange that C# Properties were not supported. Sorry! Though I do find it strange that it works this way. I would expect that by default properties and public fields are exposed, but not private fields. Feels unintuitive to me considering the access level of the declarations.
I will be honesty, this is really weird to me. I mean, it is a great idea. Being able to set it out without code. But requires you to have a single UXML per ScriptableObject. I have almost never seen a ScriptableObject class be made that was single instance use. Maybe like settings or something. I would love to hear the thinking behind this, because unless I am not understanding how it works. This feels like a pretty niche feature that just kind of bloats it a bit.
If you wanted to keep the idea of being able to set it up without using any code. I feel like using the data-source-type in the UXML would make more sense. And have the the UIDocument read it and expose a list of Object references that you could assign and have it handle the binding that way.
What is data-source-type used for anyway?
Thanks again for the answers! It is indeed helpful!
So, form your snippets, you are defining both the dataSourcePath on the element and on the binding. This will result in the resolved path being _foo._foo, this is one _foo too many.
What I meant is that the preferred way to avoid the code boiler plate would be to setup the binding object in uxml directly. That way, you can instantiate your uxml file, set the data source without having to add bindings manually.
If your data happens to be a scriptable object, you can also reference it from uxml as well to set it as a data source. This is not a mandatory step. This is also a limitation of the uxml asset, because it can only reference other assets and not arbitrary data. From code, you can set arbitrary data as a data source.
Starting from your example, you could do something like this:
UXML:
public class UITest : MonoBehaviour
{
[SerializeField] private float _foo;
private void Start()
{
var doc = GetComponent<UIDocument>();
doc.rootVisualElement.dataSource = this; // With only this line, and the UXML, nothing happens.
}
}
The data-source-type is there for the UI Builder purposes, it allows the use of auto-complete when you know which type you intend to use at runtime, but can’t link in your uxml.
Interesting to know that using SetBinding is additive. Guess it makes sense.
I see, I think I am starting to get it.
However, when I copy pasted the UXMLI get this error. Not sure if that is me understanding something, or maybe something didn’t get pushed?
Sure, here you are. Just drop it in a project and open the sample scene and hit play. I’m sure it is just some small thing I missed somewhere. I appreciate you taking a look!
Oh, I’m very sorry, while rewritting your sample here , I used the C# name for the dataSourcePath property. Those get converted to data-source-path for uxml. I have updated the snippets.
Ooh, haha. That was even something I noticed and was going to check if that was what happened, but forgot to. Thank you for your help with this and all the info! Overall the system seems pretty nice and much more flexible than I was expecting! I am looking forward to trying it out in an actual project later on