I have wrote a code with this description:
First button calls FuncPopup(); function, after that every popup dialog creates new button. New button Create FuncPopup();. Old buttons should have various behavior.
private void FuncPopup()
{
FuncMenu popup = new FuncMenu();
popup.ShowDialog();
if (popup.DialogResult.HasValue && popup.DialogResult.Value)
{
i++;
newBtn[i] = new Button();
FuncGird.Children.Add(newBtn[i]);
Grid.SetColumn(newBtn[i], i);
Grid.SetRow(newBtn[i], j);
newBtn[i].Click += (sender, e) => clicked(i);
}
}
void clicked(int g) {
if (g >= i)
{
FuncPopup();
}
else (g < i){
OtherFunction();
}
}
i is a global variable. I expect Old buttons run OtherFunction(); but they always run FuncPopup();.
That's because as you said i is global variable, and you attach the following handler:
newBtn[i].Click += (sender, e) => clicked(i);
And you increment i all the time. You might think that value of i is fixed at the moment you attach this handler but it's not so. i in clicked(i) is the same global variable, which increments with every call. So g always equals i in that handler, and so for all buttons FuncPopup is called.
Instead save i to local variable and use that:
int tmp = i;
newBtn[i].Click += (sender, e) => clicked(tmp);
Related
I made this physics calculator where I type in the acceleration and mass, for example, and it gives me the force. Now I came across 2 problems:
1) The current event is a mouse click, but I think having the answer pop up in a TextChanged event would be better. The thing is, I have around 9 textboxes and I don't think I want to add the same huge if statement to every single textbox event. How do I add this huge pile of code into every textbox without having to add an event to every single one?
2) My current method of doing things is checking if each textbox is null a number of times for each "formula". Which means I have the same formula getting repeated 3 times just because it gets written differently
For example:
F = ma
could be written as F/m = a which takes in another else if statement till it fills all the formulas. Is there a way I could make it briefer than what it currently is? Because right now I have to right this in front of every if statement:
else if (!String.IsNullOrEmpty(txtBoxAcc.Text) &&
String.IsNullOrEmpty(txtBoxFg.Text) &&
String.IsNullOrEmpty(txtBoxFn.Text) &&
String.IsNullOrEmpty(txtBoxF.Text) &&
String.IsNullOrEmpty(txtBoxFk.Text) &&
!String.IsNullOrEmpty(txtBoxMass.Text) &&
String.IsNullOrEmpty(txtBoxUk.Text) &&
String.IsNullOrEmpty(txtBoxVi.Text) &&
String.IsNullOrEmpty(txtBoxVf.Text) &&
String.IsNullOrEmpty(txtBoxTime.Text) &&
String.IsNullOrEmpty(txtBoxD.Text) &&
String.IsNullOrEmpty(txtBoxFnet.Text))
You need to register the same method in the event handler of TextChanged in all text boxes as following:
private void textBox_TextChanged(object sender, EventArgs e)
{
}
txtBoxFg.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFn.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxF.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFk.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxMass.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxUk.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxVi.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxVf.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxTime.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxD.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFnet.TextChanged += new System.EventHandler(this.textBox_TextChanged);
Thanks
1) Avoid handling a similar event at different positions in your code. Since you only deal with text-boxes it's very straight forward to do all your calculations in a single text-changed event-handler.
Just link it to every text-changed-event unsing +=
Define an event handler to use more than once:
EventHandler handler = (s, e) => {
var textBox = s as TextBox;
if (textBox == null)
return;
// handle event
};
Simple way to get all text boxes, except that it won't search within container controls:
Controls
.OfType<TextBox>()
.ToList()
.ForEach(textBox => textBox.TextChanged += handler);
Or a little more complicated way to get at all controls in the hierarchy:
var visited = new HashSet<Control>();
// tricky way to have a recursive lambda
Action<Control, Action<Control>> visitRecursively = null;
visitRecursively = (control, visit) => {
// duplicate control test might be unnecessary
// but avoids infinite recursion or duplicate events
if (visited.Contains(control))
return;
visited.Add(control);
visit(control);
control
.Controls
.Cast<Control>()
.ToList()
.ForEach(subControl => visitRecursively(subControl, visit));
};
Action<Control> addMyHandler = control => {
TextBox textBox = control as TextBox;
if (textBox != null)
textBox.TextChanged += handler;
};
visitRecursively(this, addMyHandler);
I am currently using a loop to create a new User Control on my windows form. I want it to add a single instance of this User Control in a new position where Y is incremented by 125 each time.
I'm pretty new with C# and Visual Studio, so with the below code, the first instance is being replicated each time I press the 'add' event. I was just wondering if someone can give some assistance on the best way to store the value of 'y' from this first instance to be passed into the loop the second time? Or if there is any better way to do this.
Thanks in advance!
private void btnAddRow_Click(object sender, EventArgs e)
{
int y = 175;
for (int i = 0; i <= 0; i++)
{
NewSkillRow nSkill = new NewSkillRow();
nSkill.Location = new Point(75, y += 125);
Controls.Add(nSkill);
btnAddRow.Click += new EventHandler(btnAddRow_Click);
}
}
Make your y variable local to the class (you can also initialize it with its default):
private int y = 175;
The event handler is called every time you click the button. So remove the initialization of y from there.
private void btnAddRow_Click(object sender, EventArgs e)
{
var nSkill = new NewSkillRow();
nSkill.Location = new Point(75, y += 125);
Controls.Add(nSkill);
}
Note that the event handler attachment was removed. Reattaching an event handler from within the handler would lead to an increasing number of invokations every time the button is clicked.
The loop is fine, but not necessary: For just one iteration, you can as well just omit it.
The use of the y += 125 is also ok, it relies on the specification that the return value of an assignment operator is the value that has been assigned.
I'm writing a simple Chess-Game at the moment.
In my game there are "Chessfields" and "Options". A Chessfield is every field on the board, a option is every move-possibility of a Figure on the field.
So when i click a chessfield, for every option-field i bind a new event-handler.
Like so:
private void Chessfield_Click(object sender, MouseButtonEventArgs e)
{
// ... some other stuff
PlaceOptions();
// ... some other stuff
}
Where the function PlaceOptions() does this:
private void PlaceOptions(List<(int, int)> Options, int SourceX, int SourceY)
{
foreach ((int, int) option in Options)
{
// ... some other stuff
chessfield.MouseDown -= Chessfield_Click;
// should remove all existing handlers for that field
foreach (MouseButtonEventHandler optionClickHandler in _recentOptionClickHandlers)
{
chessfield.MouseDown -= optionClickHandler;
}
chessfield.MouseDown += (sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY);
_recentOptionClickHandlers.Add((sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY));
// ... some other stuff
}
}
_recentOptionClickHandlers is a global variable that stores every handler i added to any option-field:
private List<MouseButtonEventHandler> _recentOptionClickHandlers = new List<MouseButtonEventHandler>();
Now: every time i click on a chessfield, the Chessfield_Click() handler only gets called once.
But there comes the problem:
When i then click on a option-field (so a possible move of a figure), all recently clicked normal chessfields get moved to that field, because all the previous handlers are still active, but i allready deleted them by calling:
foreach (MouseButtonEventHandler optionClickHandler in _recentOptionClickHandlers)
{
chessfield.MouseDown -= optionClickHandler;
}
And the more i click any fields, the more event handlers are getting called (1st time: 1 handler; 2nd time: 2 handlers; 3rd time: 4 handlers; ...)
This problem really drives me crazy since 2 days now.
Thanks in advance
Can't test it now and can't post a comment either, so I'll reply here.
I think that the handler added to _recentOptionClickHandlers is not the same that you're registering for the MouseDown event, as you're creating a new delegate before adding it to your list.
You should try something like this:
EventHandler evt = (sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY);
chessfield.MouseDown += evt;
_recentOptionClickHandlers.Add(evt);
So, first I generate a List containing custom usercontrols made of a button and progressbar, I generate this using a for loop.
Inside this loop I send each events to the desired methods, now what I need is access to the progress bar inside of the reset method, how do I do that?
ProgressTimerList[i].Button.Reset += Button_Reset;
ProgressTimerList[i].Progressbar //////Need access to this object
And
void Button_Reset(object sender, EventArgs e)
{
//////Inside of here
}
Create a class inherited from EventArgs with a property of type Progressbar and pass it to the handler:
public class MyButtonEventArgs : EventArgs{
public --WhateverProgressbarTypeIs-- Bar {get;set;}
}
ProgressTimerList[i].Button.Reset += (sender, e) => Button_Reset(sender, new MyEventArgs { Bar = ProgressTimerList[i].Progressbar });
void Button_Reset(object sender, MyButtonEventArgs e)
{
var wunderBar = e.Bar;
}
By far the easiest way to handle this is to use anonymous methods.
At the point in your code where you are attaching the handler, try this:
ProgressTimerList[i].Button.Reset += (s, e) =>
{
//////Inside of here
ProgressTimerList[i].Progressbar //////Can access this object
};
No need whatsoever for the Button_Reset method.
The other nice thing is that this encapsulates the event handling within a method so that other code can't directly call Button_Reset. As encapsulation is one of the four pillars of OOP this helps to make your code more robust.
If you need to detach the handler you can do this:
EventHandler button_reset = (s, e) =>
{
//////Inside of here
ProgressTimerList[i].Progressbar; //////Can access this object
///more code
///detach
ProgressTimerList[i].Button.Reset -= button_reset;
};
ProgressTimerList[i].Button.Reset += button_reset;
If you have a clash with the name of e within your MainForm_Load then just call it e2 instead.
One other gotcha you might hit is that you're accessing items in an array within your event handler. You probably need to capture the variable locally before using it in the handler.
Like this:
for (var i = 0; i < ProgressTimerList.Count(); i++)
{
var local_i = i;
EventHandler button_reset = (s, e) =>
{
//////Inside of here
ProgressTimerList[local_i].Progressbar; //////Can access this object
///more code
///detach
ProgressTimerList[local_i].Button.Reset -= button_reset;
};
ProgressTimerList[i].Button.Reset += button_reset;
}
((ProgressTimerListType)((Button)sender).Parent).ProgressBar
Solved it using this thanks to Ron Beyer! Thanks!
If someone is up for more detail can I ask, why do I need to cast the sender before I can use it as a Button, not just use sender.Parent?
I have a link button which have a regular click event :
protected void lnkSynEvent_Click(object sender, EventArgs e)
{
}
And I bind this event at the runtime :
lnkSynEvent.Click += new EventHandler(lnkSynEvent_Click);
Now I need the function to accept additional argument:
protected void lnkSynEvent_Click(object sender, EventArgs e, DataTable dataT)
{
}
And pass the same as parameter while binding this event :
lnkSynEvent.Click += new EventHandler(lnkSynEvent_Click, //somehow here);
Not sure how to achieve this.
Please help.
Thanks in advance.
Vishal
You can use anonymous delegate for that:
lnkSynEvent.Click +=
new EventHandler((s,e)=>lnkSynEvent_Click(s, e, your_parameter));
I don't know exactly when it's changed, but now it's even easier!
lnkSynEvent.Click += (s,e) => lnkSynEvent_Click(s, e, your_parameter);
All answers above seem to be fine, but I have discovered one pitfall which is not so obvious and it took some time to figure out what is going on, so I wanted to share it.
Assume that myList.Count returns 16.
In the following case, OnValueChangeWithIndex(p1, p2, i) will always be called with i = 16.
for (int i = 0; i < myList.Count; i++)
{
myList[i].OnValueChange += (p1, p2) => OnValueChangeWithIndex(p1, p2, i);
}
To avoid that, you would need to initialize a new variable inside of the loop, then pass the new variable to the function.
for (int i = 0; i < myList.Count; i++)
{
int index = i;
myList[i].OnValueChange += (p1, p2) => OnValueChangeWithIndex(p1, p2, index);
}
Closures close over variables, not over values.
Closing over the loop variable considered harmful, part one
by use of delegate:
lnkbtnDel.Click += delegate(object s, EventArgs e1) {
Dynamic_Click(s, e1, lnkbtnDel.ID);
};`
EventHandler myEvent = (sender, e) => MyMethod(myParameter);//my delegate
myButton.Click += myEvent;//suscribe
myButton.Click -= myEvent;//unsuscribe
private void MyMethod(MyParameterType myParameter)
{
//Do something
}