Fire INotifyPropertyChanged.PropertyChanged via Reflection - c#

I'm trying to fire PropertyChanged via reflection and I'm having some issues.
The following code works:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string m_test = string.Empty;
public string Test
{
get
{
return m_test;
}
set
{
m_test = value;
Notify();
}
}
protected void Notify([CallerMemberName] string name = null)
{
var handler = PropertyChanged;
if (handler != null && name != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class ViewModel : ViewModelBase
{
private string m_test2 = string.Empty;
public string Test2
{
get
{
return m_test2;
}
set
{
m_test2 = value;
Notify();
}
}
}
However, I have added an extension method to INotifyPropertyChanged that would raise it via reflection.
Instead of Notify() I could call this.Notify() instead, which is defined like so:
/// <summary>
/// Invoke sender's PropertyChanged event via Reflection
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="prop">The Property name that has changed</param>
public static void NotifyPropertyChanged(this INotifyPropertyChanged sender, [CallerMemberName] string prop = null)
{
var senderType = sender.GetType();
var methodInfo = senderType.GetField("PropertyChanged", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (methodInfo != null)
{
var delegates = (MulticastDelegate)methodInfo.GetValue(sender);
if (delegates != null)
{
foreach (var handler in delegates.GetInvocationList())
{
handler.Method.Invoke(handler.Target, new object[] { sender, new PropertyChangedEventArgs(prop) });
}
}
}
}
Unfortunately, GetField returns null for ViewModel in the example above.
Is there a way to reflect the parent's event?
I'm thinking of iterating over the base classes, but I'm hoping for a better/easier way.

I think you are going about this the wrong way.
You are breaking the framework's encapsulation of events only being raised by the declaring (owning) instance. By using a publicly available extension method that anyone can call, you are opening a can of worms.
A better solution would be to use a protected method in a base class, as was done in your "the following code works" example.
But if you are really determined on doing it, it can obviously be done.
The extension method below can be used if you want to break the normal protections and encapsulation around events.
public static class ProperyChangedEventExtensions
{
public static void RaisePropertyChanged<T, P>(this T sender, Expression<Func<T, P>> propertyExpression) where T : INotifyPropertyChanged
{
Raise(typeof(T), sender, (propertyExpression.Body as MemberExpression).Member.Name);
}
public static void RaisePropertyChanged(this INotifyPropertyChanged sender, [CallerMemberName] string prop = null)
{
Raise(sender.GetType(), sender, prop);
}
private static void Raise(Type targetType, INotifyPropertyChanged sender, string propName)
{
var evtPropType = targetType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
var evtPropVal = (PropertyChangedEventHandler)evtPropType.GetValue(sender);
evtPropVal(sender, new PropertyChangedEventArgs(propName));
}
}
Usage example (including hopefully some cases that will make you reconsider this approach):
class MyViewModel : INotifyPropertyChanged
{
// The compiler will complain about this:
// Warning 3 The event 'MyNamespace.MyViewModel.PropertyChanged' is never used
public event PropertyChangedEventHandler PropertyChanged;
private string _myProp;
public string MyProp
{
get { return _myProp; }
set
{
_myProp = value;
this.RaisePropertyChanged();
}
}
public readonly int MyImmutableValue;
}
// ...
var vm = new MyViewModel();
vm.PropertyChanged += (sender, evt) => Console.WriteLine("Prop changed {0}", evt.PropertyName);
vm.MyProp = "abc";
vm.RaisePropertyChanged(x => x.MyProp);
vm.RaisePropertyChanged("MyProp");
vm.RaisePropertyChanged("Un Oh. Do we have a problem");
vm.RaisePropertyChanged(x => x.MyImmutableValue);
vm.RaisePropertyChanged("MyImmutableValue");

/// <summary>
/// Invoke sender's PropertyChanged event via Reflection???
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="prop">The Property name that has changed</param>
public static void NotifyPropertyChanged(this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, [CallerMemberName] string prop = null)
{
handler(sender, new PropertyChangedEventArgs(prop));
}
use it like this?
class MyViewModel : INotifyPropertyChanged
{
// The compiler will complain about this:
// Warning 3 The event 'MyNamespace.MyViewModel.PropertyChanged' is never used
public event PropertyChangedEventHandler PropertyChanged;
private string _myProp;
public string MyProp
{
get { return _myProp; }
set
{
_myProp = value;
this.Notify(this.PropertyChanged);
}
}
}

Related

MVVM how to get notify event for a nested class object

Hi I know that there a posts about this topic, but I could not solve my problems with them.
I want to understand and learn a simple way to get a ViewModelBase that I can subcribe to in my View so that a UI Refresh is forced.
I have written an windows console example. The structure is Class Customer(string Name, MyAddress Address) where MyAddress is a Class(string StreetName). In Main I have a list of customers. Now I want to get a message every time there is a change in the list or in the property of the customer including a change of the streetname.
I cant get that to work. If I change the name of the customer it works but not for the 'nest' address. If I change StreetName I dont get a notify Event. I don't know how to subcribe to the ViewModelBase for all the customers in the list.
The Console Progam can be copy/paste in VisulaStudio and runs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace CS_MVVM_NotifyFromNestedClass
{
class Program
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingFiled, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingFiled, value)) return;
backingFiled = value;
OnPropertyChanged(propertyName);
}
}
public class Customer : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { SetValue(ref _name, value); }
}
public MyAddress Address { get; set; }
public Customer(string name, MyAddress address)
{
Name = name;
Address = address;
}
}
public class MyAddress : ViewModelBase
{
private string _street;
public string Street
{
get => _street;
set { SetValue(ref _street, value); }
}
public MyAddress(string street)
{
Street = street;
}
}
public static BindingList<Customer> MyCustomers = new BindingList<Customer>
{
new Customer("John", new MyAddress("JoStreet")),
new Customer("Susi", new MyAddress("SeaStreet")),
};
static void Main(string[] args)
{
//BindingList Event
MyCustomers.ListChanged += OnBindingListChanged;
// 1) Change Name <-- THIS FIRES THE 'OnBindingListChanged' EVENT
MyCustomers[0].Name = "Rick";
// 2) Change Street <-- THIS DOESN'T FIRE A CHANGE-EVENT
MyCustomers[0].Address.Street = "Rockyroad";
//I dont know how to hook up the 'property change event' from ViewModelBase for all obj. of MyCustomer-List
//MyCustomers[0].Address.PropertyChanged += OnSingleObjPropChanged; // <--doesn't work
Console.ReadLine();
}
private static void OnBindingListChanged(object sender, ListChangedEventArgs e)
{
Console.WriteLine("1) BindingList was changed");
foreach (var c in MyCustomers)
{
Console.WriteLine($"{c.Name} {c.Address.Street}");
}
}
private static void OnSingleObjPropChanged(object sender, PropertyChangedEventArgs e)
{
//Never reached --> how to 'hook'
Console.WriteLine("2) Property of List Item was changed");
foreach (var c in MyCustomers)
{
Console.WriteLine($"{c.Name} {c.Address.Street}");
}
}
}
}
First Edit: inner BindingList in the CustomerClass plus the ViewModelBase #Karoshee
I did leave the MyAdresse thing out to simplify. I added a BindingList 'MyTelNrs' to my CustomerClass and subcribt to the ListChanged Event. I didn't change the ViewModelBase from the execpted answere. I do get a notification in my UI, but I don't know if I'am doing it in a save/right way. Just to let the following readers know ... (maybe someone that is better then me answeres, if the below way is 'okay')
public class Customer: ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => SetValue(ref _name, value);
}
public BindingList<string> MyTelNrs = new();
private void OnLstChanged(object sender, ListChangedEventArgs e)
{
OnPropertyChanged(nameof(MyTelNrs));
}
public Customer(string name, BindingList<string> myTelNrs)
{
Name = name;
MyTelNrs = myTelNrs;
MyTelNrs.ListChanged += OnLstChanged;
}
}
First of all need to make Address property a notify property:
public MyAddress Address
{
get => _address;
set
{
SetValue(ref _address, value);
}
}
Than you need to add some additional logic into ViewModelBase, something like this:
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
/// <summary>
/// All child property values and names, that subscribed to PropertyChanged
/// </summary>
protected Dictionary<ViewModelBase, string> nestedProperties
= new Dictionary<ViewModelBase, string>();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingFiled, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingFiled, value)) return;
if (backingFiled is ViewModelBase viewModel)
{ // if old value is ViewModel, than we assume that it was subscribed,
// so - unsubscribe it
viewModel.PropertyChanged -= ChildViewModelChanged;
nestedProperties.Remove(viewModel);
}
if (value is ViewModelBase valueViewModel)
{
// if new value is ViewModel, than we must subscribe it on PropertyChanged
// and add it into subscribe dictionary
valueViewModel.PropertyChanged += ChildViewModelChanged;
nestedProperties.Add(valueViewModel, propertyName);
}
backingFiled = value;
OnPropertyChanged(propertyName);
}
private void ChildViewModelChanged(object? sender, PropertyChangedEventArgs e)
{
// this is child property name,
// need to get parent property name from dictionary
string propertyName = e.PropertyName;
if (sender is ViewModelBase viewModel)
{
propertyName = nestedProperties[viewModel];
}
// Rise parent PropertyChanged with parent property name
OnPropertyChanged(propertyName);
}
public void Dispose()
{ // need to make sure that we unsubscibed
foreach (ViewModelBase viewModel in nestedProperties.Keys)
{
viewModel.PropertyChanged -= ChildViewModelChanged;
viewModel.Dispose();
}
}
}
As I know this does not contradict the MVVM, the only issue with subscribing/unsubscribing child property changed.
Updated:
I added few changes and comments in code below.
The key thing here, that you need to subscribe to PropertyChanged of child properties that inherited from ViewModelBase.
But subscribing is a half way throught: you need to make sure that you unsubscribe it, when objects does not need anymore, so it's has to be stored in nestedProperties.
Also we need to replace child property name from ChildViewModelChanged with parent property name to rise PropertyChange event on parent object. For that goal I saved property name with property value than subscribed on ChildViewModelChanged, this is why I use Dictionary type in nestedProperties
Also important thing to unsubscribe all ProperyChanged, when object no longer needed. I added IDisposable interface and Dispose method to do that thing. Dispose method also needs to be rised (with using or manualy), in your case perhaps will be better to make own BindingList with IDisposable, that rise Dispose on all items.

How to implement INotifyPropertyChanged in C# 6.0?

The answer to this question has been edited to say that in C# 6.0, INotifyPropertyChanged can be implemented with the following OnPropertyChanged procedure:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
However, it isn't clear from that answer what the corresponding property definition should be. What does a complete implementation of INotifyPropertyChanged look like in C# 6.0 when this construction is used?
After incorporating the various changes, the code will look like this. I've highlighted with comments the parts that changed and how each one helps
public class Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
//C# 6 null-safe operator. No need to check for event listeners
//If there are no listeners, this will be a noop
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// C# 5 - CallMemberName means we don't need to pass the property's name
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private string name;
public string Name
{
get { return name; }
//C# 5 no need to pass the property name anymore
set { SetField(ref name, value); }
}
}
I use the same logic in my project. I have a base class for all view models in my app:
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Every view model inherits from this class. Now, in the setter of each property I just need to call OnPropertyChanged().
public class EveryViewModel : PropertyChangedBase
{
private bool initialized;
public bool Initialized
{
get
{
return initialized;
}
set
{
if (initialized != value)
{
initialized = value;
OnPropertyChanged();
}
}
}
Why does it work?
[CallerMemberName] is automatically populated by the compiler with the name of the member who calls this function. When we call OnPropertyChanged from Initialized, the compiler puts nameof(Initialized) as the parameter to OnPropertyChanged
Another important detail to keep in mind
The framework requires that PropertyChanged and all properties that you're binding to are public.
I know this question is old, but here is my implementation
Bindable uses a dictionary as a property store. It's easy enough to add the necessary overloads for a subclass to manage its own backing field using ref parameters.
No magic string
No reflection
Can be improved to suppress the default dictionary lookup
The code:
public class Bindable : INotifyPropertyChanged
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
        /// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
used like this
public class Item : Bindable
{
public Guid Id { get { return Get<Guid>(); } set { Set<Guid>(value); } }
}

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.

ObservableCollection updating List

I am new in WPF and MVVM also. I'm searching for USB devices in my program. But if I connect a new device, it is required to restart program to became visible.
How to do it, that refresh immediately.
Currently I have in my class in which I search device this:
public List<Devices> devices = new List<Devices>();
public void FindDevices() // spremeni v public bool da dobis feedback
{
_deviceList = HidDevices.Enumerate(VendorID, ProductID).ToArray();
... devices.Add(new Devices()
{
DeviceId = nod + 1,
ManufacturerId = deviceManufacturerstring[nod],
ProductId = deviceProductstring[nod],
SerialNumberId = deviceSNstring[nod],
HardwareVersionId = "test4",
FirmwareVersionId = "test5",
DateOfManufaturedId = "test6"
});
On hole for loop I add device to List. I need for loop because I read some data from each device.
I later add this devices in List in ViewModel:
public class Windows1ViewModel : ViewModelBase
{
public ObservableCollection<Devices> devfuck { get; protected set; }
List<Devices> _devicelist;
public List<Devices> Devices
{
get { return _devicelist; }
set { _devicelist = value; }
}
public Windows1ViewModel()
{
USBmiddleware cs = new USBmiddleware();
cs.FindDevices();
devfuck = new ObservableCollection<Devices>();
foreach (var item in cs.devices)
{
devfuck.Add(item);
}
List<Devices> keks = cs.devices;
NotifyPropertyChanged("devfuck");
}
public List<Devices> lvdevices
{
get { return _devicelist; }
set { _devicelist = value; }
}
What to change? Where to add INotifyPropertyChanged? Or how to solve my problem?
Please for help. Thanks!
My ViewModelBase
public class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
#region DisplayName
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void NotifyPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
protected virtual void NotifyPropertyChangedAll(object inOjbect)
{
foreach (PropertyInfo pi in inOjbect.GetType().GetProperties())
{
NotifyPropertyChanged(pi.Name);
}
}
public virtual void Refresh()
{
NotifyPropertyChangedAll(this);
}
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
~ViewModelBase()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
System.Diagnostics.Debug.WriteLine(msg);
}
}
You can do it by using a timer which gets called ever so often (as needed). Of course it would be sexier if you could do this using event management...
Thankfully there is a way to do this :
var watcher = new ManagementEventWatcher();
var query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Query = query;
watcher.Start();
as stated here: Detecting USB drive insertion and removal using windows service and c#
You can then subscribe to the event and invoke your
FindDevices()
From there.
You need to implement the INotifyPropertyChanged interface in your Windows1ViewModel class. Then you need to call it from your property setter, or after you set the property as you are currently doing.
Next, you should set up a DispatcherTimer to call your FindDevices method every so often and then update the ObservableCollection property from there.

Injecting (INotifyPropertyChanged functionality) to an instance of an class

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.

Categories

Resources