将值传递给动态按钮的事件处理程序

本文关键字:事件处理 程序 按钮 动态 值传 | 更新日期: 2023-09-27 18:24:04

我正试图在Button_Click Event 上传递两个值

public MyClass()
{
    Int64 po = 123456;
    foreach (Expense expense in pr.Expenses)
    {
        Button btnExpenseDetail = new Button();
        btnExpenseDetail.Text = expense.ExpenseName;
        btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
        btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , expense.ExpenseName); };            
        pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail);
    }
}  

void MyHandler(object sender, EventArgs e, string po, string category)
{
    FormExpenseDetails ed = new FormExpenseDetails(po, category);
    ed.Show();
}

我正在使用visual studio 2010 c#。在面板上,每个按钮的文本值都不同。但是按钮的Click_Events的作用是一样的。有人能告诉我这个逻辑错误发生在代码的哪一部分吗?

=================================================

将值传递给动态按钮的事件处理程序

看起来像是枚举器的常见陷阱。基本上,如果您为lambda使用枚举器变量(在本例中为expense),它总是在同一变量上创建一个闭包,因此它总是使用相同的值。你可以这样修复:

foreach (Expense expense in pr.Expenses)
{
    var currentExpense = expense; // <-- This should help. Also use this variable for the lambda.
    Button btnExpenseDetail = new Button();
    btnExpenseDetail.Text = currentExpense .ExpenseName;
    btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
    btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , currentExpense.ExpenseName); };            
    pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail);
}

您可以将lambda看作是传递给变量expense的引用。即使变量的值随着每次迭代而变化,引用仍然指向同一个变量。这就是为什么它有助于为每次迭代创建一个本地范围的变量(currentExpense)。字符串值和位置是不同的,因为每次迭代都会将它们分配给另一个位置(Button.TextButton.Location)。

此代码应该可以工作:

public MyClass()
{
    Int64 po = 123456;
    foreach (Expense expense in pr.Expenses)
    {
        var expenseName = expense.ExpenseName;
        Button btnExpenseDetail = new Button();
        btnExpenseDetail.Text = expense.ExpenseName;
        btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
        btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po, expenseName); };            
        pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail);
    }
}  

void MyHandler(object sender, EventArgs e, string po, string category)
{
    FormExpenseDetails ed = new FormExpenseDetails(po, category);
    ed.Show();
}

让我们复习一些更基本的东西。

static void Main(string[] args)
{
    var qs = new List<Action>();
    for (var i = 0; i < 10; i++)
        qs.Add(() => f("doer", i));
    for (var i = 0; i < 10; i++)
        qs[i]();
}
private static void f(string x, int y)
{
    Console.WriteLine("{0}: {1}", x, y);
}

当你运行上面的代码时,你总是会得到这样的输出:"实干家:10"。让我们反编译该代码:

private static void f(string x, int y)
{
    Console.WriteLine("{0}: {1}", x, y);
}
private static void Main(string[] args)
{
    List<Action> qs = new List<Action>();
    <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
    CS$<>8__locals2.i = 0;
    while (CS$<>8__locals2.i < 10)
    {
        qs.Add(new Action(CS$<>8__locals2.<Main>b__0));
        CS$<>8__locals2.i++;
    }
    for (int i = 0; i < 10; i++)
    {
        qs[i]();
    }
}
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    // Fields
    public int i;
    // Methods
    public void <Main>b__0()
    {
        Program.f("doer", this.i);
    }
}

正如您所看到的,编译器生成了一个名为c__DisplayClass1的类,并在进入循环之前对其进行了一次初始化。之后,它只是增加了变量CS$<>8__locals2i属性。

因此,当我在下一个循环中调用se lambdas时,它使用CS$<>8__locals2对象来查找内部变量。

(我的英语不太好,无法解释,但一切都在那里…)

这与C#<4处理foreach循环。基本上,实例开销是在循环外定义的,然后有一个内部循环,它将指针更改为下一项。类似这样的伪代码:

Expense expense;
for expense in pr.Expenses
   // do processing

如果你从引用的角度来思考,引用的值,即费用所指向的,在迭代过程中会发生变化。因此,当您的点击事件触发时,它已经指向最后一个项目。现在,这应该在c#5中得到解决,这里已经讨论过了。

修复方法相当简单:

Int64 po = 123456;
foreach (Expense expense in pr.Expenses)
{
    var localExpense = expense;
    Button btnExpenseDetail = new Button();
    btnExpenseDetail.Text = expense.ExpenseName;
    btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
    btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , localExpense.ExpenseName); };            
    pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail);
}