Clearing previous RegisterValueChangedCallbacks or passing custom arguments to the callback?

I have a TextField UI element that I bind to different variables based on what is selected. This field has a bunch of data it has to process at RegisterValueChangedCallback.

Currently I’m passing lambda functions as callbacks, but as there doesn’t seem to be a way to clear all previous callbacks of an UI element, I would have to store references to the callbacks I pass.

In the code below, what type of List would I have to create to store the reference to the EventCallback<ChangeEvent>? I keep getting error of the unknown T if I try to make a List outside that method (I’m sure it’s by design, but I’m not so good with generics).

Or is there a way to clear all callbacks at once, like reset them?

Or as a third option, can I somehow pass custom arguments to the callback without using a lambda? Maybe some kind of a wrapper function or delegate? In this case, how can I store references to those into a list, how would that look like?

This is where I’m at currently. The goal is to be able to unregister and then reregister the callback when I have new arguments and data. Any ideas are welcome!

private void _RegisterStaticSerializedPropertyOnChangeCallback<T>(BaseField<T> UIElement, SerializedObject serializedObject, SerializedProperty serializedProperty, long entityID, Fjylling.ServiceDataType originDataType, bool valueAffectsActionOrTaskLength = false, bool setupOnChangeCallback = true, Action performActionsBeforeRecalculate = null)
    {
        if (UIElement.IsBound()) UIElement.Unbind();

        UIElement.BindProperty(serializedProperty);

        // Save a reference to the bound element
        _boundElements.Add(UIElement);

        if (setupOnChangeCallback)
        {
            EventCallback<ChangeEvent<T>> changeEvent = (e) => {
               // Performed when a value is changed
               serializedObject.ApplyModifiedProperties();


               if (!UnityEngine.Object.Equals(e.newValue, e.previousValue))
              {
                  // This might change settings
                  performActionsBeforeRecalculate?.Invoke();

                  serializedObject.ApplyModifiedProperties();

                  if (valueAffectsActionOrTaskLength) _CalculateAndUpdateActionLengths(originDataType, true);

                  EntityService.instance.MarkDirty(entityID, ServiceType.Editor, ServiceType.Editor, originDataType);
                  EntityService.instance.NotifyChanges();
                  };
              };

           _serializedPropertyCallbacks<T>.Add(changeEvent);

           // Register callback
           UIElement.RegisterValueChangedCallback(changeEvent);


        }
    }

Easiest thing would if there was a way to remove all callbacks from a type TextField (or any other type of UI element with callbacks). Is this possible somehow? Or what is the recommended way to easily repurpose UI elements with callbacks so you can get rid of the old ones?

Maybe not the best approach, but how about building a lambda that accumulates all the UnregisterCallbacks at once? For example:

private Action _unregisterAll = null;

private void _RegisterStaticSerializedPropertyOnChangeCallback<T>(BaseField<T> UIElement, bool setupOnChangeCallback = true /* [...] other arguments */)
{
        // [...] do some stuff
 
        if (setupOnChangeCallback)
        {
           EventCallback<ChangeEvent<T>> changeEvent = (e) =>
           {
               // [...] do some more stuff (changeEvent body).
           };

           // Register callback
           UIElement.RegisterValueChangedCallback(changeEvent);

           // Add Unregister callback for later
           _unregisterAll += () => UIElement.UnregisterValueChangedCallback(changeEvent);
        }
}

private void ClearAllValueChangedCallbacks()
{
    _unregisterAll?.Invoke();
    _unregisterAll = null;
}

Wow, thank you! This is really much handier than what I tried myself (started passing callbacks to another method etc…)

Pleasure! It’s a nice pattern to know when dealing with event unregistration in general, but it can be a bit harder to track what’s happening and ensure parity between Register/Unregister when dealing with situations that are a bit more complicated.

Your List idea would be better in general, but you should consider wrapping your objects in something less typey. For example:

class CallbackRegistrationToken<T> : IDisposable
{
    public VisualElement element;
    public EventCallback<ChangeEvent<T>> changeEvent;
    public void Dispose() => element.UnregisterValueChangedCallback(changeEvent);
}

private List<IDiposable> _registeredTokens = new List<IDisposable>();

private void RegisterChangeEvent<T>(VisualElement element, EventCallback<ChangeEvent<T>> changeEvent)
{
    element.RegisterValueChangedCallback(changeEvent);
    _registeredTokens.Add(new CallbackRegistrationToken { element = element, changeEvent = changeEvent });
}

private void UnregisterAllChangeEvents()
{
    _registeredTokens.ForEach(token => token.Dispose());
    _registeredTokens.Clear();
}
2 Likes