I am working on an MVVM-based WPF app that uses the DataGrid. The grid is data-bound to an ObservableCollection<Widget> (the Widget list). The user needs to be able to insert a Widget into the list below the currently-selected Widget, to delete a selected Widget, and to reorder the Widgets on the grid. I'll implement the reordering through dragging and dropping.
Before I dig into this, I want to get a reality check on my approach. Here is what I am planning to do: The Widget class will have an Index property that will be used to order the Widget list. Additions, deletions, and reordering will be done by manipulating this Index property, and sorting will be done on the Widget list, rather than through a CollectionView.
Before the DataContext is set, the Widget list will be sorted on the Index property. When a user adds a Widget, the Index property will be incremented by 1 for each Widget below the selected item on the grid, and the new Widget will be given the index number opened up by the renumbering. The Widget list will resorted, and the bindings will be refreshed.
When a user deletes a Widget, the Widget will be removed from the list, and the Index property of items on the grid below the deleted item will be decremented by 1. The Widget list will be resorted, and the bindings refreshed, as above. Reordering will be done as a combination delete-and-insert.
Here is my question: Is this a reasonable strategy for implementing inserting, deleting, and reordering? Is there a better way to go about it? Any good articles or blog posts on the subject? I've looked, but I'm not coming up with anything on-point.
Thanks for your help.
I've got this problem figured out. I do need the Index property, but I don't need to sort the ObservableCollection. I create an interface, IIndexedObject, with one property, Index. I implement that interface on any object that needs to be indexed, and in my database table, I add an Index column.
When I load into my object model, I sort the database table on the Index property. That ensures that the list is loaded in the same order as in the last run. I add objects using the blank row provided at the bottom of the DataGrid. It gives them an index of 0. I delete objects using a Delete button bound to an ICommand in the view model.
My view model subscribes to the CollectionChanged property of any ObservableCollection that contains IIndexedObjects. When the event fires, the view model passes the collection to a ReIndexCollection service method, which takes an IIndexedObject. The method re-indexes the collection, based on its current order, by simply iterating the collection, assigning an incremental integer value to each element's Index property. When I persist the collection back to the database, the Index value gets saved, to ensure that the collection is loaded in the same order on the next run.
Since the only sorting that is needed is done at the database load, there is no need to sort the ObservableCollection. When I move items on the DataGrid, it will take care of re-ordering the collection at that time. All I have to do is re-index the collection, so that its order will be persisted back to the database.
All-in-all, it's much easier than I had expected. One of the reasons I like WPF and MVVM.
Improving sorting performance:
http://blogs.msdn.com/jgoldb/archive/2008/10/30/improving-microsoft-datagrid-sorting-performance-part-3.aspx
Related
I have a listview that contains a list of 'rows', which in term, contains a list of 'columns'. For now, I use List for both rows and columns (i.e. I have a List and it the Rows objet, I have a List, but as the number gets bigger, the time it takes to update the GUI and overall performance of the program gets pretty bad. Plus, the GUI isn't notified when the items (columns within the rows) change, so I have to do an Item.Refresh, which seems to refresh the whole thing each time, adding to the slow performance
Is there any other type of object that I could use to accelerate the whole thing. Meaning it would need to satisfied the following points :
- Faster that List
- When I add a new rows, the listview is updated. Is possible, only the new rows is added instead of the whole listview.
- When I change a Columns object, the GUI is updated. If possible, only the modified columns objet is affected so the whole Listview is not changed.
Note that there are quite a number of actual calculations behind the scene for displaying the columns object. I have currently working on optimizing this end, but I'm pretty sure using a better collection would help quite a lot here.
Thanks
I believe what you are looking for is an Observable Collection
This collection type will fire a CollectionChanged event when items are added. You will not need to refresh the collection because WPF controls support this out of the box.
Note
The CollectionChanged event will not be fired when indiviual elements are updated. If you want these updates to show on the UI, the object within the collection should implement INotifyPropertyChanged
My app has a background thread that periodically retrieves data from an external source, in the form of key/value pairs. I would like to expose this data for binding, presumably by storing them in some kind of static(?) model, as the data will be needed by numerous views throughout my app. There are potentially hundreds of these keys, and may be different for each customer, so I can't simply create an INotifyPropertyChanged model with a property for each value.
The app has multiple views visible at any one time, and each of these will have numerous controls (usually textboxes) that I want to bind to individual items in the above collection. When a value in the collection is updated, any controls bound to only that item should change to reflect the new value. I'm assuming an ObservableCollection wouldn't be suitable here, as a change to a single item will result in all controls updating, regardless of which item they are bound to?
To throw a further complexity into the mix, some values (which are numeric) will need formatting for display, e.g. number of decimal places, or adding a suffix such as "volts". The formatting rules are user-defined so I can't hardcode them into (say) the XAML binding's StringFormat expression. Ideally I should be able to access both the raw value (e.g. for calculations), and the formatted version (for display purposes). I'm sure it must be possible to achieve the latter using some clever WPF feature!
I would appreciate any pointers on how I can solve these requirements.
Edit: it's worth mentioning that I've previously tried implementing the model as some kind of collection. The problem is that it won't be initially populated with all values, and these only get added some time later. When they do eventually get added, a bound control doesn't update - presumably because it wasn't initially able to bind to the missing value.
I would take a different approach, namely a variation of Event Aggregation. I would have a single class that manages the overall collection (probably a singleton class like franssu suggested), but instead of binding directly to the collection in that class you create smaller models that are more specific to the individual views.
When your main model receives a new item, it publishes an event, which is consumed by the smaller models who can inspect the new item and determine whether or not they should add that item to their internal collection (the one the individual views are bound to). If it doesn't "belong" to their view, they can simply ignore the event.
You could use similar event publishing for updates to items and such, although if you're binding to the actual items you probably don't need that.
Just implement the INotifyCollectionChanged Interface and the INotifyPropertyChanged and you ll get a Collection like the ObservableCollection.
But rember if you select a Item from your Collection (as example a ObservableCollection) and you change that item your other controls won t update. So if you have a class Person in your Collection and you change the name of one person the other controls won t get the new name of the person.
Inside the Person object you still have to implement the INotifyPropertyChanged Interface and raise the event when your name changes.
So what I want to tell you is: A Collection with the interface INotifyCollectionChanged will only tell the bound controls: There is a new Item, there has been a item removed or a items index changed BUT not if the item itself changes.
So you ll need a Collection that provides the points above and a Item contained by the collection that raises events if a property of it changes.
ObservableCollection is perfect here. You should find that a standard ItemsControl bound to an ObservableCollection will only update the controls of the items that have changed, not every item in the collection.
This is the reason ObservableCollection exists - the events that it raises specifically identify items that have changed, so that the UI can handle them sensibly.
I've tested this locally with a small WPF app and it works fine. Worth noting, though, that a virtualised items panel would probbaly appear to break this behaviour when it scrolls...
EDIT: rereading your question, you actually say "When a value in the collection is updated..." If your collection contains instances of a class, and you update properties on the class, you don't even need ObservableCollection for this to work - you just need the class to implement INotifyPropertyChanged.
I have a custom panel control that is intended to be used as an itemspaneltemplate in a items control.
The itemscontrol will be databound to a data source.
This datasource is a List, and each item in the list is a custom business object.
In the application, the user is able to update each of these business objects in the list, and that fires the notification on property changed as expected.
Now my problem is here:
When the user updates the object's properties in the data source (the itms in the List) that the items control is bound to, my custom panel control is not able to get that notification, so as a result the items control does not get updated with the updated items in its view.
I tried using an ObservableCollection instead of List - the problem is still the same.
I must be missing something fundamental here... please help with any pointers, answers or solution.
Change notification in a collection is a bit tricky. Say you have a collection of Products. you can implement change notification is three different places.
Change notification in the Product class (implementing INotifyPropertyChanged in class Product)
Change notification in the collection itself (i.e. using ObservableCollection)
Change notification in the class that holds the collection, that is, implementing INotifyPropertyChanged on the class that contains the collection. (usually this would be the ViewModel under MVVM)
Those tree ways are not the same, and each is for a different situation.
Let's say that the collection is ObservableCollection<Product> Products {get;set;}
if you want changes in the product to register (i.e., if you're doing something like Products[0].Name = "New Product"; then #1 is the right one.
If you want to do Products.Add(new Product(...)) then #2 is the right one.
If you want to do Products = new ObservableCollection<Product>() then #3 is the correct one. This is especially tricky since i'm not changing the collection, but creating a new one, so the ObservableCollection won't help - I'd need to implement INPC in the containing class.
I appologize for the novel but I wanted to explain as much as I have done thus far.
Within my current project I have an application that consumes a service that provides a collection as a <List>. Due to how I am using this data in the application I have had to convert this data to an observable collection. This was done so that as the data was selected and moved about the application UI updates would be refreshed using INotifyPropertyChanged and INotifyCollectionChanged.
Where I am having a challenge now is I have a listbox that is bound to the observable collection within the listbox I have a datatemplate that renders out the items of the collection. This data template contains a button which needs to allow the user to click the button for each item to remove them from the collection.
The use case for this is a listbox that stores selected name as chosen from a gridview. Once the user has selected names from the gridview they are stored ( within the observable collection as a queue) and rendered out in the UI in a listbox control which shows all selected names. I need to provide the user with the ability to remove these names in any order selected.
From what I have been reading there is no means to enumerate / index an observable collection. For situations such as this you should use List or an Array. However in order for the items to refresh in the list view they need to be in an Observable Collection.
From what I have read it appears that when the event is triggered I need to convert the observable collection to an Array and then evaluate the array to determine the index and then remove the record accordingly?
I think I may be off base on this as it seems like I am over engineering this problem? The above scenario does not seem correct is because I fell as if I am doing a lot of converting to and from the collections to just remove a record?
Does anyone know of an efficient means to remove records from a collection ( in any order selected) when the collection is rendered out as an items control within a listbox?
I’ve been successful in removing the last record added to the collection using RemoveAt() however I have not had any success in randomly removing records.
Afterthought: Part of this issue could be related to the fact that I have a button inserted within the datatemplate (control item) and as a result the item is not actually being selected before the event is fired on the button event?
Sorry for the rambling on this but I have had my head in this for hours and made minor progress. Any tips or ideas would be appreciated!
ObservableCollection<T> inherits from Collection<T> which implements IList<T>, so you can certainly index and enumerate it. It has a Remove method that takes the object to remove and removes the first occurrence in the collection and a RemoveAt method that takes an index and removes the item at that index.
Based on your afterthought, it sounds like you have a WPF ListBox with an ItemTemplate that creates a Button. ListBox will set the DataContext of each instantiated template to the item in the list being bound to, so you can get a reference to the item that created a Button from the DataContext property on the Button or by using a Binding.
I am creating a custom DataGrid by deriving the traditional tookit based WPF DataGrid. I want a functionality in the grid to load items one by one asynchronously, wherein as soon as ItemsSource is changed i.e. a new collection is Set to the ItemsSource property or the bound collection is Changed dues to items that rae added, moved or removed (wherein the notifications comes to the data grid when the underlying source implements INotifyCollectionChanged such as ObservableCollection).
This is because even with virtualising stackpanel underneath the datagrid takes time to load (2-3 seconds delay) to load the data rows when it has several columns and some are template based. With above behavior that delay would "appear" to have reduced giving datagrid a feel that it has the data and is responsive enough to load it.
How can I achieve it?
Thx
Vinit.
Sounds like you are looking for data virtualization', which typically means creating your own custom type that resembles IList, and doing a lot of work to hydrate objects after-the-fact.
You will end up having your data that the grid is displaying look something like this:
Index 0: new MyDataObject(0);
Index 1: new MyDataObject(1);
And MyDataObject implements INotifyPropertyChanged.
In the constructor, you do the logic necessary to time, schedule, or interpret when the real results should be read. Until then, you return rather empty data... null and string.Empty from your properties.
Then, once the data becomes available (ideally in a background thread, read from wherever - your own local data, or a database or web service), then you can update the real underlying property values and fire the property change notifications so that the UI gets properly loaded then.
It's a little too complex to just jump into, so some searching will help. Hope this gets you started.