struggling
struggling

Reputation: 535

How to know which button is clicked in wpf button created programatically using c#

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

Answers (5)

Enigmativity
Enigmativity

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

M. Nasir Javaid
M. Nasir Javaid

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

Peuczynski
Peuczynski

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:

  1. Capture variables in closured
  2. How to use controls without Name property in a proper way (sender object in the event)
  3. How to store any object using the Tag property - even other controls
  4. That what you are doing is wrong on so many levels - DataBindig, DataTemplate etc. as commented in other answer

In 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

Ian Ringrose
Ian Ringrose

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

Kędrzu
Kędrzu

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

Related Questions