如何更改现有事件处理程序的名称?

在WinForms中,如果将方法名称(例如button1_Click )更改为其他名称,则它将不再起作用。

我发现这很奇怪,因为在控制台应用程序中,据我记得你可以根据需要命名方法。 我正在努力了解正在发生的事情。

如何更改这些方法的名称 (例如button1_Click )?

这个问题是关于重命名方法导致表单设计器停止工作的情况。 虽然我已经涵盖了(所有我能想到的)一般事件如何运作。


发生了什么?

您体验的是表单设计器的工件。

当然,你可以拥有你想要的任何方法名称,问题是表单设计者将这些方法绑定到幕后的事件,如果在表单设计者将其链接到事件之后更改方法名称它将不再起作用(它找不到方法)。


为您的事件处理程序指定正确的名称

在Visual Studio中,查看要将事件绑定到的对象的属性,然后选择事件(位于面板顶部):

打开属性面板的“事件”选项卡

在那里,您将看到可用事件的列表,您将能够绑定现有方法或键入新名称:

选择创建要绑定的新方法


如果我已经搞砸了,我该如何解决?

如果您的设计师因此没有出现,则必须编辑设计人员生成的代码文件。 设计器生成的文件具有表单的名称,后跟Form1.Designer.cs (例如: Form1.Designer.cs ),您可以使用解决方案资源管理器找到它:

找到设计器生成的代码文件

注意 :您可能必须展开在表单上创建的子树以显示该文件。

在那里你会发现一条看起来像这样的线:

 this.button1.Click += new System.EventHandler(this.button1_Click); 

Visual Studio将告诉您button1_Click未定义。 您可以在那里编辑方法的名称到新名称,或删除该行以使设计器再次工作,并绑定一个新方法。


重命名现有方法,没有麻烦

您可以召唤重命名对话框。 这可以通过以下几种方式完成:

  • 从菜单: Edit – > Refactor – > Rename
  • 通过方法名称上下文菜单: Refactor – > Rename
  • 将光标放在方法名称上,然后键入Alt + Shift + F10 ,然后选择Rename
  • 将光标放在方法名称上,然后按F2

注意 :您可以自定义Visual Studio,可以更改上面的菜单和键盘快捷键。

“重命名”对话框如下所示:

使用“重命名”对话框

在那里,您可以为Method键入一个新名称,通过这样做,任何引用或对已加载项目的方法的调用也将被更改。 这包括Forms Designer生成的代码。


手动绑定事件处理程序

表单设计器所做的就是提供一个UI,便于编辑表单并代表您编写代码。 不要让它欺骗你以为你不能自己编写代码。

实际上,您可以创建自己的方法,甚至将它们绑定到表单的事件。 我一直在说“绑定”因为在这个级别上更容易理解,尽管接受的术语是订阅的 。 所以我们要做的是创建一个按钮并订阅它的Click事件。

首先让我们来看一下表单的类,它看起来像这样:

 using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } } } 

请注意,它表示partial ,这意味着在另一个文件中可能有更多代码用于此类,实际上这是表单设计器添加代码的文件Form1.Designer.cs

第二个通知它在窗体的构造函数中调用一个方法InitializeComponent 。 此方法由表单设计器创建,它负责初始化您使用表单设计器添加的所有控件和组件(因此方法的名称)。

现在,假设我们想要添加一个没有表单设计器的按钮,我们可以这样做:

 using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { private Button myButton; public Form1() { InitializeComponent(); // Create a new Button myButton = new Button(); // Set the properties of the Button myButton.Location = new System.Drawing.Point(50, 12); myButton.Size = new System.Drawing.Size(100, 23); myButton.Text = "My Button"; // Add the Button to the form this.Controls.Add(myButton); } } } 

我们创建了一个名为myButton的类型为Button的私有字段,它将保存新按钮。 然后在构造函数中添加一些新行来创建一个新Button并将其分配给myButton并给它定位( Location ), SizeText 。 最后,我们将新创建的按钮添加到窗体的Controls中。

现在我们要在这个新按钮上为Click事件添加一个事件处理程序。 请记住,此按钮不在表单设计器中,我们将不得不“手动”执行此操作。

为此,添加新方法(您可以根据需要命名):

  private void WhenClick(object sender, System.EventArgs e) { /* code */ } 

然后将其添加为按钮的Click事件的事件处理程序(在构造函数内):

  // Add an event handler myButton.Click += new System.EventHandler(WhenClick); 

请注意,我们不会调用WhenClick 。 相反,我们正在引用它。

然后我们创建一个System.EventHandler类型的新Delegate ,它将包装对方法WhenClick的引用。 我建议学习使用代表 。

我再说一遍:我们不是在调用WhenClick 。 如果我们调用WhenClick我们会做这样的事情: WhenClick(param1, param2) 。 请注意,这不是我们在这里做的,我们没有在方法名称后添加括号(/*...*/) ,所以我们没有进行方法调用。


您还可以使用一些语法糖来简化这一切:

  public Form1() { InitializeComponent(); // Create a new Button and set its properties myButton = new Button() { Location = new System.Drawing.Point(50, 12), Size = new System.Drawing.Size(100, 23), Text = "My Button" }; // Add the Button to the form this.Controls.Add(myButton); // Add an event handler (the compiler infers the delegate type) myButton.Click += WhenClick; } 

您甚至可以使事件处理程序成为匿名方法:

  // Add an event handler (the compiler infers the delegate type) myButton.Click += (sender, e) => { /* code */ }; 

你在这里看到的是一个C#Lambda表达式 (MSDN Lambda表达式的更多信息)。 习惯这些语法,因为你会越来越频繁地看到它们。


了解事件

您已经看过这样的代码:

 button1.Click += button1_Click; 

正如我告诉你的那样,我们传递的是一个带有button1_Click引用的委托对象。 但这并不是在这里发生的……我们也将它发给了Click

让我们概括一下,看看代表们的行为方式。 您可以考虑委托,如持有方法的对象,或者如果您愿意,可以指向函数的指针。

为了理解这一点,我将介绍一些可以作为控制台应用程序运行的示例。 第一个显示您可以更改委托在运行时指向的方法:

 //Console Application Example #1 ;) static void Main() { Func myfunc = null; myfunc = Add2; Console.WriteLine(myfunc(7)); // this prints 9 myfunc = MultBy2; Console.WriteLine(myfunc(7)); // this prints 14 } static int Add2(int x) { // this method adds 2 to its input return x + 2; } static int MultBy2(int x) { // this method multiplies its input by 2 return x * 2; } 

注意myfunc被输入为Func这是一个通用委托类型,它接受一个int并返回一个int

另请注意,当我说myfunc = Add2; 它没有调用Add2 (那里没有括号),它传递方法本身的引用。 myfunc = MultBy2;也是如此myfunc = MultBy2; :它没有调用MultBy2 ,我们正在传递它。

通过lambda表达式使用匿名方法,您可以编写等效代码更少的行:

 //Console Application Example #1 ;) static void Main() { Func myfunc = null; // this anonymous method adds 2 to its input myfunc = x => x + 2; Console.WriteLine(myfunc(7)); // this prints 9 // this anonymous method multiplies its input by 2 myfunc = x => x * 2; Console.WriteLine(myfunc(7)); // this prints 14 } 

请注意,我们在这里有两个匿名方法: x => x + 2x => x * 2 。 第一个( x => x + 2 )相当于我们之前使用的方法Add2 ,第二个( x => x * 2 )相当于我们之前使用的方法MultBy2

在这个例子中,我希望你看到同一个委托可以指向不同的方法。 这是通过使用指向方法的变量来实现的!


对于第二个例子,我将呈现“回调”模式。 这是一种常见的模式,在这种模式中,您将委托作为“回调”传递,即:从您调用的代码中“回复给您”的东西:

 //Console Application Example #2 ;) static void Main() { Func filter = IsPair; // an array with numbers var array = new int[]{1, 2, 3, 4, 5, 8, 9, 11, 45, 99}; PrintFiltered(array, filter); } static bool IsPair(int x) { // true for pair numbers return x % 2 == 0; } static void PrintFiltered(int[] array, Func filter) { if (array == null) throw new ArgumentNullException("array"); if (filter== null) throw new ArgumentNullException("filter"); foreach (var item in array) { if (filter(item)) { Console.WriteLine(item); } } } 

输出:

 2 4 8 

在这段代码中,我们有一个指向IsPair方法的变量 filter 。 我将再次重复这一点:在Func filter = IsPair; 我们没有调用方法IsPair ,而是我们正在引用它。

当然,可以在不声明filter 变量的情况下执行相同的操作,您可以直接传递方法引用:

 //Console Application Example #2 ;) static void Main() { // an array with numbers var array = new int[]{1, 2, 3, 4, 5, 8, 9, 11, 45, 99}; PrintFiltered(array, IsPair); //<--- } static bool IsPair(int x) { // true for pair numbers return x % 2 == 0; } static void PrintFiltered(int[] array, Func filter) { if (array == null) throw new ArgumentNullException("array"); if (filter== null) throw new ArgumentNullException("filter"); foreach (var item in array) { if (filter(item)) { Console.WriteLine(item); } } } 

我不能强调这一点:当我说PrintFiltered(array, IsPair); 它没有调用IsPair ,它将它作为参数传递给PrintFiltered 。 实际上,您有一个方法( PrintFiltered )可以引用另一个方法( IsPair )作为参考。

当然,您可以使用替换IsPair的匿名方法编写相同的代码:

 //Console Application Example #2 ;) static void Main() { // an array with numbers var array = new int[]{1, 2, 3, 4, 5, 8, 9, 11, 45, 99}; PrintFiltered(array, x => x % 2 == 0); } static void PrintFiltered(int[] array, Func filter) { if (array == null) throw new ArgumentNullException("array"); if (filter== null) throw new ArgumentNullException("filter"); foreach (var item in array) { if (filter(item)) { Console.WriteLine(item); } } } 

输出:

 2 4 8 

在这个例子中, x => x % 2 == 0是一个匿名方法,它等同于我们之前使用的方法IsPair

我们已经成功过滤了数组,只显示了对的数字。 您可以轻松地将相同的代码重用于不同的filter。 例如,以下行可用于仅输出数组中小于10的项:

  PrintFiltered(array, x => x < 10); 

输出:

 1 2 3 4 7 8 9 

在这个例子中,我想向您展示,您可以利用代理来提高代码的可重用性,方法是让部分根据您传递的代理进行更改。


现在 - 希望 - 我们理解这一点,不难想象你可以有一个Delegate对象列表,并连续调用它们:

 //Console Application Example #3 ;) static void Main() { // List> is a List that stores objects of Type Action // Action is a Delegate that represents methods that // takes an int but does not return (example: void func(int val){/*code*/}) var myDelegates = new List>(); // We add delegates to the list myDelegates.Add(x => Console.WriteLine(x)); myDelegates.Add(x => Console.WriteLine(x + 5)); // And we call them in succesion foreach (var item in myDelegates) { item(74); } } 

输出:

 74 79 

你可以看到两个匿名方法( x => Console.WriteLine(x)Console.WriteLine(x + 5) )一个接一个地调用...这发生在foreach循环中。

现在,我们可以使用多播委托完成类似的结果:

 //Console Application Example #3 ;) static void Main() { // This ia delegate... we haven't give it a method to point to: Action myDelegates = null; // We add methods to it myDelegates += x => Console.WriteLine(x); myDelegates += x => Console.WriteLine(x + 5); // And we call them in succession if (myDelegates != null) // Will be null if we don't add methods { myDelegates(74); } } 

输出:

 74 79 

同样,两个匿名方法都已被调用。 这正是事件的工作方式。 事件的默认实现使用包含在内部的多播委托。 事件的自定义实现可以使用列表或类似结构来保存代理。

现在,如果事件只是一个委托列表......这意味着事件保持对它想要调用的所有方法的引用。 它还意味着您可以从列表中删除委托(或添加多个)。

如果您想要取消订阅或取消绑定某个活动,您可以这样做:

 this.button1.Click -= button1_Click; 

对于一个令人厌恶的方法的委托对象,它有点复杂,因为您需要将委托保留在变量中以便能够将其传回以进行删除:

 Action myDelegates = null; // Create the delegate var myDelegate = x => Console.WriteLine(x); // Add the delegate myDelegates += myDelegate; // ... // Remove the delegate myDelegates -= myDelegate; 

你说自定义事件的实现了吗?

这是否意味着您可以创建自己的活动? 是的,是的。 如果您想在其中一个类中发布事件,您可以像任何其他成员一样声明它。

这是一个使用多播委托的示例:

 // Event declaration: public event EventHandler MyEvent; // Method to raise the event (aka event dispatcher): priavet void Raise_MyEvent() { var myEvent = MyEvent; if (myEvent != null) { var e = new EventArgs(); myEvent(this, e); } } 

对于自定义实现,请使用以下示例:

 // List to hold the event handlers: List myEventHandlers = new List(); // Event declaration: event EventHandler MyEvent { add { lock (myEventHandlers) { myEventHandlers.Add(value); } } remove { lock (myEventHandlers) { myEventHandlers.Remove(value); } } } // Method to raise the event (aka event dispatcher): private void Raise_MyEvent() { var e = new EventArgs(); foreach (var item in myEventHandlers) { item(this, e); } } 

希望这不难读,因为你知道事件的运作方式。 唯一的细节应该是lock ,因为List不是线程安全的。 您还可以使用线程安全的数据结构或不同的锁定机制,为了简单起见,我保留了这一点。

即使您不编写自己的活动,也可以从这里学到一些东西:

  1. 事件处理程序连续执行(默认情况下),因此最好事件处理程序快速执行(可能会延迟执行硬件的异步操作)以防止“阻塞”事件调度程序。
  2. 事件调度程序通常不处理exception(可以完成),因此最好避免在事件处理程序中抛出exception。
  3. 事件的“发布者”保留了事件处理程序的列表,当您不再需要事件处理程序时取消订阅是个好主意。

备注

在寻找例子的过程中,我发现Ed Guzman( 第1 部分 , 第2 部分 , 第3 部分和第4部分 )中的系列文章“C#中的代表 - 尝试向内看”非常容易阅读 - 虽然有点过时 - 你应该检查它们出。 您可能希望阅读C#中的匿名函数的演变,以便掌握缺少的内容。

.NET中一些常用的委托类型构建包括:

  • 通用
    • Action<*> (即: ActionAction ...)
    • Func<*, TResult> (即: FuncFunc ...)
    • EventHandler
    • Comparison
    • Converter
  • 非通用
    • Action
    • Predicate
    • EventHandler
    • ThreadStart
    • ParametrizedThreadStart

您可能对LINQ感兴趣。

线程和线程安全是一个比代表和事件更广泛的话题,不要急于理解这一切。

如果你想要与Lambda Expression和C#一起玩,我建议你得到一份LINQPad 。 这样可以减少创建测试项目的新项目的麻烦。

您当然可以更改活动的名称。 它只是听起来你不知道设计器文件:)设计器文件只是你的UI设计的结果,如果你不在UI中更新你的事件的名称,它不会编译:)