事件处理程序变得简单
本文将解释自定义事件处理程序工作所需的条件。
引言
首先,无需下载,只需简单地创建一个新的WPF(或Windows窗体)C#应用程序,复制代码,粘贴代码,运行代码。样式由您决定。必要时,我将指导您何时创建项目(例如类库、窗体等)。
在尝试解释C#中的事件处理程序时,我费了很大劲才找到一个真正简单的例子。即使找到一个看起来简单的例子,我也发现围绕代码的解释令人困惑。所以我想到为什么不为自己和他人创建一个呢?
背景
巩固知识的最佳方法是尝试教授它!
本项目只有一个XAML或Windows窗体(取决于您偏好的技术)和一个类库。通常,单个类库会为每个不同的类拆分成单独的文件,但这并非必需。为了尽可能保持简单,我已将所有事件类放在一个文件中。
我们将创建的自定义“事件”将包含两个string
。一个将向界面返回自定义消息,另一个将提供工作状态。在这种情况下,我将从一个名为WorkStart
的事件开始(虚构的名称!)。WorkStart
将在我们的类库开始任何工作时触发。XAML(或Windows窗体)后面的代码将创建一个我们在类中定义的Worker
对象。然后,我们将使用按钮点击来告诉该Worker
对象StartWork
。Worker
类中的StartWork
方法将引发事件,事件将在我们的窗体中处理,并且窗体textbox
将更新。
开始编码
启动Visual Studio。我使用的是2013版,但2015版也可以使用。2012甚至2010等旧版本也应该可以。如果您没有Visual Studio,请前往Microsoft获取免费的社区版!(前提是您符合许可协议)。接下来,创建一个新的WPF(或Windows窗体)应用程序(使用C#)。您可以随意命名,但我称之为“EventSimplified
”。项目初始化后,我们来创建一个新类。在解决方案资源管理器中选择您的项目名称并右键单击。选择“添加”,然后在底部选择“类”。(或者您也可以直接按Shift + Alt + C)。我将其命名为EventCode
,但您可以随意命名。您将看到一个空的类库。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EventSimplified
{
class EventCode
{
}
}
好的,现在您的剪切和粘贴技能派上用场了!将下面的代码复制到剪贴板中。
// Delegate that handles the declaration in a class library as well as that in the User Interface
public delegate void WorkStartedEventHandler(object sender, WorkStartedEventArgs e);
//Class for specialized event handler
public class WorkStartedEventArgs : EventArgs
{
public string Status;
public string Message;
public WorkStartedEventArgs(string msg, string status)
{
Status = status;
Message = msg;
}
}
//Class that will trigger the event (think of this as the event sender.
//When something happens in here it triggers the event).
public class Worker
{
//Event declaration
public event WorkStartedEventHandler WorkStarted;
//The class library method that triggers the event...
public void StartWork()
{
WorkStartedEventArgs WkStart =
new WorkStartedEventArgs("Awake!", "Starting Task!");
OnWorkStart((object)this, WkStart);
}
//The method that passes the event to the event handler
void OnWorkStart(object o, WorkStartedEventArgs e)
{
if (WorkStarted != null)
WorkStarted( o, e);
}
}
现在在您的类库中,高亮显示命名空间下面的类定义。确保您选中了两个花括号。
class EventCode
{
}
粘贴您复制的代码。看,是不是没那么糟糕!现在,在我们实际将XAML(Windows窗体不需要)和后台代码放入之前,让我们看看我们刚才做了什么。
类库中命名空间后的第一行现在包含以下内容
// Delegate that handles the declaration in a class library as well as that in the User Interface
public delegate void WorkStartedEventHandler(object sender, WorkStartedEventArgs e);
我们在这里声明了一个delegate
。delegate
本质上是一个方法模板,当您的事件触发时会调用它。delegate
的重要部分是参数。参数定义了事件处理程序的签名,换句话说,就是必须传递给事件处理程序的内容。
下一部分定义了我们的WorkStartedEventArgs
//Class for specialized event handler
public class WorkStartedEventArgs : EventArgs
{
public string Status;
public string Message;
public WorkStartedEventArgs(string msg, string status)
{
Status = status;
Message = msg;
}
}
这段代码创建了一个公共类。它继承自默认的EventArgs
并添加了两个string
。当WorkStartedEventArgs
被创建时,它会传递两个string
(我们上面讨论的消息和状态)。通过传入这两个项,我们可以设置我们WorkStartedEventArgs
类的公共Status
和Message
属性的值。
我们粘贴的下一节是我们的Worker
类。它是将为我们执行工作的类库。
//Class that will trigger the event (think of this as the event sender.
//When something happens in here it triggers the event).
public class Worker
{
//Event declaration
public event WorkStartedEventHandler WorkStarted;
//The class library method that triggers the event...
public void StartWork()
{
WorkStartedEventArgs WkStart =
new WorkStartedEventArgs("Awake!", "Starting Task!");
OnWorkStart((object)this, WkStart);
}
//The method that passes the event to the event handler
void OnWorkStart(object o, WorkStartedEventArgs e)
{
if (WorkStarted != null)
WorkStarted( o, e);
}
}
首先,我们创建WorkStartedEventHandler
并将其命名为WorkStarted
。我们将在UI中再次看到它,并将其绑定到窗体级别的方法。类的public void WorkStart
部分是我们将在XAML窗体的代码中调用以开始工作的方法。StartWork
方法创建一个新的WorkStartedEventArgs
对象并传递两个string
,如我们上面所定义。然后,我们调用类级别的OnWorkStart
方法,传入被转换为对象[(object) this
]的类对象,然后是创建的WorkStartedEventArgs
对象。我们确保WorkStarted
事件处理程序不为null
,然后调用事件处理程序,传入类对象o
和WorkStartedEventArgs
对象e
。
现在我们已经完成了所有类库,接下来让我们开始用户界面及其后面的代码。
XAML (WPF) 用户请到这里
<Window x:Class="EventSimplified.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</Window>
又到了剪切和粘贴的时候了...这会创建一个非常难看的表单。我提前道歉,但它功能齐全,并且可以轻松地查看结果。复制以下代码
<Window x:Class="EventSimplified.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Name="Status" Margin="10,10,0,0" />
<TextBox Name="Message" Margin="10,10,0,0" />
<Button Content="Button" Name="btnStart"
Click="btnStart_Click" />
</StackPanel>
</Window>
选择您的整个XAML窗口,然后粘贴您上面复制的代码。我很抱歉,它确实很丑!请注意,如果您的应用程序名称与EventSimplified
不同,则需要在顶部行更改此内容,并将您的应用程序名称替换为EventSimplified
。
打开XAML页面后面的代码(当光标在XAML中时按F7)。同样,您可以剪切并粘贴此代码(假设您没有更改MainWindow
的名称!)将其粘贴在命名空间的开括号之后。
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//new instance of the worker class.
Worker wk1 = new Worker();
public MainWindow()
{
InitializeComponent();
//Create the event handler for the form and assign it to wk1_started
wk1.WorkStarted += new WorkStartedEventHandler(wk1_WorkStarted);
}
//Event Handler in the UI - This is what you want the UI to do When the event is triggered.
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
//OK on the Form I have two Textboxes One Named Status, the Other Named Message
Status.Text = e.Status;
Message.Text = e.Message;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
//Toggle the StartWork method in the class that will create the event args
//and call the OnWorkStart method to toggle the event handler
wk1.StartWork();
}
}
Windows 窗体用户请到这里
对于使用Windows Forms的用户,您现在需要打开默认窗体。
向其中添加两个文本框。将其中一个命名为Status
,另一个命名为Message
。在上面放置一个按钮并将其命名为btnStart
。按F7查看代码。使用您的剪切和粘贴技能使窗体看起来像下面的窗体。
public partial class Form1 : Form
{
//new instance of the worker class.
Worker wk1 = new Worker();
public Form1()
{
InitializeComponent();
wk1.WorkStarted += new WorkStartedEventHandler(wk1_WorkStarted);
}
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
//OK on the Form I have two Textboxes One Named Status, the Other Named Message
Status.Text = e.Status;
Message.Text = e.Message;
}
private void btnStart_Click(object sender, EventArgs e)
{
//Toggle the StartWork method in the class that will create the event args
//and call the OnWorkStart method to toggle the event handler
wk1.StartWork();
}
WPF 和 Windows Forms 用户联合起来阅读下面的内容!
在我们运行它之前,让我们看看我们做了什么。我们添加的第一行是创建一个新的Worker
对象。我们在类库中定义了它。这就是这一行
//new instance of the worker class.
Worker wk1 = new Worker();
然后,在窗体初始化代码之后(在XAML和Windows窗体上都一样!),我们将窗体的wk1_WorkStarted
方法绑定到类中的WorkStarted
事件。这是允许类在窗体上引发事件的管道,并告诉窗体在事件引发时调用什么(如下所示)。请注意,参数与我们在类库中声明的委托匹配。它必须匹配,否则将无法工作!
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
//OK on the Form I have two Textboxes One Named Status, the Other Named Message
Status.Text = e.Status;
Message.Text = e.Message;
}
这是您为响应事件而创建的代码。您可以看到这有多强大。让想象力自由驰骋一会儿...好了,回到正题,让我们完成代码的讨论。最后,在按钮点击事件下,我们放置了启动整个过程的代码。当您运行代码时,窗体会创建Worker
类对象(wk1
),但什么也没有发生。那是因为您需要点击按钮才能开始工作!
//Toggle the StartWork method in the class that will create the event args
//and call the OnWorkStart method to toggle the event handler
wk1.StartWork();
好的,花一分钟运行您的代码。成功了吗?如果不是,检查错误,更正并重试。
现在我们有了一个简单的例子,在下一节中,我将扩展它以展示一个多线程的例子。为什么要多线程?假设您在类中运行的工作需要一段时间(2-3分钟甚至更长)。您希望用户界面保持响应,向用户更新进度,而不是只变白并只显示一个等待光标。用户可能会认为您的应用程序已锁定并将其终止。应用程序并未锁定,但运行用户界面的线程与执行工作的线程是同一个,因此它很忙,无法更新界面。多线程允许您在一个线程上运行用户界面,而其他进程可以在其他线程上运行,从而为您提供一个响应迅速的界面,向用户更新长时间运行进程的状态。
历史
- 2016年3月11日 提交此第一个版本!