I have a custom collection that inherits IEnumerable.
public class MyCollection<T> : IEnumerable<T>
{
private ObservableCollection<T> currentList = new ObservableCollection<T>();
public ObservableCollection<T> Items
{
get { return currentList; }
set { currentList = value; }
}
private List<T> deletedList = new List<T>();
private List<T> addedList = new List<T>();
public MyCollection(IEnumerable<T> currentList)
{
this.currentList = new ObservableCollection<T>(currentList);
Items.CollectionChanged+=Items_CollectionChanged;
}
}
I wish to track which items were added or deleted . Also , My Items is databound to a UI datagrid in WPF. The binding works fine if i bind to the Items instead of MyCollection . However , I want to track the added , removed items as below.
Am assuming the following event is caught when I add or remove any item from the UI.
As each row gets added to the DataGrid , I wish this even to be called to save the last added entry.
Now how do I get the current added or removed item in this ?
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
//this doesn't work ( I List is not assignable to IEnumerable T
deletedList.AddRange(e.OldItems);
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
//this doesnt work
addedList.AddRange(e.OldItems);
}
}
I only get the added or deleted items .
public ObservableCollection<T> GetAddedChanges()
{
var added = new ObservableCollection<T>();
addedList.ToList().ForEach(added.Add);
return added;
}
public ObservableCollection<T> GetDeletedChanges()
{
var deleted = new ObservableCollection<T>();
deletedList.ToList().ForEach(deleted.Add);
return deleted;
}
UPDATE :
The initial answer here worked i.e the Cast() option and I was able to get only the added members from my list and save them.
There is another issue . I am getting my server to return MyCollection . The sad thing is that my server when it de serializes to collection objects , it gets caught by the Added event of the Items_CollectionChanged , hence my added List faultily returns 2 items in added list always .
To solve this :
1) Subscribe to the event after server returns instead of MyCollection constructor. i.e Subscribe in another StartTracking( ).
2) Clear the added, deletd lists after getting the response from server .
Which of these would be better ? And are there any alternate solutions ?
This is the flow basically :
UI -- > OBservableCollection --> MyCollection ( where I track added /del elements ) --> Save to Server .. To fetch from server , I have my server side code return MyCollectionwith entries in it.
OldItems is not generic (it is IList not IList<T>), so you will need to cast the items.
Either:
foreach(T item in e.OldItems)
{
deletedList.Add(item);
}
Or:
deletedList.AddRange(e.OldItems.Cast<T>());
On tracking additions, you'll want to use NewItems. As an aside, this will not capture changes occurring during replace (e.g. c[0] = <new item>) or reset (e.g. after Clear is called).
I'd also note your GetAddedChanges could be simplified (And similarly GetDeletedChanges):
return new ObservableCollection<T>(addedList);
Though it's not clear why the return type would need to be ObservableCollection<T>.
Related
I have problem with ordering data for ListView. I have EventDisplay class which is an ObservableCollection for ListView(called Events)
private ObservableCollection<EventDisplay> currentEvents = new ObservableCollection<EventDisplay>();
private void Events_Loaded(object sender, RoutedEventArgs e)
{
sv = (ScrollViewer)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this.Events, 0), 0);
Events.ItemsSource = currentEvents;
}
I then add new data by function :
private void LoadDataToList(List<EventDisplay> newItems)
{
foreach (EventDisplay ed in newItems)
{
//Set some additional data
currentEvents.Add(ed);
}
//When this line below is commented ListView data is updated
//but is not sorted, when i uncomment the listview data is not being updated
//currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
}
So what is the proper way of ordering data for ListView in Windows 8.1 apps ?
You can sort & filter the view of your ObservableCollection (explanation here)
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
Data structure for the example:
interface ICustomer
{
string CuctomerName{get;set;}
int Age{get;set;}
}
Example use of the code:
ViewableCollection<ICustomer> vCustomers = new ViewableCollection<ICustomer>();
// Sorting settings:
ViewableCollection<ICustomer> vCustomers.View.SortDescriptions.Add(new SortDescription("CustomerName", ListSortDirection.Ascending));
vCustomers.View.Filter = MyCustomFilterMethod;
// add data to collection
MyCustomers.ToList().ForEach(customer => vCustomers.Add(customer));
Examlpe filter method:
private bool MyCustomFilterMethod(object item)
{
ICustomer customer = item as ICustomer;
return customer.Age > 25;
}
when you need to refresh the filter, the only thing you need to do is call:
this.vCustomers.View.Refresh();
Then you bind your GUI to vCustomers.View
You don't need to reset binding sources etc.
Use this for your add items code:
foreach (EventDisplay ed in newItems.OrderByDescending(x => x.ed.date).ToList()
{
//Set some additional data
currentEvents.Add(ed);
}
The reason your doesn't work is that you are reassigned the currentEvents reference rather than updating the ObservableCollection.
You should do the following :
currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
Events.ItemsSource = currentEvents;
This forces the ListView to rebind to your new sorted observable collection.
Another option is to sort the Observable collection in place. However, it may introduce flickering as the ListView will constantly update as the sort progresses.
If you don't want the ScrollView to reset its position, you can save the scrollview position and then restore it after sorting the list.
I've had success with Implementing a custom ObservableCollection that supports sorting but prevents UI flickering by suspending change notification during sort and then issuing a reset notification. The ScrollView should stay at its current position even when confronted with the reset event.
I have run over a small performance problem while I was trying to add some Items of an ObservableCollection to a ListView.
Here's the ObservableCollection:
private ObservableCollection<object> children = new ObservableCollection<object>();
public ObservableCollection<object> Children
{
get
{
return children;
}
}
I'm also using a ContentProperty:
[ContentProperty(Name="Children")]
Here's the CollectionChanged Event I need to start adding the List:
void children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
InitGraphics();
}
In InitGraphics(), I will create my List and draw all elements needed for it. There's also a function called SynchronizeList(), which gets called right at the beginning of InitGraphics().
SynchronizeList() looks like this:
private void SynchronizeList()
{
foreach (var item in children)
{
//Casting ListViewItem as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards and needed attributes
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
//Add the Item to the List
List.Items.Add(item);
}
}
Now, as you can see, my ListView is called List and Items are Usercontrols called ValueBoxControl. The standards and attributes have to be the same for every item and they should be unchangeable.
I need the SynchronizeList() function in a few other functions (for example: When the List gets toggled twice, I delete all Items and add them again with SynchronizeList(), etc.).
The problem occurs when I add about 50+ Items, because it obviously has to run through the foreach loop 50 times and add each item. While debugging, I have noticed that the children Collection doesn't have all 50 items from the start. It first has 1 item, then 2, then 3, etc.
Which means that the SynchronizeList() gets called 50 times, while first adding 1 item, then 2, then 3, etc.
I have tried many things (like working with e.NewItems in the CollectionChangedEvent), but I currently see no possible solution.
Here's a complete example out of my fragments which shows my problem:
//ContentProperty to receive Items from XAML with the help of an ObservableCollection
[ContentProperty(Name="Children")]
public sealed partial class ListViewControl : ValueBoxControl
{
//Create a new ListView
ListView List = new ListView();
//Initialization
public ListViewControl()
{
//Events
children.CollectionChanged += children_CollectionChanged;
}
//ObservableCollection to receive Items from XAML
private ObservableCollection<object> children = new ObservableCollection<object>();
public ObservableCollection<object> Children
{
get
{
return children;
}
}
//Event which gets called whenever the List in XAML gets changed
void children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Calls the function to draw the List
InitGraphics();
}
private void InitGraphics()
{
SynchronizeList();
if (List.Items.Counter >= 1)
{
//Draw the ListView with a custom design
}
}
private void SynchronizeList()
{
//Get each item in the ObservableCollection and add some attributes
foreach (var item in children)
{
//ListViewItem casted as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards & Limitations
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
//Add Item to ListView
List.Items.Add(item);
}
}
}
The main problem I have here is that the CollectionChanged Event gets called 50 times for 50 items, each time receiving a new item, instead of receiving them all in one go.
Given the documentation for ItemCollection, I suspect you want the ReplaceAll method:
For implementations that track a "changed" event, the initial reset fires an event, but the items added don't fire discrete per-item events.
So something like:
private void SynchronizeList()
{
// Set properties in each item
foreach (var item in children)
{
//ListViewItem casted as ValueBoxControl
ValueBoxControl control = (ValueBoxControl)item;
//Standards & Limitations
control.ValueTextBox.IsHitTestVisible = false;
control.ClipColor = ClipColor;
control.Foreground = this.Foreground;
}
List.Items.ReplaceAll(children.ToArray());
}
Unfortunately it doesn't look like there's an AddRange which is what you really want... it's at least worth giving that a try.
I have got the following code:
private BindingList<INoun> _nouns;
private BindingList<INoun> Nouns
{
get
{
if ( _nouns == null )
{
_nouns = new BindingList<INoun>( _model.Feature.Nouns );
_nouns.Insert( 0, new Noun( -1, "Please select..." ) );
}
return _nouns;
}
}
public interface INoun
{
int Id;
string Text;
}
The Nouns property is bound to a ComboBox that adds a default entry Please select... to the BindingList.
The issue I am having here is that the Please select... entry is unexpectedly being added to the underlying_model.Feature.Nouns collection and I do not want this to happen.
Is there anyway I can add a Please select... default item to a ComboBox without it being added to the underlying collection?
Thanks
BindingList is just a wrapper, mainly to get notifications, around your _model.Feature.Nouns which remains as the underlying List of items (that's why you have AllowEdit, AllowNew, AllowRemove on BindingList) :
If you want to work on a brand new list (though I'm not sure it's the purpose of the BindingList), try :
_nouns = new BindingList<INoun>( _model.Feature.Nouns.Select(x=>x).ToList());
I have a Silverlight application which interacts with a WCF service. It periodically receives new items to add to a list from this service, and each new element is added to the end of an ObservableCollection (collection.Add() for each new element).
The items themselves don't change once they are received, and the items' class inherits INotifyPropertyChanged, however when I add new items (received from WCF), the DataGrid doesn't update.
I am also using a custom formatter for the DataGrid binding, but I don't think this is a problem as the initial set of items appear correctly (when the ItemsSource is first set).
I would have expected the new elements to appear, as I have confirmed that the ObservableCollection is emitting the correct add event. Since ObservableCollection inherits from INotifyCollectionChanged, shouldn't it update the DataGrid?
The only solution I have found so far is:
dataGrid.ItemsSource = null;
dataGrid.ItemsSource = collection;
Any ideas on how to get it updating? This method blocks the UI for a noticable amount of time.
Thanks
UPDATE: Code
The elements are expanded and extracted in the WCF callback event:
// The ItemWrapper allows the Binding converter to be passed the entire trade object, rather than just each property.
ObservableCollection<ItemWrapper<ExpandedTrade>> pastTrades = new ObservableCollection<ItemWrapper<ExpandedTrade>>();
....
// Extract and expand data - MinimalTrade is the data sent through WCF
var convertedTrades = from MinimalTrade t in e.trades
select new ItemWrapper<ExpandedTrade>(
new ExpandedTrade(t,
usernames.ContainsKey(t.UserToId) ? usernames[t.UserToId] : null, potentialWealth != null ? potentialWealth.CurrentWealth : null)); // Get name, otherwise null.
// Data now expanded (to show full information like usernames
// pastTrades is an observableCollection
foreach (var trade in convertedTrades)
{
pastTrades.Add(trade);
}
OnNewMyTradeHistory(pastTrades);
The OnNewMyTradeHistory event then does this:
if (tradeHistory.ItemsSource == null) tradeHistory.ItemsSource = trades;
This only sets ItemsSource once (to the ObservableCollection) and the add events are firing, but nothing is happening on the UI side.
The WCF callbacks might be happening in another thread.
I found the solution!
I had implemented the Equals, GetHashCode and ToString methods in both ItemWrapper and ExpandedTrade:
ItemWrapper.cs: (Calls the equivalent methods in the child class)
public override bool Equals(object obj)
{
if(obj is T) return Quote.Equals(obj);
if (obj is ItemWrapper<T>) return Quote.Equals(((ItemWrapper<T>)obj).Quote);
return this == obj;
}
public override int GetHashCode() { return Quote.GetHashCode(); }
public override string ToString() { return Quote.ToString(); }
ExpandedTrade.cs:
public override bool Equals(object obj)
{
if (obj == null) return false;
ExpandedQuote q = obj as ExpandedQuote;
if (q == null) return false;
return q.Id == Id;
}
public override int GetHashCode() { return Id; }
After removing these methods, it worked. I'd imagine that the DataGrid was testing for equality somewhere, and somehow something was returning an incorrect test. The IDs are unique, but by using the default test of equality by reference, it now works.
Tell me if this flow is correct:
DataGrid.ItemsSource == null;
[Update]
Create new observable collection: CollectionA
Get items and add them to CollectionA
[Event]
DataGrid.ItemsSource == null -> ItemsSource = CollectionA
[Update]
Create new observable collection: CollectionB
Get items and add them to CollectionB
[Event]
DataGrid.ItemsSource != null -> Do Nothing
=> DataGrid.ItemsSource == CollectionA?
Or is pastTrades a field which is only initialized once? Brackets and method boundaries would help.
What is the use of ObservableCollection in .net?
ObservableCollection is a collection that allows code outside the collection be aware of when changes to the collection (add, move, remove) occur. It is used heavily in WPF and Silverlight but its use is not limited to there. Code can add event handlers to see when the collection has changed and then react through the event handler to do some additional processing. This may be changing a UI or performing some other operation.
The code below doesn't really do anything but demonstrates how you'd attach a handler in a class and then use the event args to react in some way to the changes. WPF already has many operations like refreshing the UI built in so you get them for free when using ObservableCollections
class Handler
{
private ObservableCollection<string> collection;
public Handler()
{
collection = new ObservableCollection<string>();
collection.CollectionChanged += HandleChange;
}
private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var x in e.NewItems)
{
// do something
}
foreach (var y in e.OldItems)
{
//do something
}
if (e.Action == NotifyCollectionChangedAction.Move)
{
//do something
}
}
}
An ObservableCollection works essentially like a regular collection except that it implements
the interfaces:
INotifyCollectionChanged,
INotifyPropertyChanged
As such it is very useful when you want to know when the collection has changed. An event is triggered that will tell the user what entries have been added/removed or moved.
More importantly they are very useful when using databinding on a form.
From Pro C# 5.0 and the .NET 4.5 Framework
The ObservableCollection<T> class is very useful in that it has the ability to inform external objects
when its contents have changed in some way (as you might guess, working with
ReadOnlyObservableCollection<T> is very similar, but read-only in nature).
In many ways, working with
the ObservableCollection<T> is identical to working with List<T>, given that both of these classes
implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this
class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or if the entire collection is modified.
Like any event, CollectionChanged is defined in terms of a delegate, which in this case is
NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter, and a NotifyCollectionChangedEventArgs as the second. Consider the following Main()
method, which populates an observable collection containing Person objects and wires up the
CollectionChanged event:
class Program
{
static void Main(string[] args)
{
// Make a collection to observe and add a few Person objects.
ObservableCollection<Person> people = new ObservableCollection<Person>()
{
new Person{ FirstName = "Peter", LastName = "Murphy", Age = 52 },
new Person{ FirstName = "Kevin", LastName = "Key", Age = 48 },
};
// Wire up the CollectionChanged event.
people.CollectionChanged += people_CollectionChanged;
// Now add a new item.
people.Add(new Person("Fred", "Smith", 32));
// Remove an item.
people.RemoveAt(0);
Console.ReadLine();
}
static void people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// What was the action that caused the event?
Console.WriteLine("Action for this event: {0}", e.Action);
// They removed something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
Console.WriteLine("Here are the OLD items:");
foreach (Person p in e.OldItems)
{
Console.WriteLine(p.ToString());
}
Console.WriteLine();
}
// They added something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
// Now show the NEW items that were inserted.
Console.WriteLine("Here are the NEW items:");
foreach (Person p in e.NewItems)
{
Console.WriteLine(p.ToString());
}
}
}
}
The incoming NotifyCollectionChangedEventArgs parameter defines two important properties,
OldItems and NewItems, which will give you a list of items that were currently in the collection before the event fired, and the new items that were involved in the change. However, you will want to examine these lists only under the correct circumstances. Recall that the CollectionChanged event can fire when
items are added, removed, relocated, or reset. To discover which of these actions triggered the event,
you can use the Action property of NotifyCollectionChangedEventArgs. The Action property can be
tested against any of the following members of the NotifyCollectionChangedAction enumeration:
public enum NotifyCollectionChangedAction
{
Add = 0,
Remove = 1,
Replace = 2,
Move = 3,
Reset = 4,
}
Explanation without Code
For those wanting an answer without any code behind it (boom-tish) with a story (to help you remember):
Normal Collections - No Notifications
Every now and then I go to NYC and my wife asks me to buy stuff. So I take a shopping list with me. The list has a lot of things on there like:
Louis Vuitton handbag ($5000)
Clive Christian’s Imperial Majesty Perfume ($215,000 )
Gucci Sunglasses ($2000)
hahaha well I"m not buying that stuff. So I cross them off and remove them from the list and I add instead:
12 dozen Titleist golf balls.
12 lb bowling ball.
So I usually come home without the goods and she's never pisssssssed off the thing is that she doesn't know about what i take off the list and what I add onto it; she gets no notifications.
The ObservableCollection - notifications when changes made
Now, whenever I remove something from the list: she get's a notification.
The observable collection works just the same way. If you add or remove something to or from it: someone is notified.
And when they are notified, then bunker down or run for cover! Of course, the consequences are customisable via an event handler.
Silly story, but hopefully you'll remember the concept now.
One of the biggest uses is that you can bind UI components to one, and they'll respond appropriately if the collection's contents change. For example, if you bind a ListView's ItemsSource to one, the ListView's contents will automatically update if you modify the collection.
EDIT:
Here's some sample code from MSDN:
http://msdn.microsoft.com/en-us/library/ms748365.aspx
In C#, hooking the ListBox to the collection could be as easy as
listBox.ItemsSource = NameListData;
though if you haven't hooked the list up as a static resource and defined NameItemTemplate you may want to override PersonName's ToString(). For example:
public override ToString()
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
it is a collection which is used to notify mostly UI to change in the collection , it supports automatic notification.
Mainly used in WPF ,
Where say suppose you have UI with a list box and add button and when you click on he button an object of type suppose person will be added to the obseravablecollection and you bind this collection to the ItemSource of Listbox , so as soon as you added a new item in the collection , Listbox will update itself and add one more item in it.
class FooObservableCollection : ObservableCollection<Foo>
{
protected override void InsertItem(int index, Foo item)
{
base.Add(index, Foo);
if (this.CollectionChanged != null)
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item, index);
}
}
var collection = new FooObservableCollection();
collection.CollectionChanged += CollectionChanged;
collection.Add(new Foo());
void CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
Foo newItem = e.NewItems.OfType<Foo>().First();
}
ObservableCollection Caveat
Mentioned above (Said Roohullah Allem)
What makes the ObservableCollection class unique is that this
class supports an event named CollectionChanged.
Keep this in mind...If you adding a large number of items to an ObservableCollection the UI will also update that many times. This can really gum up or freeze your UI.
A work around would be to create a new list, add all the items then set your property to the new list. This hits the UI once. Again...this is for adding a large number of items.