How does the UIElements bindings work?
For example - I have a serialized object containing a serialized property that has a rect value.
I’d like to bind the rects position to a UIElements position. Can I utilize binding here somehow so I don’t have to poll or do events to determine if a serialized property has changed?
What do the simple elements like Labels binding look like. Is that code available? Is it doing anything special with serialized properties I don’t know about?
Or lets say I wanted to make a simpler custom control like a custom slider. How would I bind to a serialized property the values of the slider control without polling for changes in a gui call / update or through manual c# delegates.
I’m under impression there’s some magic going on behind the scenes I don’t have access to and I don’t want to work harder by figuring out polling / events for custom controls and interactions.
SerializedObject bindings can only bind to the value property of a field, to get it to write to the element’s position, you’ll need to go through some hoops.
To be able to bind to a custom element, it must implement 2 interfaces:
INotifyValueChange : provides access to value and is expected to send ChangeEvent when the value is changed through the property setter.
IBindable: provides the bindingPath that will allow the binding object to sync the property to the field’s value.
When bound, the bindings system will listen to ChangeEvent coming from the field, and update the serializedObject, and perform the polling to get the value from the object and assign it to the field, if the change comes from the data.
A custom element could inherit from BaseField to get a default working implementation.
But in your case, you’d want to inherit from BindableElement and implement INotifyValueChange with the value property simply being a proxy to the element’s style position. To get the best performance right now, set the bindingPath to the Rect property path, (and the path to any other field, then call Bind(mySerializedObject) on the common root of your fields.
Since SerializedObject is only available in the editor, this solution will not work at runtime. We intend to ship runtime bindings in the 2021 cycle.
4 Likes
Excellent answer thank you. I had discovered IBindable but only managed to generate warnings. I will try all this out.
1 Like
Worked like a charm thanks again, I spent a lot of effort making sure my data can be all managed through serialized properties ( which I normally never do because it’s a pain ) just so I can do interesting bindings like this.
Simplified for future readers:
public class PositionField : BindableElement, INotifyValueChanged<Rect> {
private Rect rect;
public Rect value {
get {
return rect;
}
set {
if (rect == value) {
return;
}
var lastValue = rect;
rect = value;
using (ChangeEvent<Rect> rectChangeEvent = ChangeEvent<Rect>.GetPooled(lastValue, value)) {
rectChangeEvent.target = this;
this.SendEvent(rectChangeEvent);
}
}
}
public void SetValueWithoutNotify(Rect newValue) {
rect = newValue;
}
}
public class MainElement : VisualElement {
private PositionField position;
void Initialize(){
position = new PositionField();
position.bindablePath = "Data.Rect";
Add(position);
Bind(mySerializedObject);
position.RegisterValueChangedCallback(e => {
transform.position = e.newValue.position;
});
public void OnStartDrag(DragContext ctx) {
AddToClassList("--dragging");
isDragging = true;
}
public void OnUpdateDrag(DragContext ctx) {
var pos = position.value;
pos.position += ctx.mouseDelta;
position.value = pos;
}
public void OnEndDrag(DragContext ctx) {
isDragging = false;
RemoveFromClassList("--dragging");
serializedViewData.ApplyModifiedProperties();
}
}
}
4 Likes