Wrapping my head around custom events

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
Hi, here is the situation, Form1 has a TextBox, Form2 has a ListBox, the TextBox will send it's text string when it is updated (after a second of inactivity) and the ListBox will be updated with the text as a new item, a simple (hah!) history of the textbox's previous contents.
There is another event I'll need (to go in reverse, when a listbox item is double clicked, the string will go to the textbox) but I'll not ask about that, I'll keep it simple, I imagine once I know how to do one, I can do the other.

Here's what I understand so far (or think I do at least), please correct me if anything below is wrong.

So after reading the links provided in my other thread I know there are four steps to this:

1) Create the event arguments
2) Create an event that uses the arguments
3) Make a method to raise the event
4) Make a method to handle the raised event

So I'll stick with step 1 one for now, get some clarification, and use the example provided in the link to base my own on which I renamed and pasted below:

C#:
public class TextUpdatedEventArgs : EventArgs
{
    public string NewText { get; set; }
}
So the eventargs will just be a string (the new text in the textbox).

What I'm wondering is do I need both set and get? I think so right? Once the event is raised it will set the new text from the textbox as the event arguments and the listbox will get the text. Am I understanding this right?

Also, the link did not tell me where to put this code so I'm guessing it goes in the namespace section of my main forms cs file?

I'd like to add that this first part seems to be the most confusing for me, everything beyond this first point does seem much more understandable but I'll guess we'll find out if that's true :)
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
1,256
Location
Virginia Beach, VA
Programming Experience
10+
What I'm wondering is do I need both set and get? I think so right? Once the event is raised it will set the new text from the textbox as the event arguments and the listbox will get the text. Am I understanding this right?
It depends. If you want anybody who gets a reference to your event args object to mess with the values, then yes, you'll want the set to be public. Sometimes event args are meant to communicate one-way data (like a broadcast), while other event args are for two way communication between the event sender and the event receiver.

If you want it to be essentially readonly then make the set private. Modern OOP is starting to lean towards immutable objects instead of the data transfer objects of the 1990's and 2000's because of the rise of multi-threaded and parallel programming. It's easier to reason with objects that won't change under you.

Anyway if you want that setter to be private, you've got to be able to set the value somehow. That's when you use a constructor along with your event args object:
Code:
public class TextUpdatedEventArgs : EventArgs
{
    public string NewText { get; private set; }

    public TextUpdatedEventArgs(string newText)
    {
        NewText = newText;
    }
}
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
What I'm wondering is do I need both set and get? I think so right?
It's a property, like any other. When do you normally make a property read-only? When you want the data contained in the property to be able to be read but not over-written. Do you want the data in that property over-written? If not, it should be read-only.

Not sure whether you've seen my blog post on this subject or not but you might like to check it out if you haven't already.
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
1,256
Location
Virginia Beach, VA
Programming Experience
10+
Hi, here is the situation, Form1 has a TextBox, Form2 has a ListBox, the TextBox will send it's text string when it is updated (after a second of inactivity) and the ListBox will be updated with the text as a new item, a simple (hah!) history of the textbox's previous contents.
The key issue here is that you are using the UI as your data model. In modern programming techniques, you keep your data model separate from your view. The view (e.g. your UI)'s job is to reflect the current state of your data model to the user and give the user a way to interact with the data. Yes, back in the 70's through the early 90's it was common to use the UI as the data model as well as drive the business logic as well. (It's why there was a big market for all the whiz-bang widgets that all advertised that if you feed it the right data it would do all kinds of fancy graphs and analysis for you -- and might even cook you breakfast in the morning.) The reason why it used to be common to keep the data within the UI was the limited amount of memory and CPU processing power available back then. Every byte of memory mattered, so it didn't make sense to have the same data in two different places. So as great as this was in terms memory and CPU efficiency, it was really difficult to actually think of and reason with the data.

So the correct modern approach for the situation you are facing above is that your data model should consist of the list of history items. When Form1 is displayed it takes the last item in the list and shows it in the textbox. Whenever new text is typed into the textbox, the text is added to the list. When Form2 is displayed, it shows the items of the list in its listbox.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
It's a property, like any other. When do you normally make a property read-only? When you want the data contained in the property to be able to be read but not over-written. Do you want the data in that property over-written? If not, it should be read-only.

Not sure whether you've seen my blog post on this subject or not but you might like to check it out if you haven't already.
I've read your blog a couple of times over, it's a very good and clear description, it's helped me a lot to understand custom events, thanks for the link.

In C# 6.0 you can have readonly auto-property with only getter, which require constructor to set them: What's New in C# 6 - C# Guide
That's exactly what I need as it turns out. Thanks.

The key issue here is that you are using the UI as your data model. In modern programming techniques, you keep your data model separate from your view. The view (e.g. your UI)'s job is to reflect the current state of your data model to the user and give the user a way to interact with the data. Yes, back in the 70's through the early 90's it was common to use the UI as the data model as well as drive the business logic as well. (It's why there was a big market for all the whiz-bang widgets that all advertised that if you feed it the right data it would do all kinds of fancy graphs and analysis for you -- and might even cook you breakfast in the morning.) The reason why it used to be common to keep the data within the UI was the limited amount of memory and CPU processing power available back then. Every byte of memory mattered, so it didn't make sense to have the same data in two different places. So as great as this was in terms memory and CPU efficiency, it was really difficult to actually think of and reason with the data.

So the correct modern approach for the situation you are facing above is that your data model should consist of the list of history items. When Form1 is displayed it takes the last item in the list and shows it in the textbox. Whenever new text is typed into the textbox, the text is added to the list. When Form2 is displayed, it shows the items of the list in its listbox.
What you should be doing is adding the data from Form1 to a list of some sort and then populating the ListBox from that when Form2 is created and displayed.
I never thought about it in those terms before, it's very interesting, I'm going to redo it in that manner, thanks for the advice.

Edit: Quick question, to avoid my nemesis (scoping issues), where would be the correct place for such a list to be created?
 

Sheepings

Senior Programmer
Staff member
Joined
Sep 5, 2018
Messages
1,159
Location
UK
Programming Experience
10+
If you have two forms, and you want to add a list, where would you put it?

Lets put it like this :
C#:
    public partial class Form1 : Form
    {
        public List<string> Lst_A = new List<string>();
        public Form1()
        {
            InitializeComponent();
        }
    }
How would you access each of these lists on either of the opposite form?
C#:
    public partial class Form2 : Form
    {
        public List<string> Lst_B = new List<string>();
        public Form2()
        {
            InitializeComponent();
        }
    }
Or would it be better to have a static list accessible in its own class from anywhere?

For example :
C#:
    public static class ListBoxList
    {
        static ListBoxList()
        {

        }
        public static List<string> Form2_ListBoxItems { get; set; }
    }
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
If you have two forms, and you want to add a list, where would you put it?

Lets put it like this :
C#:
    public partial class Form1 : Form
    {
        public List<string> Lst_A = new List<string>();
        public Form1()
        {
            InitializeComponent();
        }
    }
How would you access each of these lists on either of the opposite form?
C#:
    public partial class Form2 : Form
    {
        public List<string> Lst_B = new List<string>();
        public Form2()
        {
            InitializeComponent();
        }
    }
Or would it be better to have a static list accessible in its own class from anywhere?

For example :
C#:
    public static class ListBoxList
    {
        static ListBoxList()
        {

        }
        public static List<string> Form2_ListBoxItems { get; set; }
    }
Well, the static list class definitely seems to be the best thing to do. I never thought about a list class, only a regular list. I'm not thinking like a programmer yet :/

Thanks very much for the advice, other questions on this topic (should they arise) I'll put in a new thread and keep my custom event questions here.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
So I'm finally at the point where I'm attempting to actually write an event. The evnt is raised when someone clicks on a text item in the listbox so that text can be copied to another forms textbox.

The eventargs will just be the text string clicked on, here is my eventargs class:

C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
    // this event argument will contain the string clicked
    // in the history listbox
    public class ListBoxIndexChangedEventArgs : EventArgs
    {
        private readonly string _clickedText = "";

        public string ClickedText { get; }

        public ListBoxIndexChangedEventArgs(string str)
        {
            this._clickedText = str;
        }
    }
}
Since the event will be raised on Form2 I put the event declaration inside the Form2 class:

C#:
public event EventHandler<ListBoxIndexChangedEventArgs> ListBoxIndexChanged;
Here is the method that raises the event, I declared it inside the Form2 class since that's where I'll call it from:

C#:
// method to raise the ListBoxIndexChanged event (user clicked a new item in the list
        protected virtual void OnListBoxIndexChanged(ListBoxIndexChangedEventArgs e)
        {
            if (ListBoxIndexChanged != null)
            {
                ListBoxIndexChanged(this, e);
            }
        }
Assuming all is good with my code, could someone help me with the next step, the method that handles the event, I don't know where it should go. My guess is somewhere on Form1 since that's where the destination textbox is.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
I think I have it:

C#:
OnListBoxIndexChanged(new ListBoxIndexChangedEventArgs(lstbHistory.Text));
This should go in the SelectedIndexChanged event of the listbox right?
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
I think I have it:

C#:
OnListBoxIndexChanged(new ListBoxIndexChangedEventArgs(lstbHistory.Text));
This should go in the SelectedIndexChanged event of the listbox right?
In the SelectedIndexChanged event HANDLER. The event is what gets raised. The method that handles the event is the event handler. The ListBox raises its event, your form handles it and then raises its own event. The event is part of the ListBox while the event handler is part of your form..
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
Sorry about the delay, I got side-tracked by VB.

So, I guess everything I've done so far is correct, but what's the next step? The event was raised with the string, so do I need to make the TextBox listen for the event and get the string? I've read your blogpost JM several times now I'm finding it hard to "translate" from your example to my problem if you know what I mean.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
I have finally managed to achieve what I was trying to do. It's not an easy thing to self learn with google so if there are any self-learners out there struggling with this I'll show you how I was taught to do it.

The first place you should go is JM's blog post on the subject, it doesn't cover how to send an event from one form to another but read it first and understand the example he shows.

So, assume we have a textbox on form 1 and a listbox on form 2, this is how to use events to enable you to click an item in the listbox and have it's text sent to the textbox.
First create your custom eventargs class, it will need a property to hold the information you want to send to the other control, in this case it's a string.

C#:
public class CustomEventArgs
{
    protected string _clickedText;
    public CustomEventArgs(string listBoxText) => _clickedText = listBoxText;
    public string ClickedText => _clickedText;
}
In form 2's class, the form where the text will need to be sent from, declare your event, and make it use your custom eventargs class. In this case the event is called ListBoxSelectionChanged:

C#:
public partial class Form2 : Form
{
    public event EventHandler<CustomEventArgs> ListBoxSelectionChanged;
      
    // the rest of your code
      
}
In form 2's constructor, attach the form's "shown" event handler to it's "shown" event, we do this because inside the shown event handler is where we'll attach the listbox's click event handler to the listbox's click event (in the next step):

C#:
public Form2()
{
    InitializeComponent();
    Shown += Form2_Shown;
}
In form 2's "form shown" event handler, attach the listbox's click event handler to the listbox's click event:

C#:
private void Form2_Shown(object sender, EventArgs e)
{
    ListBox1.Click += ListBox1_Click;
}
Then, in the listbox's click event handler, raise the event, passing it the data you want sent to the other form's control:

C#:
private void ListBox1_Click(object sender, EventArgs e)
{
    ListBoxSelectionChanged?.Invoke(this, new CustomEventArgs(ListBox1.Text));
}
In this example I use a button to open form 2. This code goes inside whatever event handler opens the form (this would be on form 1). An instance of Form 2 is created, the next line subscribes the just created form's event handler, OnListBoxSelectionChanged (written in the next step) to the event ListBoxSelectionChanged, then it is shown:

C#:
private void Button_Click(object sender, EventArgs e)
        {
            var form2 = new Form2();

            form2.ListBoxSelectionChanged += OnListBoxSelectionChanged;
            form2.ShowDialog();
        }
In the OnListBoxSelectionChanged event handler (still in form 1), the textbox's text is updated with the string data from our custom eventargs:

C#:
private void OnListBoxSelectionChanged(object sender, CustomEventArgs myArgs)
{
    TextBox1.Text = myArgs.ClickedText;
}
And viola, it's done. Hopefully I used the correct terminology and explained things properly, but this works. Hopefully it helps someone out at some point in time.
 
Last edited:

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
Don't forget to unregister your event handlers. For every '+=' there should be a corresponding '-=' when you're done. Also, there's no need to declare your own delegate these days. Just use the generic EventHandler<TEventArgs> if you want to use a custom event args. Also, your event name should not begin with "On". An appropriate event name might be ListBoxClick and then you would have a method that raises that event named OnListBoxClick. The reason for the method is that you can declare it protected and then derived classes can override it to provide custom behaviour on an event.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
Oh I see, this is litterlaly the first time I'm hearing about unregistering events, I'm gonna need to look into that.

I messed up the naming convention again I see, I should have gone over your blog post again to make sure, I just didn't realize I had it wrong. I'll try and edit my post and rename the event and the handler, and also declare it as you described.
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
Oh I see, this is litterlaly the first time I'm hearing about unregistering events, I'm gonna need to look into that.
Registering an event handler creates a link between two objects that will prevent garbage collection occurring as quickly, so it is important to unregister to break that link.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
I've edited the post, I renamed the event to ListBoxSelectionChanged and the handler to OnListBoxSelectionChanged, this matches how you named them in your blog post I think. I also got rid of the delegate.

The time to unsubscribe from this event I assume would be when form2 is closed right? But since the statement has to go in form 1's code, how would form1 know that form2 is closing?
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
I've edited the post, I renamed the event to ListBoxSelectionChanged and the handler to OnListBoxSelectionChanged, this matches how you named them in your blog post I think.
Nope. The method with the name of the event prefixed with "On" is the method that RAISES the event, not the method that HANDLES it. The OnListBoxSelectionChanged method should be in the same class as the ListBoxSelectionChanged event. The only place the event should be raised is in that method and you call that mehod when you want to raise the event. By doing that, any derived class can override that method and add custom functionality to be executed on that event and then raising the event from the base class will execute that functionality. If the class containing the event is never inherited then it doesn't really matter but I prefer to stick to the convention every time. That way, I can never accidentally not use that convention on an occasion that I should have.
The time to unsubscribe from this event I assume would be when form2 is closed right? But since the statement has to go in form 1's code, how would form1 know that form2 is closing?
You're calling ShowDialog. Once that method returns, the form has closed. That's the point. If you're calling Show then you can simply handle the FormClosed event as well.

By the way, a form displayed by calling ShowDialog is not disposed automatically so you should be doing it yourself. The way to do that is to create it with a using statement, so it is disposed at the end of the block.
 

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
Ok, let me make sure I have this down, so as for the naming convention when I wrote this:

C#:
form2.ListBoxSelectionChanged += OnListBoxSelectionChanged;

private void OnListBoxSelectionChanged(object sender, CustomEventArgs myArgs)
{
    TextBox1.Text = myArgs.ClickedText;
}
I'm using "On" in the wrong place, so I need to rename "OnListBoxSelectionChanged" because this is my method that handles the raised event. Is there a naming convention for this method? What do you think would a good name for it?

Also on that subject, I used the very generic name "CustomEventArgs" just as a clear example name for a custom event args class, obviously this is actually a bad name. I read (on your blog I think it was) that an event can be used whenever you need to utilize the data it can "carry". So in my example (even though I only have one use for it right now) my custom event args class will carry a single string, therefore I don't need to write a new eventargs class if I just need one string to be passed, I can always reuse this event for that.
That makes me wonder what the best way to name it would be. Do I name it based on it's data? Something like "SingleStringEventArgs" since that clearly describes the event arguments in my eyes and naming it based on it's function such as "ListBoxSelectionEventArgs" seems somewhat less clear in terms of the actual data it carries. Would either of those names be a good choice? Or would something else be better?
 

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
2,881
Location
Sydney, Australia
Programming Experience
10+
For the record, this is just a convention that no one is required to stick to, but I recommend sticking to it because consistency is always a good thing and you are then also doing things the same way as they are done in Framework code. I'll put together a full example but it might not be for a few hours, given that it is approaching 1.00 AM here right now.
 
Top Bottom