WPF CollectionChanged Event OldItems.Count - c#

I have an ObservableCollection and I attach to the CollectionChanged event:
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
int id = -1 * i;
DocumentWatchList d = (DocumentWatchList)e.NewItems[i];
d.UID = id;
_dataDc.DocumentWatchLists.InsertOnSubmit(d);
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
for (int i = 0; i < e.OldItems.Count; i++)
{
DocumentWatchList d = (DocumentWatchList)e.OldItems[i];
_dataDc.DocumentWatchLists.DeleteOnSubmit(d);
}
}
_dataDc.SubmitChanges();
}
My collection is bound to a datagrid, and the viewmodel code (above) gets called as expected. When I select multiple rows and hit delete, I expect that the OldItems collection will contain the number of rows that I had selected (n). however, what actually happens is that the event gets called n times, and each time the OldItems collection count is 1. So under what conditions will the OldItems collection contain more than 1 item? Is this behavior coming from the datagrid control, or rather is it the way ObservableCollection.CollectionChanged is meant to work?

For some reason, ObservableCollection doesn't support a NotifyCollectionChanged event with multiple items.
You can do this:
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, singleItem));
But not this: (you'll get a NotSupportedException)
OnCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, multipleItems));
I can't think of any scenario where e.OldItems.Count would be greater than 0.
Here is a good article about it, where someone actually implemented the handling of multiple items themselves, for performance purposes.

If you take a look at the interface that observable collection offers to you, you already know what you will get :
https://msdn.microsoft.com/en-us/library/ms668604%28v=vs.110%29.aspx
Basically it does not offer any way to insert or remove multiple items at once. So this effectively means that you can clear the whole collection, but if you need to say remove 2 items while there are 6 in the collection, you will have to remove them one by one. Which is what the datagrid will do in your case , but suppose you were to implement you own datagrid, you would be forced to do it the same way.
To make the answer complete, I must add that there is a way to get multiple items in the deleted list, but only by clearing the collection.
Replacing an item in the collection is also possible, by using the index operator, you can even replace an item with itself. I' ve tried this and this works, you will get the same item in the deleted collection and inserted collection in that case, but also here one by one.
You can of course create you own implementation of observable collection that would solve these issues. But I think you would need a different implementation of datagrid too, that would use your custom new bulk insert or bulk delete operations.

Related

Update ObservableCollection element not working

I have one ObservableCollection in my ViewModel with INotifyPropertyChanged, say A. Now I am going to loop through A to get some elements updated.
public ObservableCollection<ClassA> A
{
get
{
if (this.a== null)
{
this.a= new ObservableCollection<ClassA>();
}
return this.a;
}
set
{
this.a= value;
this.OnPropertyChanged("A");// implement PropertyChangedEvent
}
}
In the loop I update the values.
foreach (var item in MyViewModel.A)
{
if(condition)
MyViewModel.A.Type= "CASH";
else
MyViewModel.A.Type= "CHECK";
}
But I see the setter part is not reached. so the collection is not updated.
It looks like you're trying to update the elements in the ObservableCollection<ClassA> and not setting the collection to a new value. If you want a property change to occur when calling MyViewModel.A.Type = "CASH" then ClassA will need to implement INotifyPropertyChanged.
(EDIT: For others seeing this, check this question/answer - I'm not able to mark this as a possible duplicate. You need to monitor for property changes of elements in the collection and then trigger the property change on your ObservableCollection manually. The container does not do this for you.)
I use my own method. By using generic list to retrieve the all items from Observable Collection. Then convert the generic list to the Observable Collection.
It is not the best way but so far it works out.

Accidentialy getting items of an observable list in reverse order

I am using a custom observable collection class (credits go to Dean Chalke: http://www.deanchalk.me.uk/post/Thread-Safe-Dispatcher-Safe-Observable-Collection-for-WPF.aspx) in order to modify a data-bound collection from a thread other that the UI thread.
This custom observable collection implements IList<> and INotifyCollectionChanged and contains a collection of type IList<> which stores all the elements of the actual (surrounding) observable collection.
When I data bind this custom observable collection to a WPF List the items of the observable list are getting displayed correctly except the fact that they are in reverse order !
A look into my code during runtime offers that the items of the embedded collection of type IList<> which resides inside the custom observable collection have the correct order. But when I look at the custom observable list it has the items in reverse order.
Maybe I should post some code to make this more clear :)
This is the custom observable collection:
public class ThreadSaveObservableCollection <T> : IList<T>, INotifyCollectionChanged {
private IList<T> collection;
public ThreadSaveObservableCollection () {
collection = new List<T>();
}
...
public void Insert (int index, T item) {
if (Thread.CurrentThread == uiDispatcher.Thread) {
insert_(index, item);
} else {
uiDispatcher.BeginInvoke(new Action<int, T>(insert_), DispatcherPriority.Normal, new object[] {index, item});
}
}
private void insert_ (int index, T item) {
rwLock.AcquireWriterLock(Timeout.Infinite);
collection.Insert(index, item);
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
rwLock.ReleaseWriterLock();
}
...
}
This is where I use the collection in the ViewModel:
...
public ThreadSaveCollection Log {get; set;}
public ViewModel () {
Log = new ThreadSaveCollection();
}
...
public void Insert() {
log.Instert(0, "entry1");
}
I create the binding between the object log and the WPF control dynamically:
LogList.ItemSource = ViewModel.Log;
Except this wrong-order issue everything seems to work quite fine: The threads do what they should do and the WPF list gets updated in time.
Again when stepping into the code the Log object of the ViewModel shows me the inverse order whereas the collection object inside the ThreadSaveObservableCollection has the items in the correct order.
I would really appreciate any help!
Thank you in advance ...
UPDATE: The statement log.Instert(0, "entry1"); is intentional since I want to have a list that is getting items over time and every new item should be inserted at the beginning of the list. In other words the newest item is always on top of the list. Nevertheless in my code the embedded collection has the desired order whereas the surrounding collection doesn't.
Why should there be a difference in the order of items anyway ?
UPDATE: Interestingly when I use Add() instead of insert the order is not getting reversed from the outside collection.
In other words: Regardless of whether I use Add(item) or Insert(0, item) I am always getting the same order of items in my ThreadSaveObservableCollection object of my ViewModel whereas the contained collection inside has the correct order.
You seem to always be inserting new records at index 0
log.Inster(0, "entry1");
creating a first-in, first-out scenario.
If you insert
A
B
C
you will get back
C B A
When you call Insert(0, "entry1") you're putting the new value at the beginning of the list, which is why the order is reversed. You could use the Add method instead.
To get a correct order in your WPF component, you can use a SortDescription:
yourListView.Items.SortDescriptions.Add( new SortDescription( "yourSourcePropertyToOrderBy", ListSortDirection.Ascending ) );
You can setup SortDescription in your gui-management oder directly in code behind of your WPF.

Why foreach works while removing items from ListView and doesn't work from ListBox?

I've started learning C# and I'm a bit confused about the behavior that I discovered. I try to figure out, why in one case the code is working and in another not:
foreach (ListViewItem l in listView1.SelectedItems) l.Remove();
foreach (object l in listBox1.SelectedItems) listBox1.Items.Remove(l);
First one works fine and there is no error, but the second one throws exception with information that the collection was changed.
Could anyone explain it to me?
PS. In case of ListView I was debugging code and collection SelectedItems was changing, but even though it worked well.
When I read the code inside .NET, more specifically ListBox.cs and ListView.cs, they have two different classes for keeping their SelectedItems collections.
ListBox.cs has SelectedObjectCollection, which has these members:
private ListBox owner;
private bool stateDirty;
private int lastVersion;
private int count;
ListView.cs has SelectedListViewItemCollection, which has these members only:
private ListView owner;
private int lastAccessedIndex = -1;
So by looking at that, I guess I can deduce that ListBox's collection is a proper enumerator that keeps track of any changes and the number of items that are in the list.
ListView, on the other hand, seems to not care about that at all, and only keep track of the current index of the enumerator and simply steps forward.
So ListBox throws the exception since it keeps track of modifications, ListView does not.
EDIT:
ListBox.cs's SelectecObjectCollection's GetEnumerator method looks like this:
public IEnumerator GetEnumerator() {
return InnerArray.GetEnumerator(SelectedObjectMask);
}
And ListView.cs's SelectedListViewItemCollection's GetEnumerator method looks like this:
public IEnumerator GetEnumerator() {
if (owner.VirtualMode) {
throw new InvalidOperationException(SR.GetString(SR.ListViewCantAccessSelectedItemsCollectionWhenInVirtualMode));
}
ListViewItem[] items = SelectedItemArray;
if (items != null) {
return items.GetEnumerator();
}
else {
return new ListViewItem[0].GetEnumerator();
}
}
So it looks like ListView returns an enumerator of an array, which is constant, whilst ListBox returns an actual enumerator as a filter of its InnerArray of items.
I know this is not what you asked about; but it is always favorable to add all items to a temporary List before looping through it to remove things, since you can never know how the enumerators are implemented on the backend, nor how they might change in the future.
while (myListBox.SelectedItems.Count > 0)
{
myListBox.Items.Remove(myListBox.SelectedItems[0]);
}
You cannot modify a collection over which you are enumerating. That's why you are getting an exception in the second example.
The Remove method on the ListView item is designed to not throw an exception in this situation.

PerformDataBinding, extract row count from ObjectDataSource

I have a custom GridView which automatically puts the row count from SqlDataSources into grids for me. It computes that count in the code below. Note that this question relates to the custom inherited GridView control, not page-level stuff.
How do I recognise in PerformDataBinding that the "IEnumerable" thing is an ObjectDataSource? I want to find out specifically what ObjectDataSource type it is, then call its "get total row count" function.
The reason is that the total row count is (say) millions, where as at the moment the ICollection clause returns the count of just what has been retrieved from the database, which is typically "one page" of data, so (say) 20 records not 20,000,000!
I only have a couple of specific ObjectDataSource types, so I could pick them out one by one if I knew how to find their names from this IEnumerable thing.
I have reviewed this answer:
How to get row count of ObjectDataSource
but I don't know how to work out which precise BLL I'm dealing with. The debugger has lots of stuff inside this object, but I can't see what I want there.
protected override void PerformDataBinding(IEnumerable data)
{
// This does not work for my Object Data Sources, which return one page of
// records only, not the whole set. There must however be a way...
if (data is IListSource)
{
IListSource list = (IListSource)data;
rowcount = list.GetList().Count;
}
else if (data is ICollection)
{
ICollection collection = (ICollection)data;
rowcount = collection.Count;
}
base.PerformDataBinding(data);
}
Just enumerate without casting.
protected override void PerformDataBinding(IEnumerable data)
{
var enum1 = data.GetEnumerator();
int count = 0;
while (enum1.MoveNext())
{
count++;
}
this.TotalRecordCount = count;
base.PerformDataBinding(data);
}

How to iterate through a list that is being modified?

I have a list of rows from a dataset that I need to iterate through.
The problem is that the processing in the iteration may delete one or more rows from the list.
Since the list is being modified, I can't use a foreach() loop.
But since it is possible some of the deletions may occur at elements BEFORE the one I'm processing, I also can't use a for() loop (i.e, if I'm processing element , and that results in the deletion of element and also other elements , I can't think of a way to adjust i to correctly point to the element following the one that I was processing).
How would you tackle this problem? My current thought it is to always process the first element in the list. If it gets deleted, process the new first element. If it doesn't get deleted, the move it to an "alreadyProcessed" list, and process the new first element.
Is there an easier way?
Typically this is done with a reverse loop:
List<string> Items = ...
for(int i = Items.Count - 1; i >= 0; i--)
{
if(Items[i] == "DELETE ME")
{
Items.RemoveAt(i);
}
}
This causes the items to be processed in reverse order, so if you delete an item, it does not affect the position of any items still to be processed.
When modifying a list I'm iterating through, I always find it easiest to build a new list with the items I want to keep, and then use the new list to do whatever it was I was going to do.
It really depends on what you're doing with the data when you're done, I suppose.
for(int i = list.Length -1, i >= 0; i--)
{
// process and delete if you want
}
int i = 0;
while (i < dataSet.Tables[0].Rows.Count) {
if (some_condition) {
dataSet.Tables[0].Rows.RemoveAt(i);
continue;
}
i++;
}
If you can get your data into a linked list, you're golden.

Categories

Resources