Introduction
First off no downloads, just simply create a new WPF (or Windows Forms) C# Application, copy code, paste code, run code. Styling is up to you. Where necessary, I will instruct you when to create an item (like a class library, form, etc.).
After poking around to try to explain event handlers in C#, I had a really hard time finding a truly simple example. Even when I found one that looked simple, I found the explanation around the code to be confusing. So I thought why not try to create this for myself and others.
Background
The best way to solidify your knowledge in something is to attempt to teach it!
This project has only 1 XAML or Windows Form (depending on what technology you prefer) and 1 class library. Typically, the single class library would be split into separate files for each different class, but that is not a necessity. In order to keep things as simple as possible, I have placed all of the event classes in one file.
Our custom "event" that we will create will contain two string
s. One that will return a custom message to the interface, and another that will give us the status of the work. In this case, I will start with an event called WorkStart
(made up name!). WorkStart
will be triggered at the start of any work by our class library. The code behind the XAML (or Windows Form) will create a Worker
object that we define in our class. Then, we will use the button click to tell that Worker
Object to StartWork
. The StartWork
method in the Worker
class will raise the event and the event will be handled in our form and the form textbox
es will be updated.
Let's Code
Fire up Visual Studio. I am using version 2013, but 2015 will work as well. Older versions such as 2012 and even 2010 should work too. If you don't have Visual Studio, go out to Microsoft and get the Community version FREE!(provided you meet the licensing agreement). Next, create a new WPF (or Windows Form) application (using C#). You can name this anything you want, but I called mine "EventSimplified
". After your project initializes itself, let's create a new class. Select the name of your project in the solution explorer and right click. Select Add, then at the bottom, Class. (Or you can just hit Shift + Alt + C). I named mine EventCode
, but you can call yours anything you want. You will be presented with an empty class library:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EventSimplified
{
class EventCode
{
}
}
Ok, here is where your cut and paste skills come in handy! Copy the code below into your clipboard:
public delegate void WorkStartedEventHandler(object sender, WorkStartedEventArgs e);
public class WorkStartedEventArgs : EventArgs
{
public string Status;
public string Message;
public WorkStartedEventArgs(string msg, string status)
{
Status = status;
Message = msg;
}
}
public class Worker
{
public event WorkStartedEventHandler WorkStarted;
public void StartWork()
{
WorkStartedEventArgs WkStart =
new WorkStartedEventArgs("Awake!", "Starting Task!");
OnWorkStart((object)this, WkStart);
}
void OnWorkStart(object o, WorkStartedEventArgs e)
{
if (WorkStarted != null)
WorkStarted( o, e);
}
}
Now in your class library, highlight the class definition below the namespace. Make sure you get both curly braces.
class EventCode
{
}
Paste in the code you copied. There, that wasn't so bad was it! Now before we actually get to putting in the XAML (not needed for Windows Forms) and the code behind, let's see what we just did.
The first line in the class library after the namespace now has this in there:
public delegate void WorkStartedEventHandler(object sender, WorkStartedEventArgs e);
What we did here is declare a delegate
. A delegate
essentially is a template for a method that will be called when your event fires. The important parts of the delegate
are the parameters. The parameters define the signature for the event handler or in other words, what must be passed to the event handler.
The next part defines our WorkStartedEventArgs
:
public class WorkStartedEventArgs : EventArgs
{
public string Status;
public string Message;
public WorkStartedEventArgs(string msg, string status)
{
Status = status;
Message = msg;
}
}
This code creates a publicly available class. It inherits from the default EventArgs
and adds two string
s. When the WorkStartedEventArgs
is created, it is passed two string
s (our message and our status as we discussed above). By passing these two items in, we can set the value of the publicly available Status
and Message
properties of our WorkStartedEventArgs
class.
The next section we pasted in is our Worker
class. It is the class library that will perform our work for us.
public class Worker
{
public event WorkStartedEventHandler WorkStarted;
public void StartWork()
{
WorkStartedEventArgs WkStart =
new WorkStartedEventArgs("Awake!", "Starting Task!");
OnWorkStart((object)this, WkStart);
}
void OnWorkStart(object o, WorkStartedEventArgs e)
{
if (WorkStarted != null)
WorkStarted( o, e);
}
}
First, we create the WorkStartedEventHandler
and name it WorkStarted
. We will be seeing this again in our UI where we will tie it to a form level method. The public void WorkStart
section of the class is the method we will call from the code behind in the XAML form to start our work. The StartWork
method creates a new WorkStartedEventArgs
object and passes it two string
s as we defined above. Then, we call the class level OnWorkStart
method with the class object CAST into an object [(object) this
] followed by the WorkStartedEventArgs
object we created. We make sure that the WorkStarted
event handler is not null
, then call the event handler passing in the class object o
and the WorkStartedEventArgs
object e
.
Now that we have all of our class library finished, let's get to work on the user interface and the code behind it.
XAML (WPF) People Go Here
<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>
More cut and paste time... This is going to make a really ugly form. I apologize in advance, but it is functional and it does let you easily see the results. Copy the below code:
<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>
Select your entire XAML window and paste in the code you copied above. My apologies, it IS ugly! Note that if you named your application something different than EventSimplified
, you will have to change this at the top line and out what you named your application in place of EventSimplified
.
Open up the code behind the XAML page (while the cursor is in the XAML hit F7). Again, you can use cut and paste and simply paste in this code (Assuming you did not change the name of the MainWindow
!) paste it AFTER the open brace of the namespace.
public partial class MainWindow : Window
{
Worker wk1 = new Worker();
public MainWindow()
{
InitializeComponent();
wk1.WorkStarted += new WorkStartedEventHandler(wk1_WorkStarted);
}
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
Status.Text = e.Status;
Message.Text = e.Message;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
wk1.StartWork();
}
}
Windows Forms People Go Here
For those of you using Windows Forms, you will now have to open up the default form.
Add two textboxes to it. Name one of them Status
, the other name Message
. Put a button on it and name it btnStart
. Hit F7 to view code. Use your cut and paste skills to make the form look like the below form.
public partial class Form1 : Form
{
Worker wk1 = new Worker();
public Form1()
{
InitializeComponent();
wk1.WorkStarted += new WorkStartedEventHandler(wk1_WorkStarted);
}
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
Status.Text = e.Status;
Message.Text = e.Message;
}
private void btnStart_Click(object sender, EventArgs e)
{
wk1.StartWork();
}
WPF and Windows Forms Folks Unite and Read What Is Next!
Before we run this, let's look at what we did. The first line we added was to create a new Worker
object. We defined this in our class library. That is this line:
Worker wk1 = new Worker();
Then, after the Form initialization code (on BOTH XAML and Windows Forms!), we tied the Form's wk1_WorkStarted
method to the WorkStarted
event in the class. This is the plumbing that allows the class to raise the event on the form and tells the form what to call (shown below) when the event is raised. Notice that the parameters match the delegate we declared in the class library. It has to or it will not work!
void wk1_WorkStarted(object sender, WorkStartedEventArgs e)
{
Status.Text = e.Status;
Message.Text = e.Message;
}
This is the code that you create to respond to the event. You can see how powerful this can be. Just let the imagination roam for a bit... OK, back at it, let's finish the discussion of the code. Finally, under the button click event, we put the code that starts the whole thing. When you run the code, the Worker
class object (wk1
) is created by the form, but nothing happens. That is because you need to click the button to start the work!
wk1.StartWork();
OK, take a minute to run your code. Did it work? If not, check for Errors, correct and try again.
Now that we have a simple example, in the next segment, I will expand this to show a multithreaded example. Why multithreaded? Suppose the job you run in the class to do work takes a while (2-3 minutes or even longer). You want the user interface to remain responsive, update the user of the progress and not just turn white and give nothing but a wait cursor. The user might think your application has locked up and terminate it. The application has not locked up, but the thread that is running your user interface is the same one doing the work so it is busy and cannot update the interface. Multithreading allows you to run the user interface on one thread and other processes can run on other threads leaving you with a responsive interface that updates the user with the status of the long running process.
History
- 3-11-2016 Submitted this first version!