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

时间:2013-12-22 22:00:01

标签: c# winforms visual-studio

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

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

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

2 个答案:

答案 0 :(得分:26)

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


发生了什么事?

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

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


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

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

open the events tab of the properties panel

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

select of create a new method to bind


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

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

Find the code file generated by the designer

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

在那里你会找到一条如下所示的行:

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,可以更改上述菜单和键盘快捷键。

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

Use the Rename dialog

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


手动绑定事件处理程序

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

实际上,您可以创建自己的方法,甚至将它们绑定到表单的事件。我一直在说" bind"因为在这个级别上更容易理解,尽管接受的术语是订阅。所以我们要做的是创建一个按钮并订阅到它的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。相反,我们正在引用它。

然后我们创建一个新的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的委托对象。但这并不是全部发生的......我们也将它交给Click

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

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

//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代表类型,其中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。 / p>

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


对于第二个例子,我将介绍&#34;回调&#34;图案。这是一种常见的模式,在这种模式中,您将委托作为&#34;回调&#34;传递,即:将被调用的东西&#34;返回给您&#34;来自你打电话的代码:

//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对象列表,并连续调用它们:

//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循环中。

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

//Console Application Example #3 ;)
static void Main()
{
    // This ia 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

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

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

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

this.button1.Click -= button1_Click;

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

Action<int> 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<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不是线程安全的。您也可以使用线程安全的数据结构或不同的锁定机制,为了简单起见,我保留了这一点。

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

  1. 事件处理程序连续执行(默认情况下),因此最好事件处理程序快速执行(可能会延迟执行辛苦工作的异步操作)以防止&#34;阻塞&#34;事件调度员。
  2. 事件调度员通常不会处理异常(可以这样做),因此最好避免在事件处理程序中抛出异常。
  3. &#34;出版商&#34;该活动的目的是保留事件处理程序的列表,当您不再需要事件处理程序时,最好取消订阅。

  4. 备注

    在寻找例子的同时,我找到了一系列文章&#34; C#中的代表 - 尝试向内看&#34;作者:Ed Guzman(Part 1Part 2Part 3Part 4)非常容易阅读 - 虽然有点过时 - 你应该检查出来。您可能需要阅读C#中的Evolution of Anonymous Functions以了解缺少的内容。

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

    • 通用
      • 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感兴趣。

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

    如果您想要与Lambda Expression和C#一起玩,我建议您获取LINQPad的副本。这样可以减少创建测试项目的新项目的麻烦。

答案 1 :(得分:0)

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