I just recently discovered an INotifyPropertyChange interface. I managed to implement this interface in my clss and everything works fine. However I was wondering if it is possible to intercept this event in code and fire a function
Let's say that I have a function
DoStuff()
and I wan't to fire this function everytime property1, property2 or property3 changes.
Of course I could put this function in set block in my class but this is not a good idea(I think).
If you mean to internal method that'll handle this event you can do it by registering to the event in the class constructor. For example:
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string content;
public AnswerViewModel()
{
PropertyChanged += (sender, args) => DoStuff();
}
public string Content
{
get { return content; }
set
{
content = value;
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised
}
}
If the intercepting method belongs to other class:
public class PropertiesInterceptor
{
private readonly AnswerViewModel viewModel;
private readonly List<string> propertiesToIntercept =
new List<string> { "property1", "property2", "property3" };
public PropertiesInterceptor(AnswerViewModel viewModel)
{
this.viewModel = viewModel;
viewModel.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (propertiesToIntercept.Contains(args.PropertyName))
{
DoStuff();
}
}
private void DoStuff()
{
// Do something with viewModel
}
}
Intercept the PropertyChanged Event:
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
You could fire the method from a RaisePropertyChanged() method:
public int Property1
{
get { return this.property1; }
set
{
if (this.property1 != value)
{
this.property1 = value;
RaisePropertyChanged("Property1");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
DoStuff(); // Call DoStuff here.
}
Stealing Elisha's answer to answer your question in Merlyn's answer
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string property1;
private string property2;
private string propertyX;
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
DoStuff();
}
}
public string Property1
{
get { return content; }
set
{
property1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property1"));
}
}
public string Property2
{
get { return content; }
set
{
property2 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property2"));
}
}
public string PropertyX
{
get { return content; }
set
{
propertyX = value;
PropertyChanged(this, new PropertyChangedEventArgs("PropertyX"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised from Property1 or Property2
}
}
If the class DoStuff is in is a member you can do
private otherClass
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
otherClass.DoStuff();
}
}
Otherwise you can just have otherClass register a event on its own in your main code.
Did you need it to replace the existing NotifyPropertyChanged event handlers, or just get called when NotifyPropertyChanged is called?
If you mean the second, you can simply register an event handler
edit
You can add an event handler that gets called on NotifyPropertyChanged, checks if the property parameter is equal to Property1, Property2, or Property3, and only then forwards it to the actual function you want to call.
Related
Full solution: https://github.com/fallingsappy/portfolio/tree/master/DDrop
I have three collections. First one:
public class Series : INotifyPropertyChanged
{
private ObservableCollection<DropPhoto> _dropPhotosSeries;
public ObservableCollection<DropPhoto> DropPhotosSeries
{
get
{
return _dropPhotosSeries;
}
set
{
_dropPhotosSeries = value;
OnPropertyChanged(new PropertyChangedEventArgs("DropPhotosSeries"));
}
}
private bool _canDrawPlot;
public bool CanDrawPlot
{
get
{
return _dropPhotosSeries?.Where(x => x.Drop.RadiusInMeters != null).ToList().Count > 1 && _dropPhotosSeries?.Where(x => x.Drop.RadiusInMeters == null).ToList().Count == 0;
}
set
{
_canDrawPlot = value;
OnPropertyChanged(new PropertyChangedEventArgs("CanDrawPlot"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
Second:
public class DropPhoto : INotifyPropertyChanged
{
private Drop _drop;
public Drop Drop
{
get
{
return _drop;
}
set
{
_drop = value;
OnPropertyChanged(new PropertyChangedEventArgs("Drop"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
Last:
public class Drop : INotifyPropertyChanged
{
private double? _radiusInMeters;
public double? RadiusInMeters
{
get
{
return _radiusInMeters;
}
set
{
_radiusInMeters = value;
OnPropertyChanged(new PropertyChangedEventArgs("RadiusInMeters"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
I want to update (invoke?) property CanDrawPlot every time something is happens to two other collection (Drop and DropPhot.cs). For example, if DropPhotosSeries.Count goes lower then 2 I need to change CanDrawPlot to false. CanDrawPlot should update UI. Here is the XAML:
<TabItem IsEnabled="{Binding CurrentSeries.CanDrawPlot, ElementName=AppMainWindow,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" x:Name="SingleSeriesPlotTabItem" Header="График" >
<uc:ScatterPlot x:Name="SingleSeriesPlot" User="{Binding User, ElementName=AppMainWindow}" ParticularSeriesIndex="{Binding ParticularSeriesIndex, ElementName=AppMainWindow}"/>
</TabItem>
CurrentSeries is instantiated in MainWindowXaml.cs:
public static readonly DependencyProperty CurrentSeriesProperty = DependencyProperty.Register("CurrentSeries", typeof(Series), typeof(MainWindow));
public Series CurrentSeries
{
get { return (Series)GetValue(CurrentSeriesProperty); }
set
{
SetValue(CurrentSeriesProperty, value);
}
}
---------------UPDATE---------------
I changed my code accordingly to Rob's answer:
public class Series : INotifyPropertyChanged
{
public Series()
{
_dropPhotosSeries = new ObservableCollection<DropPhoto>();
_dropPhotosSeries.CollectionChanged += _dropPhotosSeries_CollectionChanged;
}
private void _dropPhotosSeries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanDrawPlot)));
}
private bool _canDrawPlot;
public bool CanDrawPlot
{
get
{
return _dropPhotosSeries?.Where(x => x.Drop.RadiusInMeters != null).ToList().Count > 1 && _dropPhotosSeries?.Where(x => x.Drop.RadiusInMeters == null).ToList().Count == 0;
}
set
{
_canDrawPlot = value;
OnPropertyChanged(new PropertyChangedEventArgs("CanDrawPlot"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
Now CanDrawPlot correctly notifies changes in DropPhoto Collection. But I need to invoke it also by changes in inner Drop class of DropPhotoSeries. Here what i did:
public class Drop : INotifyPropertyChanged
{
Series _series;
public Drop(Series series)
{
_series = series;
}
private double? _radiusInMeters;
public double? RadiusInMeters
{
get
{
return _radiusInMeters;
}
set
{
_radiusInMeters = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(_series)));
OnPropertyChanged(new PropertyChangedEventArgs("RadiusInMeters"));
}
}
}
its not working, what's wrong?
You need to hook up to CollectionChanged event of the ObservableCollection and in there you have to raise PropertyChanged for CanDrawPlot.
EXAMPLE:
Add constructor to the Series class and in the constructor instantiate the observable collection and subscribe to the CollectionChanged event.
public Series()
{
_dropPhotosSeries = new ObservableCollection<DropPhoto>();
_dropPhotosSeries.CollectionChanged += _dropPhotosSeries_CollectionChanged;
}
private void _dropPhotosSeries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanDrawPlot)));
}
I would like to cache some UI-specific properties only at the time an object is DataBound, if it gets bound.
Say I have
public interface IAmA<T> ()
{
T Value {get;set;}
}
public class MyString : IAmA<string>, INotifyPropertyChanged {
private string _value = String.Empty;
public virtual string Value
{
get
{
return this._value;
}
set
{
this._value = value;
OnPropertyChanged();
}
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Is it possible to implement an action or event to fire at the time the object is databound?
event Action OnDataBound { ... }
So I can detect, say, when
MyString myStrObj = new MyString("text");
myStrObj.OnDataBound += CacheUIElements();
Do.OtherStuff();
otherObj.DataSource = myStrObj; //causes CacheUIElements() to fire
You can very well do it in the property setter and call the Delegate.
In above case it will be for the OtherObj.DataSource. Because you want the Action to get triggered when you are setting the datasource. So it should be in the setter of whichever property where you want the action to trigger.
class OtherClass
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
OnDataBound();
}
}
}
If not, assuming OtherClass Implements OnPropertyChanged and triggers for DataSource like this, If its a framework element for sure it will implement NotifyPropertyChanged for DataSource because only then UI will refresh with the changes.
class OtherClass: INotifyPropertyChanged
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
}
}
}
Then you can subscribe to this event and check against the property name and trigger the OnDataBound(); e.g:
otherClassObj.PropertyChanged += OtherClassPropertyChanged()
private void OtherClassPropertyChanged(Obj sender, PropertyChangedEventargs e)
{
if(e.PropertyName = "DataSource")
{
OnDataBound();
}
}
I Have a base Class that Implements INPC named Bindable this is my Event
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
Everything Works fine but if I have two delegates for my PropertyChangedEventHandler, only once is fired.
public class FirstClass : Bindable
{
public FirstClass()
{
PropertyChanged += Delegate1;
}
Void Delegate1 ....
}
public class SecondClass : Bindable
{
private FirstClass _MyFirstClass
public FirstClass MyFirstClass
{
get { return _MyFirstClass; }
set { SetProperty(ref _MyFirstClass, value); }
}
public SecondClass()
{
MyFirstClass = new FirstClass();
MyFirstClass.PropertyChanged += Delegate2;
}
Void Delegate2 ....
}
In this simple only Delegate1 runs Delegate2 never happens.
How Can I do implment my method properly for trigger any delegate?
EDIT 1: Corrected some code and instantiation of Firsrtclass.
EDIT 2: Full Implementation
public class Bindable : INotifyPropertyChanged //, INotifyPropertyChanging
{
#region BindableBase
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
} // Clase que implementa INotifyPropertyChanged
The problem is that when you create a new instance of FirstClass and set it, the event already fires, before giving you a chance to subscribe to it.
You could move the contents of FirstClass' constructor to a new method and call that method after the events are bound.
Here is your code, fixed:
public class FirstClass : Bindable
{
public void Init()
{
PropertyChanged += Delegate1;
// other stuff
}
Void Delegate1 ....
}
public class SecondClass : Bindable
{
private FirstClass _MyFirstClass
public FirstClass MyFirstClass
{
get { return _MyFirstClass; }
set { SetProperty(ref _MyFirstClass, value); }
}
public SecondClass()
{
MyFirstClass = new FirstClass();
MyFirstClass.PropertyChanged += Delegate2;
MyFirstClass.Init();
}
Void Delegate2 ....
}
Your code is set up wrong for this to run correctly, at all.
If you just instantiate a FirstClass, you would expect just Delegate1 to fire, as no SecondClass object was created.
If you instantiate a SecondClass, the reference to FirstClass is null, so it should throw a NullReferenceException right away on the event registration. You need to either create a FirstClass when you create a SecondClass, or pass SecondClass a FirstClass instance in its constructor that you can then use to register for the event.
I have a simple POCO with a lot of Properties. To simplify things let´s assume the POCO looks like this:
public class Project
{
public int ProjectId {get; set;}
}
Now I want to create an Event that fires when the ProjectId is changed. What I have now is this:
public class Project
{
public int ProjectId {get; set;}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged (this, args);
}
}
Now I have to extend the Property in order to call the Eventhandler:
public class Project
{
private int mProjectId;
public int ProjectId
{
get { return this.mProjectId;}
set
{
this.mProjectId = value;
this.OnProjectChanged(EventArgs.Empty);
}
}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged(this, args);
}
}
I wonder if there is an easier way to attach the Eventhandler. Maybe some kind of annotation ? Like
public class Project
{
[OnChange("OnProjectChanged", EventArgs.Empty)]
public int ProjectId {get; set;}
public event EventHandler ProjectChanged;
private void OnProjectChanged(EventArgs args)
{
if (ProjectChanged != null) ProjectChanged (this, args);
}
}
Taking some ideas from the question I posted in the comments, you could implement an abstract base class which implements INotifyPropertyChanged. Each time you declare a property, you call SetField to trigger the PropertyChanged event.
This avoids two things:
1) Explicitly implementing INotifyProperty changed each time using the abstract class
2) Explicitly implementing a method to trigger the event each time, shortening the set code to one line.
abstract class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
class Project : ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetField(ref _name, value, "Name"); }
}
}
class TestRunner
{
public TestRunner()
{
Project p = new Project();
p.PropertyChanged += (o, e) =>
{
// Changed
};
p.Name = "Test";
}
}
I'm trying to bind some XAML code to a property in my ViewModel.
<Grid Visibility="{Binding HasMovies, Converter={StaticResources VisibilityConverter}}">
...
</Grid>
My ViewModel is setup like this:
private bool _hasMovies;
public bool HasMovies
{
get { return _hasMovies; }
set { _hasMovies = value; RaisePropertyChanged("HasMovies"); }
}
In the constructor of the ViewModel, I set the HasMovies link:
MovieListViewModel()
{
HasMovies = CP.Connection.HasMovies;
}
in CP:
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
private ObservableCollection<Movie> _movies;
public ObservableCollection<Movie> MovieList
{
get { return _movies; }
set
{
_movies = value;
RaisePropertyChanged("MovieList");
RaisePropertyChanged("HasMovies");
_movies.CollectionChanged += MovieListChanged;
}
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
What am I doing wrong? How should I change this binding so that it reflects the current state of CP.Connection.HasMovies?
Either directly expose the object in the ViewModel and bind directly through that (so that the value is not just copied once which is what happens now) or subscribe to the PropertyChanged event and set HasMovies to the new value every time it changes in your source object.
e.g.
CP.Connection.PropertyChanged += (s,e) =>
{
if (e.PropertyName = "HasMovies") this.HasMovies = CP.Connection.HasMovies;
};
First of all, the setter for a collection type, such as your MovieList property, is not called when you change the content of the collection (ie. Add/Remove items).
This means all your setter code for the MovieList property is pointless.
Secondly, it's very silly code. A much better solution, is to use NotifyPropertyWeaver. Then your code would look like this, in the viewmodel:
[DependsOn("MovieList")]
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
Alternatively you would have to add a listener for the CollectionChanged event when you initialize the MovieList property the first time (no reason to have a backing property, really really no reason!), and then call RaisePropertyChanged("HasMovies") in the event handler.
Example:
public class CP : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CP()
{
MovieList = new ObservableCollection<Movie>();
MovieList.CollectionChanged += MovieListChanged;
}
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}