Implement AddRange on ObservableCollection with proper support for DataBinding - c#

I would like my own descendant of ObservableCollection to support AddRange method.
Here is what I currently have:
public class ObservableCollectionPlus<T> : ObservableCollection<T>
{
public void InsertRange(IEnumerable<T> items)
{
this.CheckReentrancy();
foreach (var item in items) Items.Add(item);
var type = NotifyCollectionChangedAction.Reset;
var colChanged = new NotifyCollectionChangedEventArgs(type);
var countChanged = new PropertyChangedEventArgs("Count");
OnPropertyChanged(countChanged);
OnCollectionChanged(colChanged);
}
}
I don't have much knowledge of what's exactly going on here and why are these events get raised. This is a solutiom that I've assembled after doing some research on google and stackoverflow.
Now, if I bind an instance of my class to say LongListSelector then, after dynamically adding items via InsertRange to ObservableCollectionPlus, a binded LongListSelector's scroll position is sent to it's top.
If I add items in this standard way: foreach (var item in items) collection.Add(item); then LongListSelector's position does not get shifted. But of course this way I get DataBinding notifications overhead in which is undesired.
Apparently, something is done wrong in my current solution. How can I implement InsertRange that will behave exactly like foreach (var item in items) collection.Add(item); but will only fire DataBinding notification once and will not do strange things to binded object's scroll position?

It could be because your sending the NotifyCollectionChangedAction.Reset notification, maybe just the NotifyCollectionChangedAction.Add will work, Maybe :)
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
public void AddRange(IEnumerable<T> collection)
{
foreach (var i in collection)
{
Items.Add(i);
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
}
}

I've used this in a project recently...
public class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException("list");
_suppressNotification = true;
foreach (T item in list)
{
Add(item);
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

The problem you are experiencing with DataBinding might be connected to the fact that you don't raise PropertyChanged for indexer (property name "Item[]") as it happens in ObservableCollection according to the source code.
You can also have a look at a nice implementation of ObservableRangeCollection by James Montemagno here on GitHub, it inherits from ObservableColection and includes AddRange and ReplaceRange methods with all PropertyChaged and CollectionChanged notifications needed for DataBinding.

It took me ages, the trouble was always with the arguments to pass to the NotifyCollectionChangedEventArgs ctor.
There are many different ctors that take different arguments, depending on the action. The following seems to finally work for me:
https://github.com/lolluslollus/Utilz/blob/master/Utilz/SwitchableObservableCollection.cs

Related

Prevent adding the new Item on ObservableCollection.CollectionChanged event

I need to check some conditions and then decide to add the new Item to my collection or not, is there anyway i can prevent adding in my CollectionChanged event or is it too late at the point? actually i can modify the new Item but can not remove it from the NewItems collection due to runtime exception:
protected void MyFilter(object sender, NotifyCollectionChangedEventArgs e)
{
f (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (Item item in e.NewItems)
{
if (!Item.CanBeAdded())
{
//Prevent adding the Item !
}
}
}
}
Well the problem here is it's already added in the collection. The only way to do it if I follow this is removing it from the collection then. But that would retrigger the MyyFilter subscription you created.
If this collection is bound on a grid or anywhere where you can create an item from the UI, then i suggest you create a custom observable collection.
Basic code would be like this.
public class MyObservableCollection<T> : ICollection<T>
, INotifyCollectionChanged
, INotifyPropertyChanged
{
}
Implement your validation logic on the Add method of ICollection.
if you want to, just go ahead and copy this guys implementation.

Custom IEnumerable as ItemsSource for ListBox

I have a class that defines a custom GetEnumerator() function(by implementing IEnumerable<>). I use it to iterate in a contiguous manner over several ObservableCollection<LogEvent> that are in every TestStep. I have a private ObservableCollection<TestStep> that contains all the needed data.
I would like to use an instance of this class as the ItemsSource of a ListBox. However, the ListBox never gets updated when the underlying data(ObservableCollection<LogEvent>) is updated. Here's a sample of that class:
public class FlatLogViewModel : IEnumerable<LogEvent>
{
public FlatLogViewModel(ObservableCollection<TestStep> subSteps)
{
m_subSteps = subSteps;
}
public IEnumerator<LogEvent> GetEnumerator()
{
foreach (TestStep step in SubSteps)
{
// step.LogEvents is an ObservableCollection<LogEvent>
foreach (LogEvent logEvent in step.LogEvents)
yield return logEvent;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private ObservableCollection<TestStep> m_subSteps;
}
I'm not sure if I should/can implement INotifyCollectionChanged here. How can I know if ObservableCollection has been modified?
My question is: how can I get the ListBox to display the changes happening in LogEvents(which is of type ObservableCollection<LogEvent>)?
When theObservableCollection changes, how does the ListBox know? You have to implement like you mentioned INotifyCollectionChanged and then update the ItemSource with the new enumerable data inside the event handler.
ObservableCollection is an INotifyCollectionChanged. Use casting
var collectionChanged = yourObCollection as INotifyCollectionChanged;
if( collectionChanged !=null)
{
collectionChanged.CollectionChanged += YourEventHandler;
}
inside the handler do your own logic to update the item source
Have you considered binding to the original collection but running it through a converter in order to pull out the LogEvents?
The converter should be able to simply return subSteps.SelectMany(s => s.LogEvents).

Implementing CollectionChanged

I have added CollectionChanged eventhandler(onCollectionChanged) to one of the ObservableCollection property.
I have found out that onCollectionChanged method gets invoked only in case of add items or remove items to the collection, but not in the case of collection item gets edited.
I would like to know how to send the list/collection of newly added, removed and edited items in a single collection.
Thanks.
You have to add a PropertyChanged listener to each item (which must implement INotifyPropertyChanged) to get notification about editing objects in a observable list.
public ObservableCollection<Item> Names { get; set; }
public List<Item> ModifiedItems { get; set; }
public ViewModel()
{
this.ModifiedItems = new List<Item>();
this.Names = new ObservableCollection<Item>();
this.Names.CollectionChanged += this.OnCollectionChanged;
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
newItem.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach(Item oldItem in e.OldItems)
{
ModifiedItems.Add(oldItem);
oldItem.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Item item = sender as Item;
if(item != null)
ModifiedItems.Add(item);
}
Maybe you have to check if some item is already in the ModifedItems-List (with List's method Contains(object obj)) and only add a new item if the result of that method is false.
The class Item must implement INotifyPropertyChanged. See this example to know how. As Robert Rossney said you can also make that with IEditableObject - if you have that requirement.
An ItemsControl listens to CollectionChanged to manage the display of the collection of items it's presenting on the screen. A ContentControl listens to PropertyChanged to manage the display of the specific item that it's presenting on the screen. It's pretty easy to keep the two concepts separate in your mind once you understand this.
Tracking whether or not an item is edited isn't something either of these interfaces does. Property changes aren't edits - that is, they don't necessarily represent some kind of user-initiated change to the state of the object. For instance, an object might have an ElapsedTime property that's being continuously updated by a timer; the UI needs to be notified of these property-change events, but they certainly don't represent changes in the object's underlying data.
The standard way to track whether or not an object is edited is to first make that object implement IEditableObject. You can then, internally to the object's class, decide what changes constitute an edit (i.e. require you to call BeginEdit) and what changes don't. You can then implement a boolean IsDirty property that gets set when BeginEdit is called and cleared when EndEdit or CancelEdit is called. (I really don't understand why that property isn't part of IEditableObject; I haven't yet implemented an editable object that didn't require it.)
Of course, there's no need to implement that second level of abstraction if you don't need it - you can certainly listen PropertyChanged event and just assume that the object has been edited if it gets raised. It really depends on your requirements.
My edit to 'this answer' is rejected!
So I put my edit here:
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
if (e.Action == NotifyCollectionChangedAction.Add)
newItem.PropertyChanged += this.ListTagInfo_PropertyChanged;
else if (e.Action == NotifyCollectionChangedAction.Remove)
newItem.PropertyChanged -= this.ListTagInfo_PropertyChanged;
}
}
// MSDN: OldItems:Gets the list of items affected by a Replace, Remove, or Move action.
//if (e.OldItems != null) <--- removed
}
I think that populating the ObservableCollection with items that implement INotifyPropertyChanged will cause the CollectionChanged event to fire when an item raises its PropertyChanged notification.
On second thought, I think you need to use BindingList<T> to get individual item changes to propagate in this way out-of-the-box.
Otherwise, you'll need to manually subscribe to each item's change notifications and raise the CollectionChanged event. Note that if you're creating your own, derived ObservableCollection<T>, you'll have to subscribe at instantiation and on Add() and Insert(), and unsubscribe on Remove(), RemoveAt() and Clear(). Otherwise, you can subscribe to the CollectionChanged event and use the added and removed items from the event args to subscribe/unsubscribe.
INotifyCollectionChanged is not one in the same with INotiftyPropertyChanged. Changing properties of underlying objects does not in any way suggest the collection has changed.
One way to achieve this behavior is to create a custom collection which will interrogate the object once added and register for the INotifyPropertyChanged.PropertyChanged event; it would then need to de-register appropriately when an item is removed.
One caveat with this approach is when your objects are nested N levels deep. To solve this you will need to essentially interrogate each property using reflection to determine if it is perhaps yet another collection implementing INotifyCollectionChanged or other container which will need to be traversed.
Here is a rudimentary un-tested example...
public class ObservableCollectionExt<T> : ObservableCollection<T>
{
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void SetItem(int index, T item)
{
base.SetItem(index, item);
if(item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
protected override void ClearItems()
{
for (int i = 0; i < this.Items.Count; i++)
DeRegisterINotifyPropertyChanged(this.IndexOf(this.Items[i]));
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
RegisterINotifyPropertyChanged(item);
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
DeRegisterINotifyPropertyChanged(index);
}
private void RegisterINotifyPropertyChanged(T item)
{
if (item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void DeRegisterINotifyPropertyChanged(int index)
{
if (this.Items[index] is INotifyPropertyChanged)
(this.Items[index] as INotifyPropertyChanged).PropertyChanged -= OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T item = (T)sender;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, item));
}
}
in winforms, BindingList is standard practice. in WPF & Silverlight, you are usually stuck working with ObservableCollection and need to listen for PropertyChanged on each item
Use the following code:
-my Model:
public class IceCream: INotifyPropertyChanged
{
private int liczba;
public int Liczba
{
get { return liczba; }
set { liczba = value;
Zmiana("Liczba");
}
}
public IceCream(){}
//in the same class implement the below-it will be responsible for track a changes
public event PropertyChangedEventHandler PropertyChanged;
private void Zmiana(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in my class PersonList implement method responsible for active increasing the value of one after button click in AppBarControl
async private void Add_Click(object sender, RoutedEventArgs e)
{
List<IceCream> items = new List<IceCream>();
foreach (IceCream item in IceCreamList.SelectedItems)
{
int i=Flavors.IndexOf(item);
Flavors[i].Liczba =item.Liczba+ 1;
//Flavors.Remove(item);
//item.Liczba += 1;
// items.Add(item);
// Flavors.Add(item);
}
MessageDialog d = new MessageDialog("Zwiększono liczbę o jeden");
d.Content = "Zwiększono liczbę o jeden";
await d.ShowAsync();
IceCreamList.SelectedIndex = -1;
}
}
I hope that it will be useful for someone to
Note that:
private ObservableCollection<IceCream> Flavors;
-
The easiest solution I found to this limitation, is to remove the item using RemoveAt(index) then add the modified item on the same index using InsertAt(index) and thus the ObservableCollection will notify the View.

Updating the listbox after adding a new item

I am using WPF and C#
I have a button that opens a window, which also includes a button that adds an User object item to a listbox and I want the listbox index to be updated after insertion. I understood that the solution is about using observable class of INotifyCollectionChanged but actually I dont know how and where to use them. Can you help me on determining what and where to implement, register, fire etc.
Edit:
I succeeded this by Quartermeister's help where my User object is collected in a list, but now I want to do the same thing where my object is collected in a dictionary
The easiest way is to use System.Collections.ObjectModel.ObservableCollection<T> as your list. This implements INotifyCollectionChanged and INotifyPropertyChanged for you.
You would have a property on your DataContext object of this type, and use data binding on ListBox.ItemsSource to bind it to that property. The ListBox will automatically update its list of elements when the collection changes.
In the class that is your DataContext:
public class MyClass
{
public ObservableCollection<string> Items { get; set; }
}
In Xaml:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
It sounds like you also want an observable dictionary, but unfortunately there is not one built into the framework. You could try using Dr. Wpf's implementation of ObservableDictionary from his post "Can I bind my ItemsControl to a dictionary?".
Implementing an observable dictionary is hard. It's much simpler to maintain an parallel observable collection, one that contains the dictionary's values. Bind the view to that collection, and make sure that any code which adds or removes values to/from the dictionary updates the both the dictionary and the parallel collection.
If you really wanted to go crazy, you could implement a subclass of ObservableCollection to hold your objects, and make that class maintain the dictionary, e.g.:
public class KeyedObject
{
public string Key { get; set; }
public object Value { get; set; }
}
public class ObservableMappedCollection : ObservableCollection<KeyedObject>
{
private Dictionary<string, KeyedObject> _Map;
public ObservableMappedCollection(Dictionary<string, KeyedObject> map)
{
_Map = map;
}
protected override void InsertItem(int index, KeyedObject item)
{
base.InsertItem(index, item);
_Map[item.Key] = item;
}
protected override void RemoveItem(int index)
{
KeyedObject item = base[index];
base.RemoveItem(index);
_Map.Remove(item.Key);
}
protected override void ClearItems()
{
base.ClearItems();
_Map.Clear();
}
protected override void SetItem(int index, KeyedObject item)
{
KeyedObject oldItem = base[index];
_Map.Remove(oldItem.Key);
base.SetItem(index, item);
_Map[item.Key] = item;
}
}
There are a bunch of potential problems in the above, mostly having to do with duplicate key values. For instance, what should SetItem do if you're adding an object whose key is already in the map? The answer really depends on your application. Issues like that also hint at why there isn't an observable dictionary class in the framework.

Which .Net collection for adding multiple objects at once and getting notified?

Was considering the System.Collections.ObjectModel ObservableCollection<T> class. This one is strange because
it has an Add Method which takes one item only. No AddRange or equivalent.
the Notification event arguments has a NewItems property, which is a IList (of objects.. not T)
My need here is to add a batch of objects to a collection and the listener also gets the batch as part of the notification. Am I missing something with ObservableCollection ? Is there another class that meets my spec?
Update: Don't want to roll my own as far as feasible. I'd have to build in add/remove/change etc.. a whole lot of stuff.
Related Q:
https://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each
It seems that the INotifyCollectionChanged interface allows for updating when multiple items were added, so I'm not sure why ObservableCollection<T> doesn't have an AddRange. You could make an extension method for AddRange, but that would cause an event for every item that is added. If that isn't acceptable you should be able to inherit from ObservableCollection<T> as follows:
public class MyObservableCollection<T> : ObservableCollection<T>
{
// matching constructors ...
bool isInAddRange = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// intercept this when it gets called inside the AddRange method.
if (!isInAddRange)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> items)
{
isInAddRange = true;
foreach (T item in items)
Add(item);
isInAddRange = false;
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
items.ToList());
base.OnCollectionChanged(e);
}
}
Well the idea is same as that of fryguybob - kinda weird that ObservableCollection is kinda half-done. The event args for this thing do not even use Generics.. making me use an IList (that's so.. yesterday :)
Tested Snippet follows...
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace MyNamespace
{
public class ObservableCollectionWithBatchUpdates<T> : ObservableCollection<T>
{
public void AddRange(ICollection<T> obNewItems)
{
IList<T> obAddedItems = new List<T>();
foreach (T obItem in obNewItems)
{
Items.Add(obItem);
obAddedItems.Add(obItem);
}
NotifyCollectionChangedEventArgs obEvtArgs = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
obAddedItems as System.Collections.IList);
base.OnCollectionChanged(obEvtArgs);
}
}
}
Not only is System.Collections.ObjectModel.Collection<T> a good bet, but in the help docs there's an example of how to override its various protected methods in order to get notification. (Scroll down to Example 2.)
If you use any of the above implementations that send an add range command and bind the observablecolletion to a listview you will get this nasty error.
NotSupportedException
at System.Windows.Data.ListCollectionView.ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
The implementation I have gone with uses the Reset event that is more evenly implemented around the WPF framework:
public void AddRange(IEnumerable<T> collection)
{
foreach (var i in collection) Items.Add(i);
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
I have seen this kind of question many times, and I wonder why even Microsoft is promoting ObservableCollection everywhere where else there is a better collection already available thats..
BindingList<T>
Which allows you to turn off notifications and do bulk operations and then turn on the notifications.
If you're wanting to inherit from a collection of some sort, you're probably better off inheriting from System.Collections.ObjectModel.Collection because it provides virtual methods for override. You'll have to shadow methods off of List if you go that route.
I'm not aware of any built-in collections that provide this functionality, though I'd welcome being corrected :)
Another solution that is similar to the CollectionView pattern:
public class DeferableObservableCollection<T> : ObservableCollection<T>
{
private int deferLevel;
private class DeferHelper<T> : IDisposable
{
private DeferableObservableCollection<T> owningCollection;
public DeferHelper(DeferableObservableCollection<T> owningCollection)
{
this.owningCollection = owningCollection;
}
public void Dispose()
{
owningCollection.EndDefer();
}
}
private void EndDefer()
{
if (--deferLevel <= 0)
{
deferLevel = 0;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IDisposable DeferNotifications()
{
deferLevel++;
return new DeferHelper<T>(this);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (deferLevel == 0) // Not in a defer just send events as normally
{
base.OnCollectionChanged(e);
} // Else notify on EndDefer
}
}
Inherit from List<T> and override the Add() and AddRange() methods to raise an event?
Take a look at Observable collection with AddRange, RemoveRange and Replace range methods in both C# and VB.
In VB: INotifyCollectionChanging implementation.
For fast adding you could use:
((List<Person>)this.Items).AddRange(NewItems);

Categories

Resources