ObservableCollection loses binding when I "new" it - c#

I have a ListBox on my UI that is bound to a property of ObservableCollection. I set a new instance of the ObservableCollection into the property in the view model's constructor and I can add items to it with a button on the form. These are visible in the list.
All is good.
However, if I reinitialize the property with new in the button callback, it breaks the binding and the UI no longer shows what is in the collection.
I assumed the binding would continue to look up the values of the property, but its presumably linked to a reference which is destroyed by the new.
Have I got this right? Can anyone expand on how this is linked up? Is there a way to rebind it when my view model has no knowledge of the view?

Make sure you are raising a PropertyChangedEvent after you reintialize your collection. Raising this event will allow the view to handle changes to the property with the model having no knowledge of the view.
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}

I think based on your description that what you need to do is refactor the property that exposes your ObservableCollection so that it raises a PropertyChanged event also when it is assigned a new value. Example:
public ObservableCollection<int> Integers
{
get { return this.integers; }
set {
if (this.integers != value)
{
this.integers = value;
RaisePropertyChanged("Integers");
}
}
}

Supposing you've implemented INotifyPropertyChanged on your ViewModel, you can raise the property changed event on your ObservableCollection whenever you assign a new value to it.
public ObservableCollection<string> MyList { get; set; }
public void SomeMethod()
{
MyList = new ObservableCollection<string>();
RaisePropertyChanged("MyList");
}

Updates to the ObservableCollection are handled by hooking in to the CollectionChanged event so when you create a new ObservableCollection your observer is still looking at the old collection.
Two simple suggestions would be either to implement INotifyPropertyChanged on the class that contains the ObservableCollection and raising the PropertyChanged event in the setter of the collection property (don't forget to unhook from the old one first in your observer if it's your own code).
private ObservableCollection<string> _myCollection = new ObservableCollection<string>();
public ObservableCollection<string> MyCollection
{
get { return _myCollection; }
set
{
if(_myCollection == value)
return;
_myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
A second, and the option I generally prefer is to just clear and repopulate the collection with your new data when it arrives.
public void HandleCollectionData(IEnumerable<string> incomingData)
{
MyCollection.Clear();
foreach(var item in incomingData)
{
MyCollection.Add(item);
}
}

Related

Bind to total of an items field in an ObservableCollection and update when values change

I have an ObservableCollection that populates a datagrid in WPF. I need to bind to the total of the "Hours" column, and have that total update when a value in the "Hours" column is changed. I can achieve this by listening to the "LostFocus" event and running a function, but would like to try my hand at binding.
The issue I am running into, is the NotifyPropertyChanged event will not fire when an items property in the collection is changed.
The sortie class NotifyPropertyChanged will fire, but the collection doesn't interpret that as its own property changing. How can I listen to the sortie PropertyChanged from the collection in the missions class?
My Models
public class Mission : INotifyPropertyChanged
{
private ObservableCollection<Sortie> sorties;
public ObservableCollection<Sortie> Sorties
{
get { return this.sorties; }
set
{
if (this.sorties != value)
{
this.sorties = value;
this.NotifyPropertyChanged("Sorties");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class Sortie : INotifyPropertyChanged
{
private double hours;
public double Hours
{
get {return this.hours;}
set
{
if (this.hours != value)
{
this.hours = value;
this.NotifyPropertyChanged("Hours");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I didn't bother posting my XAML or View Model, as I am confident I can solve the issue once I learn how to trigger a PropertyChanged event for the collection and I wanted to spare you having to read through massive amounts of code. If you believe it is needed however, let me know.
Write a readonly property in the parent viewmodel that calculates the value.
public double SortieHours => Sorties.Sum(x => x.Hours);
Parent viewmodel handles PropertyChanged on each item in Sorties, and CollectionChanged on Sorties. In CollectionChanged on Sorties, you have to add/remove PropertyChanged handlers from Sortie instances as they're added and removed. When you get a new Sorties collection (you might want to make that setter private for this reason), you need to toss out all the old handlers and add new ones.
Now, whenever a Sortie is added or removed, or its Hours changes, or somebody hands you a new Sorties collection, raise PropertyChanged:
OnPropertyChanged(nameof(SortieHours));
And bind that property to whatever you like in the XAML.
This looks horrible (because it is), but what else are you going to do?
A lot of people would advise you to give Sortie an HoursChanged event. PropertyChanged is annoying for this type of case because it can get raised for multiple different properties and you have to check which one. And it's a magic string thing.
Above is C#6. For C#5,
public double SortieHours { get { return Sorties.Sum(x => x.Hours); } }
OnPropertyChanged("SortieHours");
I found this link to be a big help. It creates a base class for ObservableCollections to "AutoMagicly" add the PropertyChanged events of the collection items to the CollectionChanged event.

How to fire Property Changed Event when it's not actually changing

so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}

MVVM: Updating ViewModel properties based on Model properties

I have some properties in ViewModel that are updated/recalculated based on Model Properties updates. I am asking for the best approach to implement this scenario?
I don't like the approach of subscribing to PropertyChanged Event Handler of Model and then updating ViewModel properties. How do you handle this scenario?
Subscribing to events is the right approach, but I agree with you about not wanting to use the PropertyChanged event. I like to leave that event alone and create my own events as needed. Here is my approach:
public class Person : INotifyPropertyChanged
{
//custom events as needed
public event EventHandler NameChanged = delegate { };
//needed for INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
this.NotifyPropertyChanged();
//Fire my custom event
this.NameChanged(this, EventArgs.Empty);
}
}
}
private int _age;
public int Age
{
get { return _age; }
set
{
if (_age != value)
{
_age = value;
this.NotifyPropertyChanged();
//I am not exposing a custom event for this property.
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example, Name and Age are observable for UI purposes, but Name is observable to anything outside of the UI. Now if you remove any PropertyChanged notifications, you don't accidentally cause that runtime error if your ViewModel was subscribed to PropertyChanged and parsing the string.
Since you don’t want to put the dependency on the view model inside the model, listening to model changes in the view model is indeed the right approach to update view model properties that are based on the model.

List<string> doesn't update the UI when change occurs

<ListBox x:Name="MainList" HorizontalAlignment="Left" Height="468" Margin="10,10,0,0" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Items,Mode=TwoWay}" DisplayMemberPath="Name"/>
[Serializable()]
public class MYcontainer : INotifyPropertyChanged,ISerializable
{
private List<MYClass> _items = new List<MYClass>();
public List<MYClass> Items
{
get{ return _items;}
set { this._items =value;
OnPropertyChanged("Items");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
When I add an item to "Items" the UI doesn't update, the binding is working fine, since if I closed the window and opened it again, the new items appear correctly.
What am I doing wrong? I know if I used ObservableCollection it will work fine, but shouldn't it work with List<>? I already have in another window a string[] property and it update fine.
If you don't want to ues ObservableCollection you will have to implement INotifyCollectionChanged.
public partial class MainWindow : Window, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public MainWindow()
{
InitializeComponent();
}
public void NotifyCollectionChanged(NotifyCollectionChangedAction action)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
}
}
}
However ObservableCollection does all this for you, adding all the same logic to your List<T> would just create a custom ObservableCollection, I see no point in this when MS has alraedy made this for you
It will currently only update if you replace the entire list with a new List<MyClass>. Replacing 1 item won't trigger the OnPropertyChanged event.
Use an ObservableCollection<MyClass> instead of a List<MyClass>. It's built specifically to handle this issue and notifies WPF whenever the items in the collection change.
It's very comparable to list in other respects so the changes to your code should be minimal (Both List and ObservableCollection implement the ICollection<T> interface, so most of the methods are shared).

How do I update a listview item that is bound to a collection in WPF?

In WPF, I have a ListView bound to an ObservableCollection in the code-behind. I have working code that adds and removes items from the list by updating the collection.
I have an 'Edit' button which opens a dialog and allows the user to edit the values for the selected ListView item. However, when I change the item, the list view is not updated. I'm assuming this is because I'm not actually adding/removing items from the collection but just modifying one of its items.
How do I tell the list view that it needs to synchronize the binding source?
You need to implement INotifyPropertyChanged on the item class, like so:
class ItemClass : INotifyPropertyChanged
{
public int BoundValue
{
get { return m_BoundValue; }
set
{
if (m_BoundValue != value)
{
m_BoundValue = value;
OnPropertyChanged("BoundValue")
}
}
}
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int m_BoundValue;
}
Do you have set the binding mode to TwoWay? If not, try to do that.

Categories

Resources