Execute method in viewmodel when property changed - c#

I have some ViewModel with string property Name
My ViewModel inherit from ViewModelBase : INotifyPropertyChanged
...
private string _name;
public string Name
{
get { return _name; }
set { SetField(ref _name, value, "Name"); }
}
...
public class ViewModelBase : 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;
}
}
I want to execute some logic when my property Name change its value (on user input). My ViewModel will have alot of such properies and i will have alot of ViewModels with same property processing.
How should i run method that process chaneged propery in a right way?
Should i subscribe on PropertyChanged event in my ViewModel, then use switch on string property name to detect actual changed property and then use it value?
Or should i just run my method from setter?
Is there any patterns for such interactions?

Since your SetField method returns true if the property has changed, I would call my method in the setter if true is returned.
...
public string Name
{
get { return _name; }
set
{
if (SetField(ref _name, value, "Name"))
MyMethod();
}
}
...

Related

Overriding RaisePropertyChanged to execute additional code

In my model i have properties which i want to execute some extra code for when the property gets changed. I want to add the new value and the property name to my database. I also want to keep a list of current alarms (value is equal to true).
public Boolean ActionAlarmLowLow
{
get
{
return _ActionAlarmLowLow;
}
set
{
if (value != this._ActionAlarmLowLow)
{
Boolean oldValue = _ActionAlarmLowLow;
_ActionAlarmLowLow = value;
RaisePropertyChanged("ActionAlarmLowLow", oldValue, value, true);
}
}
}
How can i do this properly?
I am wondering if i should add two lines of code to the property:
DB.Log.addLogItem("ActionAlarmLowLow", value);
AlarmList.UpdateItem("ActionAlarmLowLow", value);
Or if i can somehow extend/override the RaisePropertyChanged and do some extra stuff elsewhere for specific properties. I.e calling something called
RaisePropertyChangedWriteToDbUpdateAlarmList();`
Yes it is quite simple, all you need is to create a base class with INotifyPropertyChanged and call whatever you want inside.
public abstract class NotifyPropertyChangedBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> expression)
{
var memberExpression = (MemberExpression) expression.Body;
var propertyName = memberExpression.Member.Name;
var handler = PropertyChanged;
if (handler != null)
{
// Do your common actions here, before property change notification is fired
handler(this, new PropertyChangedEventArgs(propertyName));
// Do your common actions here, after property change notification is fired
}
}
}
public class MyClass : NotifyPropertyChangedBase
{
public Boolean ActionAlarmLowLow
{
get
{
return _ActionAlarmLowLow;
}
set
{
if (value != this._ActionAlarmLowLow)
{
_ActionAlarmLowLow = value;
OnPropertyChanged(() => this.ActionAlarmLowLow);
}
}
}
}

WPF: UI not updated on value change in bound object

I am relatively new to WPF and having a problem with data binding. I am binding a dependency property of a user control to a class property in my code behind. During intantiation of the class entity in my code behind the UI is sucessfully updated through INotifyPropertyChanged. However when subsequently changing the value in my code behind the OnPropertyChangedEventHandler fires, but the OnPropertyChanged method does no longer answer to this. Below the details. It would be great if someone could give me some hints what I am doing wrong.
I implemented a user control that I am binding to a property CurrentAccProp.DiscountRate of my partial class in code behind:
<local:doubleUEdit x:Name="InterestRate" LabelField="Interest rate" MinimumValue="0" MaximumValue="1" FormatStringForNumbers="P2" IncrementSize="0.01" UncertainValue="{Binding ElementName=RibbonWindow, Path=CurrentAccProp.DiscountRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The class of which CurrentAccProp is an instance implements INotifyPropertyChanged to inform the UI about value changes
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
OnPropertyChanged is called in the setter for the DiscountRate property:
doubleU discountingrate;
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged("DiscountingRate");
}
}
The property of my user control that I am binding to is implemented as a dependency property:
//Property for data binding to doubleU
[Description("The formatstring for the double boxes"), Category("Default")]
public doubleU UncertainValue
{
get { return new doubleU(0, 0, (double)doubleUSupremum.Value, (double)doubleUSupremum.Value); }
set { doubleURangeSlider.LowerValue = value.Interval.Infimum; doubleURangeSlider.HigherValue = value.Interval.Supremum; doubleUInfimum.Value = value.Interval.Infimum; doubleUSupremum.Value = value.Interval.Supremum; }
}
public static readonly DependencyProperty UncertainValueProperty =
DependencyProperty.Register(
"UncertainValue",
typeof(doubleU),
typeof(doubleUEdit),
new PropertyMetadata(default(doubleU), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
doubleUEdit MydblUEdt = d as doubleUEdit;
MydblUEdt.UncertainValue = e.NewValue as doubleU;
}
When I am instantiating CurrentAccProp in my code behind the OnPropertyChanged informs the UI and the value is updated.
AccountingProperties currentaccprop = new AccountingProperties(new doubleU(0.0));
public AccountingProperties CurrentAccProp { get { return currentaccprop; } set { currentaccprop = value; } }
However, when I later update the value of DiscountRate
CurrentAccProp.DiscountRate = new doubleU(1.0);
OnPropertyChanged gets executed, but the UI is no longer updated. Does anyone have a clue what I am doing wrong here?
The typo pointed out by HighCore and zaknotzach was indeed the problem. Thanks for your help! I implemented the approach in the thread referenced by HighCore to avoid this and it works like a charm. Below the changed AccountingProperties class from which CurrentAccProp is instantiated for reference:
public class AccountingProperties : INotifyPropertyChanged
{
doubleU discountrate;
public doubleU DiscountRate
{
get {return discountrate;}
set { SetField(ref discountrate, value, () => DiscountRate); }
}
//------------------------------------------------
//constructors
public AccountingProperties(doubleU discountrate)
{
DiscountRate = discountrate;
}
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
You need to first change the string in
OnPropertyChanged("DiscountingRate");
to "DiscountRate". The string you are giving your OnPropertyChanged function must match the property name. That is most likely the issue you are having.
As already answered, the problem is OnPropertyChanged("DiscountingRate"); providing the event with an incorrect property name.
In order to prevent errors like this, you can avoid using string literals all together. In your OnPropertyChanged parameter, use CallerMemberName. You can modify your OnPropertyChanged signature to
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Do your stuff
}
Then in your setters, you just call this.OnPropertyChanged();. The method will be given the property name that was changed.
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged();
}
}
The benefit to this is that you can refactor your code and not worry about breaking your property changed events.

How to implement INotifyPropertyChanged for derived classes?

I have a base class:
public class PersonBaseClass : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
}
and a derived class
public class TeacherClass : PersonBaseClass, INotifyPropertyChanged
{
private string id;
public string Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
NotifyPropertyChanged("Id");
}
}
}
}
and this magic code at the end of each one above!
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I show a list of Teachers collection in a list in xaml. Now if I change Id, changes will appear for user, but changes in Name which is a property in base class doesn't appear. In debug, I see after setting Name value, the handler inside NotifyPropertyChanged method is null which seems it is the problem.
How can I solve it to changes of base class also appear in the list?
Have only PersonBaseClass implementing INotifyPropertyChanged and make NotifyPropertyChange as protected so you can call it from child classes. There is not need to implement it twice. That should fix the problem as well.
Your "magic code" section should only be in PersonBaseClass. You can make the NotifyPropertyChanged function protected so that the same function can be called from TeacherClass as well.

Two way databinding in winforms, Inotifypropertychanged implemented in base class

I use Winforms Databinding and I have derived classes, where the base class implements IPropertychanged :
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) {
var handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Each propertysetter calls:
protected void SetField<T>(ref T field, T value, string propertyName) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
IsDirty = true;
this.RaisePropertyChanged(propertyName);
}
}
A typical Propertysetter:
public String LocalizationItemId {
get {
return _localizationItemId;
}
set {
SetField(ref _localizationItemId, value, "LocalizationItemId");
}
}
The way a property is bound to a textbox
private DerivedEntity derivedEntity
TextBoxDerivedEntity.DataBindings.Add("Text", derivedEntity, "Probenname");
If I programmatically assign text to the textbox, the textbox does not show it. But I can manually edit the textbox.
I know it is too late to answer, but this problem can be solved, if you set event when your binding should change value, if you set it on property value change event your problem will be solved. You can do this by this way
textBox.DataBindings.Add("textBoxProperty", entity, "entityProperty", true, DataSourceUpdateMode.OnPropertyChanged);
Binding source is updated on TextBox Validated event. TextBox validated event is called when user edit TextBox and then changes focus to other control.
Since you're changing TextBox text programmatically TextBox doesn't know that text were changed and therefore validation is not called and binding is not updated, so you need to update binding manually.
Initialize binding:
var entity;
textBox.DataBindings.Add("textBoxProperty", entity, "entityProperty");
Change TextBox.Text:
textBox.Text = "SOME_VALUE";
Update binding manually:
textBox.DataBindings["textBoxProperty"].WriteValue();
Binding.WriteValue() reads value from control and updates entity accordingly.
You could read about WriteValue at MSDN.
The subscriber isn't initialized. i.e.
private DerivedEntity derivedEntity
TextBoxDerivedEntity.DataBindings.Add("Text", derivedEntity, "Probenname");
derivedEntity is null.
Initialize it and you'll be fine.
I implemented the "INotifyPropertyChanged", but raise the PropertyChanged event only when the new value is different from the old value:
public class ProfileModel : INotifyPropertyChanged
{
private Guid _iD;
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public Guid ID
{
get => _iD;
set
{
if (_iD != value)
{
_iD = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID"));
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
Now just bind to the controls:
txtProfileID.DataBindings.Clear();
txtProfileID.DataBindings.Add("Text", boundProfile, "ID", true, DataSourceUpdateMode.OnPropertyChanged);

Wrapping the property setter

I find that I'm repeating myself alot and that is of course no good. So I wondered if I could do something about it. This is a common code in my WPF application:
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
So I was wondering if I could wrap the setter somehow to make it better and more readable. One idea was something like this:
protected void PropertySetter<T>(T property, T value, string name)
{
if (EqualityComparer<T>.Default.Equals(property, value))
{
property = value;
OnPropertyChanged(name);
}
}
Usage like this:
private string _name2;
public string Name2
{
get { return _name2; }
set
{
PropertySetter<string>(Name2, value, "Name2");
}
}
But I'm not sure this is really smart or would work as well with Value types?
I guess I'm not the first one to try something like this so if someone knows a good foolproof way to something like this please chime in. I guess I couldn't make the propertyChanged typesafe without reflection but any ideas there would also help.
Yes - this is completely acceptable and normal code.
Here's an example I found that's pretty standardized (I see a lot of this type of usage in code samples).
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, string name)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Wrap this code inside of a class that implements INotifyPropertyChanged, and inherit your data objects from this class.
In your example, you are calling the event directly - never do this. You could lose the event reference from the time the method starts to the time you call the event. Always create a local cache of the event before invoking it.
Maybe this could help you
public class ObservableObject : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
protected virtual void SetAndNotify<T>(ref T field, T value, Expression<Func<T>> property)
{
if (!object.ReferenceEquals(field, value))
{
field = value;
this.OnPropertyChanged(property);
}
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> changedProperty)
{
if (PropertyChanged != null)
{
string name = ((MemberExpression)changedProperty.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
Usage:
private String _myField;
public String MyProperty
{
get
{ return _myField; }
set
{ SetAndNotify(ref _myField, value, () => MyProperty); }
}
Edit: Your class must inherit from this OservableObject class

Categories

Resources