Page 1 of 1

Example: Delegate, Event and Custom EventArgs

#1 andrewsw  Icon User is online

  • say what now
  • member icon

Reputation: 6408
  • View blog
  • Posts: 25,889
  • Joined: 12-December 12

Posted 07 November 2014 - 01:08 PM

The following is an example demonstrating the use of a delegate, a custom event and custom EventArgs. In the process it also demonstrates how an event can be used to communicate with (to pass information to) a second (Win)Form. (It also demonstrates sharing an object between forms, passing the object as an argument to a Form's constructor.)

I do not consider this a formal tutorial on these topics. I do discuss and describe delegates, events, etc., but in fairly broad terms. I am more interested in the example itself, and how it makes use of these features. Read these tutorials for the fuller picture, and more formal discussions:

Passing Data Between Forms In C#
Passing Data Between Forms - Using Delegates And Events (blog entry)
Quick And Easy Custom Events
(without defining a delegate)

Delegates and Events
(this was the inspiration for my example)

Full disclosure: I am not an expert on these topics, I continue to study them myself. I like this example though because:

  • It pushes me nearer to a good understanding of delegates and events
  • There are a number of different features included and discussed
  • The example itself is open to discussion and debate, even criticism!

On this last point, I welcome and encourage comments.

The Application

Attached Image

Attached Image

[There is a zipped version of the application at the end of the tutorial.]

  • I wanted a Clock class - a clock that updates itself.
  • I wanted a form that binds to the clock, and itself updates to reflect the current state of the clock.
  • I wanted to be able to add alarms to the clock but, significantly, I wanted these to work as additions to the basic functionality of the clock. That is, the clock will work regardless of the existence of any alarms.
  • I also wanted to use a second form to manage the alarms.

To achieve these goals I use the INotifyPropertyChanged interface to keep the first form in-sync with the clock, but the clock also publishes a ClockChanged event that the alarms-form subscribes to. When the clock changes the alarms-form is aware of this, and checks to see whether the new time has an alarm set for it.

INotifyPropertyChanged also uses delegates but, rather than a custom event, it simply notifies of changes to an object's properties.

Egg timer?
Spoiler

The GUI

The first form, frmClock:
Three TextBoxes: txtHH, txtMM, txtSS
Button: btnAlarms

frmAlarms:
DateTimePicker: dtpAlarmTime
TextBox: txtReason
ListBox: lstActivity
Two Buttons: btnSetAlarm, btnClose

I use three small TextBoxes to display the clock's Hour, Minute and Second as these are the essential properties of our Clock. I centred the numbers using the TextAlign property of the boxes. They do not, however, display a leading zero; if the Second is 2 it displays 2, not 02. I explored using MaskedTextBoxes but there isn't a simple property to achieve this. Adding the leading zeroes to the TextBoxes requires some work, tapping into one or more events. You wouldn't think such a simple thing would require so much effort! (It's almost trivial in WPF.)

The EventArgs

Create this simple class:
    public class ClockEventArgs : EventArgs {
        public int hour;
        public int minute;
        public int second;

        public ClockEventArgs(int hour, int minute, int second) {
            this.hour = hour;
            this.minute = minute;
            this.second = second;
        }
    }


The full code is listed at the end the tutorial.

This is not always essential as event will pass some default arguments, including the object-instance. To pass some meaningful values whenever the event occurs we create a class like this, based on EventArgs, and provide some values to a constructor, which will then be accessible as public fields (or properties) of our EventArgs.

This isn't even essential here because the event passes along the object reference (the Clock instance), and the Hour, Minute and Second properties can be read from this instance directly. I could have used the EventArgs to configure the AlarmKey, that you will see used later, as a property. This would have made the usefulness of defining custom EventArgs a little more obvious. Still, I hope it is clear what their purpose is.

The Clock Class
    public class Clock : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;
        private int _hour;
        private int _minute;
        private int _second;
        private Timer _timer;

        // the delegate subscribers must implement
        public delegate void ClockChangedHandler(object clock,
                             ClockEventArgs timeInformation);
        // an instance of the delegate
        public event ClockChangedHandler ClockChanged;

        //ctor
        public Clock() {
            DateTime dt = DateTime.Now;
            this.Hour = dt.Hour;
            this.Minute = dt.Minute;
            this.Second = dt.Second;

            _timer = new Timer();
            _timer.Interval = 1000;  // 1s
            _timer.Tick += timer_Tick;
            _timer.Start();
        }

        void timer_Tick(object sender, EventArgs e) {
            DateTime dt = DateTime.Now;
            this.Hour = dt.Hour;
            this.Minute = dt.Minute;
            this.Second = dt.Second;
            
            // if anyone has subscribed, notify them
            if (ClockChanged != null) {
                // create EventArgs object to pass to subscribers
                ClockEventArgs timeInfo = new ClockEventArgs(this._hour, this._minute, this._second);
                ClockChanged(this, timeInfo);
            }
        }

        public int Hour {
            get { return _hour; }
            set {
                if (_hour == value) return;
                _hour = value;
                RaisePropertyChanged("Hour");
            }
        }
        public int Minute {
            get { return _minute; }
            set {
                if (_minute == value) return;
                _minute = value;
                RaisePropertyChanged("Minute");
            }
        }
        public int Second {
            get { return _second; }
            set {
                if (_second == value) return;
                _second = value;
                RaisePropertyChanged("Second");
            }
        }

        private void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}


INotifyPropertyChanged requires this:
using System.ComponentModel;  // INotifyPropertyChanged


        public event PropertyChangedEventHandler PropertyChanged;


I am not discussing this event, or the INotifyPropertyChanged interface, here, as it is covered elsewhere. It is not the main focus of this example. If you haven't encountered this before, you should pursue the subject.

private Timer _timer; I am using a System.Windows.Forms.Timer, and its Tick event, to update the Clock. I would prefer to make the class independent of WinForms features and use a System.Threading.Timer instead. However, this is a little more intricate to set up. You might wish to pursue this afterwards, because it also uses delegates, and callbacks, and is worth studying and knowing how to use.

Here is the important bit for us:
        // the delegate subscribers must implement
        public delegate void ClockChangedHandler(object clock,
                             ClockEventArgs timeInformation);
        // an instance of the delegate
        public event ClockChangedHandler ClockChanged;


The first statement defines a delegate. A lot happens behind the scenes with this line. It actually creats a class definition.

  • A delegate is essentially a Class, a type, that is capable of maintaining a list of methods, and associated objects and references for each method.
  • The methods must conform to the specified method signature.

The methods for our delegate will return void, and each method will have two arguments: a Clock instance, and an instance of our ClockEventArgs.

We then create an instance of our delegate.

Our application will work with or without the inclusion of the word event in the above line. (Try this later.) event is a particular type of delegate. What this achieves is to prevent members of our delegate being directly accessed. That is, it will only be possible to subscribe, or unsubscribe, methods with the event, and to trigger (to publish) the event itself. People cannot use ClockChanged. to delve into the inner workings of our delegate. (Refer to the fourth link provided at the beginning of this tutorial for further explanation.)

The Clock constructor is fairly straight-forward:
        public Clock() {
            DateTime dt = DateTime.Now;
            this.Hour = dt.Hour;
            this.Minute = dt.Minute;
            this.Second = dt.Second;

            _timer = new Timer();
            _timer.Interval = 1000;  // 1s
            _timer.Tick += timer_Tick;
            _timer.Start();
        }


As well as storing the current time-values as properties, it initiates the Timer and every second its Tick event will be used to update the clock. Here's the Tick event:
        void timer_Tick(object sender, EventArgs e) {
            DateTime dt = DateTime.Now;
            this.Hour = dt.Hour;
            this.Minute = dt.Minute;
            this.Second = dt.Second;
            
            // if anyone has subscribed, notify them
            if (ClockChanged != null) {
                // create EventArgs object to pass to subscribers
                ClockEventArgs timeInfo = new ClockEventArgs(this._hour, this._minute, this._second);
                ClockChanged(this, timeInfo);
            }
        }


This updates the clock and then, essentially, fires (publishes) the ClockChanged event.

ClockChanged() looks like a standard method-call. It is.. but it is a little more involved than that. The two arguments are passed to the delegate which then goes through its method-invocation list. It tells all subscribers (in sequence) to execute the method that they registered.



This is the part that I struggle to describe, and it is why you need to continue studying (see the references at the top). I think it helps to consider that there are two delegates. There is the front-facing delegate (an event, in our case) which we named ClockChanged. Then there is a hidden, back-end, delegate, which does all the hard work! A MultiCast Delegate. It is this back-end worker that maintains the method-list and invokes these methods (on the subscribed objects).

The Clock Form
using System;
using System.ComponentModel;  // INotifyPropertyChanged
using System.Windows.Forms;

namespace AlarmClock {
    public partial class frmClock : Form {

        private frmAlarms _frmAlarms;
        private Clock _clock;

        public frmClock() {
            InitializeComponent();
        }
        private void frmClock_Load(object sender, EventArgs e) {
            _clock = new Clock();
            this.txtHH.DataBindings.Add("Text", _clock, "Hour");
            this.txtMM.DataBindings.Add("Text", _clock, "Minute");
            this.txtSS.DataBindings.Add("Text", _clock, "Second");
        }

        private void btnAlarms_Click(object sender, EventArgs e) {
            if (_frmAlarms == null) {
                _frmAlarms = new frmAlarms(_clock);
                _frmAlarms.FormClosed += (o, ea) => _frmAlarms = null;
            }
            _frmAlarms.Show();
        }
    }


        private void frmClock_Load(object sender, EventArgs e) {
            _clock = new Clock();
            this.txtHH.DataBindings.Add("Text", _clock, "Hour");
            this.txtMM.DataBindings.Add("Text", _clock, "Minute");
            this.txtSS.DataBindings.Add("Text", _clock, "Second");
        }


This binds the properties of the Clock instance to the Text of each TextBox. INotifyPropertyChanged will cause the UI to be updated when these values change (when the clock Ticks).
        private void btnAlarms_Click(object sender, EventArgs e) {
            if (_frmAlarms == null) {
                _frmAlarms = new frmAlarms(_clock);
                _frmAlarms.FormClosed += (o, ea) => _frmAlarms = null;
            }
            _frmAlarms.Show();
        }


The Clock instance is passed to the second form in its constructor: the two forms share the same Clock-instance.

I wanted there to only ever be one copy of the Alarms Form. If _frmAlarms is null then we instantiate a new form, otherwise we show the original. However, if we open the form and close it, this isn't sufficient to make its reference null (even though its Dispose() method has been executed). Therefore, we use its FormClosed event (and a lambda) to explicitly set its reference to null.

There are two ways of communicating between forms demonstrated here: passing an object in a constructor, and subscribing to an event of the form (FormClosed).

This code is not an example of the singleton pattern. In that case, once instantiated, an object will usually persist for the lifetime of the application. It is the same object. In our case the Alarms form can be closed, and a new instance started, but there won't be two instances running. You will see that, if you close the Alarms form, and open a new one, the previous Alarm details have vanished.

The Alarm Form
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace AlarmClock {
    public partial class frmAlarms : Form {
        private Clock _clock;
        private Dictionary<string, string> _alarms = new Dictionary<string, string>();

        // hide default ctor
        private frmAlarms() {
            InitializeComponent();
        }
        public frmAlarms(Clock clock) {
            InitializeComponent();
            _clock = clock;
            // subscribe to clock's ClockChanged event
            _clock.ClockChanged += ClockHasChanged;
        }


Marking the default constructor as private makes it unavailable, so the form can only be constructed by supplying a Clock instance. Alarms without a Clock doesn't make sense.

It should be considered to create an Alarm or Alarms class, rather than having all of this functionality directly in the Form's class. As the application stands it will be difficult to extract the Clock and Alarm features and use them in some other (non-WinForms) application.

_clock.ClockChanged += ClockHasChanged; This subscribes to the clock's ClockChanged event, with a method (shown shortly) that conforms to the method signature defined (required) by the delegate.
        private Dictionary<string, string> _alarms = new Dictionary<string, string>();


This will store the alarm-times and their Reasons, using the time as key. I don't like to use DateTime as a key (Dictionary<DateTime, string>); at least, not in its raw form. So I'll construct a string like this "221240" (hhmmss) and use it as the key. However, I found it convenient to include colons "22:12:40", making it easier to display later.
        public void ClockHasChanged(object clock, ClockEventArgs cea) {
            string timeKey = string.Format("{0:00}:{1:00}:{2:00}", cea.hour, cea.minute, cea.second);
            if (_alarms.ContainsKey(timeKey)) {
                lstActivity.Items.Add(string.Format("ALARM! ALARM! {0}", timeKey));
                lstActivity.Items.Add(string.Format("Reason: {0}", _alarms[timeKey]));
                _alarms.Remove(timeKey);
            }
        }


This method conforms to the method-signature required by the delegate.

Using the ClockEventArgs we can construct an Alarm key.

If the Dictionary contains this key then there is an alarm set for this time. Confirm that the alarm is activated by writing to the ListBox. (You could make a sound, play some music, or flash the form, if you like.) Remove the alarm from the Dictionary.
        private void btnSetAlarm_Click(object sender, EventArgs e) {
            // jumping through hoops to ignore the date
            DateTime getDate = DateTime.Today;
            DateTime clockTime = new DateTime(getDate.Year, getDate.Month, getDate.Day,
                _clock.Hour, _clock.Minute, _clock.Second);
            DateTime alarmTime = new DateTime(getDate.Year, getDate.Month, getDate.Day,
                dtpAlarmTime.Value.Hour, dtpAlarmTime.Value.Minute, dtpAlarmTime.Value.Second);

            if (alarmTime <= clockTime.AddMinutes(1)) {
                MessageBox.Show("Alarm must be 1 min ahead of Clock time.");
                return;
            }
            if (string.IsNullOrWhiteSpace(this.txtReason.Text)) {
                MessageBox.Show("You must give a reason.");
                return;
            }
            string key = alarmTime.ToString("HH:mm:ss");
            if (_alarms.ContainsKey(key)) {
                MessageBox.Show(string.Format("Alarm already set for {0:HH:mm:ss}", alarmTime));
                return;
            }
            _alarms[key] = txtReason.Text;
            lstActivity.Items.Add(string.Format("Alarm set for {0:HH:mm:ss}", alarmTime));
            lstActivity.Items.Add(string.Format("Reason: {0}", txtReason.Text));
        }


Looking at the MessageBoxes first, an alarm must be set for at least one minute beyond the current clock-time, a Reason for the alarm must be given and there cannot be two alarms set for the same time.

Before these though, we have to jump through some hoops. For this simple example I am not interested in the Date, only the Time. A DateTime includes both date and time. There is TimeSpan, and Today has a TimeOfDay property, but there is no Time class or structure.

There is default(DateTime) which goes back to the beginning of time! I decided to just use the current date (DateTime.Today) and ignore this date. It is a bit clumsy I know, but other approaches are just as clumsy.

Don't stay up late to try and set alarms, you'll have trouble at and around midnight!

string key = alarmTime.ToString("HH:mm:ss"); This constructs the key, and checks if this key already exists. If not, it is used to store the Reason in the Dictionary, and the ListBox is used to confirm the setting of the alarm.
        private void btnClose_Click(object sender, EventArgs e) {
            // unsubscribe
            _clock.ClockChanged -= ClockHasChanged;
            this.Close();
        }


Unsubscribe from the event and close the form. This is followed by the FormClosed event which is subscribed in the main form, and causes its _frmAlarms reference to be set to null.

EventArgs, delegate and event -> Very Simply

Creating a class for EventArgs is optional. It allows us to customize the information that will be passed when an event occurs.

A delegate (essentially, very roughly) defines the method-signature that a method must follow in order to respond to an event.
An instance of the defined delegate (or an event) is required.
Methods can subscribe (+=), or unsubscribe (-=), to the event, as long as their method-signature is correct.
When the event is published (fired) all of the subscribed methods will be executed, in sequence.

Concluding: A Reality Check

Would there ever be a need for such a Clock class in a real application? Possibly. There is already DateTime, TimeSpan, various Timers and a Stopwatch readily available and fully-featured, and a Clock class would require some effort to keep it functioning efficiently, and accurately. An example that occurs is when you sometimes see a wall of international clocks, each showing a different capital city's local-time. A specialised Clock class could be useful to maintain these.

I just like the example. There are a number of interesting aspects to it.



frmClock.cs FULL CODE

Spoiler

frmAlarms.cs FULL CODE

Spoiler

Attached File  AlarmClock.zip (15.52K)
Number of downloads: 343

This post has been edited by andrewsw: 07 November 2014 - 03:13 PM


Is This A Good Question/Topic? 2
  • +

Replies To: Example: Delegate, Event and Custom EventArgs

#2 tlhIn`toq  Icon User is offline

  • Xamarin Cert. Dev.
  • member icon

Reputation: 6507
  • View blog
  • Posts: 14,377
  • Joined: 02-June 10

Posted 07 November 2014 - 01:47 PM

Nice use of DataBindings in a WinForms project. If more people would do it that way (like they are supposed to) it wouldn't be such a stretch for them to move to binding in WPF.

Hmmm... Should each alarm have its own Clock object? Each Clock has its own timer... So that means each Clock and Alarm will be slightly out of sync with each other and over time lead to multiple clocks appearing to update out of sync with each other. 100 alarms = 10 timers. If you were to make a single instance of a Clock at application level, then inject it into the Alarm (maybe via the constructor) you could have 500 alarms but only 1 timer and 1 event for ClockChanged and all the GUI and responses would stay in sync; not to mention far less resource intensive.
Was This Post Helpful? 1
  • +
  • -

#3 andrewsw  Icon User is online

  • say what now
  • member icon

Reputation: 6408
  • View blog
  • Posts: 25,889
  • Joined: 12-December 12

Posted 07 November 2014 - 01:56 PM

Thank you :)

I'm slightly confused. Do I have more than 1 clock and timer currently?



Ahh, perhaps you are speculating on my suggestion to have multiple clocks?

I suppose there could be a single (singleton) grandfather clock, to keep the others in check. Central time ;)

But I suppose there should only be one clock because.. in recording the passage of time.. a second is a second.

This post has been edited by andrewsw: 07 November 2014 - 02:04 PM

Was This Post Helpful? 0
  • +
  • -

#4 tlhIn`toq  Icon User is offline

  • Xamarin Cert. Dev.
  • member icon

Reputation: 6507
  • View blog
  • Posts: 14,377
  • Joined: 02-June 10

Posted 07 November 2014 - 02:09 PM

Exactly... I'm just looking forward (speculating as you say) to when someone says "Hey that's great. I'm going to use it in my app with 500 alarms." and does what they all do: Just lift as much code as they can and drop it in their own solution without *thinking* about what they are doing.

Quote

But I suppose there should only be one clock because.. in recording the passage of time.. a second is a second.

Nod. I know its a pet peeve of mine and most people may not care... But I hate seeing a computer program with 10 clocks on it (picture a world clock type application) where each second-hand moves independently and not at the same time, out of sync.
Was This Post Helpful? 0
  • +
  • -

#5 andrewsw  Icon User is online

  • say what now
  • member icon

Reputation: 6408
  • View blog
  • Posts: 25,889
  • Joined: 12-December 12

Posted 07 November 2014 - 02:18 PM

To keep them in-sync there must be a single Tick event. Can multiple objects share a single event? I wonder, might this be possible with, maybe, an abstract class?

Mmm.. probably not. An event is published with a single object-instance.

This post has been edited by andrewsw: 07 November 2014 - 02:20 PM

Was This Post Helpful? 0
  • +
  • -

#6 tlhIn`toq  Icon User is offline

  • Xamarin Cert. Dev.
  • member icon

Reputation: 6507
  • View blog
  • Posts: 14,377
  • Joined: 02-June 10

Posted 07 November 2014 - 02:28 PM

Multiple objects can subscribe to the single event.
I do this one all the time. I always create a "HeartBeat" event at program level that is raised every 1 second.

From there a class can subscribe to it or not.
Main windows use it to display a real-time clock.
Throttling classes can use it to calculate data transfer rates.
Hardware integration class that have no choice but to poll use it to poll a device every one second (like weather station data).
Idle time outs...
Etc.
Etc.

This post has been edited by tlhIn`toq: 07 November 2014 - 02:31 PM

Was This Post Helpful? 1
  • +
  • -

#7 andrewsw  Icon User is online

  • say what now
  • member icon

Reputation: 6408
  • View blog
  • Posts: 25,889
  • Joined: 12-December 12

Posted 07 November 2014 - 02:42 PM

Interesting ;)
A HeartBeat event. I like it. :tt1:

This post has been edited by andrewsw: 07 November 2014 - 03:16 PM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1