Automatically Raise PropertyChanged when an inner object property got changed - c#

I got a scenario like this
Class Parent
{
Property A;
}
Class A
{
Property X
}
How can I get a PropertyChangedNotification on Property A when X changes? I don’t want to refer ‘Parent’ in class A or any kind of event which spoils my decoupling. What I basically want is to make the Parent.IsDirty==true. This is a very simplified version of my story, I got tens of classes like Parent, so I am looking for some generic way to handle this.
Please note that this is not the actual code. I got all INotifyPropertyChanged implementation. I am just wondering any easy mechanism like RaisePropertyChanged("A.X")

You can try to register the propertychanged event in the parent class. In the constructor you can subribe to the event:
public Parent()
{
A.OnPropertyChanged += OnAPropertyChanged;
}
void OnAPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "X")
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("A"))
}
hope this helps...

I don't know if this is the best practice but:
what about something like this:
private Movie movie;
public Movie Movie
{
get { return movie; }
set
{
var oldProperties = typeof(Movie).GetProperties();
foreach (var property in oldProperties)
{
if (property.GetValue(movie).GetHashCode() != value.GetType().GetProperty(property.Name).GetValue(value).GetHashCode())
{
RaisePropertyChanged(property.Name);
}
}
movie = value;
}
}
surely you can take it outside to an external helper that take the 2 objects and the event handler

Related

INotifyPropertyChanged does not work from class held in ObservableCollection<Class>

I have a class, "BaseClass" that implements INotifyPropertyChanged and has the following:
BaseClass:
private bool isOn;
public bool IsOn
{
get { return isOn; }
set
{
isOn = value;
RaisePropertyChanged("BaseClass:IsOn");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I then have a class, "DIClass" that also implements INotifyPropertyChanged. It also has an ObservableCollection<BaseClass>:
DIClass:
public ObservableCollection<BaseClass> ClassesOfA;
private string iNPCTest;
public string INPCTest
{
get { return iNPCTest; }
set
{
iNPCTest = value;
RaisePropertyChanged("DIClass: INPCTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
My ViewModel holds an intance of "DIClass" and registers to it's PropertyChanged event. When I set the value of INPCTest in "DIClass", the ViewModel 'captures' the event correctly. However when I updated the IsOn property within the ObservableCollection, as below, the event is not picked up in the ViewModel.
ClassesOfA[0].IsOn = true;
Why is the INPC interface not working with the nested property? The question and answer here seems quite relevant, but I can't figure it out.
EDIT: additional explanation and code:
I can register to the PropetyChanged events of the ObservableCollection's items, as such:
ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;
ClassesOfA[1].PropertyChanged += DIClass_PropertyChanged;
However, this still does not bubble up to notify my ViewModel, that a property of my DIClass's ObservableCollection<BaseClass> has changed. I want to use INPC to bubble up event information / property updates up via MVVM layers. But I want to "wrap" them to make my classes cleaner/ less properties lying around
EDIT:
I add this "sketch" of my problem/scenario, with basic naming to make it easy:
To answer your question: This is by design.
ObservableCollection has two events:
CollectionChanged: Fires when the collection changes, e.g. collection.Add( item )
PropertyChanged: Fires when the property changes, e.g. collection = new ObservablecCollection<T>();
I think you need no ObservableCollection, because - as far as I understand your question - you want to observe the changes of the properties of the items in the collection. To achieve that you need to register to each observed item's PropertyChanged like this:
public List<BaseClass> Collection {get;set;}
public void InitializeCollection( IEnumerable<BaseClass> baseClassCollection){
Collection = new List<BaseClass>();
foreach(var item in baseClassCollection){
item.PropertyChanged += MethodToCallOnPropertyChanges;
Collection.Add( item );
}
}
public void MethodToCallOnPropertyChanges(object sender, PropertyChangedEventArgs e){
//react to any property changes
doSomething();
//react to specific properties
if(e != null && e.PropertyName.Equals("isOn"))
doSomethingOtherStuff();
}
This can be very annoying and can causes some other problems.
If I would come across this, I would think about redesigning the ViewModels and the UI. I would try to have an UI which is bound to each BaseClass item. For example, if I have an ListView I would provide an ItemTemplate in which the BaseClass item is bound. Doing so would prevent the need of registering to each item's PropertyChanged.
My suggestion is that you could create a customized ObservableCollection class that raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged.
I made a simple demo and you that you could check:
public class DIClass : INotifyPropertyChanged
{
public ExObservableCollection<BaseClass> ClassesOfA
... other code...
}
public sealed class ExObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public ExObservableCollection()
{
CollectionChanged += AllObservableCollectionCollectionChanged;
}
public ExObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void AllObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
Then you could use the ExObservableCollection class in the DIClass object. When the properties inside the BaseClass changes, the UI will be updated.
Update:
Finally, I found out the unexpected behavior you mentioned based on the complex sample. The ExObservableCollection class works well and fires the property changed event correctly.
The key point is you think if the property change event in baseclass is fired then it will
trigger the property change event in DIClass as well, right? I have to say that is not correct. The property change event only fires in the current class. It won't pass to the parent class unless you handle it in the parent class. It fired only once and notify the UI when the target property is changed.
If I understand your scenario correctly, you want to change the ToggleButton's status when the same property in BaseClassobject is changed. But the ToggleButtons are bind to VMData objects so that you need to get notified when the BaseClass objects are changed in the DIClass objects. So you want the the property change event of BaseCasss triggers the property change event of the DIClass.
Handling the property changed event of BaseClass in the DIClass object is the correct way to do what you want. It's the same like handling DIClass event in the ViewModel. But you don't want it since there might be many objects.
Then the first version of your sample is the recommended way to achieve what you want by triggering the property changed event of the DIClass on your own.

EventHandler is null when trying to fire event in WPF

Issue
I have created an event in the class LoginVM which looks like the following:
public class LoginVM : INotifyPropertyChanged
{
public event EventHandler<string> PasswordSet;
}
Also in this class I have a piece of code which fires this event:
public class LoginVM : INotifyPropertyChanged
{
public event EventHandler<string> PasswordSet;
private void PopulateLatestServer()
{
try
{
string SERVER_ID = Registry.GetValue(#"HKEY_CURRENT_USER\SOFTWARE\PODIA", "LATESTSERVER", null).ToString();
BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
setServerURL(latestserver.ServerURL, false);
Username = latestserver.Username;
PasswordSet(this, latestserver.Password);
}
catch (Exception)
{
Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
}
}
}
I have another class which is called LoginV and in there I create an instance of the class and subscribe to the event:
public partial class LoginV : MetroWindow
{
public LoginV()
{
InitializeComponent();
LoginVM _loginVM = new LoginVM();
this.DataContext = _loginVM;
_loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
}
private void _loginVM_PasswordSet(object sender, string e)
{
passwordBox.Password = e;
}
As you can probably tell I am trying to trigger an event from the ViewModel to the View but every time I trigger the event from the ViewModel, PasswordSet is null and errors.
An event is null when there's no listener to the event.
private void RaisePasswordSet(String pass) {
YourEventArgs args = new YourEventArgs(pass);
if(PasswordSet != null) PasswordSet(this, args);
}
Your issue is that when you try to raise the event no one listen to it yet.
It's a good idea to initialize the password in the constructor for LoginVM as you did. That's when initialization ought to happen. Ordinarily, you'd set a property and the binding in the XAML would take care of updating the control. No need for an event on the VM. But this is a password box, so you can't bind it, and the event you wrote is The Right Thing.
But in your implementation, that leaves you with this sequence of events:
Create VM
VM raises PasswordSet in its constructor -- without checking to see if there are any handlers.
View assigns VM to DataContext
View adds handler to PasswordSet event
And you get an exception at step 2, because you didn't check for handlers.
Here's what you do.
In the VM or anywhere, always use this pattern for raising events:
C# <= 5:
protected void OnPasswordSet(String e)
{
var handler = PasswordSet;
if (handler != null)
{
handler(this, e);
}
}
C#6
protected void OnPasswordSet(String e) => PasswordSet?.Invoke(this, e);
Either:
private void PopulateLatestServer()
{
try
{
string SERVER_ID = Registry.GetValue(#"HKEY_CURRENT_USER\SOFTWARE\PODIA", "LATESTSERVER", null).ToString();
BDO_SERVERS latestserver = SavedServers.Where(a => a.Server_ID == SERVER_ID).FirstOrDefault();
setServerURL(latestserver.ServerURL, false);
Username = latestserver.Username;
OnPasswordSet(latestserver.Password);
}
catch (Exception)
{
Global.WriteLog("Could not find last logged in server.", EventLogEntryType.Warning);
}
}
Can't crash now. Or at least not the same way as last time.
Problem number two: How do you update the view initially?
Easy: Take whatever's in the view's PasswordSet handler, move it into a protected method, and call that in both places. This looks a little verbose since it's only a one-liner, but it's nice to have things rolled into neatly labeled units. If that code were more complicated, you'd absolutely want not to be copying and pasting it. If it gets more complicated a year from now, you won't have to waste any time re-parsing your the old code.
public partial class LoginV : MetroWindow
{
public LoginV()
{
InitializeComponent();
LoginVM _loginVM = new LoginVM();
this.DataContext = _loginVM;
_loginVM.PasswordSet += new EventHandler<string> (_loginVM_PasswordSet);
UpdatePassword();
}
protected void UpdatePassword()
{
passwordBox.Password = e;
}
private void _loginVM_PasswordSet(object sender, string e)
{
UpdatePassword();
}
Option number two: Keep OnPasswordSet() as shown above, but instead of having the view manually update the password in the constructor, have the LoginVM require a PasswordSet handler as a parameter. This isn't the way I would do it; constructor parameters like this get on my nerves. But that may just be an irrational prejudice on my part. This way makes more clear the fact that the owner needs to handle that event to use the class, and "provide a suitable event handler" becomes the only thing the consumer needs to do in order to use the thing. The less a consumer needs to know about your class's internals, the better, for obvious reasons. Platonically ideal design would be when programmers who don't think at all can make casual glib assumptions about your class, and not end up on Stack Overflow begging somebody to read the documentation to them out loud. We'll never get there, though.
public class LoginVM : INotifyPropertyChanged
{
public LoginVM(EventHandler<string> passwordSetHandler)
{
if (passwordSetHandler != null)
{
PasswordSet += passwordSetHandler;
}
PopulateLatestServer();
}
// If the consumer doesn't want to handle it right way, don't force the issue.
public LoginVM()
{
PopulateLatestServer();
}
A third option is to set up explicit add/removes for the event, and raise the event when the handler comes in:
public class LoginVM : INotifyPropertyChanged
{
private event EventHandler<string> _passwordSet;
public event EventHandler<string> PasswordSet
{
add
{
_passwordSet += value;
// ...or else save latestServer in a private field, so here you can call
// OnPasswordSet(_latestServer.Password) -- but since it's a password,
// best not to keep it hanging around.
PopulateLatestServer();
}
remove { _passwordSet -= value; }
}

How to detect C# control name changed?

I created a composite control using C# Windows Forms Control Library, when I use the new control in a test program, I want to find a way to detect when did the name of new control changed at design time, what should I do?
You can use the IComponentChangeService (in System.ComponentModel.Design) like in this example:
public class MyControl : UserControl
{
public event EventHandler NameChanged;
protected virtual void OnNameChanged()
{
EventHandler handler = NameChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
protected override void OnCreateControl()
{
base.OnCreateControl();
IComponentChangeService changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
if (changeService == null) return; // not provided at runtime, only design mode
changeService.ComponentChanged -= OnComponentChanged; // to avoid multiple subscriptions
changeService.ComponentChanged += OnComponentChanged;
}
private void OnComponentChanged(object sender, ComponentChangedEventArgs e)
{
if (e.Component == this && e.Member.Name == "Name")
OnNameChanged();
}
}
This service is only provided in design mode, not at runtime.
I unsubscribe and subscribe again to the ComponentChanged event (to avoid multiple subscriptions).
In the event handler OnComponentChanged I check if my name has changed and raise the NameChanged event.
There is no event "OnNameChanged" and the Name-Property itself is not virtual and therefore not overrideable... I'm afraid, that there is no sure way...
Some suggestions:
Define your own Name-Property with "new", but inherited classes will not automatically overtake this and - even worse - casts to Control and usage of Control.Name will just work around your property...
Define a virtual "MyName"-Property and use this only to write through onto the Control's Name-Property. Still a call on Control.Name will fly by, but at least you can enforce the usage of MyName...
Puffer the Name within your constructor and define a Timer to look regularely if something has changed
Alltogether I must admit, that I would not use any of those... I don't know what exactly you want to achieve, but I'd try to find a different approach to reach this goal...
public MyControl : UserControl
{
public new string Name
{
get { return base.Name; }
set
{
base.Name = value;
// handle the name change here....
}
}
}

Is there an event that will raise when a variable has changed?

I know that the proper course of action is to create a class, make an event in said class, then use said class in another part of the program where the variable would be changed (e.g. Use said class in the equal button of a calculator, so that an event handler knows that a variable has been changed because an event would be fired). But, trying to streamline my code, I'm looking for a way to monitor a variable directly without an infinite loop/timer and raise an event when it changes. Is there such a thing? If not, are there any other alternatives aside for the one I mentioned?
Here is what I'm trying to mention:
Code that changes a variable -> Another piece of code (not a loop) watching for changes then throws an event if there are changes -> Event handler
You can't do it with fields but with properties:
class SomeClass : INotifyPropertyChanged
{
private string someProperty;
public string SomeProperty
{
get { return someProperty; }
set { someProperty = value; OnPropertyChanged(); }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
}
Edit (.net 4.0)
class SomeClass : INotifyPropertyChanged
{
private string someProperty;
public string SomeProperty
{
get { return someProperty; }
set { someProperty = value; OnPropertyChanged("SomeProperty"); }
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
}
Edit (Winforms example)
public partial class Form1 : Form
{
private SomeClass theObject = new SomeClass(); //keep a reference of the object.
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//here we do the binding... we want the 'Text' Property of the control to change if the 'SomeProperty' changes OnPropertyChanged
textBox1.DataBindings.Add("Text",theObject,"SomeProperty",false,DataSourceUpdateMode.OnPropertyChanged);
}
private void button1_Click(object sender, EventArgs e)
{
theObject.SomeProperty = "This works!"; //just a test button that changes the property...
}
}
Though I know that it is an often undesired practice here at Stack Overflow, you may find my project NMF Expressions interesting: http://nmfexpressions.codeplex.com/
Basically, the project aims to allow you to write such as follows:
var myObservedVariable = Observable.Expression(() => whatever you want)
In this scenario, myObservedVariable will be of INotifyValue<T> which provides a ValueChanged event. Alternatively, you can use the query syntax. Alternatively, you may have a look at other similar frameworks like Obtics, BindableLINQ or ContinuousLINQ. A comparison of the latter was done in Bindable Linq vs. Continuous Linq.
However, this only works under pretty strong assumptions like all the object models that you are working with completely support INotifyPropertyChanged and INotifyCollectionChanged.
In addition to #Florian's answer, you can inject an implementation of the INotifyPropertyChanged interface at compile time using Fody.PropertyChanged.

how to catch changes in a member variable? (C#)

This seems to be basics of the language, but I do not understand how is this accomplished in .Net. I have a member variable in a class, say a bool _isCommitted. I want something to happen whenever _isCommitted is true. Something like this:
//Whenever _isCommitted == true()
{
Foo()
}
Basically like an event, but here it is my variable. How to? Many thanks..
This is normally done through properties and a backing private field. You need to ensure you only ever access through the property.
private bool _isCommitted;
public bool IsCommitted
{
get { return _isCommitted; }
set
{
if(value)
{
//do something
}
_isCommitted = value;
}
}
At the most basic level, you can create an event in your class:
public delegate void MyHandler(bool b);
public event MyHandler CommittedChanged;
Now people can subscribe to your event like so:
public void SomeHandlerMethod(bool b) { ... }
...
someInstance.CommittedChanged += SomeHandlerMethod;
someInstance.CommittedChanged += ASecondHandlerMethod;
someInstance.CommittedChanged += x => { /* inline handler using lambda */ };
A user can unregister his event handler this way:
someInstance.CommittedChanged -= SomeHandlerMethod;
And wherever you decide to change your variable, you will follow it up with:
if (CommittedChanged != null) CommittedChanged(_isCommitted);
This will call everyone who has registered a function with your event.
Having said this, there are plenty of improvements that you can do. First, make _isCommitted into a property, and do the event callback in its setter. This way, you won't forget to call the handlers.
public IsCommitted {
get { return _isCommitted; }
set {
_isCommitted = value;
if (CommittedChanged != null) CommittedChanged(_isCommitted);
}
}
Read more about events here.
This is enough to get you going. However, if you delve further into the C# framework, you will find a standardized way of using this event framework inside of the System.ComponentModel namespace. Sepcifically, the interface INotifyPropertyChanged, which ties neatly into a more generic event system that also plays well with some of Microsoft's own technologies, such as WPF, allowing GUI elements to pick up on changes to your class automatically. Read more about INotifyPropertyChanged here.
You basically need PropertyChangedEvent PropertyChangedEventHandler Delegate
I think C# properties is what you need.
private bool _isCommitted;
public bool IsCommitted
{
get { return _isCommitted; }
set { if(value){/*DO SOMETHING HERE*/}
_isCommitted = value; }
}

Categories