This question already has answers here:
Closures in C# event handler delegates? [duplicate]
(5 answers)
Closed 6 years ago.
I am trying to add some buttons to a control programmatically using a for-statement.
What I wish to accomplish is that every button fires the click event with the iterator value inside.
Here is an example of my code:
public class Foo
{
public Foo(int val)
{
//Do something with val
}
}
//for-statement
for(int i = 0; i < 5; ++i)
{
var button = new Button();
button.Click += (sender, e) =>
{
var text = new Foo(i);
}
}
In my scenarios it intializes Foo with the last value of the iterator, that being 4.
How can I accomplish to send every value of the iterator?
Button1->Click->Foo(0)
Button2->Click->Foo(1)
... and so on.
You fall in the trap of closures. Use Button.Tag instead.
public class Foo
{
public Foo(int val)
{
//Do something with val
}
}
//for-statement
for(int i = 0; i < 5; ++i)
{
var button = new Button();
button.Tag = i;
button.Click += (sender, e) =>
{
var text = new Foo((int)((sender as Button).Tag));
}
}
You can add a Tag to your Button like this
for(int i = 0; i < 5; ++i)
{
var button = new Button();
button.Tag = i;
button.Click += Button_Click();
}
And then cast object sender back to Button to receive the tag
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var tag = (int)button.Tag;
}
It's a well known problem, which is hard to find in fact (I fail to find something simple, therefore writing an answer). Change your for to capture loop variable value inside:
for(int i = 0; i < 5; ++i)
{
var current = i; // capture ('for' scope local variable to hold current value of i)
var button = new Button();
button.Click += (sender, e) => var text = new Foo(current); // don't use i here
}
Related
NET 4.5 C# to create a windows form. I want to dynamically create & add buttons & also assign them click events but want them to be dynamically placed in a particular fashion just like the image.
My question is how do I place the buttons dynamically in the above fashion i.e. 4x4 format (4 buttons in a row, 4 columns but unlimited rows). Is it possible to do so in win forms?
Presently I'm trying the below mentioned code but have no clear idea as to how I can place the buttons as shown above.
public Form1()
{
InitializeComponent();
for (int i = 0; i < 5; i++)
{
Button button = new Button();
button.Location = new Point(160, 30 * i + 10);
button.Click += new EventHandler(ButtonClickCommonEvent);
button.Tag = i;
this.Controls.Add(button);
}
}
void ButtonClickCommonEvent(object sender, EventArgs e)
{
Button button = sender as Button;
if (button != null)
{
switch ((int)button.Tag)
{
case 0:
// First Button Clicked
break;
case 1:
// Second Button Clicked
break;
// ...
}
}
}
Please advise solution with codes.
You can use a TableLayoutPanel and create your buttons dynamically and add them to the panel.
For example:
private void Form1_Load(object sender, EventArgs e)
{
var rowCount = 3;
var columnCount = 4;
this.tableLayoutPanel1.ColumnCount = columnCount;
this.tableLayoutPanel1.RowCount = rowCount;
this.tableLayoutPanel1.ColumnStyles.Clear();
this.tableLayoutPanel1.RowStyles.Clear();
for (int i = 0; i < columnCount; i++)
{
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100 / columnCount));
}
for (int i = 0; i < rowCount; i++)
{
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100 / rowCount));
}
for (int i = 0; i < rowCount* columnCount; i++)
{
var b = new Button();
b.Text = (i+1).ToString();
b.Name = string.Format("b_{0}", i + 1);
b.Click += b_Click;
b.Dock = DockStyle.Fill;
this.tableLayoutPanel1.Controls.Add(b);
}
}
void b_Click(object sender, EventArgs e)
{
var b = sender as Button;
if (b != null)
MessageBox.Show(string.Format("{0} Clicked", b.Text));
}
Note:
Using TableLayoutPanel.Controls.Add(control) we can add controls sequentially to the panel.
Using TableLayoutPanel.Controls.Add(control, columnIndex, rowIndex) we can add controls at specific cells.
I'm playing around abit with winforms and its controls and just discovered how to do custommade buttonclicks. However, there is a problem. I've got a loop, that's looping through a list of elements, and if a condition appears - I'm creating a button that will pop up a gridview.
public void draw(ref Panel inputPanel) //draws the eventline
{
int stepCounter = 0;
for (int i = 0; i < DaysList.Count-1; i++)
{
Button b1;
if (DaysList[i].Elements.Count > max)
{
b1 = new Button(); //Create the box
b1.Width = 120;
b1.Height = 40; //Set width and height
b1.Location = new Point(stepCounter + 35, 70); //Location
inputPanel.Controls.Add(b1); //
b1.Text = "Check event date in grid";
b1.Show();
b1.BringToFront();
b1.Click += new EventHandler((sender, e) => btn_Click(sender, e, DaysList[i].Elements));
stepCounter += 200;
}
}
}
That was my method for creating the buttons and a click event for the when my condition appears. The function that is passed to the eventhandler looks like this:
public void btn_Click(object sender, EventArgs e, List<EventElement> inputElems)
{
Button button = sender as Button;
DataGridForm window = new DataGridForm(inputElems);
window.Show();
}
public class EventElement
{
public EventElement()
{
}
public int Count{get;set;}
public string Date{get;set;}
}
The clickpart of the event is fine but whenever i click the spawned buttons, I get the wrong data into the gridview. As an example: The loop has created four buttons for me and they are presented on a straight line on the form. But whenever i click one of the buttons - dosnt matter which one of them, the button always return the data of the last spawned button. A more clear example: lets say we have the list inputElems looks like this:
inputElems[0].Count -> 2644
inputElems[1].Count -> 2131
inputElems[2].Count -> 8467
inputElems[3].Count -> 5462
When i now click the second button, the input to the second buttons parameter list should have the values (sender, e, 2131), right? but for some reason, the last argument gets the same like the 4th element in the list, even though i call the secondly created button.
I figured that it has something to do with me always calling the last added button_click to the eventhandler of the button, if so, how do I call different clicks from the EventHandler?
Instead of passing inputElems with the EventHandler, you can use Tag.
E.g. use:
b1.Tag=i;
Then in your click event handler:
public void btn_Click(object sender, EventArgs e)
{
Button button = sender as Button;
DataGridForm window = new DataGridForm(DaysList[int.Parse(button.Tag.ToString())].Elements);
window.Show();
}
The problem is that the for loop is out of scope, and thus unable to provide you with the data you're looking for. A more straight forward approach might be something like this:
public void draw(ref Panel inputPanel) //draws the eventline
{
int stepCounter = 0;
for (int i = 0; i < DaysList.Count-1; i++)
{
Button b1;
if (DaysList[i].Elements.Count > max)
{
b1 = new Button(); //Create the box
b1.Width = 120;
b1.Height = 40; //Set width and height
b1.Location = new Point(stepCounter + 35, 70); //Location
inputPanel.Controls.Add(b1); //
b1.Text = "Check event date in grid";
b1.Show();
b1.BringToFront();
b1.Tag = DaysList[i].Elements;
b1.Click += btn_Click;
stepCounter += 200;
}
}
}
and then in btn_Click, do this:
public void btn_Click(object sender, EventArgs e)
{
Button button = sender as Button;
int inputElems = (List<EventElement>)button.Tag;
DataGridForm window = new DataGridForm(inputElems);
window.Show();
}
I simply want to create a list of Buttons. But each button should do something different.
Its just for training. I'm new to C#.
what I have right now:
for (int i = 0; i < answerList.Count; i++)
{
Button acceptButton = new Button { Content = "Lösung" };
acceptButton.Click += anonymousClickFunction(i);
someList.Items.Add(acceptButton);
}
I want to generate the Click-Function like this:
private Func<Object, RoutedEventArgs> anonymousClickFunction(i) {
return delegate(Object o, RoutedEventArgs e)
{
System.Windows.Forms.MessageBox.Show(i.toString());
};
}
/// (as you might see i made a lot of JavaScript before ;-))
I know that a delegate is not a Func... but I don't know what I have to do here.
But this is not working.
Do you have any suggestions how i can do something like this?
EDIT: Solution
I was blind ... didn't thought about creating a RoutedEventHandler :-)
private RoutedEventHandler anonymousClickFunction(int id) {
return new RoutedEventHandler(delegate(Object o, RoutedEventArgs e)
{
System.Windows.Forms.MessageBox.Show(id.ToString());
});
}
I'm assuming you want an array of functions, and you want to get the function by index?
var clickActions = new RoutedEventHandler[]
{
(o, e) =>
{
// index 0
},
(o, e) =>
{
// index 1
},
(o, e) =>
{
// index 2
},
};
for (int i = 0; i < clickActions.Length; i++)
{
Button acceptButton = new Button { Content = "Lösung" };
acceptButton.Click += clickActions[i];
someList.Items.Add(acceptButton);
}
Hmm, what you could do. Is the following, plain and simple.
for (int i = 0; i < answerList.Count; i++)
{
var acceptButton = new Button { Content = "Lösung" };
acceptButton.Click += (s, e) => MessageBox.Show(i.ToString());
someList.Items.Add(acceptButton);
}
you can use lambda expressions for anonymous methods:
for (int i = 0; i < answerList.Count; i++)
{
Button acceptButton = new Button { Content = "Lösung" };
acceptButton.Click += (sender, args) => System.Windows.MessageBox.Show(i.toString());
someList.Items.Add(acceptButton);
}
To create Button and its click event in run time I use:
Button b = new Button();
b.Name = "btn1";
b.Click += btn1_Click;
But now I have an array of Buttons to create in run time; how to set each button's event - I cannot interpolate because it's not a string.
Button[] b = new Button(Count);
for (int i=0; i < Count; i++)
{
b[i] = new Button();
b[i].Name = "btn" + i;
b[i].Click += ??????
}
what should I do for "?????"
Option 1:
You can pass an lambda function, and create the handler based on the buttons index in the array like this:
for (int i=0; i < Count; i++)
{
b[i] = new Button();
b[i].Name = "btn" + i;
b[i].Click += (sender, args) =>
{
// your code
}
}
Option 2:
You can pass an anonymus delegate:
b[i].Click += delegate (sender, args) {
// your code
};
Option 3:
You can specify a handler function:
b[i].Click += YourHandlerFunction
// ....
// The handler signature also has to have the correct signature
void YourHandlerFunction(object sender, ButtonEventArgs args)
{
// your code
}
You can bind all buttons to the same event, so put the line like b[i].Click += button_Click;.
Then inside the button_Click event you can differentiate between the buttons, and take the proper actions.
For example:
public void button_Click(object sender, ButtonEventArgs e)
{
if( sender == b[0] )
{
//do what is appropriate for the first button
}
...
}
It depends on what you want to do! If you want to have the same method called for all clicks, do this:
Button[] b = new Button[Count];
for (int i=0; i < Count; i++)
{
b[i] = new Button();
b[i].Name = "btn" + i;
b[i].Click += OnClick
}
private void OnClick(object sender, RoutedEventArgs e)
{
// do something
}
If you want to do something different for each button, e.g. depending on the index, you can do something like this:
Button[] b = new Button[Count];
for (int i=0; i < Count; i++)
{
b[i] = new Button();
b[i].Name = "btn" + i;
b[i].Click += (s, e) => { /*do something*/ };
}
I'm trying to create a composite ASP.NET control that let's you build an editable control collection.
My problem is that when I press the add or postback button (which does nothing other than to postback the form) any values entered in the text boxes are lost.
I can't get it to work when the number of controls change between postbacks. I need to basically be able to recreate the control tree at two different times in the control life-cycle depending on the view state property ControlCount.
This test can be used to reproduce the issue:
public class AddManyControl : CompositeControl
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
EnsureChildControls();
}
protected override void CreateChildControls()
{
var count = ViewState["ControlCount"] as int? ?? 0;
for (int i = 0; i < count; i++)
{
var div = new HtmlGenericControl("div");
var textBox = new TextBox();
textBox.ID = "tb" + i;
div.Controls.Add(textBox);
Controls.Add(div);
}
ViewState["ControlCount"] = count;
var btnAdd = new Button();
btnAdd.ID = "Add";
btnAdd.Text = "Add text box";
btnAdd.Click += new EventHandler(btnAdd_Click);
Controls.Add(btnAdd);
var btnPostBack = new Button();
btnPostBack.ID = "PostBack";
btnPostBack.Text = "Do PostBack";
Controls.Add(btnPostBack);
}
void btnAdd_Click(object sender, EventArgs e)
{
ViewState["ControlCount"] = (int)ViewState["ControlCount"] + 1;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// If I remove this RecreateChildControls call
// the collection lags behind each postback
// because the count is incremented in the btnAdd_Click event handler
// however, the values are not lost between postbacks
RecreateChildControls();
}
}
If you want to play with ASP.NET's custom controls, you have to play by its rule and its picky! When you start to play with the OnPreRender in a custom control, you know you're on the wrong track 90% of the time.
Generally, the best way to use the ViewState is to declare a property backed up by it, just like the standard ASP.NET controls do (.NET Reflector has been my teacher for years!). This way, it will be read and saved naturally during the event's lifecycle.
Here is a code that seems to do what you want, quite naturally, without any trick:
public class AddManyControl : CompositeControl
{
private void AddControl(int index)
{
var div = new HtmlGenericControl("div");
var textBox = new TextBox();
textBox.ID = "tb" + index;
div.Controls.Add(textBox);
Controls.AddAt(index, div);
}
protected override void CreateChildControls()
{
for (int i = 0; i < ControlsCount; i++)
{
AddControl(i);
}
var btnAdd = new Button();
btnAdd.ID = "Add";
btnAdd.Text = "Add text box";
btnAdd.Click += new EventHandler(btnAdd_Click);
Controls.Add(btnAdd);
var btnPostBack = new Button();
btnPostBack.ID = "PostBack";
btnPostBack.Text = "Do PostBack";
Controls.Add(btnPostBack);
}
private int ControlsCount
{
get
{
object o = ViewState["ControlCount"];
if (o != null)
return (int)o;
return 0;
}
set
{
ViewState["ControlCount"] = value;
}
}
void btnAdd_Click(object sender, EventArgs e)
{
int count = ControlsCount;
AddControl(count);
ControlsCount = count + 1;
}
}
I believe you have to add the control into the view state.