Quickly clearing all callbacks on UI elements (TextField, Button)?

Is there a way to clear all clickable.clicked callbacks besides clearing a specific handler like Button.clickable.clicked -= handler?

I bumped to this same issue with TextField.RegisterValueChangedCallback where I have to unregister each callback individually and to do this, I have to save references to them. Would be much easier to call something like TextField.ClearValueChangedCallbacks() and do Button.clickable.clicked = null

Since UI Toolkit by nature can be used for building reusable GUI’s that get bound to different elements with same structure, it would be really handy to have quick ways of resettings handlers and callbacks on objects. This would allow developer to easily repurpose objects for new data with new handlers.

There is no way to clear all registered callbacks at the moment. It could potentially break by removing internal callback that were created during the object’s construction that you would have no idea they exist from the outside.

Your request is not a bad idea in itself, it just needs to find a way to guarantee there will be no unfortunate side effect. The developers have been made aware of your feature request.

For the case of the button, you should be able to assign a new Clickable since it is part of the public API.

Let us know if you need more help with this.

button.clickable = null should also work

2 Likes

Thanks for the tip on the button. Is the recommended way so far then instead re-instantiate the reusable UXML-components each time I bind to new variables (so just get rid of the previous components)? This way I wouldn’t unregister the handlers at all, but just clear the elements that had them. Will there be trouble?

As a related question, if I proceed with this method (just regenerating the VisualElements each time I rebind, do I have to Unbind the previous before forgetting them or can I just always rebind the SerializedProperties into new elements?

The best and proper way is to do the proper maintenance and keep track of the delegate to minimize the garbage collection.

Yes you generally need to unbind to be safe.

Rules can be broken, but I really suggest you follow them. :slight_smile:

1 Like

Good to know! Thank you.

I feel that UI Toolkit needs to address this soon. I’ve worked with vanilla web, SwiftUI, UIKit, React, Angular, Android’s UI, and others, and now Unity. And I keep getting surprised by what Unity frameworks do not have. I’ve gotten the impression that Unity devs seem too eager to get something labeled “1.0”. Labeling something 1.0 sends an impression to users of their platform that can create misaligned expectations.

Sorry if that sounds too strong a complaint, but I’ve staked my livelihood on this platform. Keep’n it real.

As a user of UI Toolkit, or any UI system where you work with callbacks, events, triggers, and elements, I expect a clear and straightforward way to inspect what relationships are already present between these things.

This is two-fold. One, UI Toolkit Debugger needs a way to see event relationships. Two, the APIs need a way, or a clearer way, cause I can’t figure it out, to find existing callbacks that are registered to an element.

If I were a dev on UI Toolkit, I’d be embarrassed to release preview 15 without at least an API to do this.

9 Likes

Another one that seems pretty simple and would save so much time typing that I can’t believe is missing:

Why can’t I copy the text/lines of any of these?

@SimonDufour After having worked with UI toolkit for a while, I feel that UI Toolkit would benefit greatly of some kind of high level layer on top of it. Something that would make it really simple to take care of all bindings and callbacks without having to manually build structures to take care of them. Things would be more automatic and the main work would go into designing the layout.

My application has changing data underneath so that the UI elements are the same most of the time (or for lists the elements are instantiated of course). I find out that I have to manually serialize stuff, keep the callback references to buttons and input fields, remember the clean them up etc. I would like to see some kind of ease from ie. Angular workflow and way of binding (from user’s point of view).

In Angular I can have a list of data and in the “view” I just say that “loop this data and display it - each data has this callback to process it on click”. I don’t have to worry about unbinding when the data underneath the controllers changes. Or I can have a field that is similarly bound to a variable inside a class structure and the main class can change. I can just bind it and if the data changes, the field updates.

I wish UI toolkit would introduce a high level layer like this - to take care of all bindings and callbacks with a simple way. I really like the USS and the ability to design the UI in a new way, but all this technical stuff gets in the way really easily and I find myself using all my time figuring out the bindings or why my interface slows down because some callback gets called twice or trying to find a common base class to button-element and textfield-element to save callback references for both in one universal function call. It gets really complicated really easily and a lot of my code in my current project is related to binding and serializing fields one by one, instead of new functionality and the actual product.

SUGGESTION:
What would help would be that one could give some service where I could just make a single call that would contain: 1) ANY kind of UI element, 2) target data path to bind to (data may change, service should rebind automatically) - it should be possible to give a nested class property path as well 3) onElementChange-callback 4) onDataChangeCallback. When the UI element would be destroyed, service would clean up. But when data would change, service would just rebind without problems.

The idea would be that I could just do this once and I wouldn’t have to trigger unbindings manually but this would be done when the UI element disappears from view.

This doesn’t directly addresses your concerns but FYI if it helps you track things down, the Unity debug mode (i.e. going to about and type internal) exposes a UITK Event analyser. It’s pretty fiddly to get anything useful out of it (which is probably part of the reason it’s not exposed by default) but I’ve found it useful on occasion.

Thank you, I’ll check that out. That seems to imply though that my gripes are already on the radar. I like the direction that UITK takes us but it really feels something like a 0.8, not a 1.0.

I ran into this issue as well. Once we introduced UniRx in our project, things got significantly easier in terms of binding/clearing callbacks.

This is how we bind an action to a button:

# Binding extensions
public static IDisposable Bind(this Button button, Action action)
{
    return button.AsObservable().Subscribe(_ => action());
}

private static IObservable<Unit> AsObservable(this Button button)
{
    return Observable.FromEvent(h => button.clicked += h, h => button.clicked -= h);
}

# Usage
private void InitializeBinding(){
   _button.Bind(() => {
      # Some action
   }).AddTo(gameObject);
}

The AddTo method diposes the IDiposable returned by the bind method when the gameObject is destroyed and thus, removes the clicked handler from the button.

I thought I heard something before that if a VisualElement is removed from the hierarchy, that its events were removed as well, but I might be remembering incorrectly.

In trying to clear a Button’s callbacks and then setting a new one, setting a button.clickable = null then trying to do button.clickable.clicked += () => {} throws a null ref exception. Is this intended?

Setting the clickable to null does clear the callback list, but you can’t add new ones after.
button.clickable = new UnityEngine.UIElements.PointerClickable(); doesn’t work either because PointerClickable is private, so there’s no way to ‘undo’ the ‘= null’.

Is the answer to basically just bind your own wrapper to clickable.clicked once and then from inside the wrapper, manage all this yourself?

edit: button.clickable = new Clickable(()=>{ }); does allow you to reset the callbacks, it looks like.

3 Likes

Glad you were able to find a solution!

But I would like to insist (for all the peoples that will land on this thread in the future) that clickable.clicked is meant to act as an event and that it is a bad practice to clear all subscriber to an event. The good practice is to keep a reference to what you subscribed to and to remove the callback once it is not needed anymore.

By default, the C# event do not allow clearing the subscriber for this reason. (there is no = operator, just += and -= so that you cannot clear the full list)

2 Likes

Thanks Simon.

I’m trying to get the hang of Unity, and I’m not sure of the new best practices, so I appreciate your reiteration. Since I’m adding lambdas, I’m not sure how C# handles appending vs removing them. I think adding and removing them makes more sense when you’ve got a bunch of methods you can reference by name or whatever, and removing them is more clear.

I’ll need to experiment with lambdas and how they work with these clickables. And read more about Delegates vs Events (since they’re a new concept to me, coming from C++ and Python)

In my case as well, I’m totally clearing the button’s state to blank, so it feels okay to wipe all its handlers out at once, but I can imagine that once the project gets more advanced, you’ll have more complex handles out there and you wouldn’t want to lose track of what’s added where and when.

Implementing any type of React-like framework or element recycling system in UIToolkit is pretty painful without this functionality. I understand why you’d believe that people building specific game UI screens should never need this behavior, but enabling framework-level code sometimes means trusting your users and giving access to low-level hooks.

4 Likes

I tried this and it works well, but now pressing the space bar gives me the following warning:

FocusController has unprocessed focus events. Clearing.
UnityEngine.UIElements.UIElementsRuntimeUtilityNative:UpdateRuntimePanels () (at /Users/bokken/buildslave/unity/build/Modules/UIElementsNative/UIElementsUtility.bindings.cs:26)

Is there a way to properly unregister everything before loading a new scene? For reference, my code is here:

    void LoadMenu(MenuState menu) {

        switch(menu) {
            case MenuState.Singleplayer:
            Button level1 = doc.rootVisualElement.Q<Button>("level1-button");
            level1.clicked += () => {
                SceneManager.LoadScene("Level1");
                doc.visualTreeAsset = null;
            };
            break;
        }
    }

@SimonDufour This feature would be extremely useful when re-using VisualElement elements in a context such as ListViews (for obvious performance reasons). It’s quite painful to store all callbacks to remove those each time when updating the ListView.

Hello, is this a stupid thing to do?
Will the fact that the button is about to be destroyed mean it is unnecessary to unregister the action?