Proper way to get changed values from a textfield?

I’m probably doing something wrong here, but why does changing the label of a text field trigger a value changed event?

protected virtual void AttachToPanelHandler(AttachToPanelEvent evt)
        {
            if (DebugEnabled)
                Debug.Log($"{ToString()}.AttachedToPanel");

            _textField = new TextField();
            _textField.RegisterValueChangedCallback(evt =>
            {
                Debug.Log($"Did someone say {evt.newValue}?");
            });

            Add(_textField);

            Debug.Log($"Added field.");

            _textField.label = "1";
            _textField.value = "A";
            _textField.label = "2";
            _textField.value = "B";           
        }

The output of that code in my project is this:

I’m a bit lost, why setting the label text to “1” doesn’t trigger the event handler, while setting it to “2” a few lines later does. Is updating a label text supposed to trigger the value changed event of the text field? If so, any ideas where that first event might have disappeared?

And what would I have to do, if my code actually should be able to react to changes of the label and the actual text field value, but in different ways? How can I find out who the actual source or sender of a certain event is?

I’m using Unity 2022.2.16 btw.

Hi. I’m not sure I’d call what you’re doing “wrong”. Ideally this kind of code should work; I think setting a field’s label shouldn’t trigger a ChangeEvent. That said, it’s easy to work around this issue.

I’ll explain what I think is happening here. The callback in RegisterValueChangedCallback listens for changes to string values coming from any element inside _textField. Unfortunately, the changes to the Label element inside _textField also trigger this callback, as it holds a string value.

The reason it’s not fired the first time you set the label is because _textField doesn’t contain a Label element then. The Label element is created the first time a non-empty value is assigned to the TextField’s label property. So there’s no Label element whose value has changed that first time.

All UITK events have a target property. You can use it to know if it’s targeting a label. For example:

            _textField.RegisterValueChangedCallback(evt =>
            {
                // This returns if the target is any Label inside _textField:
                if (evt.target is Label) return;

                // Or you can check if the target is the _textField's special label
                // that displays the text from _textField.label:
                if (evt.target == _textField.labelElement) return;

                Debug.Log($"Did someone say {evt.newValue}?");
            });
2 Likes

Hi and thanks for trying to help me out.

OK, that explanation makes kind of sense.

That makes sense too, setting the label text to some initial value before adding the callback creates the label and helps to catch the first event.

Well that part unfortunately doesn’t work, if I understood correctly, that the target should always stay the same, while the event is bubbling around. For the following code …

            _textField = new TextField();
            _textField.label = "LabelAtTimeOfCreation";
            _textField.RegisterValueChangedCallback(evt =>
            {
                var tt = evt.target.GetType();
                var ctt = evt.currentTarget.GetType();
                var isLabel = tt == typeof(Label);
                var isTextField = tt == typeof(TextField);
                Debug.Log($"Target: Type = {tt.Name}, CurrentTarget = {ctt.Name}, IsLabel={isLabel}, IsTextField={isTextField}, New={evt.newValue}, Old={evt.previousValue} ");
            });

            Add(_textField);

            Debug.Log($"Added field.");

            _textField.label = "1";
            _textField.value = "A";
            _textField.label = "2";
            _textField.value = "B";

… the output is this:
8994457--1238614--upload_2023-5-5_18-19-9.png

What kind of works is to register another callback on the label element itself like so:

            _textField.labelElement.RegisterValueChangedCallback(evt =>
                {
                    Debug.Log($"Target {evt.target.GetType().Name}, NewValue={evt.newValue}");
                    // evt.StopImmediatePropagation();
                });

            _textField.RegisterValueChangedCallback(evt =>
                {
                    Debug.Log($"Target {evt.target.GetType().Name}, NewValue={evt.newValue}");
                });

The inner label of the text field is indeed reporting it’s value via the text field, which is kind of questionable but … well … ok. But when the label changes, it first triggers it’s own callback, the event target is the label itself and the value is the labels text. Right after that, the text fields callback is invoked. But now the (supposedly) same event points to the text field as it’s target, while still containing the labels text as value.

If I had to guess, I’d say the event target is rewritten internally or something? I’m not sure if that’s intended behavior. A text field should probably not report internal label changes as ValueChanged-Events or report them, but if it does, it should probably keep the event target value pointing to the label.

Anyway, thanks again for pointing me in the right direction oscarAbraham!

1 Like

Ugh! You’re right! This isn’t nice. Maybe I’d consider reporting a bug about this. My guess is that an internal thing is getting in the way; UITK has a concept of Composite Elements internally where it considers the events of their children as part of their own. Maybe that’s what’s happening here. But it really makes it impossible to know for sure what triggered the event.

Okay, a second workaround you could use is to use the textField’s value in the callback instead of evt.newValue. The callback would still be triggered when the label changes, but at least it wouldn’t use the wrong value. Something like this:

            _textField.RegisterValueChangedCallback(evt =>
            {
                Debug.Log($"Did someone say {_textField.value}?");
            });

EDIT
Fixed example.

I think setting a field’s label shouldn’t trigger a ChangeEvent.

This is clearly a bug to me. And I just stumbled over it.
See also other thread with same issue: how to stop TextField RegisterValueChangedCallback firing when setting the label of the TextField?

Oh my gosh, Unity are you kidding me?
The thing is called RegisterValueChangedCallback, not RegisterLabelChangedCallback.
How did anybody think firing a “value changed event” for the label would be a good idea? And how could such a bad design slip through your quality assurance (code reviews)?

1 Like