I'm trying to simplify an INotifyPropertyChanged implementation without using a base class. The best I've come up with is this extension method:
public static bool SetNotify<T>(
this INotifyPropertyChanged sender,
ref T storage,
T value,
PropertyChangedEventHandler handler,
[CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
handler?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
return true;
}
This way I can implement INotifyPropertyChanged in a class, declare a PropertChangedEventHandler event handler, and use it to trigger an event like so:
this.SetNotify(ref _name, value, PropertyChanged);
But I'm curious to know if it's possible to fire an event in a SetNotify() extension method to this, while not passing the event handler. Seeing as the event handler is public, it's accessible from outside the object. So why isn't this allowed?
public static bool SetNotify2<T>(
this INotifyPropertyChanged sender,
ref T storage,
T value,
[CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
PropertyChangedEventHandler handler = sender.PropertyChanged; // <-- Not allowed
storage = value;
handler?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
return true;
}
Related
Why return bool from SetProperty? It's not used.
This is from boilerplate code coming out of a Xamarin.Forms template.
It's not used in inherited classes either.
public class BaseViewModel : INotifyPropertyChanged
{
private bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
protected bool SetProperty<T>(ref T backingStore,
T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals (backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
The return value of this SetProperty method indicates if it has effectively changed its underlying backing field.
The IsBusy implementation doesn't use the return value of SetProperty, but in a derived class, which can access this protected SetProperty method, its return value could be used to perform additional specialized tasks.
By the way, I actually didn't expect the BaseViewModel class to have an IsBusy property with a public setter. As it is now, the outside world can determine if a BaseViewModel instance is busy or not. As I see it, that's somewhat strange.
What is the best way to bind a property to a control so that when the property value is changed, the control's bound property changes with it.
So if I have a property FirstName which I want to bind to a textbox's txtFirstName text value. So if I change FirstName to value "Stack" then the property txtFirstName.Text also changes to value "Stack".
I know this may sound a stupid question but I'll appreciate the help.
You must implement INotifyPropertyChanged And add binding to textbox.
I will provide C# code snippet. Hope it helps
class Sample : INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
firstName = value;
InvokePropertyChanged(new PropertyChangedEventArgs("FirstName"));
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
#endregion
}
Usage :
Sample sourceObject = new Sample();
textbox.DataBindings.Add("Text",sourceObject,"FirstName");
sourceObject.FirstName = "Stack";
A simplified version of the accepted answer that does NOT require you to type names of properties manually in every property setter like OnPropertyChanged("some-property-name"). Instead you just call OnPropertyChanged() without parameters:
You need .NET 4.5 minimum.
CallerMemberName is in the System.Runtime.CompilerServices namespace
public class Sample : INotifyPropertyChanged
{
private string _propString;
private int _propInt;
//======================================
// Actual implementation
//======================================
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
//======================================
// END: actual implementation
//======================================
public string PropString
{
get { return _propString; }
set
{
// do not trigger change event if values are the same
if (Equals(value, _propString)) return;
_propString = value;
//===================
// Usage in the Source
//===================
OnPropertyChanged();
}
}
public int PropInt
{
get { return _propInt; }
set
{
// do not allow negative numbers, but always trigger a change event
_propInt = value < 0 ? 0 : value;
OnPropertyChanged();
}
}
}
Usage stays the same:
var source = new Sample();
textbox.DataBindings.Add("Text", source, "PropString");
source.PropString = "Some new string";
Hope this helps someone.
In XAML, i have a textblock
<TextBlock x:Name="block" Text="{Binding b1}"/>
and in c# i created a property
public int _b1;
public int b1
{
get { return _b1; }
set
{
_b1 = value;
}
}
public MainPage()
{
InitializeComponent();
block.DataContext = this;
}
this worked fine, textblock show the _b1. But when i add a button to chage the _b1 variable
private void bt_click(object sender, RoutedEventArgs e)
{
_b1 = 4;
}
the textblock didn't update ?????
To add to dotNet's answer (which is the correct answer), use a baseclass where you implement INotifyPropertyChanged if you want to avoid redundand code: (this is one example, there are other ways to implement this)
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value)) { return false; }
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And use it like so:
class MyClass: BindableBase
{
private int _b1;
public int B1
{
get { return _b1; }
set { SetProperty(ref _b1, value); }
}
}
For UI to update automatically upon property value change, your property needs to either be a DependencyProperty or your class needs to implement INotifyPropertyChanged interface.
For creating a DependencyProperty, you could use Visual Studio's propdp snippet (type propdp inside your class and press Tab) and fill in respective values. If you want to go INotifyPropertyChanged path, you'll need to write the following code in the setter of your property (AFTER setting the value of _b1):
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("b1"));
thank you for reading my question.
Situation:
I have an observable collection
CheckableTags = new ObservableCollection<CheckableListItem<Item>>();
The wrapper class CheckableListItem<Item> adds a bool per object Item.
public class CheckableListItem<T> : INotifyPropertyChanged
{
private bool mIsChecked;
private T mItem;
public event PropertyChangedEventHandler PropertyChanged;
public CheckableListItem(T item)
{
mItem = item;
}
public bool IsChecked
{
get
{
return mIsChecked;
}
set
{
mIsChecked = value;
OnPropertyChanged("IsChecked");
MeasConSettings.Current.CheckableTags_CheckedChanged("ENTIRE OBJECT HERE");
}
}
public T Item
{
get
{
return mItem;
}
set
{
mItem = value;
OnPropertyChanged("Item");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
When the user checks the checkbox, the bool variable is changed, this is why I fire the function MeasConSettings.Current.CheckableTags_CheckedChanged("ENTIRE OBJECT HERE"); in the property of the bool variable.
The function is as following:
public void CheckableTags_CheckedChanged(object sender)
{}
My question is:
How can I send the entire object from the property of the bool to the function in my viewmodel, what should I send in the field "ENTIRE OBJECT HERE" in order to receive an object of CheckableListItem class with 2 elements the bool variable and the Item.
If possible get a reference to the object so I can copy it in the receiving function.
Alternatives:
If this is not possible, what else can I do?
Thanks in advance.
You can send the entire object using this keyword:
MeasConSettings.Current.CheckableTags_CheckedChanged(this);
Also you can change your method and class to use generics and accept a typed generic parameter like:
public void CheckableTags_CheckedChanged(CheckableListItem<T> sender)
{}
I use NotifyPropertyWeaverMsBuildTask to handle NotifyPropertyChanged for automatic properties. I know OnPropertyChanged() method rise when Property value is changed. But when this method is called value of property is changed and old value is lost. Is there any way to get old value?
tanx.
If you want to use the old value inside the OnPropertyChanged then write it like this
public void OnPropertyChanged(string propertyName, object before, object after)
Then if your code looks like this
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public void OnPropertyChanged(string propertyName, object before, object after)
{
// do something with before/after
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This will be injected
public class Person : INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
object before = Name;
name = value;
OnPropertyChanged("Name", before, Name);
}
}
public void OnPropertyChanged(string propertyName, object before, object after)
{
// do something with before/after
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
More information is available here https://github.com/SimonCropp/NotifyPropertyWeaver/wiki/BeforeAfter
Does this meet your requirements?
INotifyPropertyChanged doesn't provide a means to capture the previous value of a property; you'd have to implement your own.