- Home /
Creating a button, giving it a delegate (or Action), and giving that delegate a reference parameter
Hi. I've created a bunch of static functions to help me set up UI elements when I need them, things like initialising a build-units menu or a resources graph (this is for a strategy game). I've been using delegates and Actions to allow me to hand the button initialiser a function to subscribe to the button's onClick event. So far, this has worked fine. However, when I have a text field that can change its text value - for example, an input number for how many items the player wants to buy - the delegate doesn't take a reference. So if I open a menu, the menu's input field will be 0.0, and call the function Buy(0.0f). But if I change the value in the input field to 3.0, the button still calls Buy(0.0f), because that's what the value was when it was created.
Strangely, though, I wasn't having this problem until recently. See, my normal Button-with-an-Input-Field prefab has a Button, and an Input Field childed to that Button. From what I can tell, this means the Button can handle the Field's text as a reference, since it always knows where the Input Field is. Here's the function for creating a FieldButton:
// Function to quickly set up a numeric field button.
// This accomodates a function with a single float parameter.
public delegate void floatDel(float f);
public static GameObject InitFieldButton(Transform parent, Vector3 position, string text, floatDel function)
{
GameObject newElement = GameObject.Instantiate(fieldButton);
// Establish UI transform data.
newElement.transform.SetParent(parent);
newElement.transform.localPosition = position;
newElement.transform.GetChild(0).GetComponent<Text>().text = text;
// Assign the argued function to the button, with the input text as a parameter to that function.
newElement.GetComponent<Button>().onClick.AddListener(delegate { function(float.Parse(newElement.GetComponentInChildren<InputField>().text)); });
return newElement;
}
This works. If I change the value in my input field, the button will call the function with the updated value.
But I'm also using a whole bunch of buttons that reference ONE input field, for when I want to let the player buy lots of different things, but only need to specify one quantity. This is obviously two prefabs: a Button, and an InputField. So I'm doing exactly the same thing with my InitButton() function:
// This overload accomodates a function with two parameters: a trading good, and a quantity to be traded.
public delegate void purchaseDel(Good g, float f);
public static GameObject InitButton(Transform parent, Vector3 position, string text, purchaseDel function, Good good, GameObject inputField)
{
GameObject newElement = GameObject.Instantiate(button);
// Establish UI transform data.
newElement.transform.SetParent(parent);
newElement.transform.localPosition = position;
newElement.GetComponentInChildren<Text>().text = text;
// Assign the function delegated to this button.
newElement.GetComponent<Button>().onClick.AddListener(delegate { function(good, float.Parse(inputField.GetComponent<InputField>().text)); });
return newElement;
}
I've tried quite a few things. Using 'ref' doesn't seem to be an option, because anonymous functions such as the one I'm handing to onClick can't take refs. I'm currently looking at giving the button a custom script to store all the data I need, but I expect this will be messy and really non-generic.
This seems like a pretty common thing to want to do. Is there a better way?
Solved it myself. Turned out I was being immensely stupid.
The issue was actually the $$anonymous$$enu class and how it was populating itself with buttons.
// Create a buy button for every type of good that can be bought.
for (int g = 0; g < SGoods.Instance.goodsList.Count - 1; g++)
{
// Get the text input for how much the player wants to buy.
GameObject buyQuantity = $$anonymous$$enuElements.InitField(transform,
new Vector3(205.0f, -40.0f));
// Call the Button initialiser.
GameObject buyButton = $$anonymous$$enuElements.TestInitButton(transform,
new Vector3(205.0f, -60.0f - 30.0f * g),
"Buy",
market.BuyFrom,
SGoods.Instance.goodsList[g],
buyQuantity.GetComponent<InputField>());
}
I placed the variable for the buyQuantity input field inside the for loop, even though I only wanted to create one of it. Then I was just overwriting it with every loop of the for-loop. For this reason, all buttons ignored changes to the input field, except the last one ...
Strangely, after re-typing this code, I now get an error warning me about exactly this. I'm pretty sure this is how I had it before, and there were no errors.
Anyway, I guess once Unity has a reference of the object SO$$anonymous$$EWHERE, it can keep it as a reference type. I just declared that reference outside the for loop:
GameObject buyQuantity = $$anonymous$$enuElements.InitField(transform,
new Vector3(205.0f, -40.0f));
for (int g = 0; g < SGoods.Instance.goodsList.Count - 1; g++)
{
// Call the Button initialiser.
GameObject buyButton = $$anonymous$$enuElements.TestInitButton(transform,
new Vector3(205.0f, -60.0f - 30.0f * g),
"Buy",
market.BuyFrom,
SGoods.Instance.goodsList[g],
buyQuantity.GetComponent<InputField>());
}
Ultimately this doesn't need anything fancy. No ref keywords, no crazy lambda functions. Just a regular anonymous delegate works fine. The InputField being held by the $$anonymous$$enu class seems to be sufficient.
public static GameObject TestInitButton(Transform parent, Vector3 position, string text, purchaseDel function, Good good, InputField inputField)
{
GameObject newElement = GameObject.Instantiate(button);
newElement.transform.SetParent(parent);
newElement.transform.localPosition = position;
newElement.GetComponentInChildren<Text>().text = text;
// Assign the function delegated to this button.
newElement.GetComponent<Button>().onClick.AddListener(() => function.Invoke(good, float.Parse(inputField.text)));
return newElement;
}
Your answer
Follow this Question
Related Questions
Button Listener Takes Reference But I Want to Pass Value? 1 Answer
Button onClick delegate with toggling method assignment 0 Answers
Problem with onClick.AddListener 1 Answer
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers