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 :)
 
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:
C#:
public class TextUpdatedEventArgs : EventArgs
{
    public string NewText { get; private set; }

    public TextUpdatedEventArgs(string newText)
    {
        NewText = newText;
    }
}
 
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.
 
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.
 
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?
 
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; }
    }
 
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.
 
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.
 
I think I have it:

C#:
OnListBoxIndexChanged(new ListBoxIndexChangedEventArgs(lstbHistory.Text));

This should go in the SelectedIndexChanged event of the listbox right?
 
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..
 
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.
 
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:
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.
 
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.
 
Back
Top Bottom