Does anybody know how to rewrite this to avoid repeating myData.wwwww.xxxxx.yyyyy.zzzzz?
public string DeepString{
get => myData.wwwww.xxxxx.yyyyy.zzzzz;
set
{
if(myData.wwwww.xxxxx.yyyyy.zzzzz != value)
{
myData.wwwww.xxxxx.yyyyy.zzzzz = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged; // = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string s = null)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(s));
}
This is probably about as good as you are going to get...
You could like define a delegate/action to get/set the value and call that - but IMO that would be changing the actual behaviour too much and would look odd...
private String _shorterName => myData.wwwww.xxxxx.yyyyy;
public string DeepString{
get => _shorterName.zzzzz;
set
{
if (_shorterName.zzzzz != value)
{
_shorterName.zzzzz = value;
OnPropertyChanged();
}
}
Or add a property on the myData object, which is => wwwww.xxxxx.yyyyy; Then reference myData.NewProperty - but then your kindof just kicking the can down the road as it were.
Related
This question already has answers here:
Implementing INotifyPropertyChanged - does a better way exist?
(35 answers)
Closed 4 years ago.
My code has very many of these constructs:
ParamViewModel[] _btns;
public ParamViewModel[] Btns
{
get => _btns;
set => SetProperty(ref _btns, value);
}
Is there any way to do this with a generic to enable me to cut down of the lines of code as they are all the same but with just a different data type, string, int, Color etc.
For reference here's what SetProperty does:
public class ObservableObject : INotifyPropertyChanged
{
protected virtual 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;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
the simple, idiomatic get and set is this
ParamViewModel[] Btns {get;set;}
You cant get much tighter than that
In my sample, I had used property changed event. in this handler, I had an declare a method. each and every time that method fire when changing the property,
in That method, I had set the value to the property. when I set the value, it is a call to the event handler. so it's executing the circular. how to make the method call only one time?
private string name;
public string Name
{
get { return name; }
set
{
name= value;
Name.PropertyChanged+=(s,e)=>
{
Changed(s as string);
};
}
}
private void changed(string name)
{
Name = name;
}
in this code, the changed property call every time.
The basic thing is nameof keyword:
changed(nameof(Name));
You can go futher and omit the need of specifying name at all by adding the following CallerMemberName attribute to your method's parameter:
private void changed([CallerMemberName]string name=null){}
In this case you can call this method without property name: changed();
I'd hazard a guess you want to implement MVVM. The most elegant way to implement it so far is to have the following base class:
public abstract class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetPropertyAndNotifyIfNeeded<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
protected void NotifyPropertyChanged([CallerMemberName]string name=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Implementation of your MVVM:
class Class1:Observable
{
public Class1()
{
}
string propertyValue;
public string Property
{
get => propertyValue;
set => SetPropertyAndNotifyIfNeeded(ref propertyValue, value);
}
}
As per your code remove subscription from your property to avoid recursive loop:
Name.PropertyChanged+=(s,e)=>
In your property call changed(nameof(Name));
class a:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
....
private string name;
public string Name
{
get { return name; }
set
{
if (name!=value)
{
name= value;
changed();
}
}
}
private void changed([CallerMemberName]string name=null)
{
PropertyChanged.?Invoke(this, new PropertyChangedEventArgs(name));
}
...
}
As I can see from your question, and I would guess, you would like to raise a Property Changed on it and set a value. Basically implement MVVM.
A really simple and quick way, would be to use a already existing Nuget package, which would simplify your job.
One that you can use is GalaSoft.MvvmLight.
After you add the Nuget package, you can just use it in the following way:
public string name;
public string Name
{
get { return name; }
set
{
name= value;
RaisePropertyChanged(nameof(Name));
}
}
RaisePropertyChanged, is going to Raise the PropertyChanged event for you (as the name itself suggests).
Why does C# make me do this:
public class SomeClass {
public event PropertyChangedEventHandler Changed;
public void OnChanged(object sender, PropertyChangedEventArgs e)
{
if (Changed != null)
Changed(sender, e);
}
[XmlIgnore]
private string _name;
public string Name {
get { return _name; }
set
{
_name = value;
OnChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
intead of something like this:
[GeneratesChangeNotifications]
public class SomeClass {
[GeneratesChangeNotification]
public string Name { get; set; }
}
I know you can do this with PostSharp and other 3rd party libraries... but something so integral and otherwise error prone (e.g. misspelling the name in the string), I think, should be built into the language... why doesn't Microsoft do this?... is there some purist language reason why this doesn't happen. It's a common enough need.
So far here is the best that I've come up with... I've written a class called NotifyPropertyChanged... it handles the property name, the test for if it's changed or not, the update of the local variable, and the notification of the change... and the SetProperty function is generic, so one function will work with all types of properties... works pretty well.
(Also notice you can call OnPropertyChanged with several property names if you like.)
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(params string[] props)
{
foreach (var prop in props)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public bool SetProperty<T>(ref T oldValue, T newValue, [CallerMemberName]string prop = "")
{
if (oldValue == null && newValue == null)
return false;
if (oldValue != null && oldValue.Equals(newValue))
return false;
oldValue = newValue;
OnPropertyChanged(prop);
return true;
}
}
Then use it like this:
public class MyClass : NotifyPropertyChanged
{
public string Text { get => _text; set => SetProperty(ref _text, value); }
string _text;
}
My RaisePropertyChanged always uses the caller member name, or if needed reflects the property name passed in in an expression (ie, if I need to make one property notify for another). You cannot always guarantee that all properties can notify, and being able to notify more than one at a time is therefore useful.
In addition, with the standard model, using an event allows anyone to subscribe, which an attributed model can not.
It is pretty straightforward to get a method caller, or even get the property name change by using compiler services, like this:
public class EmployeeVM:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName=null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// The compiler converts the above line to:
// RaisePropertyChanged ("Name");
}
}
private string _phone;
public string Phone
{
get { return _phone; }
set
{
_phone = value;
OnPropertyChanged();
// The compiler converts the above line to:
// RaisePropertyChanged ("Phone");
}
}
}
But is it possible to get the caller of the "set" function from inside the set itself? I don't know how you'd syntactically define it in that scope. AKA, who is calling Phone= ?
Look at StackFrame, in particular GetMethod that gives you method name (you'll need to pick one of previous stack frames, depending on if writing helper function to do so). Sample from the article:
StackTrace st = new StackTrace();
StackTrace st1 = new StackTrace(new StackFrame(true));
Console.WriteLine(" Stack trace for Main: {0}",
st1.ToString());
Console.WriteLine(st.ToString());
There are other similar questions that can be found by searching for StackFrame like How do I find the type of the object instance of the caller of the current function?
Unfortunately [CallerMemberName] AttributeUsage is set to AttributeTargets.Parameter, so it can only be used for parameters, like in method signatures
But you can use StackFrame like Alexei Levenkov mentioned
public string Phone
{
get { return _phone; }
set
{
string setterCallerName = new StackFrame(1).GetMethod().Name;
_phone = value;
OnPropertyChanged();
}
}
I have a really, really simple class and I am tried to use the get/set properties but they just aren't working for me... I am sure it is the most obvious thing that I am over looking but I just can't see why they aren't working. I have checked out the code that utilizes this class and its fine that I can see.
In the main code, if I type
Report r = new Report();
string str = "Taco";
r.displayName = str;
The report is declared alright and everything is set to empty strings or a new list or whatever the parameter's default is. But every time I ran this the displayName always remained blank after the code finished executing...
so I tried putting a stop point in the Class displayName set property at set {_displayName = displayName;} and the value always passed in (displayName) was an empty string.... even though the string clearly says "Taco" in the main code.... I have no idea but I am sure its right in my face. If you need more code I can provide it...
Report r = new Report();
string str = "Taco";
r.setReportDisplayName(str);
But for some reason the above works.
public class Report
{
private string _reportPath = string.Empty;
public string reportPath
{
get { return _reportPath; }
set { _reportPath = reportPath; }
}
private string _displayName = string.Empty;
public string displayName
{
get { return _displayName; }
set { _displayName = displayName; }
}
private List<parameter> _parameters = new List<parameter>();
public List<parameter> parameters
{
get { return _parameters; }
set { _parameters = parameters; }
}
public Report() { }
public Report(string path, string display, List<parameter> param)
{
_reportPath = path;
_displayName = display
_parameters = param;
}
public void setReportDisplayName(string str)
{
_displayName = str;
}
}
You are defining your properties incorrectly. This should be done as:
private string _displayName = string.Empty;
public string displayName
{
get { return _displayName; }
set { _displayName = value; }
}
That being said, if you are using this for Silverlight, you most likely will want to implement INotifyPropertyChanged. Without this, data binding will not reflect changes made in code.
To implement this interface, you'll need to add this implementation. A "standard" way to implement this is via:
public class Report : INotifyPropertyChanged
{
// Declare the PropertyChanged event
public event PropertyChangedEventHandler PropertyChanged;
// Raise the PropertyChanged event
protected void NotifyPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
At this point, you need to define your properties like:
private string _displayName = string.Empty;
public string DisplayName
{
get { return _displayName; }
set
{
if (_displayName != value)
{
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
}
Doing this will allow you to data bind to your "Report" class. You may also want to consider using ObservableCollection<T> instead of List<T> for any collections you want to use with data binding.
You need to be assigning the value of the special variable value in your sets. value is what will be holding the (heh) value that was assigned to your property as it is passed into the set.
public string reportPath
{
get { return _reportPath; }
set { _reportPath = value; }
}