I am just now getting my toes wet in WPF after several years of working in ASP.Net exclusively. The problem I am currently struggling with is that I have a custom collection class which I need to bind to a listbox. Everything seems to be working except for removing an item from the collection. When I try to I get the error: “Collection Remove event must specify item position.” The catch is that this collection does not use indexes so I am not seeing a way to specify the position and so far Google has failed to show me a workable solution…
The class is defined to implement ICollection<> and INotifyCollectionChanged. My internal items container is a Dictionary which uses the item’s Name(string) value for a key. Aside from the methods defined by these two interfaces, this collection has an indexer that allows items to be accessed by Name, and overrides for the Contains and Remove methods so that they can also be called with the item Name. This is working for Adds and Edits but throws the above exception when I try to remove.
Here is an excerpt of the relevant code:
class Foo
{
public string Name
{
get;
set;
}
}
class FooCollection : ICollection<Foo>, INotifyCollectionChanged
{
Dictionary<string, Foo> Items;
public FooCollection()
{
Items = new Dictionary<string, Foo>();
}
#region ICollection<Foo> Members
//***REMOVED FOR BREVITY***
public bool Remove(Foo item)
{
return this.Remove(item.Name);
}
public bool Remove(string name)
{
bool Value = this.Contains(name);
if (Value)
{
NotifyCollectionChangedEventArgs E = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, Items[name]);
Value = Items.Remove(name);
if (Value)
{
RaiseCollectionChanged(E);
}
}
return Value;
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
#endregion
}
Your custom collection seems like a re-invention of KeyedCollection<TKey,TItem>, which internally uses a dictionary, and has indexes. The indexer for int indexes can get hidden if TKey is int or int-based enum, but this can be fixed.
As for making KeyedCollection work with WPF, I found this article, in which he basically makes an ObservableKeyedCollection<TKey,TItem> by implementing INotifyCollectionChanged and overriding SetItem(), InsertItem(), ClearItems(), and RemoveItem(), along with adding AddRange() and passing a Func<TItem,TKey> to the constructor for getting the TKey from a TItem.
Takes a little indirection, but you can do it with Linq. Not including error handling you can do this:
var items = dict.Keys.Select((k, i) => new { idx = i, key = k });
var index = items.FirstOrDefault(f => f.key == name).idx;
You could likewise use values instead of Keys, so long as you stay consistent.
So I threw in a temporary hack by changing the remove event to a reset, and went off to work on some other areas of my code. When I came back to this issue I discovered/realized that the SortedList class would satisfy my requirements and allow me to implement the Collection Changed events correctly with minimal changes to my existing code.
For those not familiar with this class (I had never used it before), here is a quick summary based on the reading I have done so far. In most ways it appears to behave like a dictionary, though the internal structure is different. This collection maintains sorted lists of the keys and values instead of a hash table. This means that there is a bit more overhead involved with getting data into and out of the collection but its memory consumption is lower. How noticeable this difference is appears to be dependent on how much data you need to store and what data types you are using for your keys.
Since my data volume in this instance is relatively low, and I need to have the items in the listbox sorted by their name values, using this class seems to be a good answer in my case. If anyone has an argument why this class should not be used, please let me know.
Thanks to all for their suggestions and comments, hopefully this thread helps someone else along the way.
I was able to use the NotifyCollectionChangedAction.Replace action with an empty NewItems list to raise the CollectionChanged event successfully for a non-indexed collection.
Related
I'm building a Windows Phone 8.1 (WinRt) app.
I have an ObservableCollection<object>, i need to add, modify and sort the items of this collection.
when I add an item to this list everything is ok, if i cast one of this object in the list and i edit the property of the object the INotifyPropertyChanged takes care of updating the ui.
but when i sort the list the UI doesn't respect the order of the list.
the only way to update the UI is to use Move(), but as i have found this method is veri resource hungry.
I have tried with LINQ, but as a result the list is ordered, but the element in the UI remain in the same order.
there is any alternative way to sort this list?
this is some code insede my ViewModel
ActiveServices = ActiveServices.Where(x => x is ActiveServiceControlData).OrderByDescending(x => (x as ActiveServiceControlData).NotificationNumber).ToObservableCollection();
private static ObservableCollection<object> activeServices;
public ObservableCollection<object> ActiveServices
{
get { return activeServices; }
set
{
activeServices = value;
RaisePropertyChanged(() => ActiveServices);
}
}
EDIT
My big issue is that in the ObservableCollection there are different types of object, i use this collection as a ItemsSource for a ListView with an ItemTemplateSelector based on the type of the object inside the ObservableCollection, and i need to sort only the element of a specific type.
The proper way to sort ObservableCollection is to extend base ObservableCollection and make use of internal CollectionChanged events.
Your current code recreates whole collection which is inefficient (and your user interface may "blink").
public class SortableObservableCollection<T, TSortKey> : ObservableCollection<T>
{
private readonly Func<T, TKey> _sortByKey;
public SortableObservableCollection(Func<T, TKey> sortByKey)
{
_sortByKey = sortByKey;
}
public void Sort() {
// slow O(n^2) sort but should be good enough because user interface rarely has milion of items
var sortedList = Items.OrderBy(_sortByKey).ToList();
for (int i = 0; i < sortedList.Count; ++i)
{
var actualItemIndex = Items.IndexOf(sortedList[i]);
if (actualItemIndex != i)
Move(actualItemIndex, i);
}
}
}
.. and then just call .Sort();
The above method has big advantage over recreating whole item source - your user interface can react to that in pretty way (animation of item move instead of recreate "blink")
I have an observable collection of a particular type T. Now I have a listener set to third party service and I am getting an object T which is actually a modification to an existing object in that observable collection.
What should I do?
1>Find the object that has been modified and then one by one change all the properties to the newly obtained value
OR
2>just delete the object from the list and add the obtained object
Note:
I can find the object from the collection by LINQ query using an Id property of class T and it is unique for each object. The question is after I find the object will I remove it and add the new one which has the same ID, or do I modify the existing one?
The first solution will be the fastest.
Adding and removing an object from an ObservableCollection is pretty slow. The remove-and-add operation will raise 2 CollectionChanged events.
The problem with the first solution can be the searching.
You can use a more sophisticated search algorithm or you can use a dictionary to keep your items indexed by id.
For example:
class ComplexObj //Maybe should implement INotifyPropertyChanged
{
public int Id{get;set;}
public string SomeProperty{get;set;}
}
Dictionary<int, ComplexObj> lookup = new Dictionary<int, ComplexObj>();
ObservableCollection<ComplexObj> myCollection = new ObservableCollection<ComplexObj>();
When you add an item to the collection make sure to add it in the dictionary too:
public void AddNewObj(ComplexObj obj)
{
lookup.Add(obj.Id, obj);
myCollection.Add(obj);
}
Then when you need to update a specific object:
public void Update(ComplexObj obj)
{
lookup[obj.Id].SomeProperty = obj.SomeProperty;
}
This answer is in request to comment by OP.
You could implement a class, that supports bulk updates. To do that you need to provide method(s) that set the necessary values by accessing the underlying fields directly instead of going through the properties. Once all the values are set, you trigger notification for the entire object. Here's a simple class to illustrate the idea:
class BindableTypeThatSupportsBulkUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _Text1;
public string Text1
{
get { return _Text1; }
set { _Text1 = value; NotifyPropertyChanged(); }
}
private string _Text2;
public string Text2
{
get { return _Text2; }
set { _Text2 = value; NotifyPropertyChanged(); }
}
private void NotifyPropertyChanged([CallerMemberName]string name = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void BulkUpdate(string t1, string t2)
{
_Text1 = t1;
_Text2 = t2;
// Passing String.Empty as the name causes notification to fire for all the properties
NotifyPropertyChanged(String.Empty);
}
}
This is simplified, but you probably get the idea anyway.
The BulkUpdate method sets two fields and then triggers notification on the entire object. I.e. you'll only get a single update even if you update multiple values.
Latest Updates(Thanks to #Alberto and #BlackBear for giving me directions):
I tested with XamGrid and observable collection of class T which has 10 properties binding to grid.
Each property raises a notification only if its value changes. Also used dictionary approach of #Alberto.
Deleting and Addding approach takes about 3x to 4x time where as Modification Approach takes x time. However this holds good when your actual object and modified object has only difference in 2 to 3 properties(that means not all properties of actual object are different from the properties of modified object, just 2 properties are different, so raising just two notifications for property change.)
If your modified object has about 5 or 6 properties raising the notification then the performance of modification becomes same as removing and adding a row.
So bottom line is more the difference between actual and modified object more the time taken to modify it, and at a point of time you may think of dropping the actual object and adding the modified object.
For me standard modified object has a difference of 2 to 3 properties from the actual object and performance will be better for Modification of existing object.
When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.
ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.
How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.
EDIT:
For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?
Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)
public class UserEditorViewModel
{
public ObservableCollection<UserViewModel> Users { get; set; }
public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }
public void ReloadUsersBad()
{
// bad: the collection is updated, but the WPF control is bound to the old reference.
Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
}
public void ReloadUsersWorksButIsInefficient()
{
// works: collection object is kept, and items are replaced; inefficient, though.
Users.Clear();
foreach(var user in LoadUsersFromWhateverSource())
Users.Add(user);
}
// ...whatever other stuff.
}
If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.
An example:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Whatever> _myCollection;
private void NotifyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Whatever> MyCollection
{
get
{
return _myCollection;
}
set
{
if (!ReferenceEquals(_myCollection, value))
{
_myCollection = value;
NotifyChanged("MyCollection");
}
}
}
}
With this, when you assign a collection, WPF detects this and everything gets updated.
This is how I'd solve this.
The link below explains how to implement an AddRange method.
http://web.archive.org/web/20150715112054/http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
It looks like you're stuck with implementing a sub-class that handles this case correctly.
Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.
I'm writing a class to represent a Pivot Collection, the root object recognized by Pivot. A Collection has several attributes, a list of facet categories (each represented by a FacetCategory object) and a list of items (each represented by a PivotItem object). Therefore, an extremely simplified Collection reads:
public class PivotCollection
{
private List<FacetCategory> categories;
private List<PivotItem> items;
// other attributes
}
What I'm unsure of is how to properly grant access to those two lists. Because declaration order of both facet categories and items is visible to the user, I can't use sets, but the class also shouldn't allow duplicate categories or items. Furthermore, I'd like to make the Collection object as easy to use as possible. So my choices are:
Have PivotCollection implement IList<PivotItem> and have accessor methods for FacetCategory: In this case, one would add an item to Collection foo by writing foo.Add(bar). This works, but since a Collection is equally both kinds of list making it only pass as a list for one type (category or item) seems like a subpar solution.
Create nested wrapper classes for List (CategoryList and ItemList). This has the advantage of making a consistent interface but the downside is that these properties would no longer be able to serve as lists (because I need to override the non-virtual Add method I have to implement IList rather than subclass List. Implicit casting wouldn't work because that would return the Add method to its normal behavior.
Also, for reasons I can't figure out, IList is missing an AddRange method...
public class PivotCollection
{
private class CategoryList: IList<FacetCategory>
{
// ...
}
private readonly CategoryList categories = new CategoryList();
private readonly ItemList items = new ItemList();
public CategoryList FacetCategories
{
get { return categories; }
set { categories.Clear(); categories.AddRange(value); }
}
public ItemList Items
{
get { return items; }
set { items.Clear(); items.AddRange(value); }
}
}
Finally, the third option is to combine options one and two, so that PivotCollection implements IList<PivotItem> and has a property FacetCategories.
Question: Which of these three is most appropriate, and why?
The best thing to do here is to create your own collection class that inherits System.Collections.ObjectModel.Collection<T> and overrides InsertItem.
I am wondering which one of these would be considered the cleanest or best to use and why.
One of them exposes the a list of passengers, which let the user add and remove etc. The other hides the list and only let the user enumerate them and add using a special method.
Example 1
class Bus
{
public IEnumerable<Person> Passengers { get { return passengers; } }
private List<Passengers> passengers;
public Bus()
{
passengers = new List<Passenger>();
}
public void AddPassenger(Passenger passenger)
{
passengers.Add(passenger);
}
}
var bus = new Bus1();
bus.AddPassenger(new Passenger());
foreach(var passenger in bus.Passengers)
Console.WriteLine(passenger);
Example 2
class Bus
{
public List<Person> Passengers { get; private set; }
public Bus()
{
Passengers = new List<Passenger>();
}
}
var bus = new Bus();
bus.Passengers.Add(new Passenger());
foreach(var passenger in bus.Passengers)
Console.WriteLine(passenger);
The first class I would say is better encapsulated. And in this exact case, that might be the better approach (since you should probably make sure it's space left on the bus, etc.). But I guess there might be cases where the second class may be useful as well? Like if the class doesn't really care what happens to that list as long as it has one. What do you think?
In example one, it is possible to mutate your collection.
Consider the following:
var passengers = (List<Passenger>)bus.Passengers;
// Now I have control of the list!
passengers.Add(...);
passengers.Remove(...);
To fix this, you might consider something like this:
class Bus
{
private List<Passenger> passengers;
// Never expose the original collection
public IEnumerable<Passenger> Passengers
{
get { return passengers.Select(p => p); }
}
// Or expose the original collection as read only
public ReadOnlyCollection<Passenger> ReadOnlyPassengers
{
get { return passengers.AsReadOnly(); }
}
public void AddPassenger(Passenger passenger)
{
passengers.Add(passenger);
}
}
In most cases I would consider example 2 to be acceptable provided that the underlying type was extensible and/or exposed some form of onAdded/onRemoved events so that your internal class can respond to any changes to the collection.
In this case List<T> isn't suitable as there is no way for the class to know if something has been added. Instead you should use a Collection because the Collection<T> class has several virtual members (Insert,Remove,Set,Clear) that can be overridden and event triggers added to notify the wrapping class.
(You do also have to be aware that users of the class can modify the items in the list/collection without the parent class knowing about it, so make sure that you don't rely on the items being unchanged - unless they are immutable obviously - or you can provide onChanged style events if you need to.)
Run your respective examples through FxCop and that should give you a hint about the risks of exposing List<T>
I would say it all comes down to your situation. I would normally go for option 2 as it is the simplest, unless you have a business reason to add tighter controls to it.
Option 2 is the simplest, but that lets other classes to add/remove elements to the collection, which can be dangerous.
I think a good heuristic is to consider what the wrapper methods do. If your AddPassenger (or Remove, or others) method is simply relaying the call to the collection, then I would go for the simpler version. If you have to check the elements before inserting them, then option 1 is basically unavoidable. If you have to keep track of the elements inserted/deleted, you can go either way. With option 2 you have to register events on the collection to get notifications, and with option 1 you have to create wrappers for every operation on the list that you want to use (e.g. if you want Insert as well as Add), so I guess it depends.