How to change the name of an existing event handle

2019-01-19 04:31发布

In WinForms, if you change the method name (for example button1_Click) to something else, it doesn't work any more.

I find this strange because in Console Application as far as I remember you could name the methods as you wish. I'm trying to understanding what's happening.

How can I change the name of these methods (such as button1_Click)?

2条回答
够拽才男人
2楼-- · 2019-01-19 05:07

This questions is about the case where renaming a method causes the forms designer to stop working. Although I have covered (all that I can think of) how events works in general.


What happened?

What you experience is an artifact of the forms designer.

Sure, you can have any method name you want, the matter is the forms designer is binding those methods to events behind the scenes, if you change the method name after the forms designer has linked it to the event it will no longer work (it can't find the method).


Giving proper names to your event handlers

In Visual Studio, look at the properties of the object you want to bind an event to, and then select events (on the top of the panel):

open the events tab of the properties panel

There you will see a list the available events and you will be able to bind an existing method or type the name for a new one:

select of create a new method to bind


If I have already screwed, How do I fix it?

If your designer is not appearing because of this you will have to edit the code file that is generated by the designer. The file generated by the designer has the name of the form followed by .Designer.cs (for example: Form1.Designer.cs), you can find it with your solution explorer:

Find the code file generated by the designer

Note: You may have to expand the sub-tree created on your form to reveal the file.

There you will find a line that looks something like this:

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

And Visual Studio will tell you that button1_Click is not defined. You can edit there the name of the method to the new name, or remove the line to have the designer work again, and bind a new method.


Renaming an existing method without the hassle

You can summon the Rename dialog. This can be done by several ways:

  • From the Menus: Edit -> Refactor -> Rename
  • By contextual Menu on the name of the Method: Refactor -> Rename
  • Put the cursor on the Method name and type Alt + Shift + F10 and then select Rename
  • Put the cursor on the Method name and press F2

Note: You can customise your Visual Studio, the above menus and keyboard shortcuts may be changed.

The Rename dialog looks like this:

Use the Rename dialog

There you can type a new name for the Method, and by doing so any reference or call to that method withing the loaded projects will be changed too. That includes the code generated by the Forms Designer.


Binding an event handler by hand

All that the forms designer does is present a UI that facilitates editing the form and write code on your behalf. Don't let it fool you into thinking that you can't write code yourself.

In fact, you can create your own methods and even bind them to the events of your Form. I have been saying "bind" because it is easier to understand at this level, although the accepted lingo is subscribe. So what we are going to do, is create a button and subscribe to its Click event.

First let's take a look at the class of your form, it looks something like this:

using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Notice it says partial, that means that there could be more code for this class in another file, in fact that is the file Form1.Designer.cs where the forms designer has been adding code.

Second notice it calls a method InitializeComponent inside the constructor of the form. This method has been created by the forms designer and it takes responsibility of initializing all the controls and components you have added using the forms designer (hence the name of the method).

Now, let's say we want to add a button without the forms designer we can do it like this:

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);
        }
    }
}

We have created a private field named myButton of type Button that will hold the new button. Then inside the constructor we add some new lines to create a new Button and assign it to myButton and give it position (Location), Size and Text. And finally we have added the newly created button to the Controls of the form.

Now we want to add an event handler for the Click event on this new button. Remember that this button is not in the forms designer, we we are going to have to do it "by hand".

In order to do so, add the new method (you can named whatever you want):

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

And then add it as an event handler for the Click event of the button (inside the constructor):

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

Notice we are not calling WhenClick. instead we are making a reference to it.

Then we are creating a new Delegate of type System.EventHandler that will wrap the reference to the method WhenClick. I suggest to learn about Using Delegates.

I repeat: we are not calling WhenClick. If we were to call WhenClick we would do something like this: WhenClick(param1, param2). Please note that this is not what we are doing here, we haven't added parenthesis (/*...*/) after the method name, so we are not doing a method call.


You can also use some syntactic sugar to make all this easier:

        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;
        }

You can even make the event handler an anonymous method:

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

What you see here is a C# Lambda expression (more info at MSDN Lambda expressions). Get used to these syntax, because you will see them more and more often.


Understanding events

You have already seen code like this:

button1.Click += button1_Click;

As I told you we are passing a delegate object that has a reference to button1_Click. But that's not all that happens here... we are also giving it to Click.

Let's recapitulate and see how delegates behave. You can think about a delegate like an object that holds a method, or a pointer to a function if you prefer.

To understand this, I'll present some Examples you can run as Console Applications. The first one shows that you can change the method that a delegate points to during runtime:

//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;
}

Notice myfunc is typed as Func<int, int> this is a Generic Delegate type that takes an int and returns an int.

Also notice that when I say myfunc = Add2; It is not calling Add2 (there are no parenthesis there), it is passing a reference of the method itself. the same is true for myfunc = MultBy2;: it is not calling MultBy2, we are passing it.

Using anonymous methods via lambda expressions you can write equivalent code is less lines:

//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
}

Notice that we have two anonymous methods here: x => x + 2 and x => x * 2. The first one (x => x + 2) is equivalent to the method Add2 we had before, and the second one (x => x * 2) is equivalent to the method MultBy2 we had before.

In this example I want you to see that the same delegate can point to different methods along the time. This is accomplished by having a variable that points to the methods!


For the second example, I'll present the "callback" pattern. That is a common pattern in which you pass a delegate as a "callback", that is: something that will be called "back to you" from the code you are calling:

//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);
        }
    }
}

Outputs:

2
4
8

In this code we are having a variable filter that points to the method IsPair. I'll repeat this again and and again: in the line Func<int, bool> filter = IsPair; we are not calling the method IsPair, instead we are taking a reference to it.

Of course, it is possible to do the same without declaring the filter variable, you can pass the method reference directly:

//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);
        }
    }
}

I cannot stress this hard enough: When I say PrintFiltered(array, IsPair); it is not calling IsPair, it is passing it as a parameter to PrintFiltered. Here effectively you have a Method (PrintFiltered) that can take a reference to another method (IsPair) as a reference.

Of course you can write the same code using an anonymous method that replaces 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);
        }
    }
}

Outputs:

2
4
8

In this example x => x % 2 == 0 is an anonymous method that is equivalent to the method IsPair we had before.

We have successfully filtered the array to only show the numbers that are pair. You can easily reuse the same code for a different filter. For example, the following line can be used to output only the items in the array that are less than 10:

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

Outputs:

1
2
3
4
7
8
9

With this example I want to show you that you can take advantage of the delegates to improve the reusability of your code, by having parts that change depending on the delegate you pass.


Now that - hopefully - we understand this, it is not hard to think that you could have a list of Delegate objects, and call them in succession:

//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);
    }
}

Outputs:

74
79

You can see both anonymous methods (x => Console.WriteLine(x) and Console.WriteLine(x + 5)) has been called, one after the other... this happens inside the foreach loop.

Now, we can accomplish similar results with a multicast delegate:

//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);
    }
}

Outputs:

74
79

Again, both anonymous methods has been called. And this is exactly how Events work. The default implementation of an Event uses a multicast delegated wrapped inside. Custom implementation of an event may use a list or similar structure to hold the delegates.

Now, if the event is just a list of delegates... that means the event is keeping a reference to all the methods it wants to call. And it also means that you could remove delegates from the list (or add more than one).

If you want to unsubscribe or unbind from an event, you can do so like this:

this.button1.Click -= button1_Click;

For a delegate object to an anoymous method it is a little bit more complicated, because you will need to keep the delegate in a variable to able to pass it back for removal:

Action<int> myDelegates = null;

// Create the delegate 
var myDelegate = x => Console.WriteLine(x);

// Add the delegate
myDelegates += myDelegate;

// ...

// Remove the delegate
myDelegates -= myDelegate;

Did you say Custom Implementation of an Event?

Does that means you can create your own events? Yes, and yes. If you want to publish an event in one of your classes you can declare it just like any other member.

This is an example that uses a multicast 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);
    }
}

For a custom implementation take this example:

// 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);
    }
}

Hopefully this not hard to read, now that you know how events work. The only details should be lock, it is there because List is not thread-safe. You could also use a thread-safe data structure or a different locking mechanism, I kept this one for simplicity.

Even if you don't write your own events, there are a few things to learn from here:

  1. Event handlers execute consecutively (by default), so it is good idea that event handlers execute fast (maybe defer to async operations for the hard work) to prevent "clogging" the event dispatcher.
  2. The event dispatcher usually don't handle exceptions (it can be done), so it is good idea to avoid throwing excepting in event handlers.
  3. The "publisher" of the event is keeping a list of the event handlers, it is a good idea to unsubscribe when you don't need need the event handler anymore.

Notes:

While looking for example I found the series of articles "Delegates in C# - Attempt to Look Inside" by Ed Guzman (Part 1, Part 2, Part 3 and Part 4) very easy to read - although a bit outdated - you should check them out. You may want to read the Evolution of Anonymous Functions in C# for a grasp of what's missing.

Some commonly used Delegate Types buildin in .NET include:

  • Generic
    • Action<*> (that is: Action<T>, Action<T1, T2> ...)
    • Func<*, TResult> (that is: Func<T, TResult>, Func<T1, T2, TResult> ...)
    • EventHandler<TEventArgs>
    • Comparison<T>
    • Converter<TInput, TOutput>
  • Non Generic
    • Action
    • Predicate
    • EventHandler
    • ThreadStart
    • ParametrizedThreadStart

You may be interested in LINQ.

Threading and thread-safety it a even broader topic than delegates and events, don't rush to understand it all.

If you want play along with Lambda Expression and C# in general, I suggest to get a copy of LINQPad. This make reduce the hassle of creating new project to test things.

查看更多
女痞
3楼-- · 2019-01-19 05:10

You can of course change the name of your event. it just sounds like you dont know about the designer file :) the designer file is just the result of your UI design, and if you dont update the name of your event in the UI it does not compile :)

查看更多
登录 后发表回答