C# Dynamically added button click is throwing argument out of bounds exception - c#

Basically I am trying to create an attachment window utilizing keeping everything in lists for easy use later. So, every time the form loads it goes through everything in the list and creates both labels and buttons for them. There is no errors until I click my button. If I click any of the X buttons, I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself. Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated.
ArrayList attachmentFiles;
ArrayList attachmentNames;
public Attachments(ArrayList attachments, ArrayList attachmentFileNames)
{
InitializeComponent();
attachmentFiles = attachments;
attachmentNames = attachmentFileNames;
}
private void Attachments_Load(object sender, EventArgs e)
{
ScrollBar vScrollBar1 = new VScrollBar();
ScrollBar hScrollBar1 = new HScrollBar();
vScrollBar1.Dock = DockStyle.Right;
hScrollBar1.Dock = DockStyle.Bottom;
vScrollBar1.Scroll += (sender2, e2) => { pnl_Attachments.VerticalScroll.Value = vScrollBar1.Value; };
hScrollBar1.Scroll += (sender3, e4) => { pnl_Attachments.HorizontalScroll.Value = hScrollBar1.Value; };
pnl_Attachments.Controls.Add(hScrollBar1);
pnl_Attachments.Controls.Add(vScrollBar1);
Label fileName;
for (int i = 0; i < attachmentNames.Count; i++)
{
fileName = new Label();
fileName.AutoSize = true;
fileName.Text = attachmentNames[i].ToString();
fileName.Top = (i + 1) * 22;
pnl_Attachments.Controls.Add(fileName);
Button btn_RemoveAttachment = new Button();
btn_RemoveAttachment.Text = "X";
btn_RemoveAttachment.Tag = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => removeAttachment(s, e3, attachmentFiles[i].ToString(), attachmentNames[i].ToString()));
btn_RemoveAttachment.Top = (i + 1) * 22;
btn_RemoveAttachment.Left = fileName.Right + 22;
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
}
private void removeAttachment(object sender, EventArgs e, string file, string fileName)
{
attachmentNames.Remove(fileName);
attachmentFiles.Remove(file);
pnl_Attachments.Controls.Clear();
this.Close();
}
In my test, attachmentFiles had a count of 3 and attachmentNames had a count of 3. On form load, there are no issues. But, on button click I get an exception because somehow its trying to add another click listener to a button with i = 3 (a.k.a a 4th element)

The problem is not with the event subscription, but with the event handler execution.
You are running into this problem because a closure is created for the event handler, but the value i is modified by the for loop. The last value of i will be 1 + attachmentNames.Count and this will be the value used by each invocation of the event handler.
For more detail as to why this happens you can check out the question and answer here: Access to Modified Closure.
To resolve the problem, you can assign i to another variable:
var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Or you can use the value you already captured in the Tag property of the btn_RemoveAttachment control.
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
var senderButton = (Button)s;
var currentAttachmentIndex = (int)senderButton.Tag;
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Keep in mind, though, if you are removing items from the List, the indexes will not be valid. Understanding how closures work, however, should help you solve that problem if it arises (it looks like you close the form anyway after the first removal).

Presumably, the attachmentFiles[i] is what is causing the out of bounds exception, perhaps attachmentFiles has fewer elements than attachmentNames?
Why not set a breakpoint on that line and check what is causing the out of bounds exception?

I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself
It looks like the exception is not thrown at the event subscription (+=) but at the lambda function declared in that same line
Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated
The value of i is fixed when you assign the lambda to the event. The indexes at the attachmentFiles change as you remove elements, but the indexes used by the lambda to access it don't. Let's try an example.
let's assume we have an array with 4 attchements (index:attachment))
[0:att0, 1:att1, 2:att2, 3:att3]
And 4 buttons that execute this lambdas
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
We click the second button and it correctly removes the second attachment from array, now we have:
[0:att0, 1:att2, 2:att3]
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
Now we click the fourth button. It tries to remove the attachment with index 3 and the out of bounds exception is thrown because that index doesn't exist anymore (and even if it existed, it might not point to the right attachment!)

Another approach would be to modify your 'removeAttachment' method, and use that as your event handler for all buttons.
An example of this would be:
for (int i = 0; i < attachmentNames.Count; i++)
{
var lbl_FileName = new Label
{
AutoSize = true,
Name = "Label" + i, // Give this a name so we can find it later
Text = attachmentNames[i],
Top = (i + 1) * 22
};
var btn_RemoveAttachment = new Button
{
Text = "X",
Tag = i,
Top = (i + 1) * 22,
Left = lbl_FileName.Right + 22
};
btn_RemoveAttachment.Click += removeAttachment;
pnl_Attachments.Controls.Add(lbl_FileName);
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
Then you can modify your removeAttachment method to be an EventHandler, and to detect the button and associated label using the sender As Button and Button.Tag property:
private void removeAttachment(object sender, EventArgs e)
{
// Get associated Label and Button controls
var thisButton = sender as Button;
var index = Convert.ToInt32(thisButton.Tag);
var thisLabel = (Label) Controls.Find("NameLabel" + index, true).First();
// Remove the files
int itemIndex = attachmentNames.IndexOf(thisLabel.Text);
attachmentNames.RemoveAt(itemIndex);
attachmentFiles.RemoveAt(itemIndex);
// Disable controls and strikethrough the text
thisButton.Enabled = false;
thisLabel.Font = new Font(thisLabel.Font, FontStyle.Strikeout);
thisLabel.Enabled = false;
}

Related

Dinamically generate a button to open file dialog and select a file using WPF controls and C#

Basically I'm making a program that allows you to add to a stackpanel another stackpanel with several horizontally aligned textboxes with the press of a button. So far, everything is working as intented. Here's my code so far ,Stacker is the name of the parent stackpanel and it starts off empty:
private void Add_Click(object sender, RoutedEventArgs e)
{
Stacker.Children.Add(NewXD(Stacker.Children.Count + 1));
}
public System.Windows.Controls.StackPanel NewXD(int XD)
{
System.Windows.Controls.StackPanel NewP = new StackPanel();
NewP.Orientation = Orientation.Horizontal;
System.Windows.Controls.TextBox HAHA = new TextBox();
HAHA.Name = "question" + XD.ToString();
//HAHA.Text = HAHA.Height.ToString()+" "+HAHA.Width.ToString();
HAHA.Height = Double.NaN;
HAHA.Width = 120;
HAHA.FontSize=20;
NewP.Children.Add(HAHA);
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.TextBox newBox = new TextBox();
newBox.Name = "answer"+XD.ToString()+"_"+i.ToString();
newBox.Height = Double.NaN;
newBox.Width = 120;
NewP.Children.Add(newBox);
}
System.Windows.Controls.ComboBox correct = new ComboBox();
correct.Name = "correct" + XD.ToString();
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.ComboBoxItem newItem = new ComboBoxItem();
newItem.Name = "ans" + XD.ToString() + "_" + i.ToString();
newItem.Content = i.ToString();
correct.Items.Add(newItem);
}
NewP.Children.Add(correct);
return NewP;
}
I apologize for the lack of seriousness in some of my code.
Now, what I need to do is for the child stackpanels to also contain independent file pickers that work like the one sampled in this thread: Open file dialog and select a file using WPF controls and C#
What I don't know how to perform is that each of these generated buttons have the same basic funcion but are linked with each of their corresponding textbox.
Thanks in advance :)
Edit: As I was writing this it occured to me that perhaps I could use the help of the child stackpanel's array-like properties to choose the corresponding textbox, because the file selector's textbox and button will always be the last two items in the stackpanel, but I'm not very sure how to perform this.
For functionality you can create an EventHandler that will be assigned to each button. Your event handler will then open File Dialog...
Buttons have Tag property which you could use to identify your TextBoxes, or you could derive from Button class and add AssociatedTextBox property for example.

Why can't asp.net dynamic button fire in loop?

So I found this very puzzling problem which involves dynamic buttons.
Here is my method that creates the buttons:
private void CreateButtons()
{
//Button outside loop works
Button selectItem = new Button();
selectItem.Text = "Hello World";
selectItem.ID = "btn";
selectItem.Click += selectItem_Click;
PlaceHolder1.Controls.Add(selectItem);
int ItemCounter = 0;
for (int i = 0; i < BillDate.Count; i++)
{ //Button inside loop doesnt work
ItemCounter++;
Button selectItem = new Button();
selectItem.Text = "Hello World";
selectItem.ID = "btn-" + ItemCounter.ToString();
selectItem.Click += selectItem_Click;
PlaceHolder1.Controls.Add(selectItem);
}
}
Now here is the problem,
the button that is created outside the loop works fine (event handler selectItem_Click only redirects page).
Why does the button not work inside the loop and why does it work outside the loop?
All of the buttons in your loop have the same ID, since you're not incrementing ItemCounter. While you could just use i instead, you don't appear to be using the ID at all, so you're better off just not setting it in the first place.
Also keep in mind that on the post back the buttons need to be created and added to the page in the PreInit event in order for the event handler to be able to run.
Dynamically creating controls, particularly controls that have handlers on subsequent postbacks, can be quite tricky. It's not uncommon at all to need data from the request to be able to generate the controls, but to need the controls to be generated before the request is processed by ASP for the events to fire. It's dramatically easier to create a template that you bind your data to, using something like a GridView or a Repeater instead, as it will be able to properly handle re-creating the controls before the request is processed while still allowing you to have a dynamic number of instances of the template.
**Use the below code it will work.**
private void CreateButtons()
{
//Button outside loop works
Button selectItem = new Button();
selectItem.Text = "Hello World";
selectItem.ID = "btn";
selectItem.Click += selectItem_Click;
PlaceHolder1.Controls.Add(selectItem);
int ItemCounter = 0;
for (int i = 0; i < BillDate.Count; i++)
{ //Button inside loop doesnt work
Button selectItem = new Button();
selectItem.Text = "Hello World";
selectItem.ID = "btn-" + ItemCounter.ToString();
selectItem.Click += selectItem_Click;
ItemCounter++;
PlaceHolder1.Controls.Add(selectItem);
}
}

Referencing a dynamically created control in a new TabPage

newbie programmer here after hours of searching has left me stumped.
I'm having trouble with referencing a control inside a tab created at RunTime with a button press. Basically what I have is a tabletop RPG calculator, using a Windows Form, that has a tabControl holding tab pages, with each tab page holding user-inputted stats for that individual enemy to be used in calculations.
The problem is that I want the user to be able to click a button to generate a new enemy tab page. Here is my code for generating an enemy tab page with a TextBox.
int enemyNumber = 0;
// Creates a new Enemy Tab
private void button2_Click_1(object sender, EventArgs e)
{
// Create a new TabPage
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
// Add Enemy Name Box
var newEnemyNameBox = new TextBox()
{
Name = "enemyNameBox" + enemyNumber,
Text = "",
Location = new Point(127, 11),
Size = new Size(133, 20)
};
// Add the controls to the new Enemy tab
newTabPage.Controls.Add(newEnemyNameBox);
// Add the TabPage to the TabControl
tabControl1.TabPages.Add(newTabPage);
// Increases the enemy's "reference number" by 1
// So that enemy tabs will be generated in order enemyTab0, enemyTab1, etc.
enemyNumber += 1;
}
This all works nicely. Unfortunately, after this point things have gotten ugly. I need to reference that TextBox named "enemyNameBox" + enemyNumber, and I'm not sure how to do so.
What I did was create "archVariables" to store the values from whatever enemy tab is selected, then use the appropriate archVariable in the program's calculations. IE: archEnemyName. The idea is that whatever tab the user is currently selected on (determined via SelectedIndex) the TextBox from that page will be used for the program's output.
Here are the two things I've tried after researching the matter:
// Attempt 1
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
archEnemyNameBox = ((TextBox)Controls["enemyNameBox" + i]).Text;
}
}
This code simply throws a NullReferenceException when I press the button. So after researching more I tried this:
// Attempt 2
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)));
archEnemyNameBox = tb2.Text;
}
}
This time I got an Error: Cannot convert type 'System.Windows.Forms.Control[]' to 'System.Windows.Forms.TextBox'
I feel like the second method I have here is probably closer to the correct way to do this, but apparently I'm still not getting it right. I've learned a lot by searching the information on stackoverflow and msdn.microsoft but nothing has gotten me past this problem.
Any help would be appreciated.
basically the problem with your second attemp is that enemyTab.Controls.Find("enemyNameBox" + i, true) returns an array of Controls Control[] and you're trying to convert that to a Control here is the problem, you should get the first control in that array and then convert it to a Control so it should be like this:
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)[0]));
archEnemyNameBox = tb2.Text;
}
}
but it is not the BestWay to do so it seems that everytime a user adds a new tabPage it will have the same Controls right? so why not create an userControl with any Control you have on your TabPage? so when you press the user press to add a new tab your code should be like so:
private void CreateNewEnemyTab()
{
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
EnemyTabUserControl enemyTab = new EnemyTabUserControl(enemyNumber);
here the EnemyTabUserControl should have all the components you need;
newTabPage.Controls.Add(enemyTab);
tabControl1.TabPages.Add(newTabPage);
}
and the code to bring the TextBox from the current tab could be as follow (you are going to need to reference LINQ)
using System.Linq;
//First Lets create this property, it should return the selected EnemyTabUserControl inside the tabControl
public EnemyTabUserControl CurrentTab {
get {
return tabControl1.SelectedTab.Controls.OfType<EnemyTabUserControl>().First();
}
}
// then if we make the textbox you want to reference from outside the code we can do this
CurrentTab.NameOfTheTextBox;
Patrick has solved your fundamental problem, but I don't think you need the loop in there at all. Here I've broken the steps out so you can see what needs to happen a little better:
private void defendCalcButton_Click(object sender, EventArgs e)
{
Control[] matches = this.Controls.Find("enemyNameBox" + tabControl1.SelectedIndex.ToString(), true);
if (matches.Length > 0 && matches[0] is TextBox)
{
TextBox tb = (TextBox)matches[0];
archEnemyNameBox = tb.Text;
}
}

Need help in understanding this error message that I'm getting

Please help me understand what is this error that I'm getting:
lblTabCounter is a label coded in the aspx page while the lblc[index] is a collection of label created at runtime during page load.
Declaration outside of page load:
Label[] lblc = new Label[10];
Inside Page Load Event:
for (int i = 0; i < 10; i++)
{
lblc[i] = new Label() { Text = (i + 1).ToString() };
this.Controls.Add(lblc[i]);
}
Inside another event called NodeChanged:
int TabCount = Convert.ToInt32(lblTabCounter.Text.ToString());
int TabIndex = Convert.ToInt32(lblTabCounterIndex.Text.ToString());
if(TabCount <= 10)
{
divcont.Visible = true;
string tabName = getURLName(uRL);
MenuItem myItem = new MenuItem(tabName, TabIndex.ToString());
Menu1.Items.AddAt(TabIndex, myItem);
//f1.Attributes["src"] = url;
f1.Attributes.Add("src", lblURL.Text.ToString());
MultiView1.ActiveViewIndex = TabIndex;
lblc[TabCount].Text = lblTabCounter.Text;
lblc[TabCount + 1].Text = lblURL.Text;
TabCount++;
TabIndex++;
lblTabCounter.Text = TabCount.ToString();
lblTabCounterIndex.Text = TabIndex.ToString();
tvPermissions.ExpandAll();
//tvPermissions.CollapseAll();
int i = ctr;
}
Note: This are all inside site.master.
The problem is your web page is refreshing and losing the state of the labels.
Label[] lblc = new Label[10];
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
lblc[i] = new Label();
this.Controls.Add(lblc[i]);
if (Session["lblc" + i.ToString()] == null)
Session["lblc" + i.ToString()] = lblc[i].Text = (i + 1).ToString();
else
lblc[i].Text = (string)Session["lblc" + i.ToString()];
}
Then when you want to set a label you use the following (when the page is not being refreshed by the event)
lblc[4].Text = "cool";
Session["lblc4"] = "cool";
However because your click event is refreshing the page it loses contact with the lblc so you only set the Session so upon refresh you will see your new Label. (when the page is being refreshed by the event)
Session["lblc4"] = "cool";
The page is in the process of refreshing as a result of your particular event so the label disappears but the session state remains so when you set the session upon refresh the code grabs the session instead of setting it to the default number.
Rather than change the text of the label when it refreshes you are actually generating the new label with the session string you set.
Also make sure you have <sessionState mode="InProc" /> in your Web.config file under <system.web>
Please read more on Session States here http://msdn.microsoft.com/en-us/library/87069683(v=vs.80).aspx
There are two possible issues with that line of code:
lblc[TabCount] is null.
lblTabCount is null.
Since you are paused in the debugger, you can see which of those is the case, then look around in the rest of the code to find out why.
I would follow the path of the lblc[index] array to determine if the element offset is within range as well as it being created properly and not ending up there as a null (whether the null being the object lblc[index] or the text property) being referenced.

Pass a parameter to the ApplicationBarIconButton.Click

*Hi everyone,
I'm new in WP7 dev. (i'm used to work on android) and there is a basic thing i don't know how to do.
I create programmatically a list of ApplicationBarIconButton with this:
for (int i=0; i<menus.Count(); i++)
{
ApplicationBarIconButton button = new ApplicationBarIconButton();
button.IconUri = new Uri(menus.ElementAt(i).Element("ImageUrl").Value.Trim(),UriKind.Relative);
button.Text = menus.ElementAt(i).Element("Title").Value.Trim();
button.Click += new EventHandler(button_clicked);
ApplicationBar.Buttons.Add(button);
}
and I want that the button_clicked method could retrieve the i value of the button.
How is it possible?
Thanks
I was beaten to it by #Enigmativity but his answer may still be incorrect. In my experience I've found that you need to clone the iterating i variable, otherwise on the click event, i will be the last value. If his doesn't work try this (again using a lamba function)
for (int i=0; i<menus.Count(); i++){
ApplicationBarIconButton button = new ...
...
var cloned = i;
button.Click += (sender, e) => {
sometTextBlock.Text = String.Format("App Button {0} pressed.", cloned);
};
}
Cheers,
Al.
You could do this:
for (int i=0; i<menus.Count(); i++)
{
ApplicationBarIconButton button = new ApplicationBarIconButton();
button.IconUri = new Uri(menus.ElementAt(i).Element("ImageUrl").Value.Trim(),UriKind.Relative);
button.Text = menus.ElementAt(i).Element("Title").Value.Trim();
var i2 = i; //Thanks to `ajmccall` - I forgot this.
button.Click += (s, e) =>
{
// the variable `i2` is accessible now.
};
ApplicationBar.Buttons.Add(button);
}
Rather than calling a method to handle click you can use a lambda and still get access to i (via local copy i2). You could then call any method passing i2 as a parameter if you need to.
An integrated way of achieving this is through the use of commanding in MVVM frameworks.
Granted with Application bar buttons / menu items it is a bit more tricky but far more flexible than to manipulate the UI Elements on the page.
Look in to MVVM light (http://mvvmlight.codeplex.com) or further with the likes of Calburn.Micro (http://caliburnmicro.codeplex.com/)
For application bar data binding you will need to google further (lost the link at the mo)

Categories