I'm working with ListCollectionView objects to display lists of items. Currently, I'm building a screen that has two of these lists - one, completeList, holds all available items, the other one, sortedList, holds a subset of them with the item order being relevant. Possible actions here are
add or remove any of completeList's items to/from sortedList
move items up or down in sortedList
save the content of sortedList.
Now, my question is this: is there any possibility to insert items into sortedList at a defined position?
Right now, they will always be added at the end of the list, regardless of the currently marked item, and I didn't find a way around it - apart from the obvious dirty hack, which would have me store all items after the desired position, remove them from the list, add the new item, then re-add all stored items in the correct order.
Does ListCollectionView offer any such functionality, or is there another CollectionView class that would do the trick?
You're actually asking for something that's a logical contradiction. Suppose I have a ListCollectionViewsorted alphabetically:
American
Continental
Festival
Imperial
Tower
Worldwide
Should I be able to insert Luxor between American and Continental? Not if the view is sorted. There's only one place that item can go. And where it appears in the view is independent of where it might appear in the underlying list.
Without knowing more about your application, it's hard to know exactly what to suggest. But if a collection view is sorted, the way to make an item appear at a specific place in the view is to assign its sort key(s) a value that will, once the view is refreshed, cause it to appear in the desired location.
A fairly trivial (and generic) way of doing this is to add a DateTime property to the data item class, set it to DateTime.Now in the item's constructor, and make it the last sort key that the view uses. Then, when adding a new item, set its other sort key properties to the value of the currently selected item. If you do this, new items will always appear in the appropriate place, so long as you don't change the values of any of the sort key properties.
Having said that, from the other features you want to support, I believe that you shouldn't be using a ListCollectionView at all for what you're calling sortedList. This list isn't sorted. It's ordered, which is not at all the same thing. When the user moves an item up in the list, you actually want to change its position.
What you probably want to do is implement the list as an ObservableCollection, and wrap it in a view model class that exposes Items, SelectedItem, AddNewCommand, MoveUpCommand, MoveDownCommand, and SaveCommand properties. Then you can bind the ItemsSource and SelectedItem properties of a ListBox or ListView to Items and SelectedItem properties in the class, and bind buttons or hyperlinks or whatever in the UI to the commands. The commands will manipulate the Items property, using Remove and Insert, and since the Items is an ObservableCollection, the UI will stay in sync.
Related
I have a collection of objects "SourceItemCollection" used to populate the ListBox with checkboxes. Each item of the collection consists of two fields, Item and IsChecked (I've created a small class for this combined items). I would like to track all changes when user selects or deselects something from the collection (not when the button is pressed or something else, but "on-sight"). For this I would like to use another collection "SelectedItems" which will consist only of Items, without IsChecked property (I create and would like to use this collection outside the abovementioned small class of the source collection's items).
The tricky thing is that "SourceItemCollection" doesn't change itself, it always stays the same, changes only the IsChecked property of each item. I do get a notification each time I tick or untick something, but I get it inside the small class of my combined items and I can't access my SelectedItems collection from there.
This seems to be a problem that I encounter regularly: I have a list control, in this case a DataGrid, and the items in the control come from a web service. My application regularly asks the web service for the latest list of items. Compared with what my application currently has, the resulting list may have additional items, fewer items, or different details for existing items.
How do I update my control (ie: my data grid) without:
1) De-selecting the user's currently selected items.
2) Resetting the ordering that the user may have set on various columns.
3) Doing anything else that makes it jarring for the user.
4) Incorporating too much code, turning it into an unmaintainable mess.
Am I simply going about this whole thing wrong? It seems to me this scenario should be simple to address with something as versatile as WPF.
My current thinking is to use INotifyPropertyChanged on each item, and an ObservableCollection. Then, for each item in the list, update every property when we do the refresh (adding and removing items from the collection as necessary).
You are right, use ObservableCollection
1) De-selecting the user's currently selected items.
You can use either CollectionViewSource.GetDefaultView(rootElem.DataContext).Current or create a SelectedItem property, and bind it to the UI element. I prefer SelectedItem because it is less view related. For deselecting just set it to null.
2) Resetting the ordering that the user may have set on various columns.
You can easily swap two elements in ObservableCollection, and that change immediately will appear in the view.
var tmp = myObsCollection[i];
myObsCollection[i] = myObsCollection[j];
myObsCollection[j] = tmp;
Also you can use keep order property in your items, configure sorting in CollectionView, and just rearrange orders when needed and call view.Refresh();
3) You don't need to update all properties for updated items, just find the correct item index and replace it with new one, WPF will update automatically
myObsCollection[fIndex] = updatedItem;
Use the same logic for delete/add elements
I've got C#\WPF app that simply displays a list of variable names and their values in a listbox control. When the user hits a Refresh button, the list is recreated with the new values for the variables. Some of the variables may have changed, while some have stayed the same.
I would like for the values that have changed since the last refresh to be at the top of the listbox, but can't think of an elegant way to go about this. Any ideas or examples would be appreciated.
Thank you!
It would be best if gave your list items (models) some additional functionality that is intended to simply service the view, viewmodel-style -- and of course you can wrap your models inside viewmodels if you want to keep them pure.
For example, you can give your models a Changed property that you can set (or, the Value property setter can change it automatically whenever the value changes). This property can then be used to sort the items in the ListBox by sorting the collection view that the box is bound to:
<CollectionViewSource x:Key="SortedModels" Source="{Binding ListOfModels}">
<CollectionViewSource.SortDescriptions>
<SortDescription PropertyName="Changed"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<ListBox ItemsSource="{Binding Source={StaticResource SortedModels}}"/>
Assuming that your models implement INotifyPropertyChanged and ListOfModels is an ObservableCollection, that's all it will take to have sorting done automatically.
Use something like:
var olditems = new Hashset<string>(listbox.items); // or List<T>
List<string> newitems = getNewItems();
newitems = newitems.RemoveAll(x => olditems.Contains(x));
listbox.Items.Clear();
listbox.Items.Add(newitems);
listbox.Items.Add(olditems);
I have not tested the code, bit the idea should become clear.
See also: Using LINQ to remove elements from a List<T>
There are probably more options:
Keep the state of current list. Load new list. Use linq Except to see differences. Create new list with the result from Except on the top and the rest order below.
Extend your item with some Timestamp, that will mark, when the item was created. Choose an interval, how old items should be on top. Read new values, and those that satisfies the condition (the newest one in the interval) put on the top. Or you can just sort them by that TimeStamp.
Instead of having a list of objects that are name/value pairs, think of it as a list of objects that have Name / Value / IsChanged properties. When you update the list of objects, you can set IsChanged appropriately, and just use IsChanged as part of your sort.
In particular, don't think of this as sorting a list of strings. You've got a list of variables, which have properties (name, value, isChanged). Your listbox is binding to a CollectionView (either the one that Listbox.Items gives you, or something you got from CollectionViewSource). That CollectionView has SortDescriptions on it, which you can set so as to sort by IsChanged first, then by Name.
I like Jeff's Idea but it requires extra logic to reset the IsChanged value every time the user updates the list. Try adding a time stamp to your name/ value pair and updating the time stamp every time the value is updated. Then just add the time stamp to your sort.
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.
Let me start off by saying that I am completely new with WPF (this is my first project and I have been working in it for less than a week). With that being said, please be easy on me!
I have three list ListBoxes that are being bound to ObservableCollections from a LINQ queries. In the beginning, everything is fine, all three are populated correctly. My client needs to drag and drop selections from one ListBox to another. I also have this working, but when I do the drag and drop, the new selection is placed at the bottom of the ListBox instead of being sorted alphabetically with the existing items.
How can I sort the ListBox at runtime through code behind after the drag and drop operation is complete.
Thanks!
It is not entirely clear how you handle drag and drop in your code. You say that your ListBoxes are all data-bound - which implies that you actually move items from one backing collection to another on drag and drop. If so, ListBox just displays the items in order they are present in the collection. You should either sort them there, or, if sorting is a view-only behavior in your case (i.e. items are actually unordered in data model, by design), you should use CollectionView to wrap your collections, set it up to do the sorting, and bind the ListBoxes to that.