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.
Related
Solution link: https://github.com/fallingsappy/portfolio/tree/master/DDrop
Hi, everyone! I have a problem with IsEnabled binding of TabItem. It kinda hard to explain, but I try. I have a collection called User. User contains UserSeries collection of Series class. UserSeries have another collection called DropPhotosSeries. Also UserSeries have property called:
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"));
}
}
Based on this property. TabItem should be enabled or disabled. But this TabItem doesn't seems to notice changes of the property. I've tried many approaches, but still nothing. Here some XAML:
<TabItem IsEnabled="{Binding ElementName=AppMainWindow, Path=User.IsAnySelectedSeriesCantDrawPlot}" Name="CombinedSeriesPlot" Header="Общий график серий">
<uc:ScatterPlot User="{Binding User, ElementName=AppMainWindow, UpdateSourceTrigger=PropertyChanged}"/>
</TabItem>
Some of the codebehind:
public class User : INotifyPropertyChanged
{
private ObservableCollection<Series> _userSeries;
public ObservableCollection<Series> UserSeries
{
get
{
return _userSeries;
}
set
{
_userSeries = value;
OnPropertyChanged(new PropertyChangedEventArgs("UserSeries"));
}
}
private bool _isAnySelectedSeriesCantDrawPlot;
[NotMapped]
public bool IsAnySelectedSeriesCantDrawPlot
{
get
{
return _userSeries?.Where(x => x?.CanDrawPlot == false).ToList().Count > 0;
}
set
{
_isAnySelectedSeriesCantDrawPlot = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsAnySelectedSeriesCantDrawPlot"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
I've tried to create dependent property in UserClass, but i think property in UserSeries should be enough somehow:
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);
}
}
Maybe DropPhoto should throw some event, that CanDrowProperty need to update itself and update TabItem? I really don't know. But here is DropPhoto class:
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);
}
}
It's contains Drop class, which has RadiusInMeters property:
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);
}
}
For full solution. Please check github link.
It's hard to find out what exactly wrong in your code but I'll try to answer directly to the question with simple example code
public class FirstClass : INotifyPropertyChanged
{
SecondClass secondClass = new SecondClass();
private bool _firstProperty;
public bool FirstProperty
{
get => _firstProperty;
set
{
_firstProperty = value;
OnPropertyChanged(nameof(FirstProperty));
}
}
private void SecondClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(secondClass.SecondProperty)) FirstProperty = secondClass.SecondProperty;
}
public FirstClass()
{
secondClass.PropertyChanged += SecondClass_PropertyChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class SecondClass : INotifyPropertyChanged
{
private bool _secondProperty;
public bool SecondProperty
{
get => _secondProperty;
set
{
_secondProperty = value;
OnPropertyChanged(nameof(SecondProperty));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In this example any change of SecondProperty will cause change of the FirstProperty.
I am attempting to bind a CheckBox in my View to a property in my ViewModel. I am trying to subscribe to changes of the individual ViewModel property.
I have implemented INotifyPropertyChanged on my custom class, however my handler method is never called.
I have included basic examples of the View (XAML), ViewModel, and the custom user class.
ViewModel
public class HomeViewModel: ViewModelBase
{
public HomeViewModel()
{
this.selectedUser = new usersVM();
this.selectedUser.PropertyChanged += propChangedHandler;
}
private void propChangedHandler(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "superuser") // <-- breakpoint here
{
}
}
private usersVM _selectedUser;
public usersVM selectedUser
{
get { return this._selectedUser; }
set
{
if (this._selectedUser != value)
{
this._selectedUser = value;
this.RaisePropertyChanged("selectedUser");
}
}
}
}
Custom User Class
public class usersVM : INotifyPropertyChanged
{
public usersVM()
{
this.hasChanges = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _superuser;
public int superuser
{
get
{
return this._superuser;
}
set
{
if (value != this._superuser)
{
this._superuser = value;
NotifyPropertyChanged("username");
}
}
}
XAML #
<CheckBox Margin="0,0,8,0" Content="SuperUser" IsChecked="{Binding superuser}" DataContext="{Binding selectedUser}" />
I have a breakpoint in my Hadler Method to try to verify when the event is handled, but this is never called.
How can I properly implement INotifyPropertyChanged and subscribe to these events in my ViewModel?
Detach the PropertyChanged event handler from the current selectedUser value and attach it to the new one like this:
private usersVM _selectedUser;
public usersVM selectedUser
{
get { return _selectedUser; }
set
{
if (_selectedUser != value)
{
if (_selectedUser != null)
{
_selectedUser.PropertyChanged -= propChangedHandler;
}
_selectedUser = value;
if (_selectedUser != null)
{
_selectedUser.PropertyChanged += propChangedHandler;
}
RaisePropertyChanged("selectedUser");
}
}
}
I have one button on my MasterDetailPage changing the value on an INT (named App.value1) depending on what you click looking like this:
void click1 (object s, EventArgs a)
{
if (App.value1 == 0) {
App.value1 = App.value1 + 1;
} else {
App.value1 = 0;
}
}
And I want this click function to immediately change the value on my StartPage (another ContentPage). So I have created a viewmodel looking like this, where I try to work with the current value:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public int currentValue {
get {
return App.value1;
}
set {
if (App.value1 == 0) {
App.value1 = 0;
} else {
App.value1 = 1;
}
}
}
And this is the StartPage where I want the value of the INT to update immediately depending on what you clicked on at the MasterDetailView.
public StartPage ()
{
var ourView = new StartPageViewModel ();
ourCurrentValue = ourView.currentValue;
}
protected async override void OnAppearing()
{
LoadData();
}
private async Task<List<Pin>> LoadData() //I work with pins here (not showing that code though as it is irrelavant.
{
if (ourCurrentValue == 0) {
System.Diagnostics.Debug.WriteLine ("Value is 0");
}
else {
System.Diagnostics.Debug.WriteLine ("Value is 1");
}
}
Right now I only see "Value is 0" in my log. Nothing updates when I click on my button on the MasterDetailPage.
UPDATED CODE:
public class StartPageViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
public StartPageViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And StartPage:
public StartPage ()
{
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue"); // ERROR: `An object reference is requiered to access non-static member 'Xamarin.Forms.BindableObject.PropertyChanged`
}
You can proceed with something like that:
Make following changes to your App class and value1 property inside that class:
public static event PropertyChangedEventHandler PropertyChanged;
private static void OnPropertyChanged (string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged (null, new PropertyChangedEventArgs (propertyName));
}
}
private static int _value1;
public static int value1
{
get { return _value1; }
set
{
_value1 = value;
OnPropertyChanged("value1");
}
}
Then add this line to your StartPageViewModel constructor:
App.PropertyChanged += (sender, args) => OnPropertyChanged("currentValue");
In that code you are just leveraging PropertyChanged for your own purposes (you can even create your own event for that).
I mean StartPageViewModel subscribes to PropertyChanged event in Appclass, so it will be notified when value1 change. And when it actually occurs, then it is invoking his own PropertyChanged to notify View about currentValue change.
However, I would say better solution is to share View Model between MasterDetailPage and StartPage, because using global state makes your solution hard to understand :
public class SharedViewModel : INotifyPropertyChanged
{
private ICommand clickCommand;
private int currentValue;
/* INotifyPropertyChanged implementation */
public SharedViewModel()
{
ClickCommand = new Command(() => CurrentValue = CurrentValue + 1);
}
public ICommand ClickCommand
{
get { return clickCommand; }
set
{
clickCommand = value;
OnPropertyChanged("ClickCommand");
}
}
public int CurrentValue
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("CurrentValue");
}
}
}
And you need to use the same instance of SharedViewModel in MasterDetailPage as well as StartPage
I have a WPF dialog that is bound to a list of ObservableCollection<MyEntity> type. In the dialog, I want the "OK" button to be enabled only if changes are made to the ObservableCollection<MyEntity> list - that includes adding/removing items from the list and modifying the individual items in the list.
For adding/removing items from the list, it is easy - I implemented a handler for the CollectionChanged event.
What I don't know how to do is when an individual item is modified. Say, MyEntity.Name="New Value", what interface does MyEntity class need to implement to make it 'observable'?
MyEntity needs to implement INotifyPropertyChanged, then when a property change occurs you fire the PropertyChanged event. Like this:
public class MyEntity : INotifyPropertyChanged
{
public bool MyFlag
{
get { return _myFlag; }
set
{
_myFlag = value;
OnPropertyChanged("MyFlag");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Two ways to approach this are:
have an event listener internal to the object which then sets an IsDirty flag whenever a property changes. Then OK button is bound to a command (check out the usage of the ICommand interface), and in the CanExecute method of the command you check if any of the objects in the ObservableCollection have been set to dirty. This check can be done with a simple LINQ statement: myCollection.Any(x => x.IsDirty == true)
this method is more clunky and smelly.... have an external object listening for changes (by subscribing to the PropertyChanged event on each object), and that external listener can then enable the OK button (via databinding or by setting it directly).
I like the answer provided by slugster, here is an alternative building on slugster's answer.
If you bind to your OK button using DelegateCommnd you can add event handlers for CollectionChanged and PropertyChanged to change a simple boolean flag to control the state of the OK button.
public class MainViewModel : ViewModelBase
{
public DelegateCommand<object> RunCommand { get; set; }
public DelegateCommand<object> OkCommand { get; set; }
private bool enableOk = false;
private bool setOK = false;
private ObservableCollection<MyEntity> _entites = new ObservableCollection<MyEntity>();
public MainViewModel()
{
_entites.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// handle property changing
foreach (MyEntity item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) => { if (setOK) enableOk = true; };
}
}
// handle collection changing
if (setOK) enableOk = false;
};
MyEntity me1 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
MyEntity me2 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
MyEntity me3 = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
_entites.Add(me1);
_entites.Add(me2);
_entites.Add(me3);
// allow collection changes now to start enabling the ok button...
setOK = true;
RunCommand = new DelegateCommand<object>(OnRunCommnad, CanRunCommand);
OkCommand = new DelegateCommand<object>(OnOkCommnad, CanOkCommand);
}
private void OnRunCommnad(object obj)
{
MyEntity me = new MyEntity { Name = "Name", Information = "Information", Details = "Detials" };
// causes ok to become enabled
_entites.Add(me);
MyEntity first = _entites[0];
// causes ok to become enabled
first.Name = "Zamboni";
}
private bool CanRunCommand(object obj)
{
return true;
}
private void OnOkCommnad(object obj)
{
}
private bool CanOkCommand(object obj)
{
return enableOk;
}
}
Here is a version MyEntity (similar to the one provided by slugster):
Only the Name property fires an event in this example...
public class MyEntity : INotifyPropertyChanged
{
private string _name = string.Empty;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Information { get; set; }
public string Details { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should implement INotifyPropertyChanged. You could do it by the following way
(as you can see, this implementation is fully thread safe)
private readonly object _sync = new object();
public event PropertyChangedEventHandler PropertyChanged
{
add { lock (_sync) _propertyChanged += value; }
remove { lock (_sync) _propertyChanged -= value; }
} private PropertyChangedEventHandler _propertyChanged;
protected void OnPropertyChanged(Expression<Func<object>> propertyExpression)
{
OnPropertyChanged(GetPropertyName(propertyExpression));
}
protected string GetPropertyName(Expression<Func<object>> propertyExpression)
{
MemberExpression body;
if (propertyExpression.Body is UnaryExpression)
body = (MemberExpression) ((UnaryExpression) propertyExpression.Body).Operand;
else
body = (MemberExpression) propertyExpression.Body;
return body.Member.Name;
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = _propertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Following the implementation I described above, you can notify about your changes by two ways
1) The first way
public int MyProperty
{
get { return _myProperty; }
set
{
if (value != __myProperty)
{
_subVersion = value;
OnPropertyChanged(MyPropertyPropertyName);
}
}
} private int _myProperty; const string MyPropertyPropertyName = "MyProperty";
2) And the second way
public int MyProperty
{
get { return _myProperty; }
set
{
if (value != _myProperty)
{
_subVersion = value;
OnPropertyChanged(() => MyProperty);
}
}
} private int _myProperty;
Another solution could be a custom observable collection that requires items to implement INotifyPropertyChanged. The user must attach a handler to the OnItemPropertyChanged event, which will be called whenever the property of an item in the collection is changed.
public class ObservableCollectionEnhanced<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public ObservableCollectionEnhanced()
: base()
{ }
public ObservableCollectionEnhanced(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in Items)
item.PropertyChanged += OnItemPropertyChanged;
}
public ObservableCollectionEnhanced(List<T> list)
: base(list)
{
foreach (T item in Items)
item.PropertyChanged += OnItemPropertyChanged;
}
public event System.ComponentModel.PropertyChangedEventHandler ItemPropertyChanged;
public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if (null != ItemPropertyChanged)
ItemPropertyChanged(sender, e);
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
item.PropertyChanged += OnItemPropertyChanged;
}
protected override void RemoveItem(int index)
{
T item = this.Items[index];
item.PropertyChanged -= OnItemPropertyChanged;
base.RemoveItem(index);
}
protected override void SetItem(int index, T item)
{
T oldItem = Items[index];
base.SetItem(index, item);
oldItem.PropertyChanged -= OnItemPropertyChanged;
item.PropertyChanged += OnItemPropertyChanged;
}
}
Configure the handler as follows:
public void OnItemPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Update called on {0}", sender);
}
...
collection.ItemPropertyChanged += OnItemPropertyChanged;
I have a class that implements INotifyPropertyChanged.
I create an instance of a class in some viewModel.
Is it possible to remove this functionality from the class and inject it after the instance was created? I heard that ICustomTypeDescriptor would make this happen, but i dont know how to use it.
public class C : ICustomNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int _id;
public string _name;
public int Id
{
get { return _id; }
set
{
if (_id == value)
{
return;
}
_id = value;
OnPropertyChanged("Id");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
If you are just trying to prevent the notifications from being fired when the object is first created and properties set, you can add boolean flag(s) that is/are false until the properties have been set once. You only execute the notification if the flag is true.
Edit:
I don't think there's a clean way to get the functionality in there after removing all the INotifyPropertyChanged code, but there are many ways to control the functionality from outside the instance.
Please note that I wrote all this code in the text editor, not in VisualStudio; it has not been tested in any way.
Add a method to enable notifications:
public class OptionalNotification : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name) ...
bool _shouldNotify;
public void EnableNotifications()
{
_shouldNotify = true;
}
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return
_someProperty = value;
if(_shouldNotify) OnPropertyChanged("SomeProperty");
}
}
}
You could do the same thing without the method, if you knew at the time of instantiation whether or not the instance should produce notifications, in which case you'd just need a boolean parameter in the constructor.
Another variation would be to use the Factory pattern, where your Factory has internal access to the boolean flag and sets it upon construction.
Encapsulate the condition in a proxy:
public interface IEntity : INotifyPropertyChanged
{
string SomeProperty { get; set; }
}
public class Entity : IEntity
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name) ...
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return
_someProperty = value;
OnPropertyChanged("SomeProperty");
}
}
}
public class EntityNotificationProxy : IEntity
{
IEntity _inner;
public EntityNotificationProxy(IEntity entity)
{
_inner = entity;
_inner.PropertyChanged += (o,e) => { if(ShouldNotify) OnPropertyChanged(o,e); }
}
public bool ShouldNotify { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(object sender, PropertChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) handler(sender, e);
}
public string SomeProperty
{
get { return _inner.SomeProperty; }
set
{
if(_inner.SomeProperty == value) return
_inner.SomeProperty = value;
}
}
}
Here your consuming classes get the entity proxy instead of the entity itself (but is none the wiser because it references only IEntity when you program to interfaces/abstractions). The wrapping of the proxy can happen in a factory or through an IoC container/DI framework.
The main advantage to this approach is that your entity maintains a pure INotifyPropertyChanged implementation, and the conditional aspect is handled from without. Another advantage is that it helps to enforce programming to abstractions and inversion of control.
The main disadvantage is that you'll need to create proxies for each INotifyPropertyChanged implementation that you want to have this conditional behaviour.
Create a registry to keep track of what instances should or should not raise notifications:
public static class PropertyNotificationRegistry
{
static IDictionary<INotifyPropertyChanged, bool> _registeredClasses
= new Dictionary<INotifyPropertyChanged, bool>;
static void Register(INotifyPropertyChanged o, bool shouldNotify)
{
if(!(_registeredClasses.ContainsKey(o)) _registeredClasses.Add(o, shouldNotify);
// could also implement logic to update an existing class in the dictionary
}
public static void ShouldNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
{
Register(o, true);
}
public static void ShouldNotNotifyWhenPropertiesChange(this INotifyPropertyChanged o)
{
Register(o, false);
}
public static void NotifyPropertyChanged(this INotifyPropertyChanged o, Action notificationAction)
{
if(_registeredClasses.ContainsKey(o))
{
bool shouldNotify = _registeredClasses.Where(x => x.Key == o).Single().Value;
if(shouldNotify) notificationAction();
}
}
}
public class EntityUsingNotificationRegistry : INotifyPropertyChanged
{
... // all the standard INotifyPropertyChanged stuff
string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
if(_someProperty == value) return;
_someProperty = value;
this.NotifyPropertyChanged(() => OnPropertyChanged("SomeProperty"));
}
}
}
public class SomethingInstantiatingOurEntity
{
public void DoSomething()
{
var entity1 = new EntityUsingNotificationRegistry();
entity1.ShouldNotifyWhenPropertiesChange();
var entity2 = new EntityUsingNotificationRegistry();
entity2.ShouldNotNotifyWhenPropertiesChange();
entity1.SomeProperty = "arbitrary string"; // raises event
entity2.SomeProperty = "arbitrary string"; // does not raise event
var entity3 = new EntityUsingNotificationRegistry();
entity3.SomeProperty = "arbitrary string"; // does not raise event
entity3.ShouldNotifyWhenPropertiesChange();
entity3.SomeProperty = "another arbitrary string"; // now raises event
}
}
Now, the registry has a distinct shortcoming in that it holds references to every instance and will prevent those instances from being picked up by the garbage collector. There may be a solution to this by implementing the registry with WeakReferences, but I'm not up-to-snuff on their usage to recommend a particular implementation.
This will not work. You COULD subclass and inject it, but you would have to change the byte-code to make sure the proper methods are CALLED - and that is the harder method.