C#动态添加按钮单击是抛出参数超出范围的异常

时间:2014-10-10 17:30:29

标签: c# event-handling

基本上我正在尝试创建一个附件窗口,利用列表中的所有内容,以便日后使用。因此,每次加载表单时,它都会遍历列表中的所有内容,并为它们创建标签和按钮。在我点击我的按钮之前没有错误。如果我单击任何一个X按钮,我会在click + =行上获得一个超出范围的参数。有趣的是它为什么被称为?点击不应该为自己添加另一个事件处理程序。同样有趣的是,点击标记是一个大于总数的一个,所以它甚至执行那条线是如何在我旁边考虑它应该永远不会迭代它的最大计数。任何帮助将不胜感激。

    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();
    }

在我的测试中,attachmentFiles的计数为3,attachmentNames的计数为3.在表单加载时,没有问题。但是,在按钮单击时,我得到一个例外,因为它试图将另一个点击监听器添加到i = 3的按钮(a.k.a是第4个元素)

4 个答案:

答案 0 :(得分:2)

问题不在于事件订阅,而在于事件处理程序的执行。

您遇到此问题,因为为事件处理程序创建了一个闭包,但i循环修改了值fori的最后一个值为1 + attachmentNames.Count,这将是每次调用事件处理程序时使用的值。

有关发生这种情况的详细信息,请点击此处查看问题并回答:Access to Modified Closure

要解决此问题,您可以将i分配给另一个变量:

var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => { 
    removeAttachment(s, 
                     e3,
                     attachmentFiles[currentAttachmentIndex].ToString(),
                     attachmentNames[currentAttachmentIndex].ToString())
});

或者您可以使用已在Tag控件的btn_RemoveAttachment属性中捕获的值。

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())
});

但请记住,如果要从List中删除项目,索引将无效。但是,了解闭包的工作原理应该可以帮助您解决问题(看起来您在第一次删除后仍然关闭了表单)。

答案 1 :(得分:0)

据推测,attachmentFiles [i]是导致越界异常的原因,也许attachmentFiles的元素少于attachmentNames?

为什么不在该行设置断点并检查导致越界异常的原因?

答案 2 :(得分:0)

  

我在click + =行上得到一个超出边界的参数。有趣的是为什么被称为?点击不应该为自己添加另一个事件处理程序

看起来事件订阅(+ =)没有引发异常,但是在同一行中声明的lambda函数

  

有趣的是,在点击时,标记是一个大于总数的一个,所以它甚至执行那条线是如何在我旁边考虑它应该永远不会迭代它的最大计数。任何帮助将不胜感激

将lambda分配给事件时,i的值是固定的。附件文件中的索引随删除元素而更改,但lambda用于访问它的索引不会。我们来试试吧。

让我们假设我们有一个包含4个附件的数组(索引:附件))

[0:att0,1:att1,2:att2,3:att3]

执行这个lambdas的4个按钮

[removeAt(0),removeAt(1),removeAt(2),removeAt(3)]

我们点击第二个按钮,它正确地从数组中删除第二个附件,现在我们有:

[0:att0,1:att2,2:att3]

[removeAt(0),removeAt(1),removeAt(2),removeAt(3)]

现在我们点击第四个按钮。它尝试删除带有索引3的附件,并抛出越界异常,因为该索引不再存在(即使它已存在,也可能不指向正确的附件!)

答案 3 :(得分:0)

另一种方法是修改'removeAttachment'方法,并将其用作所有按钮的事件处理程序。

这方面的一个例子是:

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);
}

然后,您可以将removeAttachment方法修改为EventHandler,并使用sender As Button和Button.Tag属性检测按钮和关联的标签:

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;
}