I’m trying to generate a bunch of buttons in my UI based on the number on objects, I do this with a for-loop and register at method call to each button that uses the i variable from the for-loop as a parameter for the method. This works fine and as expected when using IMGUI in the inspector, like this:
(Note: these code examples are simplified for clarity)
for (int i = 0; i < stages.Count; i++)
{
if (GUILayout.Button(triggerEvent.name))
mgn.PrepareStage(i);
}
However when I try to do this with UIElements it does not work because all buttons calls the method using the last i value, i.e. i is 10 for all eleven buttons as if I just had typed: mgn.PrepareStage(10);
for (int i = 0; i < stages.Count; i++)
{
Button prepareButton = stageElement.Q<Button>("prepare-button");
prepareButton.text = triggerEvent.name;
prepareButton.RegisterCallback<ClickEvent>((evt) =>
{
mgn.PrepareStage(i);
});
}
How should I do this to get it to work?
Unity 2020.3.16f1 (built-in version av UIElements)
The reason i is always 10 in your case is because the lambda passed to RegisterCallback capture the variable i which means that all callbacks reference the same variable. This is not specific to UI Toolkit but more of a general C# behavior. What you can do instead is pass the variable when calling RegisterCallback.
Here’s an example:
int i = 0;
stageElement.Query("prepare-button").ForEach(element =>
{
element.RegisterCallback<ClickEvent, int>((evt, index) =>
{
Debug.Log($"Click {index}");
}, i);
++i;
});
@jonathanma_unity Yes, that worked!
So before the variable i was passed as a reference so all buttons were reading from the same memory space, but by doing it like this instead the value is passed by value instead, so that they each get their own local copy?
@jonathanma_unity how would you go about the same issue, but with the RegisterValueChangedCallback?
I’m creating ObjectFields in a loop and need to know theindex of the ObjectField that was changed. I can’t use IndexOf(obj.previousValue) since some elements reference the same object. Lambda doesn’t work in a loop, and the event doesn’t contain any information about the ‘sender’ object.
Hi, RegisterValueChangedCallback doesn’t take any extra argument, but this is simply a wrapper around RegisterCallback<ChangeEvent> that allow less typing. If you need an extra argument you need to use the full RegisterCallback syntax.
For example with an IntField:
for (int i = 0; i < 10; i++)
{
field.RegisterCallback<ChangeEvent<int>, int>(Callback, i);
}
void Callback(ChangeEvent<int> evt, int index)
{ ... }