INotifyPropertyChanged and property on BaseClass C# - c#

I am working with WPF and MVVM model. I have a base viewmodel class called ViewModelBase. It has a property on it called Config that is a complex type. I need a derived class to be able to databind to the base class Config property in a View.
public class ViewModelBase : INotifyPropertyChanged
{
private Configuration _config;
public event PropertyChangedEventHandler PropertyChanged;
public Configuration Config
{
get { return _config; }
set
{
if(_config == null || !_config.Equals(value))
{
_config = value;
OnPropertyChanged(new PropertyChangedEventArgs("Config"));
}
}
}
public ViewModelBase()
{
}
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Databinding seems to be working in a read capacity, but when a property of the Config is altered in the OptionsView, the changes are not reflected in the Config itself. Any suggestions?
Configuration implementation, per request.
public class Configuration : IEquatable<Configuration>, INotifyPropertyChanged
{
private string _primaryUrl;
private string _secondaryUrl;
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private string _buffer;
private bool _isScheduleEnabled;
private int _logDays;
private int _retryDuration;
private int _maxRetryAttempts;
private string _registrationKey;
private string _email;
public string PrimaryURL
{
get { return _primaryUrl; }
set
{
if(_primaryUrl != value)
{
_primaryUrl = value;
OnPropertyChanged(new PropertyChangedEventArgs("PrimaryURL"));
}
}
}
public string SecondaryURL
{
get { return _secondaryUrl; }
set
{
if(_secondaryUrl != value)
{
_secondaryUrl = value;
OnPropertyChanged(new PropertyChangedEventArgs("SecondaryURL"));
}
}
}
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
if(_scheduledStart != value)
{
_scheduledStart = value;
OnPropertyChanged(new PropertyChangedEventArgs("ScheduledStart"));
}
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(_scheduledEnd != value)
{
_scheduledEnd = value;
OnPropertyChanged(new PropertyChangedEventArgs("ScheduledEnd"));
}
}
}
public string Buffer
{
get { return _buffer; }
set
{
if(_buffer != value)
{
_buffer = value;
OnPropertyChanged(new PropertyChangedEventArgs("Buffer"));
}
}
}
public bool IsScheduleEnabled
{
get { return _isScheduleEnabled; }
set
{
if(_isScheduleEnabled != value)
{
_isScheduleEnabled = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsScheduleEnabled"));
}
}
}
public int LogDays
{
get { return _logDays; }
set
{
if(_logDays != value)
{
_logDays = value;
OnPropertyChanged(new PropertyChangedEventArgs("LogDays"));
}
}
}
public int RetryDuration
{
get { return _retryDuration; }
set
{
if(_retryDuration != value)
{
_retryDuration = value;
OnPropertyChanged(new PropertyChangedEventArgs("RetryDuration"));
}
}
}
public int MaxRetryAttempts
{
get { return _maxRetryAttempts; }
set
{
if(_maxRetryAttempts != value)
{
_maxRetryAttempts = value;
OnPropertyChanged(new PropertyChangedEventArgs("MaxRetryAttempts"));
}
}
}
public string RegistrationKey
{
get { return _registrationKey; }
set
{
if(_registrationKey != value)
{
_registrationKey = value;
OnPropertyChanged(new PropertyChangedEventArgs("RegistrationKey"));
}
}
}
public string Email
{
get { return _email; }
set
{
if(_email != value)
{
_email = value;
OnPropertyChanged(new PropertyChangedEventArgs("Email"));
}
}
}
public Configuration() { }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Here is one of the culprit bindings:
<xctk:DateTimePicker Grid.Column="1" Value="{Binding Config.ScheduledStart}" Height="20" VerticalAlignment="Top"/>

The INotifyPropertyChanged implementation only applies to the class directly. So in your case, to the ViewModelBase class and its subtypes.
In this case, the PropertyChangedEvent is raised in the setter of the Config property, so whenever the Config property is set (and the setter is called), the event is raised.
This however does not mean that when mutating the Config object that the even is also raised. In general, this is not the case.
In order to raise the event when the Config object is changed, you would have to reassign the object to the view model (calling the setter again). This however does not work when data binding to the object.
A better solution is to make the Configuration implement INotifyPropertyChanged interface itself. So when a property within that object is changed, an event is raised as well. WPF will also recognize this for subobjects, so it will automatically work.

Databinding seems to be working in a read capacity..
Which is fine but if you want a change capacity, then the class Configuration will have to adhere to INotifyPropertyChanged and each property on the class needs to report PropertyChange notifications for any changes to be shown in bound xaml controls.
but when a property of the Config is altered in the OptionsView, the changes are not reflected
What you have now only notifies if the instance of Configuration has been replaced; not individual property changes within the current instance.

Related

How to bind a property of a XAML view to a public variable in the code behind every time the property changes

I am trying to take a property from a XAML control, specifically the TranslationX property, and store it in a public variable every time the value is changed.
I have tried using data binding by implementing the INotifyPropertyChanged interface and binding the TranslationX property to the public variable from my interface implementation, but had no luck
Essentially, I am needing the TranslationX property of a control to trigger function calls depending on the total displacement, ex. if the control is dragged to -200 in the X direction, it triggers function "Y". I cannot seem to access this translation value in a way that allows me to check if it is above or below a certain value.
I am very new to C# and Xamarin, so any advice is much appreciated.
EDIT:
Here is my current ViewModel class:
public class ReceiptPageViewModel : INotifyPropertyChanged
{
double shift = 0;
public double Shift
{
get => shift;
set
{
if (shift == value)
return;
else
{
shift = value;
OnPropertyChanged(nameof(Shift));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
And here is my syntax for my Binding:
TranslationX="{Binding Shift}"
in your XAML
<SomeElement ... TranslationX="{Binding TransX}" ... />
in your ViewModel
double transX;
public double TransX {
get { return transX; }
set {
transX = value;
if (transX > somethresholdvalue) {
...
}
}
}
Follow the MVVM pattern.
Create a Base View Model with the INotifyPropertyChanged.
Then your custom view model is going to inherit from that base class.
BaseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Your custom class:
ReceiptPageViewModel
public class ReceiptPageViewModel : BaseViewModel
{
double shift = 0;
public double Shift
{
get { return shift; }
set { SetProperty(ref shift, value); }
}
}
And in your Xamarin Page set the BindingContext to the ViewModel
(This is an example)
public partial class Page1 : ContentPage
{
private ReceiptPageViewModel viewModel;
public Page1()
{
BindingContext = viewModel = new ReceiptPageViewModel();
InitializeComponent();
}
}
Now you can set the property in the XAML view:
<SomeElement ... TranslationX="{Binding Shift}" ... />
Here you can view a full episode about MVVM Pattern with #JamesMontemagno as host.

Force INotifyDataErrorInfo validation

I have implemented INotifyDataErrorInfo exactly as described in the following link:
http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo
I have a TextBox which is bound to a string property in my model.
XAML
<TextBox Text="{Binding FullName,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}" />
Model
private string _fullName;
public string FullName
{
get { return _fullName; }
set
{
// Set raises OnPropertyChanged
Set(ref _fullName, value);
if (string.IsNullOrWhiteSpace(_fullName))
AddError(nameof(FullName), "Name required");
else
RemoveError(nameof(FullName));
}
}
INotifyDataError Code
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// get errors by property
public IEnumerable GetErrors(string propertyName)
{
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
return null;
}
public bool HasErrors => _errors.Count > 0;
// object is valid
public bool IsValid => !HasErrors;
public void AddError(string propertyName, string error)
{
// Add error to list
_errors[propertyName] = new List<string>() { error };
NotifyErrorsChanged(propertyName);
}
public void RemoveError(string propertyName)
{
// remove error
if (_errors.ContainsKey(propertyName))
_errors.Remove(propertyName);
NotifyErrorsChanged(propertyName);
}
public void NotifyErrorsChanged(string propertyName)
{
// Notify
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
Now all this works fine, but it only validates as soon as I type something in my TextBox. I would like some way to validate on demand, without even touching the textbox, say on a button click.
I have tried raising PropertyChanged for all my properties as described in this question, but it does not detect the errors. I somehow need my property setter to be called so the errors can be detected. I'm looking for a MVVM solution.
The INotifyDataErrorInfo implementation you use is somewhat flawed IMHO. It relies on errors kept in a state (a list) attached to the object. Problem with stored state is, sometimes, in a moving world, you don't have the chance to update it when you want. Here is another MVVM implementation that doesn't rely on a stored state, but computes error state on the fly.
Things are handled a bit differently as you need to put validation code in a central GetErrors method (you could create per-property validation methods called from this central method), not in the property setters.
public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors
{
get
{
return GetErrors(null).OfType<object>().Any();
}
}
public virtual void ForceValidation()
{
OnPropertyChanged(null);
}
public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
return Enumerable.Empty<object>();
}
protected void OnErrorsChanged([CallerMemberName] string propertyName = null)
{
OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
var handler = ErrorsChanged;
if (handler != null)
{
handler(sender, e);
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(sender, e);
}
}
}
And here are two sample classes that demonstrate how to use it:
public class Customer : ModelBase
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name))
{
if (string.IsNullOrWhiteSpace(_name))
yield return "Name cannot be empty.";
}
}
}
public class CustomerWithAge : Customer
{
private int _age;
public int Age
{
get
{
return _age;
}
set
{
if (_age != value)
{
_age = value;
OnPropertyChanged();
}
}
}
public override IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
foreach (var obj in base.GetErrors(propertyName))
{
yield return obj;
}
if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age))
{
if (_age <= 0)
yield return "Age is invalid.";
}
}
}
It works like a charm with a simple XAML like this:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" />
(UpdateSourceTrigger is optional, if you don't use it it will only work when focus is lost).
With this MVVM base class, you shouldn't have to force any validation. But should you need it, I have added a ForceValidation sample method in ModelBase that should work (I have tested it with for example a member value like _name that would have been changed without passing through the public setter).
Your best bet is to use a relay command interface. Take a look at this:
public class RelayCommand : ICommand
{
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public RelayCommand(Action executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod();
}
}
#endregion
}
You would declare this relay command in your view model like:
public RelayCommand SaveCommand { get; private set; }
Now, in addition to registering your SaveCommand with OnSave and a CanSave methods, since you extend from INotifyDataErrorInfo, you can sign up to ErrorsChanged in your constructor as well:
public YourViewModel()
{
SaveCommand = new RelayCommand(OnSave, CanSave);
ErrorsChanged += RaiseCanExecuteChanged;
}
And you'll need the methods:
private void RaiseCanExecuteChanged(object sender, EventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
public bool CanSave()
{
return !this.HasErrors;
}
private void OnSave()
{
//Your save logic here.
}
Also, each time after you call PropertyChanged, you can call this validation method:
private void ValidateProperty<T>(string propertyName, T value)
{
var results = new List<ValidationResult>();
ValidationContext context = new ValidationContext(this);
context.MemberName = propertyName;
Validator.TryValidateProperty(value, context, results);
if (results.Any())
{
_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
}
else
{
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
With this setup, and if your viewmodel both extends from INotifyPropertyChanged and INotifyDataErrorInfo (or from a base class that extends from these two), when you bind a button to the SaveCommand above, WPF framework will automatically disable it if there are validation errors.
Hope this helps.

WPF TwoWay Binding Custom with Data Provider

I am trying to use twoway binding to a custom datasource (other than a db or xml where twoway binding is supported out of the box) in WPF to update the UI automatically when the source is updated and update the source when the object is changed in the UI.
What I currently have is a class, CacheObject, which implements INotifyPropertyChanged and a custom (derived) ObservableCollection, CacheWrapper, which subscribes to changes in the cache and updates the appropriate CacheObject which in turn automatically updates the UI.
Is there a smart way that I can build into my CacheWrapper a way to update the cache - make a call using the cache API - when a CacheObject is updated in the list. If I subscribe to the PropertyChanged events I will receive events both when the objects are updated from code (changes from cache) and from the UI. Basically I want to do one thing if the update comes from the source and another if it comes from the target.
Some code examples.
CacheObject
public class CacheObject : INotifyPropertyChanged
{
private object _key;
private object _value;
public event PropertyChangedEventHandler PropertyChanged;
public CacheObjectViewModel(object key, object value)
{
_key = key;
_value = value;
}
public CacheObjectViewModel(KeyValuePair<object, object> keyValuePair)
: this(keyValuePair.Key, keyValuePair.Value)
{
}
public object Key
{
get { return _key; }
set
{
_key = value;
OnPropertyChanged("Key");
}
}
public object Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
CacheWrapper
public class CacheWrapper : ObservableCollection<CacheObject>, ICacheObserver
{
private ICache _cache;
public String Name { get; private set; }
public CacheWrapepr(string name, ICache cache)
{
_cache = cache;
Name = name;
cache.AddListener(this);
}
public void CacheEntryInserted(CacheEventArgs eventArgs)
{
CacheObject cacheObject = eventArgs.NewElement as CacheObject;
if(cacheObject != null)
{
this.Add(cacheObject);
}
}
public void EntryUpdated(CacheEventArgs eventArgs)
{
CacheObject cacheObjectNew = eventArgs.NewElement as CacheObject;
if(cacheObjectNew != null)
{
CacheObject cacheObjectExisting = this.FirstOrDefault(x => x.Key == cacheObjectNew.Key);
if(cacheObjectExisting != null)
{
cacheObjectExisting.Value = cacheObjectNew.Value;
}
}
}
public void EntryDeleted(CacheEventArgs eventArgs)
{
CacheObject cacheObject= = eventArgs.OldElement as CacheObject;
if(cacheObject != null)
{
CacheObject cacheObjectExisting = this.FirstOrDefault(x => x.Key == cacheObject.Key);
if(cacheObjectExisting != null)
{
this.Remove(cacheObjectExisting);
}
}
}
}
Help and ideas are much appreciated.

UI is not updating from other application data in wpf

My Class relation is like this Model:
Class MainModel
{
data data1 = new data();
public override string LFE
{
get { return data1.lnf.ToString(); }
set { data1.lnf = Convert.ToByte(value); }
}
public override UInt16 GetRsBValue(int index)
{
return (byte)this.data1.CConfig[index].Bline;
}
public override void SetRsBValue(UInt16 value, int index)
{
byte[] arr = BitConverter.GetBytes(value);
this.data1.CConfig[index].Bline = arr[0];
}
}
Class data
{
public byte Bline
{
get { return this.bline; }
set { this.bline = value; }
}
public byte lnf
{
get { return this.ln_frequency; }
set { this.ln_frequency = value; }
}
}
Class ViewModel : INotifyPropertyChange
{
public UInt16 Rschange
{
get
{
return this.eObj.GetRsBValue(this.index);
}
set
{
this.eObj.SetRsBValue(value, this.Index);
this.OnPropertyChanged(new PropertyChangedEventArgs("Rschange"));
this.eObj.writedata(DefaultFileName);
}
}
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
}
}
}
In Model side I have created instance of data class in main Model.
I'm getting data from other application to my application. I'm getting that updated value till data class but it's not showing that value in MainModel. So It's not updating mu UI at all. Please tell me how can I update my UI when I'm getting value from other application.
P.S: I don't want to create Model class instance in ViewModel side and I have 10 properties and 10 method like this in my class.
You have a typo in your call to property changed in your LF property.
// note the extra space V
this.OnPropertyChanged(new PropertyChangedEventArgs(" LF"));
If you create your property changed handler like this:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You can prevent such typos by using the following syntax:
public string LF
{
get
{
return this.eObj.LFE;
}
set
{
this.eObj.LFE = value;
this.OnPropertyChanged();
}
}

Refresh an ObservableCollection (overwrite)

How can I refresh the following ObservableCollection?
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties;
public ObservableCollection<ServerProperties> Properties
{
get
{
properties = new ObservableCollection<ServerProperties>();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
return properties;
}
}
}
public class ServerProperties
{
private string property;
private string value;
public ServerProperties()
{
}
public ServerProperties(string property, string value)
{
Property = property;
Value = value;
}
public string Property
{
get
{
return this.property;
}
set
{
this.property = value;
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return string.Format("[Property : {0}]", Value);
}
}
I changed the value of textProperties[,] and now I'd like to overwrite the previous content of the collection with the current content of textProperties[,].
What would be the simplest way to do this?
Any help would be appreciated.
Start off by implementing INotifyPropertyChanged in your ViewModel as well as in the ServerProperties object. This way you can raise the PropetyChanged event which will pass back to the user interface.
ViewModel
public class ViewModelProperties : INotifyPropertyChanged {
public event ProeprtyChangedEventHandler PropertyChanged;
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties {
get { return properties;}
set {
properties = value;
this.RaisePropertyChangedEvent("Properties");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implementing this on the ServerProperties object as well will allow you to change the objects, at any level, and have it bubble up to the interface.
Lastly look into your population code and in order to get the property to update successfully first populate it to a List then re-initialise the ObservableCollection using the List.
Properties = new ObservableCollection<ServerProperties>(propertiesList);
This also allows you to better handle the creation of your ObservableCollection and perform tests before posting the output to the interface. Hope it helps.
For example, one of the simpler solutions could be
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties
{
get
{
return properties;
}
}
public void SetProperties()
{
properties.Clear();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
}
}
Any time you wish to add new items to OC, just call the SetProperties method.

Categories

Resources