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";
}
}
Related
I have trouble with the ViewModel of my MVVM pattern-code.
I have a bunch of measurements and a bunch of rules to evaluate the measurements, stored in the classes Rule and Measurement. In my main class MyClass I store my Rules and Measurements then in ObservableCollections (OC) (and connected to a DataGrid).
For all n Rules I create n CollcetionOfEvaluators in one OC and pass the respective rule and all the measurements to each single one.
In each CollectionOfEvaulators I create for the one rule and the m Measurements m Evaluators in an OC.
The Evaluators take the one Rule and the one Measurement and gives back a bool if or if not the respective Measurement passes the respective Rule.
I then have a ListView that displays for each Rule a DataGrid that shows for every Measurement if it passed the Rule.
My problem is to make the Evaluator class to fire the OnPropertyChanged method, if I change the properties of one of the measurements in MyClass. How can I pass the info basically from one child to another child's child? When I play around with the DataGrid of the Evaluators, for example click on the header to rearrange it, it works. So I guess the problem is the c# code not the xaml. So I will leave it out for once. All the bindings are Mode=TwoWay (except the bool, since it has no setter) and UpdateSourceTrigger=PropertyChanged.
I tried to sketch the problem:
This is my code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;
namespace demo
{
public class MyClass : INotifyPropertyChanged
{
public class Measurement : INotifyPropertyChanged
{
private double? myValue1;
public double? MyValue1
{
get { return myValue1; }
set
{
myValue1 = value;
OnPropertyChanged("MyValue1");
}
}
private double? myValue2;
public double? MyValue2
{
get { return myValue2; }
set
{
myValue2 = value;
OnPropertyChanged("MyValue2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EvaluationRule
{
public EvaluationRule(double Value1Min, double Value2Min)
{
this.Value1Min = Value1Min;
this.Value2Min = Value2Min;
}
public double Value1Min;
public double Value2Min;
}
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement Measurement, EvaluationRule Rule)
{
this.Rule = Rule;
this.Measurement = Measurement;
}
public EvaluationRule Rule;
private Measurement measurement;
public Measurement Measurement
{
get { return measurement; }
set
{
measurement = value;
OnPropertyChanged("Measurement");
}
}
public bool IsApproved
{
get
{
if (measurement.MyValue1 > Rule.Value1Min
&& measurement.MyValue2 > Rule.Value2Min)
{
return true;
}
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CollectionOfEvaluators : INotifyPropertyChanged
{
public CollectionOfEvaluators(EvaluationRule Rule, ObservableCollection<Measurement> Measurements)
{
this.Rule = Rule;
this.Measurements = Measurements;
var Evaluators = new ObservableCollection<Evaluator>();
foreach (var _measurement in Measurements)
{
var _evaluator = new Evaluator(_measurement, this.Rule);
Evaluators.Add(_evaluator);
}
}
public EvaluationRule Rule;
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<Evaluator> evaluators;
public ObservableCollection<Evaluator> Evaluators
{
get { return evaluators; }
set
{
evaluators = value;
OnPropertyChanged("Evaluators");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<EvaluationRule> rules;
public ObservableCollection<EvaluationRule> Rules
{
get { return rules; }
set
{
rules = value;
GetCollection();
}
}
private ObservableCollection<CollectionOfEvaluators> collection;
public ObservableCollection<CollectionOfEvaluators> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public void GetCollection()
{
var Collection = new ObservableCollection<CollectionOfEvaluators>();
foreach (var _rule in rules)
{
var _collection = new CollectionOfEvaluators(_rule, Measurements);
Collection.Add(_collection);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You must delegate the event. Evaluator should listen to the PropertyChanged event of its aggregated Measurement. The handler of this event can then raise the Evaluator.PropertyChanged event in response:
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement measurement, EvaluationRule rule)
{
this.Rule = rule;
this.Measurement = measurement;
this.Measurement.PropertyChanged += OnMeasurementPropertyChanged;
}
public void OnMeasurementPropertyChanged(object sender, PropertyChangedEventAgrs e)
{
OnPropertyChanged(nameof(this.Measurement));
}
private Measurement measurement;
public Measurement Measurement
{
get => this.measurement
set
{
this.measurement = value;
OnPropertyChanged(nameof(this.Measurement));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note that you got a spelling error when naming your class. It's Measurement - you missed an 'a'. Also parameter names should always be lowercase.
Here's what I have now:
class MyClass
{
public string status;
private void DoSomething()
{
// do something and make change to this.status;
}
}
class MyClass2
{
public List<MyClass> MyClassLst;
private void DetectChangeInList()
{
// if the status property of an item in this.MyClassLst changed, remove this item from list
}
}
I have a List<MyClass>, and each of the MyClass will do some work and change the property status. I want to detect if any of the MyClass has its status changed and remove this item from MyClassLst.
I read about something on event but not very clearly about how to make it work.
If you need to be notified about changes to individual properties of each MyClass instance, it's not something that can magically happen.
Your MyClass will have to be responsible for firing an event whenever something changes (usually the PropertyChanged event, i.e. the INotifyPropertyChanged interface), and the other class will have to attach a handler to each item in the list to get notified.
C#6 has a couple of syntactic improvements which simplify this a bit, but you still have lots of work to do for each property:
public class Model : INotifyPropertyChanged
{
// this is the event which gets fired
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// you need to raise the event in each property's setter
private _someValue;
public string SomeValue
{
get { return _someValue; }
set { if (value != _someValue) { _someValue = value; OnPropertyChanged(); } }
}
private _anotherVal;
public string AnotherValue
{
get { return _anotherVal; }
set { if (value != _anotherVal) { _anotherVal = value; OnPropertyChanged(); } }
}
}
In your case, it would be:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Never use public fields!
// Status should be a property which gets and sets
// the actual private backing field (_status).
private _status;
public string Status
{
get { return _status; }
set { if (value != _status) { _status = value; OnPropertyChanged(); } }
}
}
You will also most likely want to change List<MyClass> into your own implementation of ICollection<T> which will attach and detach these handlers as you add or remove items. It is usually done by deriving from Collection<T> and overriding relevant methods. If you are not comfortable with that, a slightly simpler approach might be to make the list private and expose Add/Remove and similar methods where you will attach/detach to the PropertyChanged event.
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 can't seem to find a simple, concrete explanation of how to bind controls in a WinForms app to nested objects using data binding. For example:
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
private MyInner _Inner;
public MyInner Inner
{
get { return _Inner; }
set
{
_Inner = value;
OnPropertyChanged("Inner");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue
{
get { return _SomeValue; }
set
{
_SomeValue = value;
OnPropertyChanged("SomeValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now imagine a form with just two textboxes, the first for Name and the second for Inner.SomeValue. I'm easily able to get binding to work against Name, but Inner.SomeValue is flaky. If I populate the object and then set up the binding, it shows Inner.SomeValue in the textbox but I can't edit it. If I start from a fresh object without initializing Inner, I can't seem to get data to stick in Inner.SomeValue.
I've checked all over MSDN, all over StackOverflow, and dozens of searches with different keywords. Everyone wants to talk about binding to databases or DataGrids, and most examples are written in XAML.
Update: I've tried Marc's full test harness and have partial success. If I hit the "all change!" button, I seem to be able to write back to the inner object. However, starting with MyObject.Inner null, it doesn't know how to create an inner object. I think for now, I can work around it by just making sure my inner references are always set to a valid object. Still, I can't help feeling like I'm missing something :)
Hmm - an excellent question; I've done lots of data-binding to objects, and I would have sworn that what you are doing should work; but indeed it is very reluctant to notice the change to the inner object. I've managed to get it working by:
var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
Not ideal, but it works. Btw; you might find the following utility methods useful:
public static class EventUtils {
public static void SafeInvoke(this EventHandler handler, object sender) {
if(handler != null) handler(sender, EventArgs.Empty);
}
public static void SafeInvoke(this PropertyChangedEventHandler handler,
object sender, string propertyName) {
if(handler != null) handler(sender,
new PropertyChangedEventArgs(propertyName));
}
}
Then you can have:
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name { get { return _Name; } set {
_Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
private MyInner _Inner;
public MyInner Inner { get { return _Inner; } set {
_Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue { get { return _SomeValue; } set {
_SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
And in the bargain it fixes the (slim) chance of a null-exception (race-condition).
Full test rig, to iron out kinks (from comments):
using System;
using System.ComponentModel;
using System.Windows.Forms;
public static class EventUtils {
public static void SafeInvoke(this PropertyChangedEventHandler handler, object sender, string propertyName) {
if(handler != null) handler(sender, new PropertyChangedEventArgs(propertyName));
}
}
class MyObject : INotifyPropertyChanged
{
private string _Name;
public string Name { get { return _Name; } set { _Name = value; PropertyChanged.SafeInvoke(this,"Name"); } }
private MyInner _Inner;
public MyInner Inner { get { return _Inner; } set { _Inner = value; PropertyChanged.SafeInvoke(this,"Inner"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
class MyInner : INotifyPropertyChanged
{
private string _SomeValue;
public string SomeValue { get { return _SomeValue; } set { _SomeValue = value; PropertyChanged.SafeInvoke(this, "SomeValue"); } }
public event PropertyChangedEventHandler PropertyChanged;
}
static class Program
{
[STAThread]
public static void Main() {
var myObject = new MyObject();
myObject.Name = "old name";
// optionally start with a default
//myObject.Inner = new MyInner();
//myObject.Inner.SomeValue = "old inner value";
Application.EnableVisualStyles();
using (Form form = new Form())
using (TextBox txtName = new TextBox())
using (TextBox txtSomeValue = new TextBox())
using (Button btnInit = new Button())
{
var outer = new BindingSource { DataSource = myObject };
var inner = new BindingSource(outer, "Inner");
txtName.DataBindings.Add("Text", outer, "Name");
txtSomeValue.DataBindings.Add("Text", inner, "SomeValue");
btnInit.Text = "all change!";
btnInit.Click += delegate
{
myObject.Name = "new name";
var newInner = new MyInner();
newInner.SomeValue = "new inner value";
myObject.Inner = newInner;
};
txtName.Dock = txtSomeValue.Dock = btnInit.Dock = DockStyle.Top;
form.Controls.AddRange(new Control[] { btnInit, txtSomeValue, txtName });
Application.Run(form);
}
}
}
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.