Showing posts with label .net development. Show all posts
Showing posts with label .net development. Show all posts

Monday, October 21, 2013

Managing Data Among Multiple Forms (Part 3)

Part 1 here
Part 2 here

Firstly, let me apologise for having taken so long to finish this three-part series.  Parts 1 and 2 showed how you CAN manage data among multiple forms but this part 3 will show how you SHOULD do it.  That’s rather important I’d say, so let’s get into it.

The first and most important point to note is that forms are objects like any other, so moving data between forms is done just like it is for any other objects.  How do you usually pass data into an object?  You either set a property or else call a method and pass an argument.  How do you usually get data out of an object?  You either get a property or else call a method and get the return value.  That’s exactly how you pass data into and get data out of a form because forms are objects.

The second point to note is that, generally speaking, a control should only be accessed by the form it is on.  While it’s legal to access a control from outside its form, good practice dictates that you should not do so.  With the first point in mind, that means that getting data from a control on a different form means getting data from the other form and the other form getting it from the control.  Likewise, passing data to a control on another form means passing data to the other form and the other form passing it to the control.

This can be demonstrated fairly easily by displaying a list of records in one form and editing the selected record in another form.  To build such an example, start by creating a new Windows Forms Application project.  To Form1, add a DataGridView, a Button and a BindingSource.  Now add the following code to populate the grid with a few records at startup.

C#

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     var table = new DataTable();
  4.  
  5.     table.Columns.Add("ID", typeof(int));
  6.     table.Columns.Add("Name", typeof(string));
  7.  
  8.     table.Rows.Add(1, "Peter");
  9.     table.Rows.Add(2, "Paul");
  10.     table.Rows.Add(3, "Mary");
  11.  
  12.     this.bindingSource1.DataSource = table;
  13.     this.dataGridView1.DataSource = this.bindingSource1;
  14. }

VB

  1. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  2.     Dim table As New DataTable
  3.  
  4.     With table.Columns
  5.         .Add("ID", GetType(Integer))
  6.         .Add("Name", GetType(String))
  7.     End With
  8.  
  9.     With table.Rows
  10.         .Add(1, "Peter")
  11.         .Add(2, "Paul")
  12.         .Add(3, "Mary")
  13.     End With
  14.  
  15.     Me.BindingSource1.DataSource = table
  16.     Me.DataGridView1.DataSource = Me.BindingSource1
  17. End Sub

Add a second form to the project and add a TextBox and a Button to that form.  What we’re going to do is click the Button in Form1 to open Form2, get the record selected in the DataGridView in Form1 and edit its Name field in the TextBox in Form2.  Now remember, Form2 cannot access the DataGridView in Form1 and Form1 cannot access the TextBox in Form2.  How to move the data back and forth?  The answer is that Form1 will set a property of Form2 to pass the initial data in and get the same property to get the final data out while, internally, that property of Form2 will access the TextBox.  Which property?  Well, one that you define yourself.

C#

  1. public string TextBoxText
  2. {
  3.     get
  4.     {
  5.         return this.textBox1.Text;
  6.     }
  7.     set
  8.     {
  9.         this.textBox1.Text = value;
  10.     }
  11. }

VB

  1. Public Property TextBoxText As String
  2.     Get
  3.         Return Me.TextBox1.Text
  4.     End Get
  5.     Set(value As String)
  6.         Me.TextBox1.Text = value
  7.     End Set
  8. End Property

Back in Form1, we need to handle the Click event of the Button, open Form2 and pass it the Name from the record selected in the DataGridView.

C#

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     using (var dialogue = new Form2())
  4.     {
  5.         var selectedRecord = (DataRowView)this.bindingSource1.Current;
  6.  
  7.         // Pass the initial data into the dialogue.
  8.         dialogue.TextBoxText = (string)selectedRecord["Name"];
  9.  
  10.         // Display the modal dialogue.
  11.         if (dialogue.ShowDialog() == DialogResult.OK)
  12.         {
  13.             // The user clicked OK so get the final data from the dialogue.
  14.             selectedRecord["Name"] = dialogue.TextBoxText;
  15.             this.bindingSource1.EndEdit();
  16.         }
  17.     }
  18. }

VB

  1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  2.     Using dialogue As New Form2
  3.         Dim selectedRecord = DirectCast(Me.BindingSource1.Current, DataRowView)
  4.  
  5.         'Pass the initial data into the dialogue.
  6.         dialogue.TextBoxText = CStr(selectedRecord("Name"))
  7.  
  8.         'Display the modal dialogue.
  9.         If dialogue.ShowDialog() = DialogResult.OK Then
  10.             'The user clicked OK so get the final data from the dialogue.
  11.             selectedRecord("Name") = dialogue.TextBoxText
  12.             Me.BindingSource1.EndEdit()
  13.         End If
  14.     End Using
  15. End Sub

All that’s left to do now is for Form2 to return a result of OK when the user clicks the Button.

C#

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3.     this.DialogResult = DialogResult.OK;
  4. }

VB

  1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  2.     Me.DialogResult = DialogResult.OK
  3. End Sub

Run the project now and Form1 will appear displaying the three records.  Select one of the records and click the Button.  You will see Form2 open with the Name field value from the selected record in the TextBox.  Try editing the name and then click the Close button on the title bar of Form2.  The dialogue will close and you’ll see that the Name field of the selected record remains unchanged.  That’s because the DialogResult returned by ShowDialog was Cancel rather than OK.

Click the Button on Form1 again and this time, after editing the name in the TextBox, click the Button on Form2.  This time, you’ll see that Form2 closes and the Name field of the selected record is updated to the value that you entered in the TextBox.  Congratulations!  You just passed data between two forms the right way.

That’s nice and all but what if, in our example, you wanted to do something back in Form1 without closing Form2?  As it stands, ShowDialog returning is Form1’s notification that it should get some data from Form2 and update its own DataGridView.  If we don’t call ShowDialog though, it can’t return and we can’t use it as a notification.  What to do?  Well, how are you usually notified that something has happened in .NET code?  You handle an event.

If you want to learn all the details about custom events, I suggest that you check out my blog post here.  I’m going to do it quick and dirty here because the point of this post is how to handle the event and use that notification rather than the details of how to generate it in the first place.

In Form2, we need to add an event that will notify anyone listening that the text in the TextBox has changed and, instead of closing the form when the Button is clicked, we need to raise that event.

C#

  1. public event EventHandler TextBoxTextChanged;
  2.  
  3. private void button1_Click(object sender, EventArgs e)
  4. {
  5.     if (this.TextBoxTextChanged != null)
  6.     {
  7.         this.TextBoxTextChanged(this, EventArgs.Empty);
  8.     }
  9. }

VB

  1. Public Event TextBoxTextChanged As EventHandler
  2.  
  3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  4.     RaiseEvent TextBoxTextChanged(Me, EventArgs.Empty)
  5. End Sub

We now need to handle that event in Form1.  When it’s raised, we need to do as before and get the data from Form2 to update the selected record.

C#

  1. private Form2 dialogue;
  2. private DataRowView selectedRecord;
  3.  
  4. private void button1_Click(object sender, EventArgs e)
  5. {
  6.     if (this.dialogue == null || this.dialogue.IsDisposed)
  7.     {
  8.         this.selectedRecord = (DataRowView)this.bindingSource1.Current;
  9.  
  10.         this.dialogue = new Form2();
  11.         this.dialogue.TextBoxTextChanged += dialogue_TextBoxTextChanged;
  12.         this.dialogue.FormClosed += dialogue_FormClosed;
  13.  
  14.         // Pass the initial data into the dialogue.
  15.         this.dialogue.TextBoxText = (string)this.selectedRecord["Name"];
  16.  
  17.         this.dialogue.Show();
  18.     }
  19.  
  20.     this.dialogue.Activate();
  21. }
  22.  
  23. private void dialogue_TextBoxTextChanged(object sender, EventArgs e)
  24. {
  25.     // Get the final data from the dialogue.
  26.     this.selectedRecord["Name"] = dialogue.TextBoxText;
  27.     this.bindingSource1.EndEdit();
  28. }
  29.  
  30. private void dialogue_FormClosed(object sender, FormClosedEventArgs e)
  31. {
  32.     // Remove event handlers when the form closes.
  33.     this.dialogue.TextBoxTextChanged -= dialogue_TextBoxTextChanged;
  34.     this.dialogue.FormClosed -= dialogue_FormClosed;
  35. }

VB

  1. Private WithEvents dialogue As Form2
  2. Private selectedRecord As DataRowView
  3.  
  4. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  5.     If Me.dialogue Is Nothing OrElse Me.dialogue.IsDisposed Then
  6.         Me.selectedRecord = DirectCast(Me.BindingSource1.Current, DataRowView)
  7.  
  8.         Me.dialogue = New Form2()
  9.  
  10.         'Pass the initial data into the dialogue.
  11.         Me.dialogue.TextBoxText = CStr(selectedRecord("Name"))
  12.  
  13.         Me.dialogue.Show()
  14.     End If
  15.  
  16.     Me.dialogue.Activate()
  17. End Sub
  18.  
  19. Private Sub dialogue_TextBoxTextChanged(sender As Object, e As EventArgs) Handles dialogue.TextBoxTextChanged
  20.     'Get the final data from the dialogue.
  21.     Me.selectedRecord("Name") = dialogue.TextBoxText
  22.     Me.BindingSource1.EndEdit()
  23. End Sub

If you run the project again you will see that you can open the dialogue and edit the selected record multiple times without closing the dialogue.  If you close the dialogue and select another record then you can open a new dialogue and edit that as well.

This is a slightly contrived example but hopefully you get the idea.  If you want to update a control in a form then only do it in that form.  If you need to push and/or pull data between forms then you do so by getting or setting properties and/or calling methods of that form.  If you need to notify a form that data is available to get then you do so with an event.

Happy trails!

Post compiled using Windows Live Writer with Paste as Visual Studio Code plug-in.

Tuesday, April 3, 2012

Managing Data Among Multiple Forms (Part 2)

Part 1 here

In part 1 of this series, I looked at using default instances in VB and how that gives you direct access to controls on any form, anywhere, any time.  I also showed how you could simulate default instances in C#.  Default instances are a handy feature for beginners but I wouldn’t recommend their use to anyone who’s serious about programming.  They’re an easy way to start out though.

This instalment will describe the use of what’s commonly as global variables.  This is another technique that makes life easier for beginners but should generally be avoided because it’s not really architecturally sound.  I’ll cover it here though for completeness and because it is also an easy way to start out, when most of what you do will not be architecturally sound.

As the name suggests, global variables are intended to give global access, i.e. you can access them anywhere, any time.  Just like default form instances, the idea is that you don’t have to create an object explicitly in order to access them, which means that you’re not limited to accessing that object only where it’s created.  There are various ways that that can be achieved, but the simplest is to use a module in VB or the equivalent in C#, which is a static class.

We’ll use basically the same example as last time, transferring text from a TextBox on one form to a TextBox on another form and then back again.  This time though, instead of one form passing data to and retrieving data from the other form, each will deal only with the global variable.

So, first things first, let’s declare that global variable.  In VB, add a new module to your project and add the following code:

Module Module1

    'The text that will be transferred between TextBoxes.
    Public textBoxText As String

End Module
In C#, add a new class to your project and add the following code:

static class Class1
{
    // The text that will be transferred between TextBoxes.
    public static string textBoxText;
}
Notice the addition of the ‘static’ keyword to both the class and the field.  A static member is one that is a member of the class itself rather than each specific instance and a static class is one that can only have static members.  The class itself doesn’t actually have to be static but it’s the correct thing to do.
Set up the user interface as before, with a TextBox and a Button on Form1 and a TextBox on Form2.  Create a Click event handler for the Button on Form1 and add the following code:

VB
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    'Push the data to the global variable from the TextBox on this form.
    textBoxText = Me.TextBox1.Text

    'Show the second form as a modal dialogue.
    Using dialogue As New Form2
        dialogue.ShowDialog()
    End Using

    'Pull the data from the global variable to the TextBox on this form.
    Me.TextBox1.Text = textBoxText
End Sub
C#
private void button1_Click(object sender, EventArgs e)
{
    // Push the data to the global variable from the TextBox on this form.
    Class1.textBoxText = this.textBox1.Text;

    // Show the second form as a modal dialogue.
    using (Form2 dialogue = new Form2())
    {
        dialogue.ShowDialog();
    }

    // Pull the data from the global variable to the TextBox on this form.
    this.textBox1.Text = Class1.textBoxText;
}
Note that in both VB and C# I have included a ‘Using’/’using’ statement.  This is to create the dialogue and destroy it again after it’s been used.  I’m not using a default instance in the VB code to show that this technique of using global variables doesn’t depend on them.  Also note that the global variable is qualified with the class name in the C# code but it is not qualified with the module name in the VB code.  You can qualify module members in VB if you want to but the module name can be omitted, which is behaviour consistent with modules in VB6.

Now, create handlers for the Load and FormClosed events in Form2 and add this code:

VB
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    'Pull the data from the global variable to the TextBox on the this form.
    Me.TextBox1.Text = textBoxText
End Sub

Private Sub Form2_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
    'Push the data to the global variable from the TextBox on this form.
    textBoxText = Me.TextBox1.Text
End Sub

C#
private void Form2_Load(object sender, EventArgs e)
{
    // Pull the data from the global variable to the TextBox on the this form.
    this.textBox1.Text = Class1.textBoxText;
}

private void Form2_FormClosed(object sender, FormClosedEventArgs e)
{
    // Push the data to the global variable from the TextBox on this form.
    Class1.textBoxText = this.textBox1.Text;
}
That’s all we need.  You can now run the project and do as for the previous example.  Enter some text in the TextBox on Form1 and then click the Button and you’ll see that text appear in the TextBox on Form2.  Edit that text and close the form and you’ll see the new text appear in the TextBox on the first form.

Looking back at the code, you can see that neither form refers directly to the TextBox on other.  In fact, there’s no reference to Form1 at all in Form2.  Both forms use the global variable as an intermediary, with each form having no specific dependency on the other.

That’s it for this instalment on global variables.  In the next instalment, I’ll start looking at how to do things the “proper” way.

Part 3 here

Sunday, April 1, 2012

Managing Data Among Multiple Forms (Part 1)

Lots of people ask questions about how to pass data between forms.  It’s a slightly tricky question to answer because there are several ways to do it, all variations on a theme, and the “proper” way is the most complex.  “Complex” is a relative term though, as none of them are particularly difficult.  I’m going to dedicate a separate post to each of the various options, using the same basic example in each case.  That example will involve two forms, each with a TextBox.  The user will enter text into the TextBox on the first form and click a Button.  That will open the second form and transfer the text to be displayed in the TextBox on that.  The user can then edit the text on the second form and, when they close it, the new text will be transferred back to the TextBox on the first form.

The first example is VB-specific, but can be simulated in C#.  I have previously posted about default instances here, so for more general information you should start there.  Here we will concentrate on actually passing data between two default instances.  So, first let’s set up the project as described above.  Create a new VB Windows Forms Application project and add a TextBox and a Button to the Form1 that’s created by default.  Add a second form and add a TextBox to that as well.

There are two options for passing the data around: push and pull.  The data producer can push the data to the consumer or the consumer can pull it from the producer.  We’ll construct this example that uses both in two different combinations.  First, let’s make Form1 push the initial data to Form2 and then pull the new data back again.

So, on Form1, double-click the Button you added and add the following code:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    'Push the data to the TextBox on the second form from the TextBox on this form.
    Form2.TextBox1.Text = Me.TextBox1.Text

    'Show the second form as a modal dialogue.
    Form2.ShowDialog()

    'Pull the data from the TextBox on the second form to the TextBox on this form.
    Me.TextBox1.Text = Form2.TextBox1.Text
End Sub

The first line gets the text from the TextBox on the current form and displays it in the TextBox on the default instance of Form2.  The second line displays the default instance of Form2 as a modal dialogue.  The third line gets the text from the TextBox on the default instance of Form2 and displays it in the Textbox on the current form.  Now, run the project, enter some text into the TextBox on Form1 and click the Button.  You’ll see that whatever you entered into the TextBox on Form1 displayed in the TextBox on Form2.  Edit the text in the TextBox and close Form2 and you’ll then see the new text displayed in the TextBox on Form1.

In that example, Form1 does all the work, pushing data one way and pulling it the other.  Let’s change things up a bit and let Form2 do the work.  Change your Button’s Click event handler to this:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    'Show the second form as a modal dialogue.
    Form2.ShowDialog()
End Sub

Form1 is still displaying Form2 but it is not moving any of the data around anymore.  Double-click Form2 to create a Load event handler and add the following code:

Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    'Pull the data from the TextBox on the first form to the TextBox on the this form.
    Me.TextBox1.Text = Form1.TextBox1.Text
End Sub

That will get the data from the TextBox on the default instance of Form1 and display it in the TextBox on the current form just before Form2 is displayed.  It’s worth mentioning at this point that the startup form in a VB Windows Forms Application project is always the default instance of its type.  Using the drop-down lists at the top of the code window, create a FormClosed event handler and add this code:

Private Sub Form2_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
    'Push the data to the TextBox on the first form from the TextBox on this form.
    Form1.TextBox1.Text = Me.TextBox1.Text
End Sub

That will get the data from the TextBox on the current form and display it in the TextBox on the default instance of Form1 just after Form2 closes.  Now, as before, run the project, enter text in the TextBox on Form1, click the Button, edit the text in the TextBox on Form2 and then close Form2.  As before, you’ll see whatever you entered on Form1 transferred to Form2 and whatever you change it to on Form2 transferred back to Form1.

That’s basically it.  You can access default instances anywhere in your project so you can always access any and all public members of that instance whenever you want.  In VB, when you add a control or component to a form in the designer they will be public by default, so you can access those controls directly from other forms whenever you want.  I don’t really recommend using default instances or public controls, but it’s the easy option for beginners.

Now, as I said earlier, default instances are a VB-specific feature but, if you want, you can simulate them in C#.  Given that default instances are aimed at beginners and this C# implementation requires use of a custom generic class, it defeats the purpose somewhat, but it’s an interesting case study nonetheless.

Here is a relatively simple class that will partially simulate default instances in C#:

///
/// Provides VB-style default instance behaviour for forms.
///
///
/// The type of form for which a default instance is provided.
///
public class DefaultInstance where TForm : Form, new()
{
    ///
    /// Refers to the current default instance.
    ///
    private static TForm _instance;

    ///
    /// Gets the default instance of the form type.
    ///
    public static TForm Instance
    {
        get
        {
            // Check whether there is no current default instance or that instance has been displayed.
            if (_instance == null || _instance.IsDisposed)
            {
                // Create a new default instance.
                _instance = new TForm();
            }

            return _instance;
        }
    }
}
Just like default instances in VB, this class requires that the form class have a parameterless constructor.  Unlike the default instances in VB, the instance maintained by this class is not thread-specific.  It could be reimplemented to provide that behaviour but, to be honest, I’m not sure that that would be an improvement.  As I said in the post on default instances that I linked to earlier, the fact that they are thread-specific is actually an encumbrance at times.  Presumably there is a reason that Microsoft chose to implement them that way though, so maybe there are other issues that I’m not aware of.

Anyway, what I’ve presented there is enough for our purposes here.  Where you would normally just use the form class name in VB code to refer to the default instance, e.g.

SomeFormClass.Show()

you would use the DefaultInstance class in C# code like this:

DefaultInstance<SomeFormClass>.Instance.Show();

A little more verbose but not a big deal.

So, create a new C# Windows Forms Application project and add the same forms and controls as specified previously.  Again, double-click the Button on Form1 to create a Click event handler and add the following code:

private void button1_Click(object sender, EventArgs e)
{
    // Push the data to the TextBox on the second form from the TextBox on this form.
    DefaultInstance<Form2>.Instance.textBox1.Text = this.textBox1.Text;

    // Show the second form as a modal dialogue.
    DefaultInstance<Form2>.Instance.ShowDialog();

    // Pull the data from the TextBox on the second form to the TextBox on this form.
    this.textBox1.Text = DefaultInstance<Form2>.Instance.textBox1.Text;
}

Run the project as before and you’ll see the text transferred from Form1 to Form2 and back again.
That works when Form1 does all the work but there is an extra step required when Form2 is doing the work.  As I said earlier, the startup form is always the default instance of its type in VB applications.  That’s what allowed us to refer to the default instance of Form1.  In our C# project, we need to edit the Main method so that it uses our DefaultInstance class to create the startup form.  That means that the Main method, found in the Program.cs code file, must look like this:

///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(DefaultInstance<Form1>.Instance);
}

Because the startup form is now the default instance of its type, Form2 can access it directly via the DefaultInstance class.  Note that another difference between VB and C# is that controls and components added to a form in the designer are private by default.  That really is the proper way to go but then we’re using default instances here so we’re not doing things the proper way.  With that in mind, you’ll need to change the access modifier of the controls on the two forms from Private to Public.

Change the Button.Click event handler in Form1 to this:

private void button1_Click(object sender, EventArgs e)
{
    // Show the second form as a modal dialogue.
    DefaultInstance<Form2>.Instance.ShowDialog();
}

and add a Load event handler and a FormClosed event handler to Form2 like this:

private void Form2_Load(object sender, EventArgs e)
{
    // Pull the data from the TextBox on the first form to the TextBox on the this form.
    this.textBox1.Text = DefaultInstance<Form1>.Instance.textBox1.Text;
}

private void Form2_FormClosed(object sender, FormClosedEventArgs e)
{
    // Push the data to the TextBox on the first form from the TextBox on this form.
    DefaultInstance<Form1>.Instance.textBox1.Text = this.textBox1.Text;
}

Run the project as before and again see that the data is transferred from Form1 to Form2 and back again.

That’s really all there is to it.  If you use default instances every time you display a form then you can use that default instance to access the form from anywhere and at any time in your application.  You can access any public members of that form so, if your controls are public, you can manipulate them directly from other forms.

The techniques shown here make transferring data between forms easy for those who don’t have a lot of experience.  In the second part of this series, I’ll demonstrate another option that makes it easy for beginners but is not conducive to writing applications properly.  That technique is the use of so-called global variables.

Part 2 here
Part 3 here

Sunday, November 1, 2009

Defining and Raising Custom Events

We’ve all handled events before, e.g. the Load of a Form, the Click of a Button or the TextChanged of a TextBox. Many, even relatively experienced, developers aren’t too sure on how to raise events from their own classes though.

The .NET Framework provides a fairly simple mechanism for events but, more than that, a convention is used throughout the Framework for employing that mechanism. While you don’t have to, it’s a good idea to stick to that convention in your own code. Doing so means that your interface will be consistent with the rest of the Framework, and consistency is a good thing. Using your types will feel familiar to yourself and others because they will behave just like the types you’re used to using from the Framework.

First up, let’s define exactly what an event is. In conceptual terms, an event is a notification that something has happened. Just like your microwave oven makes a “beep” or “ding” sound to notify you that it has finished cooking your food, so a .NET object raises an event to notify any other objects that are listening that it has done something or something has been done to it.

Technically, an event is a member of a type, just like properties and methods, except its type is a delegate rather than a class or structure. A delegate, or at least an instance of a delegate, is an object that contains a reference to a method. In the case of an event, the method the delegate refers to is the event handler. As an example, the Button class has a Click event defined as type EventHandler. The EventHandler delegate is defined like so:

C#

public delegate void EventHandler(object sender, EventArgs e);

VB

Public Delegate Sub EventHandler(ByVal sender As Object, ByVal e As EventArgs)

That method signature should look familiar because it looks a lot like the signature of an event handler method, e.g.

C#

private void button1_Click(object sender, EventArgs e)
{
    // ...
}

VB

Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    '...
End Sub

When you create a handler for the Click event of a Button in your form, what you’re actually doing is creating an instance of the EventHandler delegate, providing it a reference to your method and then assigning it to the Click member of the Button. When the Button is clicked, it goes to its Click event and invokes the delegate it finds there, which in turn invokes your method.

This post isn’t about delegates though, so if you want more information on their inner workings you should look for that elsewhere. This post is about defining and raising custom events, so let’s get on with that. The first thing we need is a class that will raise an event.

C#

public class Person
{
    private string _firstName;
    private string _lastName;
 
    public string FirstName
    {
        get
        {
            return this._firstName;
        }
        set
        {
            this._firstName = value;
        }
    }
 
    public string LastName
    {
        get
        {
            return this._lastName;
        }
        set
        {
            this._lastName = value;
        }
    }
}

VB

Public Class Person
 
    Private _firstName As String
    Private _lastName As String
 
    Public Property FirstName() As String
        Get
            Return Me._firstName
        End Get
        Set(ByVal value As String)
            Me._firstName = value
        End Set
    End Property
 
    Public Property LastName() As String
        Get
            Return Me._lastName
        End Get
        Set(ByVal value As String)
            Me._lastName = value
        End Set
    End Property
 
End Class

So, we have a class with two properties, which we can get and set. It might be convenient for us to receive a notification from an instance of this class when those property values change. For instance, if you are displaying the person’s name in some TextBoxes and the name gets changed in code, you’d want to know about it so that you could update the TextBoxes, right? For that we need an event to be raised when the property value changes.

The first thing to do is to declare the events as members in the Person class. Considering that these events are notifications of the FirstName and LastName property values being changed, it’s most appropriate that they be named “FirstNameChanged” and “LastNameChanged”, which is convention throughout the Framework.

C#

public event EventHandler FirstNameChanged;
public event EventHandler LastNameChanged;

VB

Public Event FirstNameChanged As EventHandler
Public Event LastNameChanged As EventHandler

Once the events are declared, you’ll find that the Person class now has events you can handle. That doesn’t do us any good if the events are never raised though, so let’s add some basic code to raise those events when the corresponding property values change.

C#

public string FirstName
{
    get
    {
        return this._firstName;
    }
    set
    {
        this._firstName = value;
 
        if (this.FirstNameChanged != null)
        {
            this.FirstNameChanged(this, EventArgs.Empty);
        }
    }
}
 
public string LastName
{
    get
    {
        return this._lastName;
    }
    set
    {
        this._lastName = value;
 
        if (this.LastNameChanged != null)
        {
            this.LastNameChanged(this, EventArgs.Empty);
        }
    }
}

VB

Public Property FirstName() As String
    Get
        Return Me._firstName
    End Get
    Set(ByVal value As String)
        Me._firstName = value
 
        RaiseEvent FirstNameChanged(Me, EventArgs.Empty)
    End Set
End Property
 
Public Property LastName() As String
    Get
        Return Me._lastName
    End Get
    Set(ByVal value As String)
        Me._lastName = value
 
        RaiseEvent LastNameChanged(Me, EventArgs.Empty)
    End Set
End Property

Note that, when the event is raised, the object passes itself as the first argument. The first argument is passed to the sender parameter, so the object is identifying itself as the sender, i.e. the object that raised the event.

The second argument is EventArgs.Empty, which is part of the standard pattern. You should be fairly used to your event handlers having a sender parameter of type Object and an e parameter of type EventArgs or something like it. The second parameter is the event’s data. The EventArgs class acts as a place-holder for events that don’t have any data and as a base class for the types used by events that do have data. Rather than create a new EventArgs object each time, we use the static/Shared Empty field, which returns an empty EventArgs object.

Now, the code we have will do the job but we can make it better. First of all, the event will be raised every time the corresponding property is set, whether the value actually changes or not. We should actually test the new value and only raise the event if it’s different to the old value.

C#

public string FirstName
{
    get
    {
        return this._firstName;
    }
    set
    {
        if (value != this._firstName)
        {
            this._firstName = value;
 
            if (this.FirstNameChanged != null)
            {
                this.FirstNameChanged(this, EventArgs.Empty);
            }
        }
    }
}
 
public string LastName
{
    get
    {
        return this._lastName;
    }
    set
    {
        if (value != this._lastName)
        {
            this._lastName = value;
 
            if (this.LastNameChanged != null)
            {
                this.LastNameChanged(this, EventArgs.Empty);
            }
        }
    }
}

VB

Public Property FirstName() As String
    Get
        Return Me._firstName
    End Get
    Set(ByVal value As String)
        If value <> Me._firstName Then
            Me._firstName = value
 
            RaiseEvent FirstNameChanged(Me, EventArgs.Empty)
        End If
    End Set
End Property
 
Public Property LastName() As String
    Get
        Return Me._lastName
    End Get
    Set(ByVal value As String)
        If value <> Me._lastName Then
            Me._lastName = value
 
            RaiseEvent LastNameChanged(Me, EventArgs.Empty)
        End If
    End Set
End Property

The next step is to implement the common pattern that is used throughout the Framework for raising events. That involves declaring a method whose purpose in life it is to raise the event.

C#

protected virtual void OnFirstNameChanged(EventArgs e)
{
    if (this.FirstNameChanged != null)
    {
        this.FirstNameChanged(this, e);
    }
}
 
protected virtual void OnLastNameChanged(EventArgs e)
{
    if (this.LastNameChanged != null)
    {
        this.LastNameChanged(this, e);
    }
}

VB

Protected Overridable Sub OnFirstNameChanged(ByVal e As EventArgs)
    RaiseEvent FirstNameChanged(Me, e)
End Sub
 
Protected Overridable Sub OnLastNameChanged(ByVal e As EventArgs)
    RaiseEvent LastNameChanged(Me, e)
End Sub

Such a method is used basically so that the event is only ever raised in one place. If you ever want to raise the event you simply call this method.

C#

public string FirstName
{
    get
    {
        return this._firstName;
    }
    set
    {
        if (value != this._firstName)
        {
            this._firstName = value;
            this.OnFirstNameChanged(EventArgs.Empty);
        }
    }
}
 
public string LastName
{
    get
    {
        return this._lastName;
    }
    set
    {
        if (value != this._lastName)
        {
            this._lastName = value;
            this.OnLastNameChanged(EventArgs.Empty);
        }
    }
}

VB

Public Property FirstName() As String
    Get
        Return Me._firstName
    End Get
    Set(ByVal value As String)
        If value <> Me._firstName Then
            Me._firstName = value
            Me.OnFirstNameChanged(EventArgs.Empty)
        End If
    End Set
End Property
 
Public Property LastName() As String
    Get
        Return Me._lastName
    End Get
    Set(ByVal value As String)
        If value <> Me._lastName Then
            Me._lastName = value
            Me.OnLastNameChanged(EventArgs.Empty)
        End If
    End Set
End Property

The primary advantage of this is linked to the way the method is declared. Notice that the methods above are declared protected/Protected and virtual/Overridable. This means that any derived classes can override these methods and change the type’s behaviour when the events are raised. The derived class simply calls the base implementation to raise the event, so code can be added before that call or after it to add new behaviour. This new behaviour will be invoked even if the method is called from the base class, such is the behaviour of overridden members. If the event was raised directly in the property setters of the base class then derived classes wouldn’t be able to add new behaviour because the properties are not declared virtual/Overridable.

It’s also worth noting that another part of the pattern is naming the method that raises an event the same as the event it raises, but with the “On” prefix added. Note that in .NET there is no OnLoad or OnClick event. The events are named Load and Click and the methods that raise them are name OnLoad and OnClick.

So, we now have a full, working implementation that follows the standard .NET pattern. We’ve declared the events, declared methods to raise them and then called those methods when the event notification is required. But wait; there’s more!

I said earlier that events may or may not have data associated with them. In the case of our FirstNameChanged and LastNameChanged events there is no data. Listeners are simply being notified that a property value has changed. If they want to know the new value they can simply get the property. As such an EventArgs object is used as a place-holder for the event handlers. Now let’s consider a situation where the event handlers will require some information that they cannot otherwise access themselves.

In that situation the object raising the event needs to pass that data to the event handler. It does this through the e parameter. In such cases the e parameter cannot be type EventArgs because the EventArgs class has no members that can store such data. As a result, we need to use a class that inherits EventArgs and then adds the required members. The Framework already contains numerous such class, e.g. MouseEventArgs and PaintEventArgs. You should use one of those existing classes if it’s appropriate to your event, otherwise you should define your own class.

For this example, let’s consider the situation where we want to notify listeners that the property value is going to change before it happens, in addition to notifying them that the property value has changed after it happens. If the property value hasn’t actually changed yet then there’s no way an event handler can get the new value, unless that data is passed to the event handler explicitly. For this we’ll define our own derived EventArgs class. We could define one for each event but they are both notifying about a String property changing so we can use the same class for both.

C#

public class StringPropertyChangingEventArgs : EventArgs
{
    private readonly string _proposedValue;
 
    public string ProposedValue
    {
        get
        {
            return this._proposedValue;
        }
    }
 
    public StringPropertyChangingEventArgs(string proposedValue)
    {
        this._proposedValue = proposedValue;
    }
}

VB

Public Class StringPropertyChangingEventArgs
    Inherits EventArgs
 
    Private ReadOnly _proposedValue As String
 
    Public ReadOnly Property ProposedValue() As String
        Get
            Return Me._proposedValue
        End Get
    End Property
 
    Public Sub New(ByVal proposedValue As String)
        Me._proposedValue = proposedValue
    End Sub
 
End Class

Note that the StringPropertyChangingEventArgs class inherits the EventArgs class and then adds a property for the new data we need: the proposed value of the property. There’s no need to include the current value of the property because the event handler can get that itself.

There are two more points to note here. The name of the class ends with “EventArgs”, which is part of the convention. Another is using the term “Changing” for an event related to a proposed change to a property value. This goes along with the convention of using “Changed” for an event related to a property that has changed already. For the events you would normally prefix the “Changing” with the name of the property. We can’t do that for this class though, because it’s to be used by more than one property/event.

Now that we have a type to pass the event data to the event handler, the next thing we need is an event. In the case of the FirstNameChanged and LastNameChanged events, we declared them as type EventHandler. That’s not possible for our FirstNameChanging and LastNameChanging events though because they will require a StringPropertyChangingEventArgs parameter rather than an EventArgs parameter, so their signatures will not match that of the EventHandler delegate. We need a different delegate. For this we have two choices. Firstly, we could define our own delegate with a signature that matches that of our event handlers. This is good practice if you’re exposing an event outside the current assembly. We’ll look at that option later but, if the event is only going to be used within the current project, it’s considered good practice to use the generic EventHandler(TEventArgs) delegate.

C#

public event EventHandler<StringPropertyChangingEventArgs> FirstNameChanging;
public event EventHandler<StringPropertyChangingEventArgs> LastNameChanging;

VB

Public Event FirstNameChanging As EventHandler(Of StringPropertyChangingEventArgs)
Public Event LastNameChanging As EventHandler(Of StringPropertyChangingEventArgs)

In this case the generic type of the delegate specifies the type of the second parameter of the event handler. Note that this type must be EventArgs or derived from EventArgs.

The next step is to declare methods to raise the events. They will be much as were those for the other events but with a different parameter type.

C#

protected virtual void OnFirstNameChanging(StringPropertyChangingEventArgs e)
{
    if (this.FirstNameChanging != null)
    {
        this.FirstNameChanging(this, e);
    }
}
 
protected virtual void OnLastNameChanging(StringPropertyChangingEventArgs e)
{
    if (this.LastNameChanging != null)
    {
        this.LastNameChanging(this, e);
    }
}

VB

Protected Overridable Sub OnFirstNameChanging(ByVal e As StringPropertyChangingEventArgs)
    RaiseEvent FirstNameChanging(Me, e)
End Sub
 
Protected Overridable Sub OnLastNameChanging(ByVal e As StringPropertyChangingEventArgs)
    RaiseEvent LastNameChanging(Me, e)
End Sub

All that remains is for us to call the methods to actually raise the events. Remember that these events are intended to notify our listeners that a property value is going to change, so they must be raised before the actual value changes.

C#

public string FirstName
{
    get
    {
        return this._firstName;
    }
    set
    {
        if (value != this._firstName)
        {
            this.OnFirstNameChanging(new StringPropertyChangingEventArgs(value));
            this._firstName = value;
            this.OnFirstNameChanged(EventArgs.Empty);
        }
    }
}
 
public string LastName
{
    get
    {
        return this._lastName;
    }
    set
    {
        if (value != this._lastName)
        {
            this.OnLastNameChanging(new StringPropertyChangingEventArgs(value));
            this._lastName = value;
            this.OnLastNameChanged(EventArgs.Empty);
        }
    }
}

VB

Public Property FirstName() As String
    Get
        Return Me._firstName
    End Get
    Set(ByVal value As String)
        If value <> Me._firstName Then
            Me.OnFirstNameChanging(New StringPropertyChangingEventArgs(value))
            Me._firstName = value
            Me.OnFirstNameChanged(EventArgs.Empty)
        End If
    End Set
End Property
 
Public Property LastName() As String
    Get
        Return Me._lastName
    End Get
    Set(ByVal value As String)
        If value <> Me._lastName Then
            Me.OnLastNameChanging(New StringPropertyChangingEventArgs(value))
            Me._lastName = value
            Me.OnLastNameChanged(EventArgs.Empty)
        End If
    End Set
End Property

That’s done but, really, what use is that event? It tells our listeners that the property value is about to change and what it’s about to change to, but to what use can that information be put? Normally, the reason you want to know that a property is about to change is so that you can abort the change if the value is unacceptable for some reason. That can’t be done in this case though, because the property value changes after the event has been handled no matter what.

The ability to cancel an action from an event handler already exists in the Framework. Consider the FormClosing event. You can ask the user for confirmation at that stage and, if they decide they don’t want to close the form, you simply set the e.Cancel property to True. This property is available because the e parameter is type CancelEventArgs. We can’t use CancelEventArgs for our methods though, because we need to provide the extra data consisting of the proposed property value. The solution is to have our StringPropertyChangingEventArgs class inherit CancelEventArgs instead of EventArgs. That way we get the Cancel property and we can add our own data to that.

C#

public class StringPropertyChangingEventArgs : CancelEventArgs
{
    private readonly string _proposedValue;
 
    public string ProposedValue
    {
        get
        {
            return this._proposedValue;
        }
    }
 
    public StringPropertyChangingEventArgs(string proposedValue)
    {
        this._proposedValue = proposedValue;
    }
}

VB

Public Class StringPropertyChangingEventArgs
    Inherits CancelEventArgs
 
    Private ReadOnly _proposedValue As String
 
    Public ReadOnly Property ProposedValue() As String
        Get
            Return Me._proposedValue
        End Get
    End Property
 
    Public Sub New(ByVal proposedValue As String)
        Me._proposedValue = proposedValue
    End Sub
 
End Class

The class name hasn’t changed so we don’t need to change any of the event and method declarations. We do, however, have to change the code in the property to handle the situation where the listener cancels the action. In that case the property value shouldn’t change.

C#

public string FirstName
{
    get
    {
        return this._firstName;
    }
    set
    {
        if (value != this._firstName)
        {
            StringPropertyChangingEventArgs e = new StringPropertyChangingEventArgs(value);
 
            this.OnFirstNameChanging(e);
 
            if (!e.Cancel)
            {
                this._firstName = value;
                this.OnFirstNameChanged(EventArgs.Empty);
            }
        }
    }
}
 
public string LastName
{
    get
    {
        return this._lastName;
    }
    set
    {
        if (value != this._lastName)
        {
            StringPropertyChangingEventArgs e = new StringPropertyChangingEventArgs(value);
 
            this.OnLastNameChanging(e);
 
            if (!e.Cancel)
            {
                this._lastName = value;
                this.OnLastNameChanged(EventArgs.Empty);
            }
        }
    }
}

VB

Public Property FirstName() As String
    Get
        Return Me._firstName
    End Get
    Set(ByVal value As String)
        If value <> Me._firstName Then
            Dim e As New StringPropertyChangingEventArgs(value)
 
            Me.OnFirstNameChanging(e)
 
            If Not e.Cancel Then
                Me._firstName = value
                Me.OnFirstNameChanged(EventArgs.Empty)
            End If
        End If
    End Set
End Property
 
Public Property LastName() As String
    Get
        Return Me._lastName
    End Get
    Set(ByVal value As String)
        If value <> Me._lastName Then
            Dim e As New StringPropertyChangingEventArgs(value)
 
            Me.OnLastNameChanging(e)
 
            If Not e.Cancel Then
                Me._lastName = value
                Me.OnLastNameChanged(EventArgs.Empty)
            End If
        End If
    End Set
End Property

This is as much as most people will usually need to do when it comes to custom events. There are a couple more points to consider though. As I said earlier, if your event is only going to be handled within your own project then it’s considered good practice to use the generic EventHandler(TEventArgs) delegate as your event’s type. If you’re exposing your event outside your assembly though, it’s considered good practice to declare your own delegate and declare your event as that type. This is in much the same vein as declaring properties that expose a collection. In that case, properties that will be accessible only within the assembly can use the generic List(T) class while, for properties exposed outside the assembly, you should declare your own custom collection type.

In this case we have two choices if we want to declare our own custom delegate. We can either declare a single delegate, just as we’ve declared a single EventArgs class, or declare a delegate for each event. Good practice dictates that we choose the latter.

C#

public delegate void FirstNameChangingEventHandler(object sender, StringPropertyChangingEventArgs e);
public delegate void LastNameChangingEventHandler(object sender, StringPropertyChangingEventArgs e);

VB

Public Delegate Sub FirstNameChangingEventHandler(ByVal sender As Object, ByVal e As StringPropertyChangingEventArgs)
Public Delegate Sub LastNameChangingEventHandler(ByVal sender As Object, ByVal e As StringPropertyChangingEventArgs)

Note that our delegates’ signatures match those of the methods that will be used to handle the events. Notice, also, that the names follow convention: the name of the event with an “EventHandler” suffix. Now we simply declare our events as these types instead of type Eventhandler(TEventArgs).

C#

public event FirstNameChangingEventHandler FirstNameChanging;
public event LastNameChangingEventHandler LastNameChanging;

VB

Public Event FirstNameChanging As FirstNameChangingEventHandler
Public Event LastNameChanging As LastNameChangingEventHandler

Finally, consider how our new cancellable event works. The caller sets the property and the appropriate “Changing” event is raised. The caller can either cancel the event, in which case the new property value is never committed and the corresponding “Changed” event is never raised, or else the event can be accepted, in which case the new property value is committed and the “Changed” event is raised.

That’s just what we want, but consider what happens if we have two event handlers for the same “Changing” event. Let’s say that the property is set and the “Changing” event is raised, invoking the first event handler. If e.Cancel is set to True in that event handler, what should happen? If the event has been cancelled then that should be the end of it, right? That’s not what will happen though. As it stands, all event handlers will be invoked no matter what. That means that the first event handler to get invoked might set e.Cancel to True, but then the second event handler can set it back to False again. That would mean that the property value change would be committed, even though it was cancelled by the first listener. It would depend on the circumstances but, more often than not, I would think that this would be undesirable behaviour.

To remedy this we need to create truly custom events, which means providing our own implementation to handle an event handler being added, an event handler being removed and the event being raised. The first step is to create a collection for our delegates so that we can loop through them and invoke each one individually rather than invoking them all as a group.

C#

private List<FirstNameChangingEventHandler> firstNameChangingHandlers = new List<FirstNameChangingEventHandler>();
private List<LastNameChangingEventHandler> lastNameChangingHandlers = new List<LastNameChangingEventHandler>();

VB

Private firstNameChangingHandlers As New List(Of FirstNameChangingEventHandler)
Private lastNameChangingHandlers As New List(Of LastNameChangingEventHandler)

Next we need to provide a custom implementation for our events that handles adding and removing event handlers.

C#

public event FirstNameChangingEventHandler FirstNameChanging
{
    add
    {
        this.firstNameChangingHandlers.Add(value);
    }
    remove
    {
        this.firstNameChangingHandlers.Remove(value);
    }
}
 
public event LastNameChangingEventHandler LastNameChanging
{
    add
    {
        this.lastNameChangingHandlers.Add(value);
    }
    remove
    {
        this.lastNameChangingHandlers.Remove(value);
    }
}

VB

Public Custom Event FirstNameChanging As FirstNameChangingEventHandler
    AddHandler(ByVal value As FirstNameChangingEventHandler)
        Me.firstNameChangingHandlers.Add(value)
    End AddHandler
 
    RemoveHandler(ByVal value As FirstNameChangingEventHandler)
        Me.firstNameChangingHandlers.Remove(value)
    End RemoveHandler
 
    RaiseEvent(ByVal sender As Object, ByVal e As StringPropertyChangingEventArgs)
        For Each handler As FirstNameChangingEventHandler In Me.firstNameChangingHandlers
            handler(sender, e)
            If e.Cancel Then Exit For
        Next
    End RaiseEvent
End Event
 
Public Custom Event LastNameChanging As LastNameChangingEventHandler
    AddHandler(ByVal value As LastNameChangingEventHandler)
        Me.lastNameChangingHandlers.Add(value)
    End AddHandler
 
    RemoveHandler(ByVal value As LastNameChangingEventHandler)
        Me.lastNameChangingHandlers.Remove(value)
    End RemoveHandler
 
    RaiseEvent(ByVal sender As Object, ByVal e As StringPropertyChangingEventArgs)
        For Each handler As LastNameChangingEventHandler In Me.lastNameChangingHandlers
            handler(sender, e)
            If e.Cancel Then Exit For
        Next
    End RaiseEvent
End Event

Notice that now, when a handler is added for the event, we store it in our own collection. We remove the handler from the collection when it’s removed from the event as well. In the case of VB, we also add extra code to control what happens when we call RaiseEvent. That allows the VB code that raises the event to remain unchanged, while the C# code that raises the event must provide the extra functionality for allowing the event to be cancelled before all event handlers have been executed.

C#

protected virtual void OnFirstNameChanging(StringPropertyChangingEventArgs e)
{
    foreach (FirstNameChangingEventHandler handler in this.firstNameChangingHandlers)
    {
        handler(this, e);
        if (e.Cancel) break;
    }
}
 
protected virtual void OnLastNameChanging(StringPropertyChangingEventArgs e)
{
    foreach (LastNameChangingEventHandler handler in this.lastNameChangingHandlers)
    {
        handler(this, e);
        if (e.Cancel) break;
    }
}

VB

Protected Overridable Sub OnFirstNameChanging(ByVal e As StringPropertyChangingEventArgs)
    RaiseEvent FirstNameChanging(Me, e)
End Sub
 
Protected Overridable Sub OnLastNameChanging(ByVal e As StringPropertyChangingEventArgs)
    RaiseEvent LastNameChanging(Me, e)
End Sub

In both cases, raising the event now consists of looping through the registered event handlers one by one. As soon as one of the event handlers cancels the event, no more event handlers are executed.

That’s everything. We’ve covered declaring our own events that have no data, defining our own custom EventArgs class, declaring events that use that custom class using the generic EventHandler(TEventArgs) delegate as well as our own custom delegates, passing data to the event handler and back again and, finally, defining custom events that provide their own implementation for adding and removing event handlers as well as raising the event itself. There’s now nothing you can’t do with events of your own. Here’s hoping you have an eventful future. ;-)