C#动态添加的按钮单击将引发参数越界异常

C#动态添加的按钮单击将引发参数越界异常,c#,event-handling,C#,Event Handling,基本上,我正在尝试创建一个附件窗口,利用列表中的所有内容方便以后使用。因此,每次加载表单时,它都会遍历列表中的所有内容,并为它们创建标签和按钮。在我单击我的按钮之前没有错误。如果我单击任何X按钮,我会在click+=行上得到一个参数越界异常。有趣的是它为什么被称为?单击不应向自身添加另一个事件处理程序。同样有趣的是,点击时的标记比总计数大一个,所以考虑到它的迭代次数永远不应该高于其最大计数,它如何执行该行就在我旁边。任何帮助都将不胜感激 ArrayList attachmentFiles

基本上,我正在尝试创建一个附件窗口,利用列表中的所有内容方便以后使用。因此,每次加载表单时,它都会遍历列表中的所有内容,并为它们创建标签和按钮。在我单击我的按钮之前没有错误。如果我单击任何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();
    }
ArrayList附件文件;
ArrayList附件名称;
公共附件(ArrayList附件、ArrayList AttachmentFileName)
{
初始化组件();
attachmentFiles=附件;
attachmentNames=AttachmentFileName;
}
私有无效附件加载(对象发送方、事件参数)
{
滚动条vScrollBar1=新的VScrollBar();
滚动条hScrollBar1=新的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.horizontalcoll.Value=hScrollBar1.Value;};
pnl_Attachments.Controls.Add(hScrollBar1);
pnl_Attachments.Controls.Add(vScrollBar1);
标签文件名;
for(int i=0;iRemoveAttachment(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);
}
}
私有void removeAttachment(对象发送方、事件参数、字符串文件、字符串文件名)
{
attachmentNames.Remove(文件名);
附件文件。删除(文件);
pnl_Attachments.Controls.Clear();
这个。关闭();
}

在我的测试中,attachmentFiles的计数为3,attachmentNames的计数为3。在表单加载时,不存在任何问题。但是,在点击按钮时,我得到了一个异常,因为它试图向一个I=3(又称第四个元素)的按钮添加另一个点击侦听器。

大概是attachmentFiles[I]导致了越界异常,也许attachmentFiles的元素比attachmentNames少

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

我在click+=行上得到一个参数越界异常。有趣的是它为什么被称为?单击不应向自身添加另一个事件处理程序

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

同样有趣的是,点击时的标记比总计数大一个,所以考虑到它的迭代次数永远不应该高于其最大计数,它如何执行该行就在我旁边。任何帮助都将不胜感激

将lambda指定给事件时,i的值是固定的。删除元素时,attachmentFiles处的索引会发生变化,但lambda用于访问它的索引不会发生变化。让我们来举个例子

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

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

和4个执行此命令的按钮

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

单击第二个按钮,它将正确地从阵列中删除第二个附件,现在我们有:

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

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


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

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

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

有关发生这种情况的原因的更多详细信息,请查看此处的问题和答案:

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

var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => { 
    removeAttachment(s, 
                     e3,
                     attachmentFiles[currentAttachmentIndex].ToString(),
                     attachmentNames[currentAttachmentIndex].ToString())
});
或者,您可以使用已在
btn\u 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())
});

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

另一种方法是修改“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);
}

单击似乎指定了一个值,这似乎超出了范围。值是多少?最小值和最大值是多少?如果您提供
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;
}