Listening to PropertyChanged from another class - c#

How can I listen in class B to the PropertyChanged events from class A? I would like to listen to changes of a property from class A.
class A : INotifyPropertyChanged
{
private int _x;
public int X
{
get => _x;
set
{
if (_x == value) return;
_x = value;
OnPropertyChanged(nameof(X));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class B
{
public B(int x)
{
// In this class I want to listen to changes of the property X from class A
}
}

Just listen to the event:
class B
{
public A _myA;
public B(int x)
{
_myA = new A();
_myA.PropertyChanged += A_PropertyChanged;
}
private void A_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(_myA.X)) return;
}
}

Related

Inheritance with INotifyPropertyChanged

I was wondering if anyone knows how to solve the following problem... I have a base class that needs to update it's modifiedDateTime property when a derived class property is changed.
BaseObject.cs
public class BaseObject
{
private DateTime? _modifiedDateTime;
public DateTime? modifiedDateTime
{
get { return _modifiedDateTime ; }
set { _modifiedDateTime = value; }
}
public BaseObject
{
_modifiedDateTime = DateTime.Now.ToUniversalTime();
}
public void Update(object sender, PropertyChangedEventArgs e)
{
_modifiedDateTime = DateTime.Now.ToUniversalTime();
}
}
ExampleClass1.cs
public class ExampleClass1: BaseObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _data;
public int data
{
get { return _data; }
set
{
_data = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("data"));
}
}
public ExampleClass1()
{
PropertyChanged += base.Update;
}
}
The previous example is working as expected.
However if the derived class contains an object of another class. For example:
ExampleClass2.cs
public class ExampleClass2: BaseObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _data;
private ExampleClass1 _objClass1;
public int data
{
get { return _data; }
set
{
_data = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("data"));
}
}
public ExampleClass1 objClass1
{
get { return _objClass1; }
set
{
_objClass1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("objClass1"));
}
}
public ExampleClass2()
{
PropertyChanged += base.Update;
}
}
When I change the data property of the objClass1, the modifiedDateTime property of the ExampleClass2 inherited by the base class BaseObject is not updated.
How can I solve this problem?
When you set the value of objClass1 subscribe to its property changed event as well
public ExampleClass1 objClass1 {
get { return _objClass1; }
set {
//in case one already existed. unsubscribe from event
if(_objClass1 != null) _objClass1.PropertyChanged -= base.Update
_objClass1 = value;
//subscribe to event
if(_objClass1 != null) _objClass1.PropertyChanged += base.Update
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("objClass1"));
}
}

How to use DataBindings property properly?

I'm attempting to bind a windows form label to a variable. when i run that code lable1 was displayed as 50 but when i increment using button1 it doesn't change to 51. here is my attemp
int x = 50;
public int X
{
get { return x; }
set { x = value; }
}
public Form1()
{
InitializeComponent();
label1.DataBindings.Add("Text", this, "X", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void button1_Click(object sender, EventArgs e)
{
X++;
}
TIA
You can put your data in another class that implements INotifyPropertyChanged so that the UI will be notified and updated:
public partial class Form1 : Form
{
DataClass dc;
public Form1()
{
InitializeComponent();
dc = new DataClass();
label1.DataBindings.Add("Text", dc, "X", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void button1_Click(object sender, EventArgs e)
{
dc.X++;
}
}
public class DataClass : INotifyPropertyChanged
{
int x = 50;
public int X
{
get { return x; }
set
{
x = value;
NotifyPropertyChanged("X");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
int X = 50;
public Form1() {
InitializeComponent();
label1.Text = X.ToString();
}
private void button1_Click(object sender, EventArgs e) {
X += 1;
label1.Text = X.ToString();
}
Your Class Form must be implement the INotifyPropertyChanged interface, like this:
public partial class Form1 : Form, INotifyPropertyChanged
{
int x = 50;
public int X
{
get { return x; }
set { x = value; OnPropertyChanged("X"); }
}
private void Form1_Load(object sender, EventArgs e)
{
label1.DataBindings.Add("Text", this, "X", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void button1_Click(object sender, EventArgs e)
{
X++;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}

Event Handling - sender as main class

Apologies if the title is misleading, but I'm not sure precisely what to call what I'm looking for here. I have the following "main" class:
public class Entity : INotifyPropertyChanged
{
public string Name { get; set; }
public EntityRole Role { get; set; }
public EntityStats Stats = new EntityStats();
//Other stuff....
}
And a ... sub class? (proper name would be appreciated for this) ... called EntityStats:
public class EntityStats : INotifyPropertyChanged
{
public int CurrentHealth
{
get { return _currentHealth; }
set
{
if (value != _currentHealth)
{
_currentHealth = value;
OnPropertyChanged("CurrentHealth");
}
}
}
//other properties...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Where an Entity object contains a property of type EntityStats.
On the other side of my code, I'm registering an Entity object to listen for PropertyChanged events:
public void RegisterEntity(Entity entity)
{
entity.Stats.PropertyChanged += entity_PropertyChanged;
}
void entity_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentHealth")
{
Message.Write("CurrentHealth property changed!");
DeathCheck((Entity)sender);
}
}
The issue I'm having is with DeathCheck((Entity)sender); -- Because the CurrentHealth OnPropertyChanged event is part of the EntityStats class, the object is of type EntityStats, which only contains part of the data I need.
How can I determine the Entity object that sender belongs to, or how can I refactor this code so that when a property inside EntityStats changes, an event is raised in the Entity class?
You can listen the EntityStats event's in Entity constructor and dispatch a Entity event's. Like this:
public class Entity : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Entity() {
Stats.PropertyChanged += Stats_PropertyChanged
}
//Other stuff....
void Stats_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
e.PropertyName = "Stats." + e.PropertyName;
PropertyChanged(this, e);
}
}
}
Now, in the RegisterEntity, the listener method can be associated direct to the Entity instance:
public void RegisterEntity(Entity entity)
{
entity.PropertyChanged += entity_PropertyChanged;
}
and the sender of the listener method is Entity instance, but is possible identify if the property was changed in Stats property:
void entity_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Stats.CurrentHealth")
{
Message.Write("Stats.CurrentHealth property changed!");
DeathCheck((Entity)sender);
}
}
You could give the EntityStats class a property named Entity (or somesuch) and set that property in the EntityStats constructor:
public class Entity : INotifyPropertyChanged
{
public string Name { get; set; }
public EntityRole Role { get; set; }
public EntityStats Stats = new EntityStats(this);
//Other stuff....
}
public class EntityStats : INotifyPropertyChanged
{
private Entity _entity;
public Entity Entity
{
get { return _entity; }
}
public EntityStats(Entity entity)
{
_entity = entity;
}
// ...
}

c# object with a ValueChanged event that fires when each property is changed

What is the best way to create a class with an event that fires when one of its Properties is changed? Specifically, how do you convey to any subscribers which Property was changed?
Ex:
public class ValueChangedPublisher
{
private int _prop1;
private string _prop2;
public static event ValueChangedHandler(/*some parameters?*/);
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
}
public class ValueChangedSubscriber
{
private int _prop1;
private string _prop2;
public ValueChangedSubscriber()
{
ValueChangedPublisher.ValueChanged += ValueChanged;
}
private void ValueChanged(/*parameters?*/)
{
/*how does the subscriber know which property was changed?*/
}
}
My goal is to make this as extensible as possible (e.g. I don't want a bunch of huge if/else if/switch statements lumbering around). Does anybody know of a technique to achieve what I'm looking for?
EDIT:
What I'm really looking for is how to utilize the INotifyPropertyChanged pattern on the subscriber side. I don't want to do this:
private void ValueChanged(string propertyName)
{
switch(propertyName)
{
case "Prop1":
_prop1 = _valueChangedPublisher.Prop1;
break;
case "Prop2":
_prop2 = _valueChangedPublisher.Prop2;
break;
// the more properties that are added to the publisher, the more cases I
// have to handle here :/ I don't want to have to do it this way
}
}
.NET provides the INotifyPropertyChanged interface. Inherit and implement it:
public class ValueChangedPublisher : INotifyPropertyChanged
{
private int _prop1;
private string _prop2;
public event PropertyChangedEventHandler ValueChangedHandler;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
NotifyPropertyChanged();
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
NotifyPropertyChanged();
}
}
}
}
Here is what I did to solve my problem. It's a bit large, so maybe not performant, but works for me.
Edit
See this question for performance details: C# using properties with value types with Delegate.CreateDelegate
Base Class for publishing property change stuff:
public abstract class PropertyChangePublisherBase : INotifyPropertyChanged
{
private Dictionary<string, PropertyInfo> _properties;
private bool _cacheProperties;
public bool CacheProperties
{
get { return _cacheProperties; }
set
{
_cacheProperties = value;
if (_cacheProperties && _properties == null)
_properties = new Dictionary<string, PropertyInfo>();
}
}
protected PropertyChangePublisherBase(bool cacheProperties)
{
CacheProperties = cacheProperties;
}
public bool ContainsBinding(PropertyChangedEventHandler handler)
{
if (PropertyChanged == null)
return false;
return PropertyChanged.GetInvocationList().Contains(handler);
}
public object GetPropertValue(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) || String.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Argument must be the name of a property of the current instance.", "propertyName");
return ProcessGetPropertyValue(propertyName);
}
protected virtual object ProcessGetPropertyValue(string propertyName)
{
if (_cacheProperties)
{
if (_properties.ContainsKey(propertyName))
{
return _properties[propertyName].GetValue(this, null);
}
else
{
var property = GetType().GetProperty(propertyName);
_properties.Add(propertyName, property);
return property.GetValue(this, null);
}
}
else
{
var property = GetType().GetProperty(propertyName);
return property.GetValue(this, null);
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Base Class for receiving property change stuff:
public abstract class PropertyChangeSubscriberBase
{
protected readonly string _propertyName;
protected virtual object Value { get; set; }
protected PropertyChangeSubscriberBase(string propertyName, PropertyChangePublisherBase bindingPublisher)
{
_propertyName = propertyName;
AddBinding(propertyName, this, bindingPublisher);
}
~PropertyChangeSubscriberBase()
{
RemoveBinding(_propertyName);
}
public void Unbind()
{
RemoveBinding(_propertyName);
}
#region Static Fields
private static List<string> _bindingNames = new List<string>();
private static List<PropertyChangeSubscriberBase> _subscribers = new List<PropertyChangeSubscriberBase>();
private static List<PropertyChangePublisherBase> _publishers = new List<PropertyChangePublisherBase>();
#endregion
#region Static Methods
private static void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
string propertyName = args.PropertyName;
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
var subscriber = _subscribers[i];
subscriber.Value = publisher.GetPropertValue(propertyName);
}
}
public static void AddBinding(string propertyName, PropertyChangeSubscriberBase subscriber, PropertyChangePublisherBase publisher)
{
if (!_bindingNames.Contains(propertyName))
{
_bindingNames.Add(propertyName);
_publishers.Add(publisher);
_subscribers.Add(subscriber);
if (!publisher.ContainsBinding(PropertyChanged))
publisher.PropertyChanged += PropertyChanged;
}
}
public static void RemoveBinding(string propertyName)
{
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
_bindingNames.RemoveAt(i);
_publishers.RemoveAt(i);
_subscribers.RemoveAt(i);
if (!_publishers.Contains(publisher))
publisher.PropertyChanged -= PropertyChanged;
}
}
#endregion
}
Actual class to use for subscribing to property change stuff:
public sealed class PropertyChangeSubscriber<T> : PropertyChangeSubscriberBase
{
private PropertyChangePublisherBase _publisher;
public new T Value
{
get
{
if (base.Value == null)
return default(T);
if (base.Value.GetType() != typeof(T))
throw new InvalidOperationException(String.Format("Property {0} on object of type {1} does not match the type Generic type specified {2}.", _propertyName, _publisher.GetType(), typeof(T)));
return (T)base.Value;
}
set { base.Value = value; }
}
public PropertyChangeSubscriber(string propertyName, PropertyChangePublisherBase bindingPublisher)
: base(propertyName, bindingPublisher)
{
_publisher = bindingPublisher;
}
}
Here is an example of the Class with properties that you wish to be notified about:
public class ExamplePublisher: PropertyChangedPublisherBase
{
private string _id;
private bool _testBool;
public string Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public bool TestBool
{
get { return _testBool; }
set
{
if (value.Equals(_testBool)) return;
_testBool = value;
RaisePropertyChanged("TestBool");
}
}
}
Here is an example of the Class that will be notified when the properties in the class above change:
public class ExampleReceiver
{
public PropertyChangeSubscriber<string> Id { get; set; }
public PropertyChangeSubscriber<bool> TestBool { get; set; }
public MyExampleClass(PropertyChangePublisherBase publisher)
{
Id = new PropertyChangeSubscriber<string>("Id", publisher);
TestBool = new PropertyChangeSubscriber<bool>("TestBool", publisher);
}
}

What is the best way of refreshing dependency tree of calculated properties?

I've got some classes here that all more or less rely on each other. The relationships form kinda like a dependency tree:
class A {
List<B> _bs = new List<B>();
public int ValueOfA {
get {
return _bs.Sum(p => p.ValueOfB);
}
}
class B {
List<C> _cs = new List<C>();
public int ValueOfB {
get {
return _cs.Where(p => p.ValueOfC > 1).Sum(p => p.ValuOfC);
}
}
class C {
public int ValueOfC { get; set }
}
So, whenever _bs, _cs or ValueOfC change, every property relating to them should be notified as has changed, too, and hence be recalculated.
What's the best way of consistently and reliably achieving this goal? Is there by any chance a way to do this automatically?
You'll want to implement INotifyPropertyChanged with your class C. In the set of ValueOfC, you'll fire the event:
class C : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int valueOfC;
public int ValueOfC
{
get { return valueOfC; }
set
{
valueOfC = value;
OnPropertyChanged(PropertyChanged);
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventHandler handler)
{
if (handler != null)
handler(this, new PropertyChangedEventArgs("ValueOfC"));
}
}
I just tested it, and it works perfectly.
Having a protected virtual method fire the event for you is just common practice.
As a side note, if you want to do something if the lists change, you may want to look into using a BindingList or an ObservableCollection.
EDIT
I've written up a small example that does refresh the whole tree:
public class Bank : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private BindingList<Customer> customers = new BindingList<Customer>();
public int Worth
{
get { return customers.Sum(cust => cust.FullBalance); }
}
public Bank()
{
customers.ListChanged += new ListChangedEventHandler(customers_ListChanged);
}
void customers_ListChanged(object sender, ListChangedEventArgs e)
{
Console.WriteLine("A customer has changed.");
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Worth"));
}
public void Add(Customer c) { customers.Add(c); }
}
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private BindingList<Account> accounts = new BindingList<Account>();
public int FullBalance
{
get { return accounts.Sum(acc => acc.Balance); }
}
public Customer()
{
accounts.ListChanged += new ListChangedEventHandler(accounts_ListChanged);
}
void accounts_ListChanged(object sender, ListChangedEventArgs e)
{
Console.WriteLine("An account has changed.");
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("FullBalance"));
}
public void Add(Account a) { accounts.Add(a); }
}
public class Account : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int balance = 0;
public int Balance
{
get { return balance; }
set
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Balance"));
}
}
}
class Program
{
static void Main(string[] args)
{
Account a1 = new Account() { Balance = 5 };
Account a2 = new Account() { Balance = 10 };
Account a3 = new Account() { Balance = 15 };
Customer c1 = new Customer(); c1.Add(a1); c1.Add(a2);
Customer c2 = new Customer(); c2.Add(a3);
Bank b = new Bank(); b.Add(c1); b.Add(c2);
Console.WriteLine();
a1.Balance += 100;
}
}
Now you can write something like if (e.ListChangedType == ListChangedType.ItemChanged) in the event handlers, or something similar.

Categories

Resources