Saturday, March 2, 2013

Quick And Easy Custom Events

There are some really great IN DEPTH articles out there on custom events. 
But sometimes we just need down and dirty explanation with some examples. That's what this article is.

Don't be afraid of the idea of creating a custom event. You have probably already worked with events. If you have ever put a button on a form, then double-clicked it to create a method that handles the button.click event, then you have already worked with events.


private void button1_Click(object sender, EventArgs e)
{
    // Here is where you put your code for what to do when the button is clicked.
}
An event handler receives two parameters: The object that sent the event, and an EventArgs. You can define any kind of event arguments you want. Maybe your arguments only need to be a string... maybe your arguments for an event is a picture... maybe you don't need any custom arguments because you only need to be notified with some task is done.

We're going to make:
  • A string event argument
  • An event that uses the string event argument
  • A method that raises the event
  • A method that handles the raised event
In the real would this could work well for logging steps your program is taking, or providing feedback to the user. First the EventArgs, which in this example is just a string:
public class TextArgs : EventArgs
{
    #region Fields
    private string szMessage;
    #endregion Fields

    #region ConstructorsH
    public TextArgs(string TextMessage)
    {
        szMessage = TextMessage;
    }
    #endregion Constructors

    #region Properties
    public string Message
    {
        get { return szMessage; }
        set { szMessage = value; }
    }
    #endregion Properties
}

We have a private field, a public constructor and a public property. That's it: Nothing scary. When you make a new TextArgs you will be providing a string to become the Message to be passed.

Now for the event:

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

 #region Events
 public event EventHandler Feedback;
 #endregion Events
      

That's it in line 9: A new event called "Feedback" that uses your new TextArgs. Basically this a way for your Form1 to yell something to any other form (or class) that is listening.

There is no point raising an event if nobody is listening. So we are going to use a method to check first. If there is a subscriber to the event, then we raise the event. If nobody is listening, then we do nothing.

private void RaiseFeedback(string p)
{
 EventHandler handler = Feedback;
 if (handler != null)
 {
  handler(null, new TextArgs(p));
 }
}

That's it! You have created a custom argument, a custom event that uses the argument, and a method to raise the event if someone is listening. To use this in your program yo might do something like this

private void ProcessMyData()
{
   RaiseFeedback("Data process starting...");
   variableOne = variableTwo / variableThree * variableFour;
   string results = variableOne.ToString();
   // Do a bunch of cool charting stuff
   RaiseFeedback("Stage one complete at: " + DateTime.Now.ToString());
   // Do the more complex stuff as part of stage two
   RaiseFeedback("Stage two complete at: " + DateTime.Now.ToString());
}

Notice that while Form1 does it's processing it is not directly trying to force any other work. It is not logging. It is not trying to make Form6 display a MessageBox. It is not trying to force Form3 to display the feedback in a ListBox. It doesn't know or care about anything other than it's own job. This is an important concept. Each class of your program should do one thing, do it well, and do no more. If you need 6 things done then write six methods. Don't try to make one all-encompassing method that does 6 things. It will just make your life tough later when you need to change the sequence of those 6 things, or add things 7, 8, 9 but temporarily stop thing 4. Whatever you do in response to a Feedback event is NOT tightly bound to the process that raises the event and thus the two won't break each other due to minor changes.

Let's subscribe to the Feedback event:

private void Form1_Load(object sender, EventArgs e)
{
   // Do your initial setup of the form once it loads
   Feedback += new EventHandler(Feedback_Received);
}

and create the event handling method:

void Feedback_Received(object sender, TextArgs e)
{
 HistoryListBox.Items.Add(e.Message);
}

Notice the e.Message. That comes from the TextArgs you made earlier. It is the public property Message.

Let's walk through what actually happens when you use this:

// Do some processing;
RaiseFeedback("Igor, it's alive");
// Do some MORE processing;

Code jumps to the RaiseFeedback method where a new TextArgs is created putting "Igor, it's alive" into the Message property.
The event Feedback is raised with the new TextArgs.
Execution then splits. Once the event is raised program flow returns to the next line: // Do some more processing
But, execution also starts in the Feedback_Received event handling method which is going to put the Message of the TextArgs into our HistoryListBox

======= 2 weeks later =======
As your program grows you realize that everything you are sending to the HistoryListBox really should also go to a text file as a log of what your program is doing. You don't have to go through hundreds of places where you called the Feedback. You just create a logging method, and subscribe it to your Feedback event. Boom! Everything to screen now also goes to a text file.

private void Form1_Load(object sender, EventArgs e)
{
   // Do your initial setup of the form once it loads
   Feedback += new EventHandler(Feedback_Received);
   Feedback += new EventHandler(LogFeedback);
}

void LogFeedback(object sender, TextArgs e)
{
 //Write e.Message to my log text file
}

======== Form1 and Form2 =======
Or maybe you need Form2 to react to something that happens in Form1

private void Form1_Load(object sender, EventArgs e)
{
   // Do your initial setup of the form once it loads
   Feedback += new EventHandler(Feedback_Received);
   Feedback += new EventHandler(LogFeedback);

   Form2 myForm2 = new Form2();
   Feedback += new EventHandler(myForm2.FeedbackResponse);
}

public partial class Form2 : Form
{
   public void FeedbackReponse(object sender, TextArgs e)
   {
      //  Handle feedback from some other form
   }
}

Notice that Form2 doesn't have to know where the feedback is coming from. Its is happy in it's ignorance. Some other form subscribed it to it's feedback event. You could have 10 other forms all raise a feedback event and have this Form2 subscribed to all of them. It then becomes a single location to show the entire running operation of your program. 

Original from here

No comments:

Post a Comment