I have a hierarchy of items like the following:
MainClass
{
List<Configuration> MyList = new List<Configuration>;
}
Configuration
{
CustomObject MyObject = new CustomObject;
string Property = "";
}
CustomObject
{
string InnerProperty = "";
}
If I want that MainClass gets notified from every change made to InnerProperty and Property, am I correct to assume that I have to transform the List into an ObservableCollection and that both Configuration and CustomObject should derive from INotifyPropertyChanged, right?
If I want only to get a notify when InnerProperty gets changed, and NOT Property, should only CustomObject derive from INotifyPropertyChanged or Configuration too (since I would lose the notification transmission to the parent)?
To get notification about InnerProperty and Property you don't need ObservableCollection in general. ObservableCollection is only for notifications about added\removed items. So you need to implement INotifyPropertyChanged on both Configuration and CustomObject. If you are only interested in changes of InnerProperty, INotifyPropertyChanged on Configuration is not necessary in case your MyObject property never changes and assigned in constructor. Otherwise, you need again to implement INotifyPropertyChanged on both. Same story with ObservableCollection by the way - if your list contains fixed list of items - you don't need that to receive notifications from properties. Otherwise you do.
If I want only to get a notify when InnerProperty gets changed, and NOT Property, should only CustomObject derive from INotifyPropertyChanged or CustomObject too (since I would lose the notification transmission to the parent)?
(I assume that for the second CustomObject in that sentence you actually mean Configuration.)
If you only change InnerProperty and MyProperty doesn't change, then you only need to have CustomObject implement INotifyPropertyChanged. There is no sense of changes propagating up the chain.
If you have a binding path:
{Binding Path=A.B.C.D}
Then if any of those properties change and you want the binding to update, then the relevant level must implement INotifyPropertyChanged. If a property is immutable (doesn't change) then there's no need to support notification of changes. You can think that the binding is listening for changes on each object returned through the evaluation of the binding path.
I'll try to answer your conceptual question. But code is nice...
I'll first just point to C#:: When to use events or a collection of objects derived from an event handling Interface?, which I think is quite helpful.
ObservableCollection takes care of raising events when items are added or removed. There is no need for INotifyPropertyChanged, that is automatically there. If you bind to an ObservableCollection in your XAML, then your XAML will register for those events, and you don't need to do anything yourself. However, nothing else is listening to the events, unless you register. You can do that like this:
myObservablecollection.CollectionChanged += myHandler;
But I don't think that is what you want. If you want to now when something is added to, or removed from MainClass.MyList, then making it an ObservableCollection will do that for you. But you want to know when some particular item in your list is modified, and ObservableCollection does not help with that.
Let's suppose you want a Configuration to do something when its CustomObject changes. Perhaps it sets Property (not a good name?) to MyObject.ToString(). Then you make CustomObject implement INotifyPropertyChanged, like this:
Class CustomObject : INotifyPropertyChanged
{
private string _innerProperty;
public string InnerProperty
{get { return _innerProperty; }
{set
{
_innerProperty = value;
OnPropertyChanged("InnerProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Then in Configuration:
Class Configuration
{
...
public Configuration(...)
{
... // set up stuff
MyObject.PropertyChanged += myObjectChanged_DoSomethingAboutIt;
}
private void myObjectChanged_DoSomethingAboutIt()
{
DoSomething();
// for example:
Property = MyObject.ToString();
}
}
I hope this helps a bit.
More here: http://www.codeproject.com/Articles/41817/Implementing-INotifyPropertyChanged
Related
I have a ViewModel like this
Public class AboutPageViewModel
{
public AboutPageViewModel()
{
AppName = Settings.MyAppName;
}
private string _appName;
public string AppName
{
get{return _appName;}
set{_appName = value; RaisePropertyChanged("AppName");}
}
}
Now in a static class
public static class Settings
{
public static string MyAppName{get;set;} = "LOL"
}
How do I notify the ViewModel everytime MyAppName is changed, and update it to the Binded UI?
Thanks!
As you define it in your question, Settings isn't a static class (ah, I see in comments that was a typo, and it's static in your code). It should not be static. PropertyChanged notifications on a static class are theoretically possible but it's not worth your time to mess with, and there's no need to bother.
Have Settings implement INotifyPropertyChanged, just like your viewmodel. When MyAppName changes, Settings should raise PropertyChanged, just as AboutPageViewModel does when its own AppName property changes.
Now give Settings a static property called Instance:
public static Settings Instance { get; private set; }
static Settings()
{
Instance = new Settings();
}
And handle its PropertyChanged event in AboutPageViewModel:
public AboutPageViewModel()
{
AppName = Settings.Instance.MyAppName;
Settings.Instance.PropertyChanged += (s,e) =>
{
// If you're in C#6:
//if (e.PropertyName == nameof(Settings.MyAppName))
if (e.PropertyName == "MyAppName")
{
AppName = Settings.Instance.MyAppName;
}
}
}
Option Number Two
Arguably a better option; I've done it this way more than once.
In comments, #MikeEason makes the very good point that this could also be done with a custom *Changed event such as MyAppNameChanged, which has two advantages: It lets you go back to a static class, and it lets you skip the check on the property name, which is extra code and also a "magic string". Working with INotifyPropertyChanged we get a little bit numb to the danger of magic strings, but they are in fact bad. If you're in C#6, you can and absolutely should use the nameof() operator, but not all of us are in C#6 just yet. My main responsibility at work is an application that we're hoping to migrate to C#6 this summer.
public static event EventHandler<String> MyAppNameChanged;
private static String _myAppName = "";
public static String MyAppName {
get { return _myAppName; }
set {
if (_myAppName != value)
{
_myAppName = value;
// C#6 again. Note (thanks OP!) you can't pass this for sender
// in a static property.
MyAppNameChanged?.Invoke(null, value);
}
}
}
The drawback of this is that, well, this class is called Settings, not Setting. Maybe it's got a dozen properties changing here and there. That gets to be a real thicket of distinct property-changed events ("so what?" you may ask -- and you may have a point). My tendency is to stick with PropertyChanged if there's a whole sheaf of them, and to add an event if the class has only one or two important properties that somebody needs to keep an eye on. Either way is annoying in my view; try both and you'll eventually settle on a preference.
You don't need to store value in ViewModel if you already have it somewhere (I assume what you are not going to change it in ViewModel itself):
public class AboutPageViewModel : INotifyPropertyChanged
{
public string AppName => Settings.MyAppName;
}
And as for View to know when this property is changed you need 2 things: 1) there should be a way to inform ViewModel when value is changed 2) rise PropertyChanged(nameof(AppName)) (notice INotifyPropertyChanged).
Several possibilities to make it:
Settings should rise event when MyAppName value is changed, ViewModel subscribe to it and rises PropertyChanged;
Store initial value, check periodically if value is changed;
Use another type which implement INotifyPropertyChanged, bind to that type property instead, this will update view automatically if that type rises PropertyChanged.
You have to implement INotifyPropertyChanged interface on Settings class!
then use the same piece of code like this:
private string _myAppName;
public string MyAppName
{
get{return _myAppName;}
set{_appName = value; RaisePropertyChanged("MyAppName");}
}
Using RoutedEvents, you can do things such as have a single control which hosts thousands of child controls, but rather than subscribe to MouseDown on each child, you set a handler on the root control and inspect the 'sender' property to find which child was actually clicked on.
I'm wondering if there's any such thing for INPC objects, or if not, can one be created.
For instance, if you have a collection which contains thousands of objects which all implement INPC, currently you have to subscribe to each and every one individually. I'm wondering if there's a way around that.
The only thing I can think of is in the setter property for these properties you're interested in, in addition to raising the standard INPC notification, call a delegate in the containing collection and have the collection raise the appropriate notification. That way the consumer would just have to subscribe to a single handler on the collection for any of its children.
My hesitation here is if you're going to do that, why not just make that collection subscribe to the children itself, then re-raise the notification from the collection? My thought however is that calling a delegate directly from the specific setters you're interested in avoids string comparison in the PropertyChanged handler that would delegate such notifications.
Note: This is pseudo-code typed off the top of my head so it may not compile. It's to illustrate a concept/idea, not to be an example of actual code.
public class ItemCollection : ObservableCollection<Item>
{
public EventHandler ChildItemPropertyChanged(object sender, string propertyName);
internal void RaiseChildItemPropertyChanged(object sender, string propertyName)
{
var childItemPropertyChanged = ChildItemPropertyChanged;
if(childItemPropertyChanged != null)
childItemPropertyChanged(sender, propertyName);
}
}
public class Item : INotifyPropertyChanged
{
public ItemCollection OwningCollection;
public Item(ItemCollection owningCollection)
{
OwningCollection = owningCollection;
}
private string _name;
public string Name
{
get{ return _name; }
set
{
if(_name == value)
return;
_name = value;
PropertyChanged(this, "Name");
OwningCollection.RaiseChildItemPropertyChanged(this, "Name");
}
}
}
Thoughts?
I've tried looking at other topics on this but I haven't found a working implementation to my question. Basically, I have an ObservableCollection called "FruitBasket" that contains different kinds of fruit. FruitBasket itself contains ObservableCollections for each respective type of fruit that passes through so that they can be used as ItemSources for ListViews (Denoted by their names "AppleContainer" and "OrangeContainer"), each displaying one kind of fruit. Because the fruit classes themselves implement INotifyPropertyChanged, modifying their values triggers updates to the ListView controls just fine, however, FruitBasket has a "TotalWeight" property derived from the weights of all the other fruits in the collections. I want "TotalWeight" to update the Label control in the UI without me having to refresh the UI. Triggerering a notification on a property change of the actual ObservableCollection itself, and not simply its constituent members is more difficult and I haven't found any solutions that work so far (or that I've implemented correctly).
public class FruitBasket : ObservableCollection<IFruit>
{
private decimal _totalWeight;
public FruitBasket()
{
this.Add(new OrangeContainer(this));
this.Add(new AppleContainer(this));
}
public OrangeContainer Oranges
{
get { return (OrangeContainer)this.Items[0]; }
}
public AppleContainer Apples
{
get { return (AppleContainer)this.Items[1]; }
}
public decimal TotalWeight
{
get { return _totalWeight; }
set { _totalWeight = value; }
}
internal void UpdateWeight(IFruit caller)
{
_totalWeight = 0;
foreach (Orange orng in (OrangeContainer)this.Items[0])
{
_totalWeight += orng.Weight;
}
foreach (Apple appl in (AppleContainer)this.Items[1])
{
_totalWeight += appl.Weight;
}
}
You need to call INotifyPropertyChanged.PropertyChanged event of your FruitBasket whenever items are added, removed or Weight property of any item has changed.
Let's split it into two tasks:
TotalWeight should be recalculated when items are added, removed, or items' weight is changed. We need to handle those events.
Raise FruitBasket.PropertyChanged event
I have splitted these two tasks into two classes in order to follow Single Responsibility Principle:
1) - this handles items' PropertyChanged events:
public abstract class ExtendedObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void ClearItems()
{
foreach (var item in Items) item.PropertyChanged -= ItemPropertyChanged;
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
item.PropertyChanged += ItemPropertyChanged;
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
this[index].PropertyChanged -= ItemPropertyChanged;
base.RemoveItem(index);
}
protected override void SetItem(int index, T item)
{
this[index].PropertyChanged -= ItemPropertyChanged;
item.PropertyChanged += ItemPropertyChanged;
base.SetItem(index, item);
}
abstract void ItemPropertyChanged(object sender, PropertyChangedEventArgs e);
}
2) - this recalculates TotalWeight when necessary
public class FruitBasket : ExtendedObservableCollection<IFruit>
{
protected override void ItemPropertyChanged(object sender, PropertyChangedEventArgs e){
UpdateWeight();
OnPropertyChanged("TotalWeight")
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
UpdateWeight();
OnPropertyChanged("TotalWeight")
base.OnCollectionChanged(e);
}
}
Of course your Fruit should implement INotifyPropertyChanged interface. You will find plenty of examples how to do it. It is very simple.
I found the root(s) of the problem(s). I'll start with the most obvious:
I wasn't as diligent in assigning datacontext in the UI for the Fruit Basket observable collection object itself as I was the for the members of its collection (OrangeContainer and AppleContainer). In the initialization of the UI window, assigning datacontext to the ListView objects is second nature. I wasn't quite matching the right node's datacontext in the XAML to the Fruit Basket object in the initialization method in the code behind (I really should have checked that earlier).
Because of the misaligned assignments of datacontext/binding, between the XAML and initialization method, the propertychanged event was never firing for my fruit basket observable collection like it was for the Apple and Orange objects inside the OrangeContainer and AppleContainer collections that were members of FruitBasket. So, in the Orange class declaration we'd have this:
public class Orange : INotifyPropertyChanged, IFruit
And the implementation like so
public event PropertyChangedEventHandler PropertyChanged;
public void PropChange(string prop)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
And when the PropChange method was called in the Weight property setter, this.PropertyChanged would not be null, and everything would work fine.
The FruitBasket class was a bit more tricky. Because of the aformentioned issue of improper matching in the UI Code, this.PropertyChanged would return null every time I tried to notify a change in property. However, it got a bit more confusing because unlike the Orange or Apple classes, it inherits ObservableCollection (ObservableCollection in the declaration if we want to be specific). I know ObservableCollection is really just a Collection class that implements INotifyPropertyChanged and INotifyCollectionChanged interfaces. It's really nice to see the plumbing now that .NET is open source (praise the lord)
http://referencesource.microsoft.com/#System/compmod/system/collections/objectmodel/observablecollection.cs
In any case, implementing this became more confusing, because I kept seeing this:
Warning 1 'TestingObsColNotify.FruitBasket.PropertyChanged' hides inherited member 'System.Collections.ObjectModel.ObservableCollection.PropertyChanged'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. C:\Testing VS Project\TestingObsColNotify\TestingObsColNotify\FruitBasket.cs 60 50 TestingObsColNotify
I still see this, but my implementation works, because while it was a result of the inheritance from INotifyProperty changed via ObservableCollection as seen in my original class declaration
public class FruitBasket : ObservableCollection<IFruit>
This was just shy of the last element need to make everything work, which was adding the INotifyPropertyChanged to the class itself like so:
public class FruitBasket : ObservableCollection<IFruit>, INotifyPropertyChanged
It seems a bit redundant and inelegant but I didn't get very far trying to override and wrestle with the inheritance of INotifyPropertyChanged from ObservableCollection (Or as best as I can understand).
So there we have it, everything works now, sans MVVM. I'll certainly move on to that pattern later, but it's nice to have resolved this issue instead of lazily just re-assigning the contents of controls in the code behind methods on the UI side of things.
Thank you to those who came in here and contributed, I appreciate you taking the time to respond.
If using binding, Add the interface INotifyPropertyChanged to your class. If you have ReSharper installed, accept the recommendation to implement the interface. Then, whenever you want to update any text box, call PropertyChanged with the name of the property TotalWeight, see https://softwareengineering.stackexchange.com/questions/228067/where-do-put-inotifypropertychanged-interface-in-model-or-viewmodel. Whenever you update any of the ObservableCollections, manually update the TotalWeight, then call the aforementioned PropertyChanged to tell the UI to update itself. I've used this technique to push updates from the ViewModel into the View (i.e. from the class into the XAML) for some fairly complex scenarios, it works very well.
I'd also recommend following the learning curve for MVVM, projects written in that way tend to be more scalable, are easier to maintain, and just easier to work with.
I've started to learn WPF\MVVM approach and get bit confused.
I've:
class ModelAAA {
public List<Foo> Foos{get; protected set;}
//..
public void Boo()
{
//Some complex logic updating Foos
}
}
class ViewModelAAA{
private ModelAAA _modelAAA
public ObservableCollection<Foo> Foos{get; protected set;}
public void ViewModelAAA(ModelAAA modelAAA)
{
this._modelAAA = modelAAA;
this.Foos = new ObservableCollection(modelAAA.Foos)
}
public void Boo()
{
this._modelAAA.Boo();
//What should I do here?
}
}
So if I use Boo method of view model, what is proper view to update collection in ViewModel. I've got few ideas, but they all seems to by ugly. Should I manauly recreate\change viewModel Foos each time? As I understad ObservableCollection is not wrapper like object.
P.S. I'm want to make it whitout using ObservableCollection in model
Your Model does not need to use ObservableCollection, but has to notify your ViewModel that something changed in the Collection.
this creates a copy of your List, which is indeed observable, but is not changed at all after that:
this.Foos = new ObservableCollection(modelAAA.Foos);
I would not recommend to create a new ObservableCollection, each time the Model-Collection changed. Instead implement the INotifyCollectionChanged in your Model-Collection and handle the events in your Viewmodel properly.
No, you do not need manually change it, as this is ObservableCollection, but you are changing original collection and not observable one.
To notify listeners of your Observable you need to act on Observable itself.
Example:
public void Boo()
{
this.Foos.Boo();
}
I have a class:
public class A : INotifyPropertyChanged
{
public List<B> bList { get; set; }
public void AddB(B b)
{
bList.Add(b);
NotifyPropertyChanged("bList");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
And a binding (DataContext of UserControl is an instance of A):
<ListBox ItemsSource="{Binding Path=bList}" />
Elements are shown, the ListBox is not updated after new object is added to List
After changing list to ObservableCollection and removing the NotifyPropertyChanged handler everything works.
Why the list is not working?
Your property has to be public, or the binding engine won't be able to access it.
EDIT:
After changing list to ObservableCollection and removing the NotifyPropertyChanged handler everything works.
That's precisely why the ObservableCollection<T> class was introduced... ObservableCollection<T> implements INotifyCollectionChanged, which allows it to notify the UI when an item is added/removed/replaced. List<T> doesn't trigger any notification, so the UI can't detect when the content of the list has changed.
The fact that you raise the PropertyChanged event does refresh the binding, but then it realizes that it's the same instance of List<T> as before, so it reuses the same ICollectionView as the ItemsSource, and the content of the ListBox isn't refreshed.
First thought is that you're trying to bind to a private member. That certainly doesn't seem right.
I think that the problem is that, although you are notifying the binding framework that the property has changed, the actual value of the property remains the same. That is to say that although the listbox may reevaluate the value of its ItemsSource binding, it will find that it is still the same object instance as previously. For example, imagine that the listbox reacts by to the property changed event somehow similar to the below.
private void OnItemsSourceBindingChanged()
{
var newValue = this.EvaluateItemsSourceBinding();
if (newValue != this.ItemsSource) //it will be equal, as its still the same list
{
this.AddNewItems();
}
}
In your example, this would mean that it would not reevaluate the items.
Note: I do not know how the listbox works with the ItemsSource property - I'm just speculating!
ObservableCollections send CollectionChanged events not PropertyChanged events
By signaling
NotifyPropertyChanged("bList");
you are basically saying you have a new list object, not that the content of the List has changed.
If you Change the type to ObservableCollection, the collections automatically sends
CollectionChanged
notifications that the collection items have changed, which is what you want.