如何知道在使用 C# 以编程方式创建的 wpf 按钮中单击了哪个按钮
How to know which button is clicked in wpf button created programatically using c#
我处于这样一种情况,我必须单击使用 c# 编程动态创建的按钮(因为用户在 运行 时间决定要创建多少按钮)。
我通过编写这样的代码来做到这一点:
int runTimeIntegerValue = 6; //Lets say its 6
Button[] childGridSavebutn = new Button[runTimeIntegerValue -1];
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
txtBoxOrder[j].Text = "I am from " + j;
MessageBox.Show("I am from " + j); //This message always show "I am From 5"
};
}
private Button GenerateButton(string p)
{
Button btn = new Button();
//btn.Name = p;
btn.Content = p;
btn.Width = 80;
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.Height = 20;
return btn;
}
弹出消息后MessageBox.Show("I am from " + i);
知道消息框是从哪个按钮弹出的。它总是弹出 5(因为 i 的最后计数是 5)。
那么如何知道点击了哪个按钮呢?因为有 5 个按钮,所以我应该通过单击按钮知道按钮编号 1/2/3/4/5 是通过弹出消息与我的位置相关的时钟来计时的。
(我的意思是第 i 个位置的按钮被点击,当点击第二个按钮时消息必须显示 "I am from 2")
总结:我有 childGridSavebutn[1 到 5] 数组(它甚至可以由用户决定 运行 10 次)。它显示 5 个按钮,如何知道单击了哪个按钮? (因为它总是弹出第 5 个)因为我还必须接受地生成按钮单击事件。如果我不知道点击了哪个按钮,我该怎么做。 最后 UI 必须像这样重复 5 次
编辑 2:
您在评论中提出的要求
<Grid Name="ButtonsContainer">
<ListBox Name="lb_GlobalList">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel>
<Button Tag="{Binding SomeNumberValue}" Content="Click me" Click="Button_Click" />
<TextBlock Text="{Binding Path=SomeNumberValue, StringFormat={}I am from {0}}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List<SomeBusinessObject> businessObjects = new List<SomeBusinessObject>();
for (int i = 0; i < 5; i++)
{
businessObjects.Add(new SomeBusinessObject() { SomeNumberValue = i, SomeTextValue = "sample text" });
}
lb_GlobalList.ItemsSource = businessObjects;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("I am from " + (sender as Button).Tag.ToString());
}
}
public class SomeBusinessObject
{
public int SomeNumberValue { get; set; }
public string SomeTextValue { get; set; }
}
编辑:
我不是故意使用你的代码所以你会明白:
- 在闭包中捕获变量
- 如何在没有
Name
属性 的情况下以正确的方式使用控件(事件中的发送者对象)
- 如何使用
Tag
属性 存储任何对象 - 甚至其他控件
- 你所做的事情在很多层面上都是错误的——DataBindig、DataTemplate 等,正如其他答案中所评论的那样
在我看来,最好的解决方案是使用 Tag
添加一些您想与 Button
class 相关联的元数据,然后以通常的方式添加 Click 事件并使用
(sender as Button)
在事件处理程序中访问按钮
看我的例子
private void Window_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
Button b = new Button();
b.Tag = i;
b.Click += B_Click;
sp_Container.Children.Add(b);
}
}
private void B_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show((sender as Button).Tag.ToString());
}
此外,我认为您的做法是错误的。你应该需要这个例子,而不是在特殊情况下。你有WPF中的DataBinding能力,用吧!
您确定需要以编程方式创建这些按钮吗?考虑在 XAML 中完全根据您的视图模型生成按钮的方法。
假设您有一个用于单个按钮的视图模型:
public class SingleButtonViewModel {
public string Text { get; set; }
public ICommand Command { get; set; }
}
您的父视图模型:
public class ParentViewModel {
public IList<SingleButtonViewModel> Buttons { get; set; }
}
在你的 XAML 中:
<ItemsControl ItemsSource="{Binding Buttons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
然后以编程方式创建按钮视图模型,给它任何你想要的命令。
请注意,我没有实施 INotifyPropertyChanged
等是为了便于阅读。
替换
childGridSavebutn[i].Click += (source, e) =>
{
txtBoxOrder[i].Text = "I am from " + i;
MessageBox.Show("I am from " + i); //This message always show "I am From 5"
};
和
childGridSavebutn[i].Tag = i;
childGridSavebutn[i].Click += (source, e) =>
{
var button = source as Button;
var index = (int)button.Tag;
txtBoxOrder[index].Text = "I am from " + index;
MessageBox.Show("I am from " + index);
};
你必须在订阅事件时设置i
的值,你可以使用Tag
属性。否则,您将在每条消息上获得 i
的最后更新值。
尝试将您的代码更改为。
for (int jj = 0; jj < runTimeIntegerValue; jj++)
{
int j = jj;
some code as you had before
}
您遇到的问题是由于 C# 在闭包中捕获变量的方式。
Eric Lippert 对此行为有深入的解释:
基本上,捕获的是循环变量,而不是值。
每个人都至少遇到过一次捕获变量的问题,如果你不知道它是什么,你就无法google解决。
像其他答案一样使用Tag
也是一个有效的解决方案。同样使用数据绑定模板,可能是最好的解决方案。 因此,我不会投票将其作为关于闭包中捕获变量的 101 个问题之一的副本来关闭。
当您当前的代码获得一个按钮时,单击 j
的值已完成循环,因此对于所有按钮单击,您将获得 j
的最后一个值。
但它很容易修复。像这样改变你的循环:
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
int j2 = j;
txtBoxOrder[j2].Text = "I am from " + j2;
MessageBox.Show("I am from " + j2);
};
}
行int j2 = j;
在循环运行时捕获了j
的值,这样点击发生时j2
是正确的
我处于这样一种情况,我必须单击使用 c# 编程动态创建的按钮(因为用户在 运行 时间决定要创建多少按钮)。 我通过编写这样的代码来做到这一点:
int runTimeIntegerValue = 6; //Lets say its 6
Button[] childGridSavebutn = new Button[runTimeIntegerValue -1];
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
txtBoxOrder[j].Text = "I am from " + j;
MessageBox.Show("I am from " + j); //This message always show "I am From 5"
};
}
private Button GenerateButton(string p)
{
Button btn = new Button();
//btn.Name = p;
btn.Content = p;
btn.Width = 80;
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.Height = 20;
return btn;
}
弹出消息后MessageBox.Show("I am from " + i);
知道消息框是从哪个按钮弹出的。它总是弹出 5(因为 i 的最后计数是 5)。
那么如何知道点击了哪个按钮呢?因为有 5 个按钮,所以我应该通过单击按钮知道按钮编号 1/2/3/4/5 是通过弹出消息与我的位置相关的时钟来计时的。 (我的意思是第 i 个位置的按钮被点击,当点击第二个按钮时消息必须显示 "I am from 2")
总结:我有 childGridSavebutn[1 到 5] 数组(它甚至可以由用户决定 运行 10 次)。它显示 5 个按钮,如何知道单击了哪个按钮? (因为它总是弹出第 5 个)因为我还必须接受地生成按钮单击事件。如果我不知道点击了哪个按钮,我该怎么做。 最后 UI 必须像这样重复 5 次
编辑 2: 您在评论中提出的要求
<Grid Name="ButtonsContainer">
<ListBox Name="lb_GlobalList">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel>
<Button Tag="{Binding SomeNumberValue}" Content="Click me" Click="Button_Click" />
<TextBlock Text="{Binding Path=SomeNumberValue, StringFormat={}I am from {0}}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List<SomeBusinessObject> businessObjects = new List<SomeBusinessObject>();
for (int i = 0; i < 5; i++)
{
businessObjects.Add(new SomeBusinessObject() { SomeNumberValue = i, SomeTextValue = "sample text" });
}
lb_GlobalList.ItemsSource = businessObjects;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("I am from " + (sender as Button).Tag.ToString());
}
}
public class SomeBusinessObject
{
public int SomeNumberValue { get; set; }
public string SomeTextValue { get; set; }
}
编辑: 我不是故意使用你的代码所以你会明白:
- 在闭包中捕获变量
- 如何在没有
Name
属性 的情况下以正确的方式使用控件(事件中的发送者对象) - 如何使用
Tag
属性 存储任何对象 - 甚至其他控件 - 你所做的事情在很多层面上都是错误的——DataBindig、DataTemplate 等,正如其他答案中所评论的那样
在我看来,最好的解决方案是使用 Tag
添加一些您想与 Button
class 相关联的元数据,然后以通常的方式添加 Click 事件并使用
(sender as Button)
在事件处理程序中访问按钮
看我的例子
private void Window_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
Button b = new Button();
b.Tag = i;
b.Click += B_Click;
sp_Container.Children.Add(b);
}
}
private void B_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show((sender as Button).Tag.ToString());
}
此外,我认为您的做法是错误的。你应该需要这个例子,而不是在特殊情况下。你有WPF中的DataBinding能力,用吧!
您确定需要以编程方式创建这些按钮吗?考虑在 XAML 中完全根据您的视图模型生成按钮的方法。
假设您有一个用于单个按钮的视图模型:
public class SingleButtonViewModel {
public string Text { get; set; }
public ICommand Command { get; set; }
}
您的父视图模型:
public class ParentViewModel {
public IList<SingleButtonViewModel> Buttons { get; set; }
}
在你的 XAML 中:
<ItemsControl ItemsSource="{Binding Buttons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
然后以编程方式创建按钮视图模型,给它任何你想要的命令。
请注意,我没有实施 INotifyPropertyChanged
等是为了便于阅读。
替换
childGridSavebutn[i].Click += (source, e) =>
{
txtBoxOrder[i].Text = "I am from " + i;
MessageBox.Show("I am from " + i); //This message always show "I am From 5"
};
和
childGridSavebutn[i].Tag = i;
childGridSavebutn[i].Click += (source, e) =>
{
var button = source as Button;
var index = (int)button.Tag;
txtBoxOrder[index].Text = "I am from " + index;
MessageBox.Show("I am from " + index);
};
你必须在订阅事件时设置i
的值,你可以使用Tag
属性。否则,您将在每条消息上获得 i
的最后更新值。
尝试将您的代码更改为。
for (int jj = 0; jj < runTimeIntegerValue; jj++)
{
int j = jj;
some code as you had before
}
您遇到的问题是由于 C# 在闭包中捕获变量的方式。
Eric Lippert 对此行为有深入的解释:
基本上,捕获的是循环变量,而不是值。
每个人都至少遇到过一次捕获变量的问题,如果你不知道它是什么,你就无法google解决。
像其他答案一样使用Tag
也是一个有效的解决方案。同样使用数据绑定模板,可能是最好的解决方案。 因此,我不会投票将其作为关于闭包中捕获变量的 101 个问题之一的副本来关闭。
当您当前的代码获得一个按钮时,单击 j
的值已完成循环,因此对于所有按钮单击,您将获得 j
的最后一个值。
但它很容易修复。像这样改变你的循环:
for (int j = 0; j < runTimeIntegerValue; j++)
{
childGridSavebutn[j] = GenerateButton("Save Table");
childSmallgrid[j] = generateRowColumnGrid(1, 1);
Grid.SetColumn(childGridSavebutn[j], 1);
Grid.SetRow(childGridSavebutn[j], i);
childSmallgrid[j].Children.Add(childGridSavebutn[j]);
childGridSavebutn[j].Click += (source, e) =>
{
int j2 = j;
txtBoxOrder[j2].Text = "I am from " + j2;
MessageBox.Show("I am from " + j2);
};
}
行int j2 = j;
在循环运行时捕获了j
的值,这样点击发生时j2
是正确的