Home > Back-end >  Detect when custom class's property is changed
Detect when custom class's property is changed

Time:09-27

I have 2 classes: ParentClass and ChildClass and both are used as datacontext for UserControl with similar hierarchy.

I want to have parent class be notified when it's custom class property has a change in one of it's variables.

    public class ParentClass
    {
        private ChildClass _child;
        private int _value;

        public ChildClass Child
        {
            get
            {
                if (_child == null)
                    Child = new ChildClass();
                return _child;
            }
            set
            {
                _child = value;
                Value = _child.Score;
                OnPropertyChanged(nameof(Child));
            }
        }
        public int Value
        {
            get { return _value; }
            set
            {
                _value = value;
                OnPropertyChanged(nameof(Value));
            }
        }
    }
    public class ChildClass
    {
        private int _score;
        public int Score
        {
            get { return _score; }
            set
            {
                _score = value;
                OnPropertyChanged(nameof(Score));
            }
        }
    }

What I want is that on change of Score, set part of Parent class's Child property is executed and Value updated.

How do I do that?

Edit: Code

PropertyChangedNotify

internal class PropertyChangedNotify : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(string? property)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
}

Proficiency_Counter - xaml

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBlock Text=""/>

    <Viewbox Grid.Column="1">
        <ComboBox ItemsSource="{Binding ProficiencyList}" SelectedItem="{Binding Proficiency}" Margin="1">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Viewbox>
                        <TextBlock Text="{Binding}"/>
                    </Viewbox>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Viewbox>
</Grid>

Proficiency_Counter_Model - aka ChildClass

internal class Proficiency_Counter_Model: PropertyChangedNotify
{
    #region private
    private int _proficiencyScore;
    private List<char> _proficiencyList;
    private char _proficiency;
    private int _level;
    #endregion
    #region public
    public int ProficiencyScore
    {
        get { return _proficiencyScore; }
        set
        {
            _proficiencyScore = value;
            OnPropertyChanged(nameof(ProficiencyScore));
        }
    }
    public List<char> ProficiencyList
    {
        get
        {
            if (_proficiencyList == null)
            {
                ProficiencyList = new List<char>(); 
                ProficiencyList.Add('U');
                ProficiencyList.Add('T');
                ProficiencyList.Add('E');
                ProficiencyList.Add('M');
                ProficiencyList.Add('L');
                Proficiency = 'U';
            }
            return _proficiencyList;
        }
        set
        {
            _proficiencyList = value;
            OnPropertyChanged(nameof(ProficiencyList));
        }
    }
    public char Proficiency
    {
        get { return _proficiency; }
        set
        {
            _proficiency = value;
            if (Proficiency == 'U')
            {
                ProficiencyScore = 0;
            }
            else if (Proficiency == 'T')
            {
                ProficiencyScore = 2   _level;
            }
            else if (Proficiency == 'E')
            {
                ProficiencyScore = 4   _level;
            }
            else if (Proficiency == 'M')
            {
                ProficiencyScore = 6   _level;
            }
            else if (Proficiency == 'L')
            {
                ProficiencyScore = 8   _level;
            }
            OnPropertyChanged(nameof(Proficiency));
        }
    }
    public int Level
    {
        get { return _level; }
        set
        {
            _level = value;
            Proficiency = _proficiency;
            OnPropertyChanged(nameof(Level));
        }
    }
    #endregion

}

General_Skill_Counter_Model - aka ParentClass

internal class General_Skill_Counter_Model: PropertyChangedNotify
{
    #region private
    private string _skillName;
    private int _abilityMod;
    private Proficiency_Counter_Model _proficiency;
    private int _proficiencyMod;
    private int _itemMod;
    #endregion
    #region public
    public string SkillName
    {
        get
        {
            if (_skillName == null)
                SkillName = "Lorem Impsum";
            return _skillName;
        }
        set
        {
            _skillName = value;
            OnPropertyChanged(nameof(SkillName));
        }
    }
    public int SkillScore
    {
        get
        {
            return (AbilityMod   Proficiency.ProficiencyScore   ItemMod);
        }
    }
    public int AbilityMod
    {
        get { return _abilityMod; }
        set
        {
            _abilityMod = value;
            OnPropertyChanged(nameof(AbilityMod));
            OnPropertyChanged(nameof(SkillScore));
        }
    }
    public Proficiency_Counter_Model Proficiency
    {
        get
        {
            if (_proficiency == null)
            {
                _proficiency = new Proficiency_Counter_Model();
            }
            return _proficiency;
        }
        set
        {
            _proficiency = value;
            ProficiencyMod = _proficiency.ProficiencyScore;
            OnPropertyChanged(nameof(Proficiency));
        }
    }
    public int ProficiencyMod
    {
        get { return _proficiencyMod; }
        set
        {
            _proficiencyMod = value;
            OnPropertyChanged(nameof(ProficiencyMod));
            OnPropertyChanged(nameof(SkillScore));
        }
    }
    public int ItemMod
    {
        get { return _itemMod; }
        set
        {
            _itemMod = value;
            OnPropertyChanged(nameof(ItemMod));
            OnPropertyChanged(nameof(SkillScore));
        }
    }
    #endregion
}

Proficiency_Counter_Model is set as Proficiency_Counter's DataContext, same for Genereal_Skill_Counter, which I believe is irrelevant to this.

CodePudding user response:

Ok, I figured it out. Instead of notifying the parent, I made the child part of the parent.

Here's the code:

internal class Proficiency_Counter_Model: PropertyChangedNotify
{
    #region private
    protected int _proficiencyScore;
    private List<char> _proficiencyList;
    private char _proficiency;
    protected int _level;
    #endregion
    #region public
    public virtual int ProficiencyScore
    {
        get { return _proficiencyScore; }
        set
        {
            _proficiencyScore = value;
            OnPropertyChanged(nameof(ProficiencyScore));
        }
    }
    public List<char> ProficiencyList
    {
        get
        {
            if (_proficiencyList == null)
            {
                ProficiencyList = new List<char>(); 
                ProficiencyList.Add('U');
                ProficiencyList.Add('T');
                ProficiencyList.Add('E');
                ProficiencyList.Add('M');
                ProficiencyList.Add('L');
                Proficiency = 'U';
            }
            return _proficiencyList;
        }
        set
        {
            _proficiencyList = value;
            OnPropertyChanged(nameof(ProficiencyList));
        }
    }
    public char Proficiency
    {
        get { return _proficiency; }
        set
        {
            _proficiency = value;
            if (Proficiency == 'U')
            {
                ProficiencyScore = 0;
            }
            else if (Proficiency == 'T')
            {
                ProficiencyScore = 2   _level;
            }
            else if (Proficiency == 'E')
            {
                ProficiencyScore = 4   _level;
            }
            else if (Proficiency == 'M')
            {
                ProficiencyScore = 6   _level;
            }
            else if (Proficiency == 'L')
            {
                ProficiencyScore = 8   _level;
            }
            OnPropertyChanged(nameof(Proficiency));
        }
    }
    public int Level
    {
        get { return _level; }
        set
        {
            _level = value;
            Proficiency = _proficiency;
            OnPropertyChanged(nameof(Level));
        }
    }
    #endregion

}

I made the ProficiencyScore property virtual so that I could override it.

internal class General_Skill_Counter_Model : Proficiency_Counter_Model
{
    #region private
    private string _skillName;
    private int _skillScore;
    private int _abilityMod;
    private int _itemMod;
    #endregion
    #region public
    public string SkillName
    {
        get
        {
            if (_skillName == null)
                SkillName = "Lorem Impsum";
            return _skillName;
        }
        set
        {
            _skillName = value;
            OnPropertyChanged(nameof(SkillName));
        }
    }
    public int SkillScore
    {
        get { return _skillScore; }
        set
        {
            _skillScore = value;
            OnPropertyChanged(nameof(SkillScore));
        }
    }
    public int AbilityMod
    {
        get { return _abilityMod; }
        set
        {
            _abilityMod = value;
            OnPropertyChanged(nameof(AbilityMod));
        }
    }
    public int ItemMod
    {
        get { return _itemMod; }
        set
        {
            _itemMod = value;
            OnPropertyChanged(nameof(ItemMod));
        }
    }
    #endregion

    #region override
    public override int ProficiencyScore
    {
        get { return _proficiencyScore; }
        set
        {
            SkillScore = _skillScore   value - _proficiencyScore;
            _proficiencyScore = value;
            OnPropertyChanged(nameof(ProficiencyScore));
        }
    }
    #endregion
}

CodePudding user response:

The general implementation principle is to listen for the PropertyChanged event of the child object. When replacing a child object, you need to unsubscribe from the event of the old object and subscribe to the event of the new object. In the event handler, the name of the changed property is checked and the necessary actions are taken.

The implementation depends on how you have implemented the base class for INotifyPropertyChanged. For example, this implementation of BaseInpc uses the "Set" method to set a property, which returns true if the value of the property has changed. For such an implementation, you can write the following code:

    public class ChildClass : BaseInpc
    {
        private int _score;
        public int Score { get => _score; set => Set(ref _score, value); }
    }
    public class ParentClass : BaseInpc
    {
        private ChildClass _child = new ChildClass();
        private int _value;

        public ChildClass Child
        {
            get => _child;
            set
            {
                var old = _child;
                if (Set(ref _child, value ?? new ChildClass()))
                {
                    old.PropertyChanged -= OnChildPropertyChanged;
                    _child.PropertyChanged  = OnChildPropertyChanged;
                    Value = Child.Score * 2;
                }
            }
        }

        private void OnChildPropertyChanged(object? sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ChildClass.Score) ||
                string.IsNullOrEmpty(e.PropertyName))
            {
                Value = Child.Score * 2;
            }
        }

        public int Value { get => _value; set => Set(ref _value, value); }

        public ParentClass() => Child = new ChildClass();
    }

Also in this implementation, you can lighten the logic of the property by using an override of the OnPropertyChanged virtual method instead:

    public class ParentClass : BaseInpc
    {
        private ChildClass _child = new ChildClass();
        private int _value;

        public ChildClass Child { get => _child; set => Set(ref _child, value ?? new ChildClass()); }
        public int Value { get => _value; set => Set(ref _value, value); }
        public ParentClass() => Child = new ChildClass();

        protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);

            if (propertyName == nameof(Child))
            {
                ChildClass oldChild = (ChildClass)oldValue;
                oldChild.PropertyChanged -= OnChildPropertyChanged;
                ChildClass newChild = (ChildClass)newValue;
                newChild.PropertyChanged  = OnChildPropertyChanged;
                Value = newChild.Score * 2;
            }
        }
        private void OnChildPropertyChanged(object? sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(ChildClass.Score) ||
                string.IsNullOrEmpty(e.PropertyName))
            {
                Value = Child.Score * 2;
            }
        }
    }
  • Related