Binding to an ICollectionView converted to a dictionary - c#

As I am quite new to WPF and MVVM, this might be something quite obvious and trivial, so bear with me here.
Anyway, I have a view model with these properties:
class ViewModel : INotifyPropertyChanged {
ICollectionView Items; // a list of Items object, wraps _items field, each Item has a Date property
string Filter; // a filter key, wraps _filter field and calls ApplyFilter() as it is changed
void ApplyFilter(); // based on the filter key, _items.Filter gets set to some predicate
}
The properties raise the PropertyChanged event when set and all that common MVVM stuff.
In the view i have a simple ItemsControl which binds to the Items property, and some fancy data template to display each Item.
A request has been made to show the items grouped by day so that you see a date header for each day and a list of Items whose Date property corresponds to the date in the header.
Since this is strictly a display issue, I decided to leave the view model as it is, but use a converter to convert the ICollectionView Items into a Dictionary where the key is the date, and the collection is a subset of Items with that date.
The ItemsControl now has a StackPanel with a TextBlock to show the date header (the dictionary key) and another ItemsControl which is basically a copy of the old one, which just listed the items (the dictionary value).
The view renders nicely, but the filter no longer works. As the control is bound to Items, and the ICollectionView implements INotifyCollectionChanged, I was expecting the filter to work as it is changing the Items list, and that the converter would be re-run to rebuild the dictionary. Well, it is not. Changing the filter does call the ApplyFilter(), and the _items.Filter gets set to the required predicate, but the view never changes. I have also tried calling the PropertyChanged for Items from the ApplyFilter, but that does not work either.
Obviously, my contrived scenario of how this should work is wrong, and to be honest I am out of ideas, apart from creating new objects which will hold the date and list of items as properties and then using a list of those in the VM. But, as I said, in my mind, this is strictly a view issue, so the model just needs to provide a list of items, and it is the view's responsibility to decide how to render them.
Any help is greatly appreciated and thanks in advance.
EDIT:
Now I'm thinking that if I'm changing the _filter.Filter, then the PropertyChanged event for the Items is actually never raised, as it is in fact not changed (the internals have changed, but Items themselves are still the same ICollectionView).
Hence, the converter is never again triggered.
If this is the case, how can I trigger the converter? Raising the PropertyChanged for Items after doing ApplyFilter() also did nothing.

Perhaps using ListView instead of simple ItemsControl+converter is better idea? ListView has many good functions. Such as virtualizing, grouping, etc.
All you have to do, is modify your ICollectionView grouping property and apply templates(GroupStyle)
As for your problem, your current behaviour makes sense to me. If you want to converter re-run, you're supposed to create new method RefreshBinding() and do something like this;
var referenceCopy = Items;
Items = null;//make sure INotifyPropertyCHanged is fired.
Items = referenceCopy; //converter should be called again.
you will call it after you need your converter to be re-run. But to be completely honest, just use ICollectionView+Grouping property and ListView. Or you can implement ListView functionality yourself. Using converter does not seem good solution.

Related

Removing an item from a WPF binding listbox

I have a WPF application with a ListBox (called listMyItems) which is successfully bound to a class of MyItems that I created. I have a List of MyItems called currentMyItems which is then assigned as ItemSource to the ListBox. It all works fine, if I add an item to the currentMyItems it pops up on the list, etc.
The problem occurs when I try to remove the selected item in the ListBox. This is the code that I use:
currentMyItems.Remove((MyItem)listMyItems.SelectedItem);
The item disappears from the ListBox but the next time I update it, it pops back up as it was never deleted. Any tips?
I think you may be confused about how data binding works. When you bind a property, you are telling WPF to go look somewhere else for the value of that property.
When you bind the ListBox.ItemsSource property to currentMyItems, you are telling WPF to go look at the currentMyItems list to find its list of items. If currentMyItems is an ObservableCollection instead of a List<T>, then the UI will automatically receive a notification to update the bound value when you add or remove an item from the collection.
Based on what you say in the question, it sounds like you you have two collections, one of which is bound, and the other which is used to recreate the first collection anytime a change occurs. All that is not needed.
Just create one ObservableCollection<MyItem>, bind it to the ListBox.ItemsSource property, and then add or remove items from that single collection. It should work as you would expect.
<ListBox x:Name="listMyItems" ItemsSource="{Binding MyItems}" />
and
MyItems.Add((MyItem)listMyItems.SelectedItem)
MyItems.Remove((MyItem)listMyItems.SelectedItem)
If you're interested, I also have some beginner articles on my blog for WPF users who are struggling to understand the DataContext. You may want to check out Understanding the change in mindset when switching from WinForms to WPF and What is this “DataContext” you speak of?
If you bound it correctly to an ObservableCollection and currentMyItems is that collection. Than it means that you must have reloaded currentMyItems in meantime.
Also consider binding the SelectedItem property of your ListView - your view model doesn't have to know about the view at all.
Your source collection must be modufy (inherit from IList or ICollection). If your source collection does not support this method of your interface Remove, you can't remove item from source.
So, when you want to remove item you must cast ItemsSource to IList or ICollection:
var source = listbox.ItemsSource as IList ?? listbox.ItemsSource as ICollection;
and then check:
if (source == null) return;
then:
listbox.SelectedItems.ForEach(source.Remove);
listbox.Items.Refresh();
Make the currentMyItems<MyItem> an ObservableColection<MyItem>. This way it will raise a property change whenever modified and the UI gets updated accordingly.
By using ObservableCollection you will automatically get updates on the UI.
You should use an ObservableCollection instead of List.
A good thing is to always use ObservableCollection instead of List when something to do with UI

Binding List and UI controls, not updating on edit

I am binding a BindingList two way to a listbox. The Binding list contains a number of images which apparently only update the listbox if items are added or removed from the binding list. How can I make it so that the bindinglist also raises the listchanged event when an item is modified?
EDIT: I find the problem I am having is that a property of an object is not being changed, rather the base object.
BindingList<ImageSource>();
This wont work however if I did this:
BindingList<Image>();
And then set the binding path to Image.Source, it would update correctly and this is because a property of the Image has changed but in the case of the first example, only a direct item in the list has changed. So how may I get the same behaviour as the second example?
FINAL EDIT : It seems that using ObservableCollection instead of BindingList fixes this issue. I was under the impression that they were identical in notifying of changes in the collection. Full answer below
The list does raise that event but only if the underlying items provides the proper notifications via INotifyPropertyChanged.
The BindingList differs from ObservableCollection in that BindingList does not notify that its direct items are changed (except when items are added or removed from the collection). ObservableCollection however implements INotifyCollectionChanged and INotifyPropertyChanged interfaces. This means that any change to direct items of an ObservableCollection are reported to the UI.
If you are using bindings to direct items and need to update items and not properties of those items, it seems that you have to use ObservableCollection. Another solution would be to derive from BindingList and implement INotifyCollectionChanged.
I am not an expert but this is what i have gathered during the last hour, if anyone has anything to add or correct please let me know.

How do I keep the selection in ListBox when reloading ItemsSource

I am experimenting with WPF and MVVM in our system. However iam having a problem with keeping things selected in lists using only MVVM ( without doing extra CollectionViews ).
What i currently have is the list
ObservableCollection<ReservationCustomerList> Customers;
And then a property storing the selected Customer
ReservationCustomerList SelectedCustomer;
In my opinion now, when the list reloads (actually from another thread async), the selection should be able to be kept, however this does not happen.
Does someone have a nice clean way of achieving this ?
The way we did it was that we did not replace the collection. We added/removed the entries and updated existing entries if required. This maintains the selection.
You can use LINQ methods like Except to identify items that are new or removed.
In case the reloaded list still contains the last selected item and you want that item to be selected, then you can raise the PropertyChange event for the property SelectedCustomer after your collection gets reloaded.
Please make your sure your viewmodel class implements INotifyPropertyChanged interface.
you can use the ICollectionView to select the entity you want.
ICollectionview view = (ICollectionView)CollectionViewSource.GetDefaultView(this.Customers);
view.MoveCurrentTo(SelectedCustomer);
in your Xaml the itemsControl must have IsSynchronizedWithCurrentItem=true
or if the ItemsControl has a SelectedItem property you can simply bind it to your SelectedCustomer Property.
When you "reload" your collection you basically replace all values in it with new values. Even those that look and feel identical are in fact new items. So how do you want to reference the same item in the list when it is gone? You could certainly use a hack where you determine the item that was selected by its properties and reselect it (i.e. do a LINQ search through the list and return the ID of the matching item, then reselect it). But that would certainly not be using best practices.
You should really only update your collection, that is remove invalid entried and add new entries. If you have a view connected to your collection all the sorting and selecting and whatnot will be done automagically behind the scenes again.
Edit:
var tmp = this.listBox1.SelectedValue;
this._customers.Clear();
this._customers.Add(item1); this._customers.Add(item2);
this._customers.Add(item3); this._customers.Add(item4);
this.listBox1.SelectedValue = tmp;
in the method that does the reset/clear works for me. I.e. that is the code I put into the event handling method called when pressing the refresh button in my sample app. That way you dont even need to keep references to the customer objects as long as you make sure that whatever your ID is is consistent. Other things I have tried, like overwriting the collections ´ClearItems()´ method and overwriting ´Equals()´ and ´GetHashCode()´ didn't work - as I expected.

Should I bind to ICollectionView or ObservableCollection

Should one bind DataGrid to the
ICollectionView = CollectionViewSource.GetDefaultView(collection)
or to the
ObservableCollection<T> collection; ???
What is the best practice for MVVM and why?
You always bind to an ICollectionView, whether you make it explicit or not.
Assume that we have
var collection = new ObservableCollection<string>();
var collectionView = CollectionViewSource.GetDefaultView(collection);
In this case, binding to collection or to collectionView is one and the same: the binding engine will bind to the default collection view (which is reference equal to collectionView) if you tell it to bind to collection.
This means that the answer to your question is "it makes absolutely no difference".
Just to be totally clear: even if you bind to the collection directly, the binding engine will bind to the default view. Modifying properties of the view such as sort criteria will affect the binding that appears to bind directly to the collection, since behind the covers it's a binding to the default view instead.
However, there is another interesting and related question: should one bind to the default collection view (i.e., to the collection itself, because there's no reason to explicitly bind to the default view) or to another view of the same collection?
Considering that each view has its own notion of current item, sort criteria, etc, it follows that if you intend to have multiple bindings to the same collection, and the bound controls need to have distinct notions of current item, filters and company, then what you want is to explicitly bind to multiple views of the same underlying collection.
ObservableCollection<T> implements INotifyCollectionChanged and will notify the UI when the items in the collection have been changed.
ICollectionView will give you the ability to filter, sort, or group the collection in addition to propogating INotifyCollectionChanged events if the underlying collection implements it.
Either type works well with MVVM as long as you bind to it. Use ICollectionView when you need sorting, filtering, or grouping. Use ObservableCollection<T> directly when you don't.
Just to add to what Jon said. The main difference is, that by using CollectionViewSource.GetDefaultView(collection), you are making you ViewModel dependent on WPF. Many MVVM purists don't like this and this would leave ObservableCollection only valid option.
Other option would be to use ICollectionView and use a class, that implement it, but is not part of WPF itself.
I don't think so it has to do anything with MVVM itself. ICollectionView provides additional features like soring grouping and etc if you need those use IColectionView otherwise simply use ObservableCollection
You would bind to the view if you wish your grid to display the settings applied to the view, e.g. filtering, otherwise the view is redundant.

ObservableCollection of objects containing Lists

I am using an ObservableCollection for databinding as ItemsSource for DataGrid. Collection contains complex type objects. One of this type properties is a List of strings.
Just for now I see that when I update this List property from code nothing changes in the UI (the primary binding works fine). So, my question is: is it an expected behaviour? Maybe I should not use List as part of the type, but also use an ObservableCollection?
Update
Mode is set to OneWay.
Use a collection, instead of List, that implementes the interface INotifyCollectionChanged (like ObservableCollection). Then changes to the collection get populated to the ui.
Yes it is expected behaviour. The observable collection only notifies of changes to its own contents - that is add, delete, reorder.
What you are looking at is a change to an element in the observablecollection - if you want to see your changes to the class you put in, your element has to implement INotifyPropertyChanged.
So currently: If your list property on you complex object changes you won't see it, however if you change that too to be an observablecollection you could see changes to that collection in a sub-itemscontrol like a combobox - but not if you change the collection object to another one - so if you do not implement INotifyPropertyChanged you should set the collectionproperty before the binding is applied.
When you are updateding your list u have to call INotifyPropertyChange other wise UI wont get update the list result..
INotifyPropertyChange is the indication that here some changes occurred in the items source so update it.
This might help as well:
ObservableCollection that also monitors changes on the elements in collection

Categories

Resources