I have the following class structure
class Top : NotifyPropertyChanged
{
private List<Inner> innerList;
public bool IsInnerTrue
{
get
{
foreach (Inner inner in innerList)
{
if (inner.IsTrue)
return true;
}
return false;
};
}
}
class Inner : NotifyPropetyChanged
{
private bool isTrue;
public bool IsTure
{
get
{
return isTrue;
}
set
{
isTrue = value;
NotifyPropretyChanged("IsTrue");
}
}
}
In my view I'm binding to the IsInnerTrue property of the Top class. My problem is that I can't figure out how to fire the PropertyChanged event for IsInnerTrue when the IsTrue property of one of the Inner objects changes value.
Does anyone have a suggestion, short of setting up an event handler for each Inner object?
Here are two options. One, subscribe to the inner PropertyChanged events:
class Top : NotifyPropertyChanged
{
private List<Inner> innerList = new List<Inner>();
public bool IsInnerTrue
{
get
{
foreach (Inner inner in innerList)
{
if (inner.IsTrue)
return true;
}
return false;
}
}
public void Add(Inner inner)
{
innerList.Add(inner);
inner.PropertyChanged += InnerPropertyChanged;
}
public void Remove(Inner inner)
{
innerList.Remove(inner);
inner.PropertyChanged -= InnerPropertyChanged;
}
private void InnerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsTrue")
RaisePropertyChanged("IsInnerTrue");
}
}
Or two, use a dependency tracking library like Update Controls. These libraries can detect that IsInnerTrue depends upon IsTrue, and will fire the top-level property changed event when the inner property changes.
Related
i am looking for a solution to fire PropertyChanged of the whole property, if one of the inner properties changes. so here is the model:
public class MainStatus : ObservableObject
{
private bool _safety;
public bool Safety
{
get { return _safety; }
set
{
if (_safety == value)
return;
_safety = value;
RaisePropertyChanged("Safety");
}
}
private bool _setupMode
public bool SetupMode
{
get { return _setupMode; }
set
{
if (_setupMode == value)
return;
_setupMode = value;
RaisePropertyChanged("SetupMode");
}
}
}
it has some more properties (bool, int and string).
in the vm it is used like this:
private MainStatus _mainStatus;
public MainStatus MainStatus
{
get { return _mainStatus; }
set
{
if (_mainStatus == value)
return;
_mainStatus = value;
RaisePropertyChanged("MainStatus");
}
}
i can bind to the inner properties like this:
<DataTrigger Binding="{Binding MainStatus.Safety}" Value="true">
this all works fine. but i need to send the whole MainStatus to other VMs, if one of the inner property changes. so i changed the property like the this:
public MainStatus MainStatus
{
get { return _mainStatus; }
set
{
if (_mainStatus == value)
return;
_mainStatus = value;
RaisePropertyChanged("MainStatus");
Messenger.Default.Send<MainStatusMessage>(new MainStatusMessage() { MainStatus = _mainStatus });
}
}
but this does not work. it seems like the PropertyChanged for the MainStatus is never fired if one of the inner properties changes. how can i fix that?
In your VM, you could subscribe to Mainstatus.PropertyChanged event something like:
this.MainStatus.PropertyChanged += new PropertyChangedEventHandler(MainStatus_PropertyChanged);
//..........
void MainStatus_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("MainStatus");
}
If I uderstand the question you want to raise the property changed events for ALL properties on a class that implements INotifyPropertyChanged, this can be achieved by calling the event handler with a NULL instead of the string containing the property name:
e.g. RaisePropertyChanged(null) would raise both Safety and SetUpMode on the MainStatus class defined above.
i have view model class A and it has the property "a".
and i have class called B , and it ha sproperty "b" where i store list of all A[] as list.
if any change in the property a , i would like to change the property in the parent class A.
that is
class B
{
property b1;
List A[]
}
Class A
{
property a1;
}
I am using MVVM and notify property chnaged event is there in both the places. what i dont know how to wire up both .
In my BaseCollection class which extends ObservableCollection<T>, I have a created a CollectionItemPropertyChanged delegate that I can attach a handler to, so I can be notified when property values have changed in any item in the collection:
public delegate void ItemPropertyChanged(T item, string propertyName);
Then I add a getter and setter of the type of the delegate:
public virtual ItemPropertyChanged CurrentItemPropertyChanged { get; set; }
To achieve this, I have to attach a handler to each item's PropertyChanged event:
public BaseCollection(IEnumerable<T> collection)
{
foreach (T item in collection) Add(item);
}
public new void Add(T item)
{
item.PropertyChanged += Item_PropertyChanged;
base.Add(item);
}
I also need to remove the handlers when objects are removed:
public new bool Remove(T item)
{
if (item == null) return false;
item.PropertyChanged -= Item_PropertyChanged;
return base.Remove(item);
}
Then there is the Item_PropertyChanged handler:
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (CollectionItemPropertyChanged != null) CollectionItemPropertyChanged(
currentItem, e.PropertyName);
}
This can then be used in your class B like so:
public List<A> Items
{
get { return items; }
set
{
items = value;
items.CollectionItemPropertyChanged += YourHandler;
NotifyPropertyChanged(Items);
}
}
Then your handler:
private void YourHandler(A item, string propertyName)
{
// propertyName is the name of the changed property from the A object named item
}
To answer your question more directly, you can attach a handler from one view model to this delegate in another... for example:
Items.CollectionItemPropertyChanged += ViewModel2.YourHandler;
Or from the other view model:
ViewModel1.Items.CollectionItemPropertyChanged += YourHandler;
Create View Model A as Singleton and update the a property using that instance.
I am using WPF and MVVM for a project. I have a view with a GridView control. User can Insert/Update/Delete In Grid View. when any of the action happen changes reflect in ViewModel. This part is working Ok. But when I want to save the changes in Database I need to loop through each Item in ItemSource one by one. which takes the extra time to complete. I want to process only those Items which are changes.
To accomplish this , I add a boolean property in my Model to indicate whether the Item is changed or note. But problem is that I can not see any way to set this boolean property whenever any other property is changed.
Can any body help me how to do it?
EDIT
I have a SelectedItem Property , and I am assuming that whenever an Item is selected in GridView , User will update or insert the row. so on SelectedItem property I have set boolean property of SelectedItem to True. and while looping to save records I am saving all those records who have True in their boolean property. I know its not the perfact way, but right now I do not have any other way to do it. Your thoughts?
You could subscribe to the PropertyChanged event on your Model and set the Flag to True.
But keep in mind that you have to set the Flag to false after you loaded the data from the database, because the initialization of the model will also call the propertychanged event.
Example for class with IsDirty-Flag:
public class Sample : INotifyPropertyChanged
{
private int id;
private string name;
private bool isDirty;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return id; }
set
{
if(id != value)
{
id = value;
RaisePropertyChanged("Id");
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
RaisePropertyChanged("Name");
}
}
}
public bool IsDirty
{
get { return isDirty; }
set
{
if (isDirty != value)
{
isDirty = value;
RaisePropertyChanged("IsDirty");
}
}
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (propertyName != "IsDirty")
{
IsDirty = true;
}
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
if you are using an ObservableCollection, you can also add an eventhandler to track the rows that are newly added or deleted
If you use MVVM you should have implementation of INotifyPropertyChanged. And you can add some logic to set up yours boolean property in OnPropertyChanged handler.
If you are willing to take a dependency on a build time tool you can do this with ILWeaving.
So if you combine Fody with the PropertyChanged addin then IsDirty functionality is supported out of the box.
Then Martins example can be simplified to this
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
public string Name { get; set; }
public bool IsChanged { get; set; }
}
Note the use if IsChanged instead of IsDirty.
And then this will exist in the compiled assembly
public class Sample : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int id;
public int Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
IsChanged = true;
OnPropertyChanged("Id");
}
}
}
bool isChanged;
public bool IsChanged
{
get { return isChanged; }
set
{
if (isChanged != value)
{
isChanged = value;
OnPropertyChanged("IsChanged");
}
}
}
string name;
public string Name
{
get { return name; }
set
{
if (!string.Equals(name, value, StringComparison.Ordinal))
{
name = value;
IsChanged = true;
OnPropertyChanged("Name");
}
}
}
}
I have a Silverlight application that I am currently working that implements Caliburn.Micro for its MVVM framework. Things are working fine but I am noticing some funniness in some of the binding. What I have is a ShellViewModel and a ShellView that handle the navigation for the application. The ShellViewModel has a list of the loaded ViewModels for the application. The ShellViewModel inherits from Conductor so that it can handle all the activation and deactivation.
I also have a type of ViewModel base class called BaseConductorViewModel that also inherits from Conductor. This is for ViewModel’s that are basically Master-Detail views. For these BaseConductorViewModels I have a BindableCollection called Items. The idea being to bind this collection to a ListBox or other ItemsControl.
When I create an child of this ViewModel and an associated View I have noticed that the ListBox(in this case) only refreshes the binding when I change the ActiveItem at the ShellViewModel level. So when the application initially loads and this view is the default active view you won’t see anything in the list (I am calling Ria service to get the data for this list). But, if I click on another ViewModel on the ShellViewModel/ShellView and then click back it will show the items in the list. This also follows for adding items to the list or removing them. It won’t refresh unless I switch active views. This seems very odd to me and I can’t seem to figure out a way to get it bind as I would except. One more thing to note, when I am adding/removing items; I call the Refresh method, currently I am not using the NotifyOfPropertyChange method though I did try that previously to the same result.
Does anyone have any ideas of what might be going on here? Or any ideas on how I could go about trying to debug this?
Thank you in advance!
Here is the ShellViewModel
public abstract class ShellViewModel<V,M>:Conductor<IViewModel<V, M>>.Collection.OneActive, IViewModelCatalogShell<V,M>
where V:IView
where M:IModel
{
#region Properties/Members
public ViewModelSelectedItemList<V, M> Catalog { get; set; }
#endregion
#region Constructors
public ShellViewModel()
{
Catalog = new ViewModelSelectedItemList<V, M>();
}
#endregion
}
And here is the BaseConductorViewModel
public abstract class BaseConductorViewModel<T,V,M>:Conductor<T>, IViewModel<V, M>
where V:IView
where M:IModel
{
#region Properties/Members
protected Guid _id=Guid.Empty;
public Guid Id
{
get{return _id;}
set
{
_id =value;
NotifyOfPropertyChange("Id");
}
}
protected string _name=string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange("Name");
}
}
public string TypeName
{
get
{
return this.GetType().FullName;
}
}
protected string _description = string.Empty;
public string Description
{
get { return _description; }
protected set
{
_description = value;
NotifyOfPropertyChange(() => Description);
}
}
protected V _view;
public V View
{
get { return _view; }
set
{
_view = value;
NotifyOfPropertyChange("View");
}
}
protected M _model;
public M Model
{
get { return _model; }
set
{
_model = value;
NotifyOfPropertyChange("Model");
}
}
protected SelectedItemList<T> _items;
public SelectedItemList<T> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
protected Guid _lastModifiedBy = Guid.Empty;
public Guid LastModifiedBy
{
get { return _lastModifiedBy; }
set
{
_lastModifiedBy = value;
NotifyOfPropertyChange("LastModifiedBy");
}
}
protected DateTime _lastModifiedOn = DateTime.Today;
public DateTime LastModifiedOn
{
get { return _lastModifiedOn; }
set
{
_lastModifiedOn = value;
NotifyOfPropertyChange("LastModifiedOn");
}
}
protected string _imageSource = string.Empty;
public string ImageSource
{
get { return _imageSource; }
protected set
{
_imageSource = value;
NotifyOfPropertyChange("ImageSource");
}
}
#endregion
#region Constructors
public BaseConductorViewModel()
{
_items = new SelectedItemList<T>();
Items.SelectItemChanged += new SelectedItemChangedEvent(Items_SelectItemChanged);
Items.SelectedIndexChanged += new SelectedIndexChangedEvent(Items_SelectedIndexChanged);
LoadData();
}
public BaseConductorViewModel(V view, M model)
:this()
{
_items = new SelectedItemList<T>();
View = view;
Model = model;
Items.SelectItemChanged += new SelectedItemChangedEvent(Items_SelectItemChanged);
Items.SelectedIndexChanged += new SelectedIndexChangedEvent(Items_SelectedIndexChanged);
LoadData();
}
#endregion
#region Methods
public abstract void LoadData();
#endregion
#region Event Handlers
private void Items_SelectItemChanged()
{
ChangeActiveItem(Items.SelectedItem, true);
OnActiveItemChanged();
}
private void Items_SelectedIndexChanged(int index)
{
ChangeActiveItem(Items.SelectedItem, true);
OnActiveItemChanged();
}
#endregion
}
The ViewModelSelectedItemList is just a typed version of this class
public class SelectedItemList<T>:IObservableCollection<T>
{
#region Properties/Members
protected BindableCollection<T> _items = new BindableCollection<T>();
protected bool _isReadOnly = false;
protected bool _isNotifying = true;
public bool IsNotifying
{
get
{
return _isNotifying;
}
set
{
_isNotifying = value;
}
}
public int Count
{
get { return _items.Count; }
}
protected int _selectedIndex = -1;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyOfPropertyChange("SelectedIndex");
FireSelectedIndexChangedEvent(_selectedIndex);
}
}
public T SelectedItem
{
get
{ return _items[_selectedIndex]; }
set
{
_selectedIndex = _items.IndexOf(value);
NotifyOfPropertyChange("SelectedItem");
FireSelectedItemChangedEvent();
}
}
#endregion
#region Events
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
public event SelectedIndexChangedEvent SelectedIndexChanged;
public event SelectedItemChangedEvent SelectItemChanged;
#endregion
#region Constructors
#endregion
#region Methods
public void AddRange(System.Collections.Generic.IEnumerable<T> items)
{
if (!_isReadOnly)
{
foreach (T item in items)
{
_items.Add(item);
}
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public void RemoveRange(System.Collections.Generic.IEnumerable<T> items)
{
if (!_isReadOnly)
{
foreach (T item in items)
{
_items.Remove(item);
}
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public int IndexOf(T item)
{
return _items.IndexOf(item);
}
public void Insert(int index, T item)
{
if (!_isReadOnly)
{
_items.Insert(index, item);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public void RemoveAt(int index)
{
if (!_isReadOnly)
{
_items.RemoveAt(index);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public T this[int index]
{
get
{
return _items[index];
}
set
{
_items[index] = value;
}
}
public void Add(T item)
{
if (!_isReadOnly)
{
_items.Add(item);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
_items.Refresh();
}
if (_items.Count == 1)
{
SelectedIndex = 0;
}
}
}
public bool Remove(T item)
{
if (!_isReadOnly)
{
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
return _items.Remove(item);
}
else
{
return false;
}
}
public void Clear()
{
_items.Clear();
}
public bool Contains(T item)
{
return _items.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
if (!_isReadOnly)
{
_items.CopyTo(array, arrayIndex);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public bool IsReadOnly
{
get { return _isReadOnly; }
}
public void Lock()
{
_isReadOnly = true;
}
public void Unlock()
{
_isReadOnly = false;
}
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
public void NotifyOfPropertyChange(string propertyName)
{
FirePropertyChangedEvent(propertyName);
}
public void Refresh()
{
_items.Refresh();
}
#region Helper Methods
protected void FirePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected void FireCollectionChangedEvent(NotifyCollectionChangedAction action)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(action));
}
}
protected void FireSelectedIndexChangedEvent(int index)
{
if (SelectedIndexChanged != null)
{
SelectedIndexChanged(index);
}
}
protected void FireSelectedItemChangedEvent()
{
if (SelectItemChanged != null)
{
SelectItemChanged();
}
}
#endregion
#endregion
}
Not sure if you're problem has something to do with this, from the docs:
Since all OOTB implementations of IConductor inherit from Screen it
means that they too have a lifecycle and that lifecycle cascades to
whatever items they are conducting. So, if a conductor is deactivated,
it’s ActiveItem will be deactivated as well. If you try to close a
conductor, it’s going to only be able to close if all of the items it
conducts can close. This turns out to be a very powerful feature.
There’s one aspect about this that I’ve noticed frequently trips up
developers. If you activate an item in a conductor that is itself not
active, that item won’t actually be activated until the conductor gets
activated. This makes sense when you think about it, but can
occasionally cause hair pulling.
edit:
I think I see what you're trying to do, a couple questions though:
Your ShellViewModel is
Conductor<IViewModel<V,M>>.Collection.OneActive, when is Catalog
activated? I'd think you'd want to add Catalog to Items, then
Activate it.
With the BaseConductorViewModel, it inherits from Conductor which
inherits from Screen, which gets a reference to it's view when it's
bound. I'm not sure what the View property you add is for.
CM can handle setting the selected item for you. So for a master
detail situation where you have an ItemsControl, CM will set the
SelectedItem and from that you can populate the detail.
If i have a folder-like structure that uses the composite design pattern and i bind the root folder to a TreeView. It would be quite useful if i can display certain properties that are being accumulated from the folder's contents. The question is, how do i best inform the folder that changes occurred in a child-element so that the accumulative properties get updated?
The context in which i need this is a small RSS-FeedReader i am trying to make. This are the most important objects and aspects of my model:
Composite interface:
public interface IFeedComposite : INotifyPropertyChanged
{
string Title { get; set; }
int UnreadFeedItemsCount { get; }
ObservableCollection<FeedItem> FeedItems { get; }
}
FeedComposite (aka Folder)
public class FeedComposite : BindableObject, IFeedComposite
{
private string title = "";
public string Title
{
get { return title; }
set
{
title = value;
NotifyPropertyChanged("Title");
}
}
private ObservableCollection<IFeedComposite> children = new ObservableCollection<IFeedComposite>();
public ObservableCollection<IFeedComposite> Children
{
get { return children; }
set
{
children.Clear();
foreach (IFeedComposite item in value)
{
children.Add(item);
}
NotifyPropertyChanged("Children");
}
}
public FeedComposite() { }
public FeedComposite(string title)
{
Title = title;
}
public ObservableCollection<FeedItem> FeedItems
{
get
{
ObservableCollection<FeedItem> feedItems = new ObservableCollection<FeedItem>();
foreach (IFeedComposite child in Children)
{
foreach (FeedItem item in child.FeedItems)
{
feedItems.Add(item);
}
}
return feedItems;
}
}
public int UnreadFeedItemsCount
{
get
{
return (from i in FeedItems
where i.IsUnread
select i).Count();
}
}
Feed:
public class Feed : BindableObject, IFeedComposite
{
private string url = "";
public string Url
{
get { return url; }
set
{
url = value;
NotifyPropertyChanged("Url");
}
}
...
private ObservableCollection<FeedItem> feedItems = new ObservableCollection<FeedItem>();
public ObservableCollection<FeedItem> FeedItems
{
get { return feedItems; }
set
{
feedItems.Clear();
foreach (FeedItem item in value)
{
AddFeedItem(item);
}
NotifyPropertyChanged("Items");
}
}
public int UnreadFeedItemsCount
{
get
{
return (from i in FeedItems
where i.IsUnread
select i).Count();
}
}
public Feed() { }
public Feed(string url)
{
Url = url;
}
Ok, so here is the thing, if i bind a TextBlock.Text to the UnreadFeedItemsCount there won't be simple notifications when an item is marked unread, so one of my approaches has been to handle the PropertyChanged event of every FeedItem and if the IsUnread-Property is changed i have my Feed make a notification that the property UnreadFeedItemsCount has been changed. With this approach i also need to handle all PropertyChanged events of all Feeds and FeedComposites in Children of FeedComposite, from the sound of it, it should be obvious that this is not such a very good idea, you need to be very careful that items never get added to or removed from any collection without having attached the PropertyChanged event handler first.
Also: What do i do with the CollectionChanged-Events which necessarily also cause a change in the sum of the unread items count? Sounds like more event handling fun.
It is such a mess; it would be great if anyone has an elegant solution to this since i do not want the feed-reader to end up as awful as my first attempt years ago when i did not even know about DataBinding...
Well I thought I'd give your question a go, to see what I would come up with. Its untested and its is kinda along the same lines as what you already had. The major difference I made is added methods to handle the add and removal of feeds which handle the event binding needed for it to work. Theres a bit of code so here goes,
I all my code is in a single file, youll need to modify slightly if you want in in separate files.
First the groovy extension method for the PropertyChangedEventHandler
You dont need to use it, but I like it alot.
public static class NotifyPropertyChangedExtention
{
public static void Raise<T, TP>(this PropertyChangedEventHandler pc, T source, Expression<Func<T, TP>> pe)
{
if (pc != null)
{
pc.Invoke(source, new PropertyChangedEventArgs(((MemberExpression)pe.Body).Member.Name));
}
}
}
Second the FeedItem minus the feed stuff :) I have a check to raise a change event only when the value actually changes. You can see the Raise extension method in use here, no strings lovely.
class FeedItem : INotifyPropertyChanged
{
private bool _isUnread;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsUnread
{
get { return _isUnread; }
set
{
if (_isUnread != value)
{
_isUnread = value;
PropertyChanged.Raise(this, x => x.IsUnread);
}
}
}
}
Now the Interfaces, I made mine differ slightly, in that my folders can contain other folders as well as feeds.
internal interface IFeedComposite : INotifyPropertyChanged
{
string Title { get; set; }
int UnreadFeedItemsCount { get; }
}
internal interface IFeedFolder : IFeedComposite
{
ObservableCollection<IFeedFolder> FeedFolders { get; }
ObservableCollection<IFeed> Feeds { get; }
void AddFeed(IFeed newFeed);
void RemoveFeed(IFeed feedToRemove);
void AddFeedFolder(IFeedFolder newFeedFolder);
void RemoveFeedFolder(IFeedFolder feedFolderToRemove);
}
internal interface IFeed : IFeedComposite
{
ObservableCollection<FeedItem> FeedItems { get; }
void AddFeedItem(FeedItem newFeedItem);
void RemoveFeedItem(FeedItem feedItemToRemove);
}
Now the Feed Class, the AddFeedItem method hooks the property changed event for you, and if it is marked as unread, raises the property changed event for the count. You could overload this method, to accept a list of items, then once they have been added to the list, if any where unread, raise a single property changed event for them all.
class Feed : IFeed
{
private readonly ObservableCollection<FeedItem> _feedItems = new ObservableCollection<FeedItem>();
public event PropertyChangedEventHandler PropertyChanged;
public string Title { get; set; }
public int UnreadFeedItemsCount
{
get
{
return (from i in FeedItems
where i.IsUnread
select i).Count();
}
}
public ObservableCollection<FeedItem> FeedItems
{
get { return _feedItems; }
}
public void AddFeedItem(FeedItem newFeed)
{
newFeed.PropertyChanged += NewFeedPropertyChanged;
_feedItems.Add(newFeed);
PropertyChanged.Raise(this, x => x.FeedItems);
if (newFeed.IsUnread)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
public void RemoveFeedItem(FeedItem feedToRemove)
{
_feedItems.Remove(feedToRemove);
PropertyChanged.Raise(this, x => x.FeedItems);
if (feedToRemove.IsUnread)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
void NewFeedPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsUnread")
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
}
Now the FeedFolder class, much the same as the feed class but this one can hold a list of feeds and a list of feed folders (which hold their own feeds). You can easily add a method or property to return all feeditems from feeds and feedfolders if you need. Again, various checks to only raise change events if needed.
class FeedFolder : IFeedFolder
{
private readonly ObservableCollection<IFeedFolder> _feedFolders = new ObservableCollection<IFeedFolder>();
private readonly ObservableCollection<IFeed> _feeds = new ObservableCollection<IFeed>();
public event PropertyChangedEventHandler PropertyChanged;
public string Title { get; set; }
public int UnreadFeedItemsCount
{
get { return Feeds.Sum(x => x.UnreadFeedItemsCount) + FeedFolders.Sum(x => x.UnreadFeedItemsCount); }
}
public ObservableCollection<IFeedFolder> FeedFolders
{
get { return _feedFolders; }
}
public ObservableCollection<IFeed> Feeds
{
get { return _feeds; }
}
public void AddFeed(IFeed newFeed)
{
newFeed.PropertyChanged += NewFeedPropertyChanged;
_feeds.Add(newFeed);
PropertyChanged.Raise(this, x => x.Feeds);
if (newFeed.UnreadFeedItemsCount > 0)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
void NewFeedPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "UnreadFeedItemsCount")
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
public void RemoveFeed(IFeed feedToRemove)
{
_feeds.Remove(feedToRemove);
PropertyChanged.Raise(this, x => x.Feeds);
if (feedToRemove.UnreadFeedItemsCount > 0)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
public void AddFeedFolder(IFeedFolder newFeedFolder)
{
newFeedFolder.PropertyChanged += NewFeedPropertyChanged;
_feedFolders.Add(newFeedFolder);
PropertyChanged.Raise(this, x => x.FeedFolders);
if (newFeedFolder.UnreadFeedItemsCount > 0)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
public void RemoveFeedFolder(IFeedFolder feedFolderToRemove)
{
_feedFolders.Remove(feedFolderToRemove);
PropertyChanged.Raise(this, x => x.FeedFolders);
if (feedFolderToRemove.UnreadFeedItemsCount > 0)
{
PropertyChanged.Raise(this, x => x.UnreadFeedItemsCount);
}
}
}
Now for usage, remember I havent tested this, but it should be mostly right.
var myFolder = new FeedFolder();
var myFeed = new Feed();
var myFeedItem = new FeedItem();
myFeedItem.IsUnread = true;
myFeed.AddFeedItem(myFeedItem);
myFolder.AddFeed(myFeed);
var mySecondFeedItem = new FeedItem();
//add a second feeditem to feed, but it is marked as read, so no notifications raised for unread count.
myFeed.AddFeedItem(mySecondFeedItem);
//this should fire off change events all the way up to the folder
mySecondFeedItem.IsUnread = true;