Search Unity

Need help with Toggle

Discussion in '2D' started by TheWebExpert, Jan 9, 2017.

  1. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,558
    Prefabs don't actually exist in the scene until you Instantiate them. You don't want to be changing values of a prefab at runtime. Prefabs are your configuration to make copies from. You want to create an instance of the prefab, either by dragging one into the scene before running the game, or by running the game and using Instantiate to create one.

    So you're confused then, about how you can use the Toggle's "OnValueChanged" event, when those don't exist until the game runs, and they can't reference scene objects since they are prefabs, and prefabs can only reference other project files like prefabs.

    Here is how you can do that the moment that the ListObjects get instantiated:

    Instead of dragging the GameHandler prefab to the ListItem prefab to set up the "OnValueChanged" list in the inspector, that list can be empty, and then each ListItem script can do FindObjectOfType<GameHandler> and then add a function from the GameHandler to their OnValueChanged in code when the game starts.

    If you keep your GameHandler in the scene, then you can do this in the ListItem script:
    Code (CSharp):
    1.  
    2. public RectTransform checkMarkToggle;
    3. public RectTransform textToggle;
    4.  
    5. private GameHandler gameHandler;
    6. private Toggle myToggle;
    7.  
    8. private void Awake() {
    9.     gameHandler = FindObjectOfType<GameHandler>();
    10.     myToggle = checkMarkToggle.GetComponentInChildren<Toggle>();
    11. }
    12.  
    13. private void OnEnable() {
    14.     if(gameHandler != null && myToggle != null) {
    15.         myToggle.onValueChanged.AddListener(gameHandler.ButtonClicked);
    16.     }
    17. }
    18. private void OnDisable() {
    19.     if(gameHandler != null && myToggle != null) {
    20.         myToggle.onValueChanged.RemoveListener(gameHandler.ButtonClicked);
    21.     }
    22. }
     
    Last edited: Jan 18, 2017
  2. TheWebExpert

    TheWebExpert

    Joined:
    Apr 14, 2015
    Posts:
    137
    The one thing I thought of (potentially) is to get rid of the GameHandler prefab, get rid of the OnValueChanged on the prefab, and somehow do an AddListener after the Instantiate. However, I tried a couple of times; find only attached the listener to the first toggle. I tried getting the component of the toggle, but the AddListener needs to have the boolean on it.

    I found an example like this:
    Code (CSharp):
    1.             Toggle t = go.GetComponent<Toggle>();
    2.             t.onValueChanged.AddListener((bool) => ButtonClicked(bool));
    3.  
    ... but it says 'Unexpected symbol `)', expecting identifier'
     
  3. TheWebExpert

    TheWebExpert

    Joined:
    Apr 14, 2015
    Posts:
    137
    Oh wow. That worked. So, therefore: You really CAN'T have a function from a prefab to a prefab, where the OnValueChanged is linked; you really HAVE TO have a reference, from the function. That's good to know.

    If I link the Prefab GameObject to the TextToggle (so I can use the OnValueChanged THERE), THAT function works. I'm guessing it's because no values are being passed. I don't understand why it would work one way and not the other, but there it is.

    Here's the working program. As you see, it's correctly counting the checks, highlighting ONE ONLY of any item, and then using that highlight to display the proper card. This was a very, very frustrating problem. I thank you again for your help (and your patience). I'm learning!

     
  4. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,558
    I'm very glad you were able to get it working.

    Just FYI-
    This is lambda syntax for delegate functions. Essentially they are nameless anonymous functions you can pass to be executed somewhere else. The syntax you copied is wrong though, the first parentheses define the parameters just like a function. Then on the other side of the '=>' is the body of the function, where you use the parameters by name.

    Here are correct examples:
    Code (CSharp):
    1. // lambda delegate syntax
    2. t.onValueChanged.AddListener((bool b) => ButtonClicked(b));
    3.  
    4. // same as
    5. t.onValueChanged.AddListener(delegate(bool b){ ButtonClicked(b); });
    6.  
    7. // better way
    8. t.onValueChanged.AddListener(ButtonClicked);
    9.  
    10. // 'onValueChanged' passes a bool, and so AddListener expects a function that accepts a bool.
    11. // No extra syntax needed, and you can remove this specific listener later easily.
    The first syntax is not so convenient, because you can't explicitly remove that listener since it's newly defined nameless anonymous function.
    Code (CSharp):
    1. // won't work as "(bool b) =>" is a brand new function
    2. t.onValueChanged.RemoveListener((bool b) => ButtonClicked(b));
    3.  
    4. // will work, as this exact function was added before
    5. t.onValueChanged.RemoveListener(ButtonClicked);
     
  5. TheWebExpert

    TheWebExpert

    Joined:
    Apr 14, 2015
    Posts:
    137
    Thanks again - I'm not sure why you'd remove a listener (I generally ignore them once that screen is gone), and I'm also not sure how you'd specify the "bool b"; in my case, I really don't think it would've worked, as the value was being gotten from the object itself, not from the code.

    I suppose if I was passing in a specific string (as I was with the TextToggle), this COULD work - but as it was, I didn't need it to.