在WinForms中,如果您将方法名称(例如button1_Click
)更改为其他名称,则它将不再起作用。
我发现这很奇怪,因为在控制台应用程序中,据我记得你可以根据需要命名方法。我正在努力了解正在发生的事情。
如何更改这些方法的名称(例如button1_Click
)?
答案 0 :(得分:26)
这个问题是关于重命名方法导致表单设计器停止工作的情况。虽然我已经涵盖了(所有我能想到的)事件如何发挥作用。
您所体验的是表单设计器的工件。
当然,你可以拥有你想要的任何方法名称,问题是表单设计者将这些方法绑定到幕后的事件,如果在表单设计者将其链接到事件后更改方法名称,它将不再工作(它无法找到方法)。
在Visual Studio中,查看要将事件绑定到的对象的属性,然后选择事件(位于面板顶部):
在那里,您将看到可用事件的列表,您将能够绑定现有方法或键入新名称:
如果您的设计师因此未出现,则必须编辑设计人员生成的代码文件。设计人员生成的文件的格式名称后面跟.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,便于编辑表单并代表您编写代码。不要让它欺骗你以为你不能自己编写代码。
实际上,您可以创建自己的方法,甚至将它们绑定到表单的事件。我一直在说" 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
),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
的委托对象。但这并不是全部发生的......我们也将它交给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 + 2
和x => 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
不是线程安全的。您也可以使用线程安全的数据结构或不同的锁定机制,为了简单起见,我保留了这一点。
即使你不编写自己的活动,也可以从这里学到一些东西:
备注:
在寻找例子的同时,我找到了一系列文章&#34; C#中的代表 - 尝试向内看&#34;作者:Ed Guzman(Part 1,Part 2,Part 3和Part 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中更新事件的名称,它就不会编译:)