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.
Related
I have a ObservableCollection and I want to display only 10 items of it in the ListView. How must the Filter look like?
CollectionView altView = (CollectionView)CollectionViewSource.GetDefaultView(alteParkingListe.ItemsSource);
altView.Filter += //Show only 10 Items
The signature of the the Filter property is as follows:
public virtual Predicate<object> Filter { get; set; }
Consequently, you need to supply a Predicate<object> which is a funtion that takes an argument of type object and returns a bool. The argument passed in is an item of the underlying collection. The filter predicate is called for each item. The return value indicates whether the item is preserved (true) or filtered out (false) of the view.
As you can see, the filter does not know anything about the underlying collection itself, only each item individually. Hence, there is no direct way of doing this with a filter.
Of course you can do dirty tricks like keeping the count of filtered items like this:
var itemsCount = 0;
altView.Filter = obj => ++itemsCount <= 10;
However, this is not a good solution and you need to ensure that itemsCount is reset each time.
Now for what you should do instead: Simply create another collection property and assign a filtered variant of your main collection, no collection view and no dirty tricks involved. Filtering can be done using Linq's Take method:
FilteredItemsCollection = AllItemsCollection.Take(10);
I want to be able to bind two different collection NewItems and OldItems QueryAbleCollection as ItemsSource depending on which radiobutton is active,
let's say we have a button called NewItems and OldItems and each button should change which collection the Itemssource in the GridView should use.
How can I achieve this in the easiest way with XAML and C# ViewModel?
Under is my attempt, but I'm not sure if the smartest thing is to assign SelectedCollection another collection. Can someone correct me if this is a good approach or suggest a better approach? Right now I'm having two buttons which just trigged the isOldItems between true and false.
private bool isOldItems;
public bool isOldItems
{
get
{
return this.isOldItems;
}
set
{
this.isOldItems= value;
this.OnPropertyChanged("isOldItems");
this.OnPropertyChanged("SelectedCollection");
}
}
private QueryableCollectionView _selectedCollection;
public QueryableCollectionView SelectedCollection
{
get
{
if (isOldItems== true)
return SelectedCollection= this.OldItemsCollection;
else
return SelectedCollection= this.NewItemsCollection;
}
set
{
if (this._selectedCollection!= value)
{
this._selectedCollection= value;
this.OnPropertyChanged("SelectedCollection");
}
}
}
I see no obvious huge downside with what you're doing if these are collections of the same type. Broadly speaking anyhow.
That's a reference you're setting so if you set SelectedCollection to NewItemsCollection then it's pointing to NewItemsCollection. Add an item to SelectedCollection and you add to NewItemsCollection ( or whichever it's set to).
BUT
The if check you have will mean it never sets the backing variable and raises propertychanged from setter.
Objects of the same type are always equal unless you override equals.
Your
if (this._selectedCollection!= value)
Is not going to check every item in the collection and see if they're different, or the name of the thing you set. What it will do is compare types. These will presumably always be equal.
Remove that if check out the setter.
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.
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.
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.