I'm working on a system to represent data. In it we use a templetized interface that implements INotifyPropertyChanged.
public interface IScalar<T> : ISignal, INotifyPropertyChanged
{
void Check(T value);
/// <summary>
/// Formats the specified value based on the item's formatting
/// characteristics. Will throw an exception if the specified value
/// could not be properly converted to the underlying type.
/// </summary>
/// <param name="value">Value to format.</param>
/// <returns>Formatted value.</returns>
string Format(T value);
T Value { get; set; }
string Units { get; set; }
}
We end having a class that implements IScalar<double> and IScalar<string>. Is there a way to make sure the correct PropertyChanged event is fired? It uses a string representation of the property name. And since I have two properties with the same name I can't guarantee the right event will be fired. We are looking to have a grid in WPF bind to a list of IScalar
You can't implicitly implement an generic interface with two different type parameters. You have to make at least one explicit. Here you see a sample implementation for your class. As you can see you can bind to StringValue and DoubleValue:
public class Both : IScalar<string>, IScalar<double>
{
public string StringValue { get; set; }
string IScalar<string>.Value
{
get
{
return StringValue;
}
set
{
this.StringValue = value;
}
}
public double DoubleValue { get; set; }
double IScalar<double>.Value
{
get
{
return DoubleValue;
}
set
{
DoubleValue = value;
}
}
// other methods and properties left out
}
When you need to raise PropertyChanged, you can raise that event for either StringValue or DoubleValue.
You can't have two properties with the same name on your datacontext.
If you did, you would have a compile error reflecting ambiguity.
Remember that your source is your datacontext.
In addition, the databinding system relies on both source and path in order to perform databinding.
Apart from Scott's proper answer, you may also want to get away from calling the PropertyChanged Method by passing in strings. Here's how you do that:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
INPC using member/method name is now supported in the attribute class CallerMemberName found in the System.runtime.compilerservices assembly.
You can read more about it here.
ADVANTAGES:
It allows you to easily establish a base class once and for all that handles all notifications based only on the method name. The setter methods merely have this line of code:
OnPropertyChanged();
Related
I would like to have a public property that can be both got / set. But I would also like an NameChanged event to be triggered when it is set, but I do not want to have a non-public field to store its value.
My problem is, within a setter, how can I assign the property with the new value without causing infinite recursion ?
public delegate void NameChangedHandler( object sender, EventArgs e );
event NameChangedHandler NameChanged;
public String Name
{
get;
set { Name = value; NameChanged(this, null); } // Recursion ?
}
[...] but I do not want to have a non-public field to store its value.
This requirement can't be met. Even auto-properties have a private field backing their values. They're still created during C# compilation. And, anwyay, you can't mix auto-properties and properties with body in the getter or setter.
Maybe you can be interested in some open source project I created and published some months ago called TrackerDog which can turn any object into a change-trackable one. And INotifyPropertyChanged is auto-implemented during run-time using proxies.
This way, you don't need to switch to non-auto-properties and you can still get the event injected and each set of properties within a given object will raise INotifyPropertyChanged.PropertyChanged event.
For example, given the following class:
public class User
{
public virtual string Name { get; set; }
public virtual byte Age { get; set; }
}
...you can turn an instance of User into change-trackable as follows:
User user = new User().AsTrackable();
// or
User user = Trackable.Of<User>();
...and now that User instance implements INotifyPropertyChanged:
INotifyPropertyChanged userWhichCanHookChangeHandlers =
(INotifyPropertyChanged)user;
userWhichCanHookChangeHandlers.PropertyChanged += (sender, e) =>
{
string propertyName = e.PropertyName;
};
Check project's full how-to to get further details.
If you want your Setter to not actually Set anything, you CAN do that. (Not sure why you want to, but, nothing is stopping you.)
public String Name
{
get { return _Name; }
set { NameChanged(this, null); }
}
That is valid and will compile just fine.
value is nothing more than a parameter to your setter method, and just like with any other method, you can choose to do absolutely nothing with your parameter(s).
Given a standard view model implementation, when a property changes, is there any way to determine the originator of the change? In other words, in the following view model, I would like the "sender" argument of the "PropertyChanged" event to be the actual object that called the Prop1 setter:
public class ViewModel : INotifyPropertyChanged
{
public double Prop1
{
get { return _prop1; }
set
{
if (_prop1 == value)
return;
_prop1 = value;
// here, can I determine the sender?
RaisePropertyChanged(propertyName: "Prop1", sender: this);
}
}
private double _prop1;
// TODO implement INotifyPropertyChanged
}
Alternatively, is it possible to apply CallerMemberNameAttribute to a property setter?
If I understood correctly, you're asking about the caller of the setter. That means, the previous method call in the call stack before getting to the setter itself (which is a method too).
Use StackTrace.GetFrames method for this. For example (taken from http://www.csharp-examples.net/reflection-callstack/):
using System.Diagnostics;
[STAThread]
public static void Main()
{
StackTrace stackTrace = new StackTrace(); // get call stack
StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames)
// write call stack method names
foreach (StackFrame stackFrame in stackFrames)
{
Console.WriteLine(stackFrame.GetMethod().Name); // write method name
}
}
The output:
Main
nExecuteAssembly
ExecuteAssembly
RunUsersAssembly
ThreadStart_Context
Run
ThreadStart
Basically, what you're asking for would be stackFrames[1].GetMethod().Name.
My first approach to your problem would be to derive from PropertyEventArgs. The new class would have a member called, for instance PropertyChangeOrigin in addition to PropertyName. When you invoke the RaisePropertyChanged, you supply an instance of the new class with the PropertyChangeOrigin set from the information gleaned from the CallerMemberName attribute. Now, when you subscribe to the event, the subscriber could try casting the eventargs to your new class and use the information if the cast is successful.
This is what I always use as a middle-ground between INotifyPropertyChanged and my View Models:
public class NotifyOnPropertyChanged : INotifyPropertyChanged
{
private IDictionary<string, PropertyChangedEventArgs> _arguments;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged([CallerMemberName] string property = "")
{
if(_arguments == null)
{
_arguments = new Dictionary<string, PropertyChangedEventArgs>();
}
if(!_arguments.ContainsKey(property))
{
_arguments.Add(property, new PropertyChangedEventArgs(property));
}
PropertyChanged(this, _arguments[property]);
}
}
Two things here. It uses the [CallerMemberName] attribute to set the property name. This makes the usage syntax as follows:
public string Words
{
set
{
if(value != _words)
{
_words = value;
OnPropertyChanged( );
}
}
}
Beyond that, it stores the PropertyChangedEventArgs object in a dictionary so it's not created a ton of times for properties that are frequently set. I believe this addresses your problem. Good luck!
Whenever I have had to pass in extra information down into a VM I have a great success with using commands:
Commands, RelayCommands and EventToCommand
I want to be notified when a property changes so that I can log the oldvalue and new value of the property in database.
So I decided to go with the approach of property setter and have a generic method that handles all properties.
I created below class:
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName,
T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
//write to database the values!!!
}
}
and on my property I call it as such:
private string _surname;
public string Surname
{
get { return _surname; }
set
{
string temp = Surname;
_surname = value;
Helper.PropertyChangedExtendedEventArgs("Surname", temp, value);
}
}
but it is first time working with generics so got few concerns :
how do I call this on my property?
is this a good approach?
would I be able to call a function in public
PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue) and save to database?
You seem to have got a bit of confused in property change usage.
Typically, components that wish to be observable about their property changes INotifyPropertyChanged interface. So as such correct implementation would be something like
private string _surname;
public string Surname
{
get { return _surname; }
set
{
if (_surname != value) // IMP: you want to inform only if value changes
{
string temp = Surname;
_surname = value;
// raise property change event,
NotifyPropertyChanged(temp, _surname);
}
}
}
Typically, base implementation could provide helper implementation to raise the event - for example,
public abstract Component : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(T oldVal, T newVal, [CallerMemberName] String propertyName = "")
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedExtendedEventArgs(propertyName, oldVal, newVal));
}
}
}
Now, its consumer's responsibility on how to react to property changes. This separates observable components from unrelated concern of what to do when some property changes. Typically, one will have some the common implementation that would say - save the current object state in stacked manner as to provide undo-redo functionality.
So in your case, you wish to log them to database (?), there should be code that would listen to this property change events and does the logging. There will be some controller/binding code that would iterate through all objects implementing this interface and hook up the event. Typically, the root level container does such house keeping - for example, in a designer surface, its the root element (or code that is handling root element) would hook up event whenever a new component is created and added to the design surface.
I'm building a WPF application and I'm slowly uncovering some of the joys and also the frustrations of using WPF. My latest question involves updating the UI using INotifyPropertyChanged
My app has stacked UserControls with each UserControl containing multiple controls, so overall there are hundreds of controls which update every second providing live data. In order to update all controls I'm using something similar to below which does currently work as intended.
namespace ProjectXAML
{
public partial class ProjectX : UserControl, INotifyPropertyChanged
{
#region Declare Getter/Setter with INotifyPropertyChanged groupx3
private string m_group1Text1;
public string group1Text1
{
get
{
return m_group1Text1;
}
set
{
m_group1Text1 = value;
NotifyPropertyChanged("group1Text1");
}
}
private string m_group1Text2;
public string group1Text2
{
get
{
return m_group1Text2;
}
set
{
m_group1Text2 = value;
NotifyPropertyChanged("group1Text2");
}
}
private string m_group2Text1;
public string group2Text1
{
get
{
return m_group2Text1;
}
set
{
m_group2Text1 = value;
NotifyPropertyChanged("group2Text1");
}
}
private string m_group2Text2;
public string group2Text2
{
get
{
return m_group2Text2;
}
set
{
m_group2Text2 = value;
NotifyPropertyChanged("group2Text2");
}
}
private string m_group3Text1;
public string group3Text1
{
get
{
return m_group3Text1;
}
set
{
m_group3Text1 = value;
NotifyPropertyChanged("group3Text1");
}
}
private string m_group3Text2;
public string group3Text2
{
get
{
return m_group3Text2;
}
set
{
m_group3Text2 = value;
NotifyPropertyChanged("group3Text2");
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
/// Notifies the property changed.
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
}
My questions are:
Is there a more elegant way to raise PropertyChanged events for lots of controls rather than lots of get/set code?
Is there a way to raise 1 PropertyChanged event covering the whole UserControl containing multiple controls instead of a separate event for every control? Is there a better method than what I'm attempting?
In strict reference to this part of your question..."Is there a way to raise 1 PropertyChanged event covering the whole UserControl containing ".
Yes, you can raise a PropertyChanged notification which says all my properties on my object are updated.
Use:
NotifyPropertyChanged(null);
then this informs the listener of INotifyPropertyChanged that all properties have changed on an object.
This isn't normally used...and can be abused....and cause inefficient updates e.g. if you were only changing a few properties and used that.
But you could argue the case for using it if you have lots of properties in your object, that you were always changing anyway at the same time...and you wanted to collapse lots of individual notifications into 1 that was raised after you had modified all properties.
Example use case (i.e. presumes you are updating all your groups in some way):
void UpdateAllGroupTextProperties()
{
group1Text1 = "groupA";
group1Text2 = "groupA2";
group2Text1 = "groupB";
group2Text2 = "groupB2";
group3Text1 = "groupC";
group3Text2 = "groupC2";
NotifyPropertyChanged(null);
}
For point 1 if you are using VS 2012 you can do the below
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
and then you can use your set property method without having to hard code the name of the properties.
Note the above code is an except of the below link
http://danrigby.com/2012/03/01/inotifypropertychanged-the-net-4-5-way/
Use the design pattern model view controler. So the model will raise the changes for you. Together with MVVM the controls will see with its dependency objects the changes and view them automatically.
I have a parent class implementing INotifyPropertyChanged, and the parent class has multiple children. The children have different properties that all call PropertyChanged. I want to add validation, but I really don't want to have to write validations for every child class. The validation rules are supplied from the database, so I would have to eventually pull the validation rules for each child, then check the value against the rules. If I did that, I think that it would have too much redundant code, and I would like to have it placed at the parent level since PropertyChanged triggers on the string value of the value itself.
Is it possible to have the validation method on the parent class so I wouldn't have to write a validation method for every child class? Mind you, the properties in every child class are different.
Below is what I currently have, with validation in the child class.
public Parent : INotifyChanged {
/// <summary>
/// Occurs when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> for a given
/// property.
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(String propertyName) {
// Get the hanlder
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that the event handler is not null
if(null != handler) {
// Fire the event
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Child1 Class:
public Child1 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child1Description");
if(!Regex.IsMatch(Child1Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child1Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child1Description {
get {
return Child1description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child1Description");
}
}
}
Child2 Class:
public Child2 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();
private void Validate() {
this.RemoveError("Child2Description");
if(!Regex.IsMatch(Child2Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
this.AddError("Child2Description", "Only non-numerics allowed.");
}
}
private void AddError(string columnName, string msg) {
if(!m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Add(columnName, msg);
}
}
private void RemoveError(string columnName) {
if(m_validationErrors.ContainsKey(columnName)) {
m_validationErrors.Remove(columnName);
}
}
public string Error {
get {
if(m_validationErrors.Count > 0) {
return "Field data is invalid.";
}
else return null;
}
}
public string this[string columnName] {
get {
if(m_validationErrors.ContainsKey(columnName)) {
return m_validationErrors[columnName];
}
else {
return null;
}
}
}
/// <summary>
/// Description of the air entity
/// </summary>
public string Child2Description {
get {
return Child2description;
}
set {
description = value;
Validate();
OnPropertyChanged("Child2Description");
}
}
}
I don't believe you're going to be able to do what you want.
In the trivial case, you may be able to make it work. Once you move into more complex types, I don't know that you're saving yourself much effort by having the validation in the parent instead of the child.
The trivial case is going to be where multiple children have similar types of properties. You can enforce calling the properties the same way and then you can write a validation rule within the parent that triggers on the name of the property. However, you could argue that those properties should then be part of the parent and inherited by the child.
The more complex case is individual properties at each child with little to no similarity to the properties of other children. Whether you put the validation code at the child or the parent makes no difference. You have to write the validation code for each individual property you wish to validate.
Given that your validation rules will be stored in a DB, you could write a method in the parent that would allow the children to retrieve the validation rule(s) for their properties. The child would still validate its own properties, but you would have common code for accessing the rules.
I ended up passing the property name, the property value and the list of rules I wanted to use to validate. Storing the Validate method in the parent, so it would run regardless if which child used it, it would use its own rules, but keep the same validation error message dictionary.
protected void Validate(string propertyName, string propertyValue, List<ValidRule> validRules) {
string temp = propertyValue.ToString();
this.RemoveError(propertyName);
if(propertyName.Equals("Description")) {
foreach(ValidRule validRule in validRules) {
if(!Regex.IsMatch(propertyValue, validRule.Rule) && !String.IsNullOrWhiteSpace(propertyValue)) {
this.AddError(propertyName, validRule.ErrorMessage);
break;
}
}
}
}
Actually it is do able just not the way you think you want it to occur.
The below would be the steps I would follow to do something similar.
get the Microsoft enterprise library for starters as you will be using the Microsoft.Practices.EnterpriseLibrary.Validation reference.
Create a validation class that inherits from Validator<T> (this is part of the Enterprise library).
Override the method DoValidate(T objectToValidate, object currentTarget, string key, ValidationResults validationResults) and currentTarget is the object being validated. You can pull the validation rules from the current target.
You then create attribute for that validate making it inherit from ValueValidatorAttribute.
You override the method DoCreateValidator(Type targetType, Type ownerType, MemberValueAccessBuilder memberValueAccessBuilder, ValidatorFactory validatorFactory) in the attribute class.
Once these first 5 steps are done it means you can attribute properties you want to validate and let the validator pick up the validation rule to use from the class (list of rules or dictionary or rules to perform to the property totally your choice).
The next step is to move the interface IDataErrorinfo to the parent class, create a Validate() method that takes the results from the call Microsoft.Practices.EnterpriseLibrary.Validation.Validation.Validate(this); which returns validation errors should they have occurred.
Its up to you on how where you want to place the method call. The simplest way when testing would be to place it in the OnPropertyChanged method you have.