How to have a callback when a toggle is clicked in a toggle group?

I want a callback funcion to be called when one of my toggles in a toggle group is clicked.

I thought about making something like this

foreach (Toggle t in toggles) { t.onValueChanged.AddListener(callback); }

but then the callback would be called twice, because one toggle was selected, and other was deselected.

Any ideas?

1 Like

I made a class which inherits the ToggleGroup which has an OnChange event:

using UnityEngine;
using UnityEngine.UI;
using System.Linq;

public class BetterToggleGroup : ToggleGroup {
	public delegate void ChangedEventHandler(Toggle newActive);

    public event ChangedEventHandler OnChange;
	public void Start() {
		foreach (Transform transformToggle in gameObject.transform) {
			var toggle = transformToggle.gameObject.GetComponent<Toggle>();
			toggle.onValueChanged.AddListener((isSelected) => {
				if (!isSelected) {
					return;
				}
				var activeToggle = Active();
                DoOnChange(activeToggle);
			});
		}
	}
	public Toggle Active() {
		return ActiveToggles().FirstOrDefault();
	}

    protected virtual void DoOnChange(Toggle newactive)
    {
        var handler = OnChange;
        if (handler != null) handler(newactive);
    }
}

Add the above class to your project and replace the ToggleGroup script in the inspector with it.
The class requires that all of the ToggleGroup’s Toggles are children of it:
58791-a95xcut.png
To listen to the event, from another script use the following code:

TglGroup = GameObject.Find("Toggle Group Name").GetComponent<BetterToggleGroup>();
TglGroup.OnChange += TglGroup_OnChange;
//...
void TglGroup_OnChange(Toggle newActive)
{
  Debug.Log(string.Format("Toggle {0} selected",newActive.name));
}

@thatshowifeel had a good answer. Here’s a slightly improved, more modern version:

using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[RequireComponent(typeof(ToggleGroup))]
public class EventToggleGroup : MonoBehaviour
{
    [System.Serializable]
    public class ToggleEvent : UnityEvent<Toggle> { }

    [SerializeField]
    public ToggleEvent onActiveTogglesChanged;

    [SerializeField]
    private Toggle[] _toggles;

    private ToggleGroup _toggleGroup;

    private void Awake()
    {
        _toggleGroup = GetComponent<ToggleGroup>();
    }

    // Start is called before the first frame update
    void OnEnable()
    {
        foreach (Toggle toggle in _toggles)
        {
            if (toggle.group != null && toggle.group != _toggleGroup)
            {
                Debug.LogError($"EventToggleGroup is trying to register a Toggle that is a member of another group.");
            }
            toggle.group = _toggleGroup;
            toggle.onValueChanged.AddListener(HandleToggleValueChanged);
        }
    }

    void HandleToggleValueChanged(bool isOn)
    {
        if (isOn)
        {
            onActiveTogglesChanged?.Invoke(_toggleGroup.ActiveToggles().FirstOrDefault());
        }
    }

    void OnDisable()
    {
        foreach (Toggle toggle in _toggleGroup.ActiveToggles())
        {
            toggle.onValueChanged.RemoveListener(HandleToggleValueChanged);
            toggle.group = null;
        }
    }
}

To use:

  1. Add EventToggleGroup as a sibling of ToggleGroup.
  2. Drag Toggle into EventToggleGroup.toggles.
  3. Add something to listen to onActiveToggleChanged. Note: It may return null if no toggles are selected!

154112-eventtogglegroup.png

Changes:

*Rather than assuming the Toggles are children of the ToggleGroup transform you must explicitly specify the toggles. This allows you to put the ToggleGroup/EventToggleGroup wherever you want in the GameObject hierarchy.

*It is not necessary to specify the ā€œgroupā€ on the individual Toggle components anymore. (Note: This does allow a toggle to be a member of multiple EventToggleGroups! Don’t do this.)

*Rather than deriving from ToggleGroup, EventToggleGroup is a sibling component of ToggleGroup. This conforms to the Entity/Component model better.

*Rather than raising a C# event, it raises a UnityEvent which allows us to hook up event listeners in the UnityEditor AND code.

Great solution. Thanks.
I added a small function to set one option to ā€˜on’ and disable the others (useful for radio buttons).

    /**
     * Programmatically set a toggle to on (and set all others to off).
     */
    public void SetToggle(Toggle toggle)
    {
        foreach (Toggle t in Toggles)
        {
            bool isOn = toggle == t;
            t.isOn = isOn;
            Debug.Log(("setting " + t.name + " to " + isOn));
        }
    }