Bradley Hastings
Bradley Hastings

Reputation: 51

How to add a PropertyChanged event for a property?

I have created various properties inside of a User Control, and have had great success with accessing and editing them. I'm now trying to set up events for a number of these to be raised when one of these properties is changed. I have tried the MSDN example code for doing this (see here), but it is giving me this error when I try to build the solution:

Severity Code Description Project File Line Suppression State Error CS0079 The event 'AbilityScoreDisplay.AbilityTitleChanged' can only appear on the left hand side of += or -= DnD Character Sheet C:\Users\bradley beasley\Documents\Visual Studio 2019\Projects\DnD Character Sheet\DnD Character Sheet\AbilityScoreDisplay.Designer.cs 199 Active

Another issue that I am having is that I am struggling to figure out how to get that event to appear in the Visual Studio 2019 Designer Properties window.

Here is the code that I have added to the designer file:

namespace DnD_Character_Sheet
{
    partial class AbilityScoreDisplay : UserControl
    {
        public string AbilityTitle
        {
            get
            {
                return AbiltyTitleLabel.Text;
            }
            set
            {
                AbiltyTitleLabel.Text = value;
                Invalidate();
            }
        }
        public int AbilityModifier
        {
            get
            {
                return Convert.ToInt32(AbilityModifierTextBox.Text);
            }
            private set
            {
                if (value >= 0) AbilityModifierTextBox.Text = String.Format("+{0}", value);
                else AbilityModifierTextBox.Text = value.ToString();
                Invalidate();
            }
        }
        public int AbilityScore
        {
            get
            {
                return Convert.ToInt32(AbilityScoreLabel.Text);
            }
            set
            {
                AbilityModifier = (int)(Math.Floor((double)(value) / 2)) - 5;
                Invalidate();
            }
        }

        private EventHandler onAbilityTitleChanged { get; set; }
        private EventHandler onAbilityScoreChanged { get; set; }

        public event EventHandler AbilityTitleChanged
        {
            add
            {
                onAbilityTitleChanged += value;
            }
            remove
            {
                onAbilityTitleChanged -= value;
            }
        }
        public event EventHandler AbilityScoreChanged
        {
            add
            {
                onAbilityScoreChanged += value;
            }
            remove
            {
                onAbilityScoreChanged -= value;
            }
        }

        protected virtual void OnAbilityTitleChanged(EventArgs e)
        {
            AbilityTitleChanged?.Invoke(this, e);
        }
        protected virtual void OnAbilityScoreChanged(EventArgs e)
        {
            AbilityScoreChanged?.Invoke(this, e);
        }
    }
}

The aim is to enable an event to be raised whenever a property is changed so that it can do other stuff elsewhere in the form that the controls will be in. I'm fairly certain that I am missing some very important stuff, or that my code is not that effective at all, but I am learning this kind of code for the first time, and I have tried many different things that have just not worked.

Any help at all would be greatly appreciated :)

Upvotes: 2

Views: 6959

Answers (2)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112259

You typically do this by implementing INotifyPropertyChanged. This allows you to use one single event for all the properties. The property name is passed in the event arguments.

partial class AbilityScoreDisplay : UserControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    ...
}

In the properties do this (with AbilityModifier as an example):

private int _abilityModifier;
public int AbilityModifier
{
    get { return _abilityModifier; }
    private set {
        if (value != _abilityModifier) {
            _abilityModifier = value;
            AbilityModifierTextBox.Text = value >= 0
                ? String.Format("+{0}", value)
                : value.ToString();
            OnPropertyChanged(nameof(AbilityModifier));
        }
    }
}

Assuming this event handler

private void ScoreDisplay_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    ...
}

You can subscribe the event with

PropertyChanged += ScoreDisplay_PropertyChanged;

You need to use the add/remove syntax only in rare cases. Typically, when you create your own event store, because you have a lot of events and don't want to consume space for unsubscribed events.


You can use INotifyPropertyChanged together with data binding to immediately update the UI when changes are made to the data. To do this you would create a class with properties and the INotifyPropertyChanged implementation. In the form you then assign an instance of this class to the DataSource of a BindingSource. The controls are then bound to this BindingSource.

Then you can drop all the code used to read from or to write to text boxes or labels etc., as the binding mechanism does it automatically for you.

Upvotes: 2

JuanR
JuanR

Reputation: 7783

I think you are confusing a few concepts. Let's do it step by step.

First, you need to be able to track event handlers:

private EventHandler _onAbilityTitleChanged; 

You expose this event through a public property:

public event EventHandler AbilityTitleChanged
{
    add
    {
        _onAbilityTitleChanged += value;
    }
    remove
    {
        _onAbilityTitleChanged -= value;
    }
}

Finally, you need to fire the event so that all subscribed handlers can react to it. You can do so when the title changes (setter):

public string AbilityTitle
{
    get
    {
        return AbiltyTitleLabel.Text;
    }
    set
    {
        AbiltyTitleLabel.Text = value;
        //Raising the event!
        _onAbilityTitleChanged?.Invoke(this, new EventArgs());
    }
}

Other classes can then subscribe to your event:

var control = new AbilityScoreDisplay();
control.AbilityTitleChanged += SomeHandlerForWhenTitleChanges;

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

You might want to read up a bit on the INotifyPropertyChanged interface as well.

Upvotes: 6

Related Questions