• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
26
Question by LordYabo · Sep 16, 2014 at 09:24 AM · button4.6

4.6 UI How to apply OnClick handler for button generated at runtime (script)?

I'm dynamically generating UI buttons based on locations the player knows about.

How do I add my function to the OnClick listener?

 foreach (string a in places)
 {
    GameObject go = (GameObject)Instantiate(buttons);
                
    go.transform.parent = panel.transform;
    go.transform.localScale = new Vector3(1, 1, 1);
    Button b = go.GetComponent<Button>();
    b.onClick.AddListener(...???...)
 }
 

As a work around I made a "PointerDown" trigger on the button object but I would rather do it properly and use Unity's onclick handling.

Comment
Add comment · Show 1
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Baste · Sep 16, 2014 at 12:15 PM 1
Share

I think this is answered here. In your case, it would be:

 b.onClick.AddListener(() => {
   //handle click here
 });

The syntax used is a lambda expression, if you're not familiar with them. Not that I haven't actually tested this myself, just linking another answer.

1 Reply

· Add your reply
  • Sort: 
avatar image
67
Best Answer

Answer by AyAMrau · Sep 16, 2014 at 12:48 PM

I'm just going to make some assumptions here; Since you said in the other post about t$$anonymous$$s, that you tried my version with lambda expressions and that you always got the last button being called, my guess it that you are trying to somehow use the string from the loop in the listener. The issue you are having there is with capturing the loop iterator instead of the value.

Eg. if you are going to write it somet$$anonymous$$ng like t$$anonymous$$s:

 foreach (string a in places)
 {
    GameObject go = (GameObject)Instantiate(buttons);
  
    go.transform.parent = panel.transform;
    go.transform.localScale = new Vector3(1, 1, 1);
    Button b = go.GetComponent<Button>();
    b.onClick.AddListener(() => MyMethod(a));
 }

At the time where you click the button MyMethod is going to receive the element from places that was last used in the loop (if you executed the full loop, then t$$anonymous$$s will always be the last one)

You can solve t$$anonymous$$s by capturing the variable separately before adding the listener.

 foreach (string a in places)
 {
    GameObject go = (GameObject)Instantiate(buttons);
  
    go.transform.parent = panel.transform;
    go.transform.localScale = new Vector3(1, 1, 1);
    Button b = go.GetComponent<Button>();
    string captured = a;
    b.onClick.AddListener(() => MyMethod(captured));
 }

or alternatively by making the part with listener adding a separate method:

 foreach (string a in places)
     {
        GameObject go = (GameObject)Instantiate(buttons);
      
        go.transform.parent = panel.transform;
        go.transform.localScale = new Vector3(1, 1, 1);
        Button b = go.GetComponent<Button>();
        AddListener(b, a); // Using the iterator as argument will capture the variable
     }
 
 // Outside of method running the above
 
 void AddListener(Button b, string value) 
 {
    b.onClick.AddListener(() => MyMethod(value));
 }
Comment
Add comment · Show 18 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image LordYabo · Sep 16, 2014 at 04:06 PM 0
Share

Works perfect. Thanks! The lambda expression was confusing me in the other example.

avatar image AyAMrau · Dec 02, 2014 at 03:44 PM 1
Share

@invadererik

In a statement foreach(string a in places) a is an iteration variable, which means that on every iteration of the loop it is assigned the next value in the collection you iterating over. In most cases it doesn't cause you any issues, because if you are executing statements immediately, so it is the right value. It shows it's true nature when a anonymous method is involved. A statement:

 b.onClick.AddListener(() => MyMethod(a));

doesn't actually execute MyMethod(a) it just adds a reference to it in the button b. Because all this happens before you can press any buttons, the loop would have worked all the way to the end constantly changing the value of a until it stopped on the very last element in the loop. (Normally a would be destroyed afterwards, but instead it lives on because of the stored references in the buttons).

When a button is pressed it executes all the attached listeners and MyMethod is called with the current value of a. This makes sense, because you can capture and object and you are interested in it's state when the button is pressed (e.g. if instead of a string we were talking about an InputField you would probably want to read the value of that field once the button is pressed and not the one it had when you were assigning the listener).

When you add a statement:

 string captured = a;

The current value that a is stored in the variable captured, because it is executed while the loop is working. A separate variable captured is created on every iteration, so it won't be changing its value every single time.

This changes in C# 5 Once Unity upgrades to C#5 this issue will disappear, the iteration variable will be a new one on every loop iteration.

I tried my best here to explain this. If you are really interested in how C# works, how it evolved and what are the gotchas I really recommend the book *C# in Depth* by Jon Skeet.

avatar image RalliantoDeLaVega · Dec 02, 2014 at 11:30 PM 1
Share

This also worked for me! It's strange that, when you run trough a foreach loop, only the last method with AddListener(() => MyMethod(value) is stored, and this simple work-around

      foreach (string a in places)
     {
     GameObject go = (GameObject)Instantiate(buttons);
     go.transform.parent = panel.transform;
     go.transform.localScale = new Vector3(1, 1, 1);
     Button b = go.GetComponent<Button>();
     AddListener(b, a); // Using the iterator as argument will capture the variable
     }
     // Outside of method running the above
     void AddListener(Button b, string value)
     {
     b.onClick.AddListener(() => MyMethod(value));
     }

makes the code behave as you expect. Thx !!

avatar image AyAMrau · Feb 21, 2015 at 04:02 PM 1
Share

@EMOTION THEORY from what I gathered they don't show up there because Unity considers them separately from listeners added in editor. If you add a listener in editor it's known as a persistent listener, if you add it in code it's a non persistent listener. You can see those terms mentioned in the UnityEvent doc (which is what onClick is).

Not sure if there is an easier solution than writing own inspector that will show the non persistent listeners.

avatar image Meltdown · Mar 05, 2015 at 02:02 AM 1
Share

Thanks! Only the outside the method version seems to work for me. Awesome answer though.

Show more comments

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

15 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

(4.6 UI) How to set up a window with x buttons, with a scroll bar? 1 Answer

ArgumentException: method return type is incompatible 1 Answer

Bool script error 1 Answer

How to access SourceImage component of a button in new GUI 4.6 1 Answer

[Kinda Solved] Changing the Selectable Area of a Button 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges