Implementing INotifyPropertyChanged for nested properties - c#

I have a Person class:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name{
get { return _name; }
set {
if ( _name != value ) {
_name = value;
OnPropertyChanged( "Name" );
}
}
private Address _primaryAddress;
public Address PrimaryAddress {
get { return _primaryAddress; }
set {
if ( _primaryAddress != value ) {
_primaryAddress = value;
OnPropertyChanged( "PrimaryAddress" );
}
}
//OnPropertyChanged code goes here
}
I have an Address class:
public class Address : INotifyPropertyChanged
{
private string _streetone;
public string StreetOne{
get { return _streetone; }
set {
if ( _streetone != value ) {
_streetone = value;
OnPropertyChanged( "StreetOne" );
}
}
//Other fields here
//OnPropertyChanged code goes here
}
I have a ViewModel:
public class MyViewModel
{
//constructor and other stuff here
private Person _person;
public Person Person{
get { return _person; }
set {
if ( _person != value ) {
_person = value;
OnPropertyChanged( "Person" );
}
}
}
I have a View which has the following lines:
<TextBox Text="{Binding Person.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
<TextBox Text="{Binding Person.Address.StreetOne, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged />
Both values show up in the text box ok when the view loads.
Changes to the first text box will fire OnPropertyChanged( "Person" ) in MyViewModel. Great.
Changes to the second text box ("Person.Address.StreetOne") does NOT fire OnPropertyChanged( "Person" ) inside MyViewModel. Meaning it doesn't call the Person object's SET method. Not great. Interestingly the SET method of StreetOne inside the Address class is called.
How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???
Do I need to flatten my data so SteetOne is inside Person and not Address??
Thanks!

While adding 'pass-through' properties to your ViewModel is a fine solution, it can quickly become untenable. The standard alternative is to propagate changes as below:
public Address PrimaryAddress {
get => _primaryAddress;
set {
if ( _primaryAddress != value )
{
//Clean-up old event handler:
if(_primaryAddress != null)
_primaryAddress.PropertyChanged -= AddressChanged;
_primaryAddress = value;
if (_primaryAddress != null)
_primaryAddress.PropertyChanged += AddressChanged;
OnPropertyChanged( "PrimaryAddress" );
}
void AddressChanged(object sender, PropertyChangedEventArgs args)
=> OnPropertyChanged("PrimaryAddress");
}
}
Now change notifications are propagated from Address to person.
Edit: Moved handler to c# 7 local function.

if you want the viewmodel SET to be called you could create a street property
public class MyViewModel
{
//constructor and other stuff here
public string Street{
get { return this.Person.PrimaryAddress.StreetOne; }
set {
if ( this.Person.PrimaryAddress.StreetOne!= value ) {
this.Person.PrimaryAddress.StreetOne = value;
OnPropertyChanged( "Street" );
}
}
}
xaml
<TextBox Text="{Binding Street, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged />
but this solution has its drawbacks. i go with Reeds answer in my projects

How do I get the SET method of the Person object inside the ViewModel to be called when Person.Address.StreetOne is changed???
Why do you want to do this? It should not be required - you only need the StreetOne property changed event to fire.
Do I need to flatten my data so SteetOne is inside Person and not Address??
If you want to actually cause this to trigger, you don't need to flatten it (though that is an option). You can subscribe to the Address's PropertyChanged event within your Person class, and raise the event for "Address" within Person when it changes. This shouldn't be necessary, however.

Since I wasn't able to find a ready-to-use solution, I've done a custom implementation based on Pieters (and Marks) suggestions (thanks!).
Using the classes, you will be notified about any change in a deep object tree, this works for any INotifyPropertyChanged implementing Types and INotifyCollectionChanged* implementing collections (Obviously, I'm using the ObservableCollection for that).
I hope this turned out to be a quite clean and elegant solution, it's not fully tested though and there is room for enhancements. It's pretty easy to use, just create an instance of ChangeListener using it's static Create method and passing your INotifyPropertyChanged:
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
the PropertyChangedEventArgs provide a PropertyName which will be always the full "path" of your Objects. For example, if you change your Persons's "BestFriend" Name, the PropertyName will be "BestFriend.Name", if the BestFriend has a collection of Children and you change it's Age, the value will be "BestFriend.Children[].Age" and so on. Don't forget to Dispose when your object is destroyed, then it will (hopefully) completely unsubscribe from all event listeners.
It compiles in .NET (Tested in 4) and Silverlight (Tested in 4). Because the code in seperated in three classes, I've posted the code to gist 705450 where you can grab it all: https://gist.github.com/705450 **
*) One reason that the code is working is that the ObservableCollection also implements INotifyPropertyChanged, else it wouldn't work as desired, this is a known caveat
**) Use for free, released under MIT License

There is a spelling mistake in your property change notification:
OnPropertyChanged( "SteetOne" );
should be
OnPropertyChanged( "StreetOne" );

Related

Two Way binding to a Dependency Property in a User Control and call a method

I know, title is a little confusing so let me explain. I have a user control that has a dependency property. I access this dependency property with a regular property called Input. In my view model I also have a property called Input. I have these two properties bound together in XAML using two-way binding as shown below:
<uc:rdtDisplay x:Name="rdtDisplay" Input="{Binding Input, Mode=TwoWay}" Line1="{Binding myRdt.Line1}" Line2="{Binding myRdt.Line2}" Height="175" Width="99" Canvas.Left="627" Canvas.Top="10"/>
Okay in my view model, I call a method whenever the value of Input is changed as shown in my property:
public string Input
{
get
{
return input;
}
set
{
input = value;
InputChanged();
}
}
The problem with this is that when I set the value of Input in my view model it only updates the value of the variable input as per my setter in my property. How can I get this to update back to the dependency property in the user control? If I leave the code input = value; out then I get a compilation error.
I need something like this:
public string Input
{
get
{
return UserControl.Input;
}
set
{
UserControl.Input = value;
InputChanged();
}
}
If I make the Input property in my view model look like this:
public string Input
{
get; set;
}
then it works, however, I am unable to call the InputChanged() method that I need to call when the Property is changed. All suggestions are appreciated.
Implement INotifyPropertyChanged in your ViewModel
public class Sample : INotifyPropertyChanged
{
private string input = string.Empty;
public string Input
{
get
{
return input;
}
set
{
input = value;
NotifyPropertyChanged("Input");
InputChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In your case, you can do it in the code behind of your usercontrol

ViewModel properties with multiple calls to PropertyChanged

Recently I've been learning C# and WPF for work. I'm trying to use MVVM on a project I'm working on, just to keep the code organized and learn how it works.
In MVVM, controls on the View bind to properties on the ViewModel, which implements INotifyPropertyChanged. Pretty often, when a certain property is updated, I'll want a bunch of other properties to get updated as a result.
For example, I have a ListBox with a TextBox above it. You can type in the TextBox, and it filters the stuff in the ListBox. But I also need to be able to clear the TextBox from code in certain cases. The code ends up looking like this:
private Collection<string> _listOfStuff;
public Collection<string> FilteredList
{
get
{
if (String.IsNullOrWhiteSpace(SearchText))
{
return _listOfStuff;
}
else
{
return new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
set
{
if (value != _listOfStuff)
{
_listOfStuff = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText"); // Tells the view to change the value of the TextBox
OnPropertyChanged("FilteredList"); // Tells the view to update the filtered list
}
}
}
As this project gets bigger, this is starting to feel sloppy. I have one setter with 6 calls to OnPropertyChanged and it's getting hard to keep track of stuff. Is there a better way to do this?
I tried out Assisticant on a project about a year ago. It figures out which of your properties need to raise notifications and also which are related. There is a good course for it on Pluralsight and the examples on the website are pretty good. If nothing else you could check out the source code to see how he did it.
Also some good suggestions from Change Notification in MVVM Hierarchies.
They mentioned:
Use an attribute -> e.g. [DependsUpon(nameof(Size))]
and
Josh Smith's PropertyObserver
Could put the raise property change calls in a method if you just need to raise the same notifications every time.
First you shouldn't do potentially expensive operations in a command, then you'll be able to remove the OnPropertyChanged("FilteredList"); from your SearchText.
So you should move that code from the getter and into it's own command and bind it from XAML (either as Command on a button or using Blends Interactivity Trigger to call it when the text fields value changes).
public ICommand SearchCommand { get; protected set; }
// Constructor
public MyViewModel()
{
// DelegateCommand.FromAsyncHandler is from Prism Framework, but you can use
// whatever your MVVM framework offers for async commands
SearchCommand = DelegateCommand.FromAsyncHandler(DoSearch);
}
public async Task DoSearch()
{
var result = await _listOfStuff.Where(x => x.Contains(SearchText)).ToListAsync();
FilteredList = new Collection<string>(result);
}
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get
{
return _searchText;
}
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
}
}
}
On a side note: You can also use OnPropertyChanged(nameof(FilteredList)); to have a refactor friendly version, when you rename your property all of your OnPropertyChanged calls will be updated to. Requires C# 6.0 though, but it's compatible with older .NET Frameworks (back to 2.0), but requires Visual Studio 2015 or later
For anyone searching for a good solution to this type of problem: Check out ReactiveUI.
It is a framework based on Reactive Extensions (Rx), with the idea that you model this type of dependencies between properties explicitly, without a jungle of RaisePropertyChanged(..).
Specifically check out the ObservableAsPropertyHelper (sometimes called OAPH).
You should only raise OnPropertyChanged in the setter of the property itself.
A cleaner implementation of your ViewModel can be:
private Collection<string> _listOfStuff;
private Collection<string> _filteredList;
public Collection<string> FilteredList
{
get
{
return _filteredList;
}
set
{
if (value != _filteredList)
{
_filteredList = value;
OnPropertyChanged("FilteredList");
}
}
}
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
if (value != _searchText)
{
_searchText = value;
OnPropertyChanged("SearchText");
FilteredList = new Collection<string>(_listOfStuff.Where(x => x.Contains(SearchText)));
}
}
}
if you just don't wanna type only other option is to fire OnPropertyChanged for all properties which can be done by passing a null or string.Empty, although it will be sloppier code!
OnPropertyChanged(Null);
or
OnPropertyChanged(String.Empty);

Binding ListBox.SelectedItem to Property

This might be a duplicate question, but I'm unable to find a good answer. All the answers like Binding WinForms ListBox to object properties don't work on my WinForm. I'll explain.
I have a list of Firms that I show in a ListBox. I would like when the SelectedItem changes, that it updates a property on my model. So that I can read the Firms properties.
// the classes
public class Firm
{
public string Name { get; set; }
public int Id { get; set; }
// more properties ...
}
public class MyModel : INotifyPropertyChanged
{
private Firm _firm = new Firm();
public Firm Firm
{
get { return _firm; }
set
{
if (Equals(value, _firm)) return;
_firm = value;
OnPropertyChanged();
}
}
// more properties and OnPropertyChanged() ...
}
// the form
private MyModel Model;
public void MyForm(List<Firm> firms)
{
lstFirm.DataBindings.Add("SelectedItem", Model, "Firm",
true, DataSourceUpdateMode.OnPropertyChanged);
lstFirm.DisplayMember = "Name";
lstFirm.ValueMember = "Id";
lstFirm.DataSource = firms;
}
public void lstFirm_SelectedIndexChanged(object sender, EventArgs e)
{
// Do something with Model.Firm
}
The problem is that Model.Firm null is. Does anybody have an idea what I need to do to make a databinding between the ListBox and the Model? I bind other stuff on my WinForm (such as TextBoxes to String properties) and those work nicely.
From what I can see, your code never sets Model.Firm... Where's the constructor for MyModel? If you don't provide one, Model.Firm will stay null unless you explicitly set it. Here's an example constructor:
public MyModel(Firm firm)
{
_firm = firm;
}
Also, Equals() doesn't do what you think it does. Instead of if (Equals(value, _firm)) return;, use this: if (value == _firm) return;
Ok, so after a weekend of testing, I figured it out.
I was debuging in the SelectedIndexChanged event and didn't see the change in my Model.Firm just yet. But as the SelectedItemChanged event is only internal, I couldn't use that and that's where the databinding on SelectedItem applies the values to databound items.
Now the reason why the change isn't visible yet, is because the SelectedItemChanged is only fired after the SelectedIndexChanged is executed. So internally in the ListBox control, it probably looks like
this.SelectedIndex = value;
this.SelectedItem = FindItem(value);
this.SelectedIndexChanged(/*values*/);
this.SelectedItemChanged(/*values*/); // Apply databinding changes
So it's quite normal that you don't see the changes, before the change has occured. And I didn't know this, so I was kinda stumped why the SelectedItem (who was displaying the changed value) wasn't copied over to the databound model property.
So I didn't have to change anything major to get it all working. :)

Bind control to property not working

I am creating an application that uses several threads as a result I want to try to use UIControls in my code behind as few as possible. The way I do it is by binding the controls to a property in my code behind that way I will be able to update the control by changing that property it does not matter if that property is updated on a different thread. Anyways I am creating the following code in order for the class to create the bindings form me.
public static class MyExtensionMethods
{
public static TextBoxBind<T> BindTextBox<T>(this TextBox textbox, string property=null)
{
return new TextBoxBind<T>(textbox,property);
}
}
public class TextBoxBind<T> : INotifyPropertyChanged
{
string property;
protected T _Value;
public T Value
{
get { return _Value; }
set { _Value = value; OnPropertyChanged(property); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(string propertyName){
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public TextBoxBind(TextBox textbox, string property)
{
if (property == null)
{
property = "Value";
}
this.property = property;
Binding b = new Binding(property)
{
Source = this
};
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
textbox.SetBinding(TextBox.TextProperty, b);
}
}
And on my XAML I have:
<TextBox Name="textBox2" />
Therefore I will be able to use the first code that I posted as:
var newTextBox2 = textBox2.BindTextBox<int>();
newTextBox2.Value = 50; // this will update the textBox2.Text = "2"
// also every time I update the value of textBox2 newTextBox2.Value will update as well
The problem is when I try to bind it to a custom object. Take this code for example:
public class Person
{
public string Name { get; set; }
public string Age { get; set; }
public override string ToString()
{
return Age.ToString();
}
}
void LogIn_Loaded(object sender, RoutedEventArgs e)
{
txtUsuario.Focus();
var newTextBox2 = textBox2.BindTextBox<Person>("Age");
// here newTextBox2 never updates....
}
When it comes to data binding one should update an object (doesn't matter CLR property or DependencyObject) from the same thread, as the UI is running at. If you have a UI element bound to something in code, updating that from a separate thread will lead to exception. However, you can always retrieve your UI thread and perform property update there.
Here's a piece of code, that I am using in a similar situation as you have:
ThreadStart updateLogs = delegate()
{
ObservableCollection<LogMessage> newLogs = this._parcer.Parce();
foreach (LogMessage log in newLogs)
LogMessages.Add(log);
};
App.Current.Dispatcher.BeginInvoke(updateLogs, null);
This block of code is running in a thread different to one UI is running at. So I extract the code, that actually updates the binding source (which is LogMessages) into a delegate updateLogs and then run this delegate in a UI thread, passing it to the application dispatcher.
Nevertheless, WPF application can have more than one Dispather if, for example, you create separate windows in separate threads, although this approach is rare. But just in case, DependencyObject class has a Dispatcher property, which references the Dispather that owns this object.
OnPropertyChanged(property); should be pointing to Value, since that's the Name of your Property.
This should not be pointing to the type T.
So this code is not right:
if (property == null)
{
property = "Value";
}
because property should always be "Value"
public T Value
{
get { return _Value; }
set { _Value = value; OnPropertyChanged("Value"); }
}

INotifyPropertyChanged Implemented now what?

I am trying to create a list box that displays a set of data that will be updated over time. I have a simple list box:
<ListBox Name="lbRegisters" ItemsSource="{Binding}" />
And I have defined a class for my objects:
public class register : INotifyPropertyChanged
{
private int address;
public int Address { get { return address; } }
private int value;
public int Value
{
get{ return value; }
set{
this.value = value;
OnValueChanged("Value");
}
}
public register(int a)
{
address = a;
value = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnValueChanged(string name){
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public override string ToString()
{
return address + ": " + value;
}
}
And I hold a list of these in an ObservableCollection<register>. Then set the ListBox.ItemsSource=registerslist; in the CodeBehind. The list is initialized and the inital data displays correctly.
Now what do I need to do to get my ListBox to update when a "register.Value" changes. The event handler is called but there nothing is subscribed to the event.
I guess I need to trigger something in the ListBox or ObservableCollection to tell the GUI to update. I have read dozens of posts of a similar problem but they all seem to indicate that once you have implemented INotofyPropertyChanged it just automagicaly works.
What is the next step?
The problem is on your ToString() function. Yes it could be used to display complex string in ListView items but it is not a proper way to bindings because ListView does not knows when part of this string was changed.
Do the following:
1. Declare property on register class like
public string AddressValue
{
get { return address + ": " + value; }
}
2. Add OnValueChanged("AddressValue") in value and address setters like:
public int Value
{
get{ return value; }
set{
this.value = value;
OnValueChanged("Value");
OnValueChanged("AddressValue")
}
}
3. Declare you ListBox with ItemTemplate like:
<ListBox x:Name="lbRegisters" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AddressValue}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So the idea is adding new property to register class which will be updated when address and value changed. And bind list item text to this property.
I think understand the spirit of what you're asking (even though it's obviously unfinished ATM). I'd recommend using something that inherits from DependencyObject and using dependency properties. Use the propdp snippet in Visual Studio. It will save you a ton of boilerplate code and wiring.

Categories