Hi everyone,
I found a behavior in UI Toolkit that looks unexpected to me, and I want to confirm whether this is a bug or intended event propagation behavior.
I have this sample code:
using UnityEngine.UIElements;
[UxmlElement]
public partial class SampleElement : VisualElement
{
public SampleElement()
{
var textField = new TextField("");
textField.RegisterValueChangedCallback(evt =>
{
UnityEngine.Debug.Log(evt.newValue);
});
Add(textField);
textField.label = "Sample Text Field";
schedule.Execute(() =>
{
textField.label = "121212";
}).Every(1000);
}
}
Expected behavior:
- The callback should run only when the TextField value changes.
Actual behavior:
- The callback is triggered even when I only change textField.label.
- The log appears twice in my scene test.
- Surprisingly, evt.newValue is the label string (for example “121212”), even though I did not edit the input value.
I currently think this might be related to event bubbling from child elements (such as Label) with the same generic ChangeEvent type.
My workaround/fix was to filter events so I only process the event when the target is the TextField itself:
textField.RegisterValueChangedCallback(evt =>
{
if (!ReferenceEquals(evt.target, textField))
return;
UnityEngine.Debug.Log(evt.newValue);
});
Questions:
- Is this behavior expected in UI Toolkit, or should this be considered a bug?
- Is filtering by evt.target like above the correct and recommended approach?
- Is there a better/official way to receive only true TextField value changes without catching label-related ChangeEvent propagation?
Thanks.
Hi,
This behavior is by design. In UI Toolkit, using RegisterValueChangedCallback registers a callback for a generic ChangeEvent<T>.
Because UI Toolkit uses an event propagation system consisting of a trickle-down and a bubble-up phase, change events bubble up through the visual tree hierarchy.
This means a handler on a parent element will receive bubbled events from its descendant controls anytime the generic type matches.
What you are doing is the correct way to handle this if you only want the event for a specific element.
If this is entirely by design, then I have nothing more to say.
However, from a common-sense perspective, is it really correct for an event to fire even when the value was not changed directly?
If a user did not even input a value, but a critical defect occurs because of that event, I am not sure whether a defensive response of “this is intended behavior” is appropriate.
For example, if the changed label is numeric and that ends up triggering RegisterValueChangedCallback, there is a potential risk that sensitive values (such as financial figures) could be modified.
Considering that risk, I do not think saying “this is intended behavior” is a good answer.
A TextField is a composite control that contains a Label (along with the text input area) in its internal hierarchy. When textField.label is changed , the underlying Label element dispatches a ChangeEvent<string> . Because change events bubble up the visual tree, and the generic type <string> matches the callback on the parent, the RegisterValueChangedCallback on your TextField catches it.
Event firing is not dependent on user interaction; any change can trigger the change event.
So this behavior is expected, it follows from how the control is composed and how UI Toolkit event propagation works.
I’m not asking for an explanation of the internal implementation or architecture.
If a method is named RegisterValueChangedCallback , then the event should only be raised when the value changes. If there is also a need to notify users when the label changes, then a separate event should be provided for that purpose.
The reason I intentionally don’t use the bubbling ChangeEvent is precisely because I want an event that is dedicated to value changes. If changing the label also triggers the same callback, then the API should never have been named RegisterValueChangedCallback in the first place.
If a user wants A , then the API should provide A . If they want B , then it should provide B . I honestly don’t understand why we’re discussing design philosophy or internal architecture instead of the actual behavior of the API. From my perspective, this is a clear defect.
You mentioned that event dispatching is not dependent on user interaction. However, doesn’t this issue also occur while interacting with the UI Builder during the design process? Isn’t that still a form of user interaction?
I don’t think this behavior provides any practical benefit. Instead, it only reduces confidence in UI Toolkit. If this behavior is specific to the label property, then wouldn’t that itself indicate a design flaw?
I strongly believe this should be acknowledged as a bug and fixed.
Finally, I hope my strong wording doesn’t come across as offensive. I’ve been a Unity user for many years, and I’m only expressing my opinion because I genuinely want the engine to become better. I hope you’ll take my comments in that spirit.
To be honest everything Karl’s mentioned is already in the documentation: Unity - Scripting API: UIElements.INotifyValueChangedExtensions.RegisterValueChangedCallback
And the text of a label is it’s value. It in fact has a INotifyValueChanged<string>.value property but it’s explicitly implemented: UnityCsReference/Modules/UIElements/Core/TextElement.cs at master · Unity-Technologies/UnityCsReference · GitHub
So a value has changed. It’s the value of the label. This is how all event propagation works. Can be surprising at first, but it makes a lot sense once you get used to it.
Thank you for your reply. It seems that I may have misunderstood the behavior.
In any case, now that I understand the correct way to use it, I’ll make sure to be careful when using it going forward.