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

标签 c# winforms visual-studio

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

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

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

最佳答案

这个问题是关于重命名方法导致表单设计者停止工作的情况。尽管我已经介绍了(我能想到的所有)事件在总体上是如何工作的。

发生了什么?

您所体验的是表单设计器的人工制品。

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

给事件处理程序起个适当的名字

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

在此处,您将看到可用事件的列表,并且您可以绑定(bind)现有方法或键入新方法的名称:



如果我已经拧紧了,该如何解决?

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

注意:您可能必须扩展在窗体上创建的子树才能显示文件。

在那里,您会发现一条类似于以下内容的行:

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

而且Visual Studio会告诉您未定义button1_Click。您可以在此处将方法名称编辑为新名称,或删除该行以使设计器再次工作,然后绑定(bind)新方法。

重命名现有方法

您可以召唤“重命名”对话框。这可以通过以下几种方法完成:
  • 从菜单:Edit-> Refactor-> Rename
  • 通过上下文菜单上的方法名称:Refactor-> Rename
  • 将光标放在“方法”名称上,键入Alt + Shift + F10,然后选择Rename
  • 将光标放在“方法”名称上,然后按F2

  • 注意:您可以自定义Visual Studio,以上菜单和键盘快捷方式可能会更改。

    重命名对话框如下所示:

    在那里您可以为该方法键入一个新名称,这样一来,随装入的项目一起对该方法的任何引用或调用也将被更改。这包括由窗体设计器生成的代码。

    手动绑定(bind)事件处理程序

    表单设计者所做的全部工作就是提供一个UI,该UI有助于您编辑表单并代表您编写代码。不要让它欺骗您以为您自己不能编写代码。

    实际上,您可以创建自己的方法,甚至将它们绑定(bind)到Form的事件。我之所以说“绑定(bind)”是因为在此级别更容易理解,尽管可接受的术语是 subscription 。因此,我们要做的是创建一个按钮并订阅其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的私有(private)字段,其类型为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。相反,我们正在引用它。

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

    我再说一遍:我们不叫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 expression(有关更多信息,请参见MSDN Lambda expressions)。习惯这些语法,因为您会越来越频繁地看到它们。

    了解事件

    您已经看过这样的代码:
    button1.Click += button1_Click;
    

    正如我告诉您的那样,我们正在传递一个具有button1_Click引用的委托(delegate)对象。但这还不是这里发生的全部...我们也将其提供给Click

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

    为了理解这一点,我将提供一些示例,您可以将它们作为控制台应用程序运行。第一个显示您可以在运行时更改委托(delegate)指向的方法:
    // Console Application Example #1 ;)
    static void Main()
    {
        Func<int, int> 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>,这是一种generic委托(delegate)类型,它接受int并返回int

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

    通过lambda表达式使用匿名方法,您可以编写等效代码的代码更少:
    // Console Application Example #1 ;)
    static void Main()
    {
        Func<int, int> 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方法。

    在此示例中,我希望您看到同一委托(delegate)人可以同时指向不同的方法。这是通过具有一个指向方法的变量来实现的!

    对于第二个示例,我将介绍“回调”模式。这是一种常见的模式,在该模式中,您将委托(delegate)作为“回调”传递,即:从您正在调用的代码中将其称为“返回给您”:
    // Console Application Example #2 ;)
    static void Main()
    {
        Func<int, bool> 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<int, bool> 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
    

    在这段代码中,我们有一个变量filter,它指向方法IsPair。我将一遍又一遍地重复此操作:在Func<int, bool> 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<int, bool> 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<int, bool> 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方法。

    我们已经成功过滤了数组以仅显示成对的数字。您可以轻松地将相同的代码重用于不同的过滤器。例如,以下行可以用于仅输出数组中小于10的项目:
        PrintFiltered(array, x => x < 10);
    

    输出:
    1
    2
    3
    4
    7
    8
    9
    

    在此示例中,我想向您展示,您可以利用委托(delegate)来提高代码的可重用性,方法是使部分随传递的委托(delegate)而变化。

    现在-希望-我们理解了这一点,不难想到您可以拥有一个Delegate对象的列表,并依次调用它们:
    // Console Application Example #3 ;)
    static void Main()
    {
        // List<Action<int>> is a List that stores objects of Type Action<int>
        // Action<int> is a Delegate that represents methods that
        //  takes an int but does not return (example: void func(int val){/*code*/})
        var myDelegates = new List<Action<int>>();
    
        // 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循环内。

    现在,我们可以使用多播委托(delegate)来完成类似的结果:
    // Console Application Example #3 ;)
    static void Main()
    {
        // This is a delegate... we haven't give it a method to point to:
        Action<int> 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
    

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

    现在,如果该事件只是一个委托(delegate)列表,这意味着该事件将保留对其要调用的所有方法的引用。这也意味着您可以从列表中删除代表(或添加多个代表)。

    如果您想退订或取消绑定(bind)某个事件,则可以这样操作:
    this.button1.Click -= button1_Click;
    

    对于匿名方法的委托(delegate)对象,它有点复杂,因为您需要将委托(delegate)保留在变量中,以便能够将其传递回以进行删除:
    Action<int> myDelegates = null;
    
    // Create the delegate
    var myDelegate = x => Console.WriteLine(x);
    
    // Add the delegate
    myDelegates += myDelegate;
    
    // ...
    
    // Remove the delegate
    myDelegates -= myDelegate;
    

    您是否说过事件的自定义实现?

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

    这是一个使用多播委托(delegate)的示例:
    // 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<EventHandler> myEventHandlers = new List<EventHandler>();
    
    // 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并非线程安全的,所以才存在。您也可以使用线程安全的数据结构或其他锁定机制,为简单起见,我保留了这一结构。

    即使您不编写自己的事件,也可以从这里学习一些内容:
  • 事件处理程序连续执行(默认情况下),因此,最好将事件处理程序快速执行(为了辛苦工作,可能要遵循异步操作),以防止“阻塞”事件分发程序。
  • 事件分派(dispatch)器通常不处理异常(可以做到),因此最好避免在事件处理程序中抛出异常。
  • 事件的“发布者”保留了事件处理程序的列表,当您不再需要事件处理程序时,退订是个好主意。


  • 笔记:

    在查找示例时,我发现Ed Guzman撰写的系列文章“C#中的委托(delegate)-尝试向内看”(Part 1Part 2Part 3Part 4)非常容易阅读-尽管有些过时了-您应该将它们 check out 。您可能需要阅读C#中的Evolution of Anonymous Functions,以了解缺少的内容。

    .NET内置的一些常用的委托(delegate)类型包括:
  • 通用
  • Action<*>(即:Action<T>Action<T1, T2> ...)
  • Func<*, TResult>(即:Func<T, TResult>Func<T1, T2, TResult> ...)
  • EventHandler<TEventArgs>
  • Comparison<T>
  • Converter<TInput, TOutput>
  • 非通用
  • Action
  • Predicate
  • EventHandler
  • ThreadStart
  • ParametrizedThreadStart

  • 您可能对LINQ感兴趣。

    线程和线程安全是一个比委托(delegate)和事件更广泛的话题,不要着急了解所有内容。

    如果您想与Lambda Expression和C#一起使用,建议您复制LINQPad。这减少了创建新项目以测试事物的麻烦。

    关于c# - 如何更改现有事件处理程序的名称?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20734477/

    相关文章:

    c# - 托管 UserControl 设计器的 ToolStripControlHost 不会发生序列化

    c# - F# 属性与 C# 属性

    vb.net - 在VB .net 2010中设置文本框的最小值和最大值

    .net - 如何使 DateTimePicker 显示空字符串?

    c - 需要 C 编译器选项来创建易于逆向的可执行文件来教授逆向

    windows - 基于操作系统的C++条件编译

    c# - Microsoft 报表查看器中的列重复方向

    C# .NET 2 Threading.Timer - 时间漂移

    c# - 部署 Windows 窗体应用程序时要采取的步骤?

    Visual Studio Community 2019 中的 C++ 代码分析产生警告 C26486 和 C26414