在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
),Size
和Text
。最后,我们将新创建的按钮添加到表单的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 + 2
和x => 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
并非线程安全的,所以才存在。您也可以使用线程安全的数据结构或其他锁定机制,为简单起见,我保留了这一结构。即使您不编写自己的事件,也可以从这里学习一些内容:
笔记:
在查找示例时,我发现Ed Guzman撰写的系列文章“C#中的委托(delegate)-尝试向内看”(Part 1,Part 2,Part 3和Part 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/