Reputation: 535
I am under a situation where i have to click the button which are created dynamically with programming in c# (because user decides at run time how many button to be created). I do so by writing code like this:
int runTimeIntegerValue = 6; //Lets say its 6
Button[] childGridSavebutn = new Button[runTimeIntegerValue -1];
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
txtBoxOrder[j].Text = "I am from " + j;
MessageBox.Show("I am from " + j); //This message always show "I am From 5"
};
}
private Button GenerateButton(string p)
{
Button btn = new Button();
//btn.Name = p;
btn.Content = p;
btn.Width = 80;
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.Height = 20;
return btn;
}
After I popup a message MessageBox.Show("I am from " + i);
to know the message box is popuped from which button . It always popups 5 (as last count of i is 5).
So How to know which button is clicked, by click on that button? As there are 5 buttons, so i should know by click on the button that button number 1/2/3/4/5 is clocked by pop up of message with respected i position. (I mean button at ith position is clicked, and message must show this the "I am from 2" when 2nd button is clicked)
SUMMARY: I have childGridSavebutn[1 to 5] array (it could be even 10 decided run time by user). And it display 5 buttons, How to know which button is clicked ? (as it always popups 5th one) because further i have to generate button click events receptively. How will i do that if i do not know which button is clicked. At last UI has to repeat like this 5 times
Upvotes: 0
Views: 2795
Reputation: 117174
When your current code gets a button click the value of j
has completed the loop so for all button clicks you get the last value of j
.
But it's easy to fix. Just change your loop like this:
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
int j2 = j;
txtBoxOrder[j2].Text = "I am from " + j2;
MessageBox.Show("I am from " + j2);
};
}
The line int j2 = j;
captures the value of j
at the time the loop is run so that j2
is correct when the click occurs.
Upvotes: 1
Reputation: 5990
Replace
childGridSavebutn[i].Click += (source, e) =>
{
txtBoxOrder[i].Text = "I am from " + i;
MessageBox.Show("I am from " + i); //This message always show "I am From 5"
};
with
childGridSavebutn[i].Tag = i;
childGridSavebutn[i].Click += (source, e) =>
{
var button = source as Button;
var index = (int)button.Tag;
txtBoxOrder[index].Text = "I am from " + index;
MessageBox.Show("I am from " + index);
};
You must need to set the the value of i
while subscribing the event, you can use Tag
property for this. Otherwise you will get the last updated value of i
on every message.
Upvotes: 1
Reputation: 4733
EDIT 2: What you asked for in the comments
<Grid Name="ButtonsContainer">
<ListBox Name="lb_GlobalList">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel>
<Button Tag="{Binding SomeNumberValue}" Content="Click me" Click="Button_Click" />
<TextBlock Text="{Binding Path=SomeNumberValue, StringFormat={}I am from {0}}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List<SomeBusinessObject> businessObjects = new List<SomeBusinessObject>();
for (int i = 0; i < 5; i++)
{
businessObjects.Add(new SomeBusinessObject() { SomeNumberValue = i, SomeTextValue = "sample text" });
}
lb_GlobalList.ItemsSource = businessObjects;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("I am from " + (sender as Button).Tag.ToString());
}
}
public class SomeBusinessObject
{
public int SomeNumberValue { get; set; }
public string SomeTextValue { get; set; }
}
EDIT: I am not using your code on purpose so you will understand:
Name
property in a proper way (sender object in the event)Tag
property - even other controlsIn my opinion the best solutions is to add some metadata you want to associate with your Button
class using Tag
and then add Click event the usual way and use
(sender as Button)
in the event handler to access the button
see my example
private void Window_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
Button b = new Button();
b.Tag = i;
b.Click += B_Click;
sp_Container.Children.Add(b);
}
}
private void B_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show((sender as Button).Tag.ToString());
}
ALSO in my opinion what you are doing is wrong. You shoud be needing this example rather in special cases. You have the DataBinding ability in WPF, use it!
Upvotes: 4
Reputation: 51927
Try changing your code to.
for (int jj = 0; jj < runTimeIntegerValue; jj++)
{
int j = jj;
some code as you had before
}
The problem you are getting is due to how C# captured variables in closures.
Eric Lippert has an in-depth explanation of this behaviour:
Basically, it's the loop variable that is captured, not it's value.
Everyone hits this issue with captured variables at least once, and if you don't know what it is, you can't google for the solution.
Using Tag
as in other answers is also a valid solution. Likewise using a data binding template, may be the best solution. Hence I am not voting to close this as a duplicate of one of the 101 questions on captured variables in closures.
Upvotes: 4
Reputation: 2425
Are you sure you need to create these buttons programatically? Consider approach of generating buttons purely in XAML basing on your view models.
Suppose you hava a view model for single button:
public class SingleButtonViewModel {
public string Text { get; set; }
public ICommand Command { get; set; }
}
Your parent view model:
public class ParentViewModel {
public IList<SingleButtonViewModel> Buttons { get; set; }
}
And in your XAML:
<ItemsControl ItemsSource="{Binding Buttons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then you create your button view models programmaticaly, give it any command you want.
Consider, that I was not implementing INotifyPropertyChanged
and so for the sake of readibility.
Upvotes: 4