• (2 Pages)
  • +
  • 1
  • 2

Quick and easy custom events Demonstrated with simple event-triggered logging

#1 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 07 June 2010 - 11:21 AM

*
POPULAR

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<TextArgs> 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<TextArgs> 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<TextArgs>(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<TextArgs>(Feedback_Received);
   Feedback += new EventHandler<TextArgs>(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<TextArgs>(Feedback_Received);
   Feedback += new EventHandler<TextArgs>(LogFeedback);

   Form2 myForm2 = new Form2();
   Feedback += new EventHandler<TextArgs>(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.

Is This A Good Question/Topic? 22
  • +

Replies To: Quick and easy custom events

#2 Rico Diesel  Icon User is offline

  • D.I.C Head

Reputation: 62
  • View blog
  • Posts: 122
  • Joined: 06-May 10

Posted 08 June 2010 - 07:11 AM

Nice article, it has all the information necessary for custom events (at least in my humble opinion), but I got a question though. Why would one use an extra private variable to raise the event?

public event EventHandler<TextArgs> Feedback;

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



Why not call it like this?
if (Feedback != null)
  Feedback(null, new TextArgs(p));



I have seen this now quite a few times on several different fora and articles but I couldn't find an explanation for this so far. Most programmers always seem to use the extra variable 'handler'. As far as I can tell it seems to be a preference, instead of being really functional, but I could be wrong of course...

Rico
Was This Post Helpful? 3
  • +
  • -

#3 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 08 June 2010 - 07:53 AM

I wish I had an answer for you, but I don't. Like most of us I picked up habits and coding style as I learned. I've fixed and tweaked things that didn't work until I had a core set of functions that I know I can depend on. Then I keep using them unchanged until I have a need to re-examine them.

I'm starting a new project where I can try out the smaller code block you suggested. If it holds up then *that* will probably become my new style. Either way... I'll post back here after that project is complete and passed QC.

Thanks for showing it to us all.
Was This Post Helpful? 1
  • +
  • -

#4 MentalFloss  Icon User is offline

  • "ADDICTED"[2:5]
  • member icon

Reputation: 526
  • View blog
  • Posts: 1,397
  • Joined: 02-September 09

Posted 26 October 2010 - 05:52 AM

Quote

Why would one use an extra private variable to raise the event?


It's to avoid the possible race condition of the event becoming null in between the time you check it's not null and the time you actually raise the event.

When you assign it this way, it creates an actual copy that will not possibly be set to null because you are in control of the copy... but it COULD have been null which is what the check would correctly identify.

Hope that makes sense. Nice post here by the way.
Was This Post Helpful? 3
  • +
  • -

#5 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 26 October 2010 - 06:15 AM

View PostMentalFloss, on 26 October 2010 - 04:52 AM, said:

Quote

Why would one use an extra private variable to raise the event?


It's to avoid the possible race condition of the event becoming null in between the time you check it's not null and the time you actually raise the event.

When you assign it this way, it creates an actual copy that will not possibly be set to null because you are in control of the copy... but it COULD have been null which is what the check would correctly identify.

Hope that makes sense. Nice post here by the way.


Thank you for the insight on that. I appreciate it.
Was This Post Helpful? 0
  • +
  • -

#6 german129  Icon User is offline

  • New D.I.C Head

Reputation: 5
  • View blog
  • Posts: 43
  • Joined: 03-September 10

Posted 26 October 2010 - 10:20 AM

I was curious if there is any difference or reason to use an event over a delegate? The only thing I can think of is that using an event will allow that event to be viewable in the event portion of the properties window if attached to a UI component.
Was This Post Helpful? 0
  • +
  • -

#7 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 26 October 2010 - 10:30 AM

View Postgerman129, on 26 October 2010 - 09:20 AM, said:

I was curious if there is any difference or reason to use an event over a delegate? The only thing I can think of is that using an event will allow that event to be viewable in the event portion of the properties window if attached to a UI component.


Frankly I've always been fuzzy on the whole delegate thing. But I seem to make a living without it. I use events for exactly what you described: UI and other classes to notify each other of when something happens. This was the main aim of the tutorial.

event LogThis(string Message)
event SaleComplete (SaleTransaction SalesTicket)
event ThumbnailRebuilt (Image NewThumbnail)
event NewFileReady(string PathToFinishedFile)
event KeyboardShortcutPressed (Keys KeyPressed)
Was This Post Helpful? 1
  • +
  • -

#8 MentalFloss  Icon User is offline

  • "ADDICTED"[2:5]
  • member icon

Reputation: 526
  • View blog
  • Posts: 1,397
  • Joined: 02-September 09

Posted 29 October 2010 - 02:41 AM

You know how interfaces are contracts for classes?

Well, a delegate is a contract for a method.

You specify what the delegate signature is and then an event is of that type of delegate. Therefore, any object can subscribe to the event provided its target method matches the signature of the delegate.

Hope that clears things up.
Was This Post Helpful? 4
  • +
  • -

#9 I2Code  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 1
  • Joined: 02-January 11

Posted 02 January 2011 - 06:44 PM

Great article first off! I learned alot from it. I have a question regarding a twist on the article example. From another article on using the backgroundworker and 2 separate forms and how that example was passing info I ended up here which was a good thing.

So on Form2 I have a Cancel button that I need to trigger the Cancel of the backgroundworker. So I thought, why not just change the RaiseFeedback on Form1 to Public instead of Private. I also created a new event handler called Feedback_Cancel where the handler checks for the string "Cancel process" and then if matches executes backgroundWorker1.CancelAsysnc. But Form2, even with 'Public' set, I cannot see the RaiseFeedback, so I tried Form1 frm1 = new Form1() and then frm1.RaiseFeedback("Cancel process"). Well it takes it, but the statement "if(Feedback != null)" keeps seeing null so it never calls "Feedback_Cancel".

Can you help explain why I would see that and how I can accomplish this?

Thanks,
Mark
Was This Post Helpful? 1
  • +
  • -

#10 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 03 January 2011 - 07:45 AM

View PostI2Code, on 02 January 2011 - 05:44 PM, said:

Great article first off! I learned alot from it. I have a question regarding a twist on the article example. From another article on using the backgroundworker and 2 separate forms and how that example was passing info I ended up here which was a good thing.

So on Form2 I have a Cancel button that I need to trigger the Cancel of the backgroundworker. So I thought, why not just change the RaiseFeedback on Form1 to Public instead of Private. I also created a new event handler called Feedback_Cancel where the handler checks for the string "Cancel process" and then if matches executes backgroundWorker1.CancelAsysnc. But Form2, even with 'Public' set, I cannot see the RaiseFeedback, so I tried Form1 frm1 = new Form1() and then frm1.RaiseFeedback("Cancel process"). Well it takes it, but the statement "if(Feedback != null)" keeps seeing null so it never calls "Feedback_Cancel".

Can you help explain why I would see that and how I can accomplish this?

Thanks,
Mark

Rather than take this tutorial thread way off topic for something the looks to get rather involved, I'll write you a PM about this so we can discuss it at length.
Was This Post Helpful? 0
  • +
  • -

#11 Bez84  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 33
  • Joined: 10-October 09

Posted 24 March 2011 - 03:38 PM

I've worked through this, it's working but I'm still having trouble processing it. How could I for example, then raise an event from myForm2 that Form1 reacts to in the situation above. I understand this method subscribes the child to an event of its parent but how would that work the other way around?

This post has been edited by Bez84: 24 March 2011 - 03:40 PM

Was This Post Helpful? 0
  • +
  • -

#12 here.to.code  Icon User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 55
  • Joined: 15-February 11

Posted 15 April 2011 - 08:33 AM

Great Article.

Correct me if I'm wrong(which I probably am) why not just have a method like:

public void Log(string msg)
{
    // log the file or whatever
}



And call it just like you're calling RaiseFeedback?
I just don't see why your example would even need custom events if you're calling them yourself?
I probably just don't understand why you would be using a custom event like this I dunno...
Was This Post Helpful? 0
  • +
  • -

#13 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5481
  • View blog
  • Posts: 11,762
  • Joined: 02-June 10

Posted 15 April 2011 - 08:52 AM

View Posthere.to.code, on 15 April 2011 - 09:33 AM, said:

Great Article.

Correct me if I'm wrong(which I probably am) why not just have a method like:

public void Log(string msg)
{
    // log the file or whatever
}



And call it just like you're calling RaiseFeedback?
I just don't see why your example would even need custom events if you're calling them yourself?
I probably just don't understand why you would be using a custom event like this I dunno...


This would not be driven by an event. It now makes things happen sequentially. It also requires you make the method public. As a method Log(string) works for the one purpose. But I often do several things when a LogThis(string) event is called.

I can wire the one event to several handlers and they all hear the event at the same time.
So when a class yells out LogThis("Operation Complete"); that is wired to the logger, and to on-screen feedback.

Plus I can do this all at one place like the form load, making it easy to maintain. If you hardcode your calls to methods as you've suggested then every time you want to add one more handler you have to scrub through thousands of lines of code to add the extra step at each and every point you called your log method.

public Form1_Load(object sender, eventargs e)
{
   someClassInstance.LogThis += LogToTextFileMethod();
   someClassInstance.LogThis += UpdateTheStatusBarMethod();
   someClassInstance.LogThis += SendToMyServerOverTcpIpMethod();
   // 6 months later add another handler to this event to notify an internet server
}

Also its a lot harder to control program flow if all you do is hardcode all your method calls the way you've suggested. But you can start and stop subscriptions with ease.

if (chkLogToServer.Checked) someClassInstrance.LogThis += SendToMyServerOverTcpIpMethod();
else someClassInstrance.LogThis -= SendToMyServerOverTcpIpMethod();

With events you can decide at runtime what subscriptions are active and disabled.

This post has been edited by tlhIn`toq: 15 April 2011 - 08:54 AM

Was This Post Helpful? 2
  • +
  • -

#14 here.to.code  Icon User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 55
  • Joined: 15-February 11

Posted 15 April 2011 - 09:02 AM

Ok I understand it a bit more but still lost a little. I'll look around more on the internet for examples when it becomes a huge life saver or something. Thanks for the response!
Was This Post Helpful? 0
  • +
  • -

#15 here.to.code  Icon User is offline

  • D.I.C Head

Reputation: 20
  • View blog
  • Posts: 55
  • Joined: 15-February 11

Posted 15 April 2011 - 09:51 AM

View PosttlhIn`toq, on 15 April 2011 - 08:52 AM, said:

This would not be driven by an event. It now makes things happen sequentially...


After reading this I thought I'd test it out.

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

        public event EventHandler<TextArgs> Feedback;

        private void Form1_Load(object sender, EventArgs e)
        {
            Feedback += new EventHandler<TextArgs>(Feedback_Received);
            Feedback += new EventHandler<TextArgs>(Feedback_Received2);
            RaiseFeedback("testing");
            for (int i = 0; i < 100; i++)
                Console.WriteLine("test");
        }

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

        void Feedback_Received(object sender, TextArgs e)
        {
            HistoryListBox.Items.Add(e.Message);
            for (int i = 0; i < 100; i++)
                Console.WriteLine("lol");
        }

        void Feedback_Received2(object sender, TextArgs e)
        {
            HistoryListBox.Items.Add(e.Message + "lol");
            Console.WriteLine("GOTCHA");
        }
    }

    public class TextArgs : EventArgs
    {
        private string szMessage;

        public TextArgs(string txt)
        {
            szMessage = txt;
        }

        public string Message
        {
            get { return szMessage; }
            set { szMessage = value; }
        }
    }



This shows that events are also sequential. At least based on output I get.

lolx100
GOTCHA
testx100

If events were not sequential they should show something like this:

lol
gotcha
test
lol
test
lol
test
lol...

Just thought I'd add that in since I thought about using them if they did not work sequentially but it seems they're the same as current methods using functions that I'm working with.
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2