In my Windows Phone app I used CollectionViewSource.SortDescriptors to sort my LongListSelectors content. Now I migrated to Windows Runtime app and am using ListView to display my content. (And WinRT doesn't have SortDescriptors.)
Using OrderBy<>() on my ObserveableCollection is not an option, since I add the items dynamically (and this would cause a complete reload of the ListView).
How do I "binary insert" on ObservableCollection (similar to what's possible with List<>) or is there any alternative to CollectionViewSource.
You could use something like this A winRT CollectionView with filtering and sorting
Alternatively you could create a class and override ObservableCollection.InsertItem like this.
public class SortedCollection<T> : ObservableCollection<T> where T : IComparable<T>
{
protected override void InsertItem(int index, T item)
{
int sortedIndex = FindSortedIndex(item);
base.InsertItem(sortedIndex, item);
}
private int FindSortedIndex(T item)
{
//simple sorting algorithm
for (int index = 0; index < this.Count; index++)
{
if (item.CompareTo(this[index]) > 0)
{
return index;
}
}
return 0;
}
}
To use this class create a new collection and add the items.
SortedCollection<int> sortedCollection = new SortedCollection<int>();
sortedCollection.Add(3);
sortedCollection.Add(1);
sortedCollection.Add(5);
sortedCollection.Add(4);
sortedCollection.Add(2);
//the sorted collection will be 1,2,3,4,5
Related
Im designing an UWP movie app and i need several ObservableCollections to store different movie lists. I wanna use ObservableCollection[Index], but dont know how to initialize it.
I've tried the following code but failed:
public static ObservableCollection<Subject>[] FullMovieList = new ObservableCollection<Subject>[6];
How can i accomplish it? Thx~
You create array of ObservableCollection<Subject> correctly.
But you create array only, not collection in array itself.
You should create collections also;
for (int a = 0; a < FullMovieList.Length; a++)
{
FullMovieList[a] = new ObservableCollection<Subject>();
}
Building a new collection each time you want to add range of objects is not good rather i would follow below design.
Build a class inheriting from ObservableCollection , so that you can access Items property which is protected and then create a AddRange method which will add items into it
public class MyObObservableCollection<T> : ObservableCollection<T>
{
public void AddRange(IEnumerable<T> items)
{
foreach (var item in items)
{
this.Items.Add(item);
}
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}
Following this Topics Xceed WPF propertyGrid show item for expanded collection, how is it possible with XAML to sort the list by Name when Binding to Classes ?
Everytime I would like to add a new Item it should be immediately sorted. It doesn't work because if I use Classes.orderby(x=>x.name) it breaks all the bindings?
Hello After lot's of time the solution is at the same time and not, but here I gave you all the solution:
1) the getters and setter for your Observablecollection
private TheCollection yourcollection;
public TheCollection Yourcollection{
get{
yourcollection.CollectionChanged -= Your_CollectionChanged;
// use sort-Extension to sort pointprofil
yourcollection.Sort();
// read CollectionChange-Event
yourcollection.CollectionChanged += Your_CollectionChanged;
return yourCollection;
}
}
And your Collection have to inherit from ObservableCollection but at the Same time to get an Extension to sort()
here we go:
static class Extensions
public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
{
List<T> sorted = collection.OrderBy(x => x).ToList();
for (int i = 0; i < sorted.Count(); i++)
collection.Move(collection.IndexOf(sorted[i]), i);
}
}
public class YourCollections : ObservableCollection<YourPoints>, ICustomTypeDescriptor, INotifyPropertyChanged
{
}
and YourPoints have to inherit from IComparable
I know that you can set up a ListBox to sort automatically. Is there a way to "catch" the sorting so that when the ListBox swaps the position of two items so that I can do the same reordering on another list box? I want sort one list box by by value but keep those values at the same relative index locations compared to another ListBox somewhere else.
I could write a routine to bubble sort the list so that I could make the changes myself, but it I am wondering if there is a more automated since I will likely have to do this at a few different places in the program.
Unfortunately, the Sorted property does not use the IComparable interface implementation just sorts based on the result of ToString of the items. But instead of setting the Sorted property, you can use a sorted data source (a List<>, for example).
Create a wrapper class for the items in the ListBox and implement the IComparable<T> interface on it. Populate a List<> with these ListBoxItem instances, then call the Sort method on the list. Thus you will able to dispatch the CompareTo calls.
public partial class Form1 : Form
{
private class ListBoxItem<T> : IComparable<ListBoxItem<T>>
where T : IComparable<T>
{
private T item;
internal ListBoxItem(T item)
{
this.item = item;
}
// this makes possible to cast a string to a ListBoxItem<string>, for example
public static implicit operator ListBoxItem<T>(T item)
{
return new ListBoxItem<T>(item);
}
public override string ToString()
{
return item.ToString();
}
public int CompareTo(ListBoxItem<T> other)
{
return item.CompareTo(other.item); // here you can catch the comparison
}
}
public Form1()
{
InitializeComponent();
var items = new List<ListBoxItem<string>> { "Banana", "Apple"};
items.Sort();
listBox1.DataSource = items;
}
I am using WPF and C#
I have a button that opens a window, which also includes a button that adds an User object item to a listbox and I want the listbox index to be updated after insertion. I understood that the solution is about using observable class of INotifyCollectionChanged but actually I dont know how and where to use them. Can you help me on determining what and where to implement, register, fire etc.
Edit:
I succeeded this by Quartermeister's help where my User object is collected in a list, but now I want to do the same thing where my object is collected in a dictionary
The easiest way is to use System.Collections.ObjectModel.ObservableCollection<T> as your list. This implements INotifyCollectionChanged and INotifyPropertyChanged for you.
You would have a property on your DataContext object of this type, and use data binding on ListBox.ItemsSource to bind it to that property. The ListBox will automatically update its list of elements when the collection changes.
In the class that is your DataContext:
public class MyClass
{
public ObservableCollection<string> Items { get; set; }
}
In Xaml:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
It sounds like you also want an observable dictionary, but unfortunately there is not one built into the framework. You could try using Dr. Wpf's implementation of ObservableDictionary from his post "Can I bind my ItemsControl to a dictionary?".
Implementing an observable dictionary is hard. It's much simpler to maintain an parallel observable collection, one that contains the dictionary's values. Bind the view to that collection, and make sure that any code which adds or removes values to/from the dictionary updates the both the dictionary and the parallel collection.
If you really wanted to go crazy, you could implement a subclass of ObservableCollection to hold your objects, and make that class maintain the dictionary, e.g.:
public class KeyedObject
{
public string Key { get; set; }
public object Value { get; set; }
}
public class ObservableMappedCollection : ObservableCollection<KeyedObject>
{
private Dictionary<string, KeyedObject> _Map;
public ObservableMappedCollection(Dictionary<string, KeyedObject> map)
{
_Map = map;
}
protected override void InsertItem(int index, KeyedObject item)
{
base.InsertItem(index, item);
_Map[item.Key] = item;
}
protected override void RemoveItem(int index)
{
KeyedObject item = base[index];
base.RemoveItem(index);
_Map.Remove(item.Key);
}
protected override void ClearItems()
{
base.ClearItems();
_Map.Clear();
}
protected override void SetItem(int index, KeyedObject item)
{
KeyedObject oldItem = base[index];
_Map.Remove(oldItem.Key);
base.SetItem(index, item);
_Map[item.Key] = item;
}
}
There are a bunch of potential problems in the above, mostly having to do with duplicate key values. For instance, what should SetItem do if you're adding an object whose key is already in the map? The answer really depends on your application. Issues like that also hint at why there isn't an observable dictionary class in the framework.
I have a very simple database for which I'm using linq to sql.
I have a datagridview to show the contents of the table. I want the user to be able to filter the rows appearing in the datagridview, if possible without making another query to the database (I'm really low on resources, so the solution has to be as fast as possible).
I thought about using the Filter property of BindingSource class, so I created one, set the DataSource property to the linq to sql expression. When the user added a filter, I set the Filter property. After like half an hour I found out, that BindingSource does not support filtering. Hell, great; but then what does? After spending another half an hour using Google and finding basically nothing usable, I deceided to use a System.Collections.Generic.List to store the rows, because I'm able to filter that. That was all right, but I also needed to store the original List (in case the user removes a filter) and I also need to support multiple filters.
So I had two Lists: one with all the rows the query resulted and one with the rows that met the filter's conditions. I didn't test it with multiple filters, though.
That worked, although it wasn't a really nice solution (at least I didn't find it appealing), but that was all I'd got. I deceided to write a wrapper class, because I may need to re-use this solution anytime later. I thought about creating a FilteredList class (after I made some searches with Google and didn't find any existing implementations), based on the following theory:
I store a List with all the rows in the table,
I store the filters (which are Predictate expressions) in a BindingList (so I can know if the list changed and re-filter the rows),
I store the filtered rows in a List, serving as a cache when there are no modifications made on the source list or the filters,
I keep a boolean value (_NeedsRefiltering) meaning whether or not the existing filters have to applied on the source rows to regenerate the cache,
The class has to implement the IList interface, so it can serve as a DataSource for the DataGridView.
Here comes the source code of my FilteredList class:
public class FilteredList<T> : IList<T>
{
private bool _NeedsReFiltering = false;
private BindingList<Predicate<T>> _Filters;
public BindingList<Predicate<T>> Filters
{
get
{
if (this._Filters == null)
{
this._Filters = new BindingList<Predicate<T>>();
this._Filters.RaiseListChangedEvents = true;
this._Filters.ListChanged += delegate(object sender, ListChangedEventArgs e)
{
this._NeedsReFiltering = true;
};
}
return this._Filters;
}
set
{
this._Filters = value;
this._NeedsReFiltering = true;
}
}
private List<T> _Source;
public List<T> Source
{
get
{
return this._Source;
}
set
{
this._Source = value;
this._NeedsReFiltering = true;
}
}
private List<T> __FilteredSource = new List<T>();
private List<T> _FilteredSource
{
get
{
if (this._NeedsReFiltering)
{
this._NeedsReFiltering = false;
this.Refilter();
}
return this.__FilteredSource;
}
set
{
this.__FilteredSource = value;
}
}
public List<T> FilteredSource // Only for setting it as the DataGridView's DataSource - see my comments after the code
{
get
{
return this._FilteredSource;
}
}
public FilteredList()
{
this._Source = new List<T>();
}
public FilteredList(int capacity)
{
this._Source = new List<T>(capacity);
}
public FilteredList(IEnumerable<T> source)
{
this._Source = new List<T>(source);
this._NeedsReFiltering = true;
}
public void Refilter()
{
this.__FilteredSource = this._Source;
if (this._Filters == null)
{
return;
}
foreach (var filter in this._Filters)
{
this.__FilteredSource.RemoveAll(item => !filter(item));
}
}
public int IndexOf(T item)
{
return this._FilteredSource.IndexOf(item);
}
public void Insert(int index, T item)
{
this._FilteredSource.Insert(index, item);
this._Source.Add(item);
}
public void RemoveAt(int index)
{
//this._Source.RemoveAt(index);
this._Source.Remove(this.__FilteredSource[index]);
this._NeedsReFiltering = true;
}
public T this[int index]
{
get
{
return this._FilteredSource[index];
}
set
{
this._Source[this._Source.FindIndex(item => item.Equals(this._FilteredSource[index]))] = value;
this._NeedsReFiltering = true;
}
}
public void Add(T item)
{
this._Source.Add(item);
this._NeedsReFiltering = true;
}
public void Clear()
{
this._Source.Clear();
this._FilteredSource.Clear();
this._NeedsReFiltering = false;
}
public bool Contains(T item)
{
return this._FilteredSource.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
this._FilteredSource.CopyTo(array, arrayIndex);
}
public int Count
{
get { return this._FilteredSource.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
var r = this._Source.Remove(item);
this._FilteredSource.Remove(item);
return r;
}
public IEnumerator<T> GetEnumerator()
{
return this._FilteredSource.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._FilteredSource.GetEnumerator();
}
}
I had some problems because of the two lists (the source list and the filtered list), but I think I've handled them correctly. Or maybe I haven't, because DataGridView doesn't seem to accept it as DataSource: no exception thrown, simply, nothing appears (not an empty datagridview appears, but nothing at all - not the columns, nor an empty row to add more items). Well, well, that's weird. I tried setting the _FilteredSource directly as a DataSource, and it was fine - until I added a filter and tried to scroll down when I get the error: System.IndexOutOfRangeException: Index 180 does not have a value.
Screenshot:
alt text http://shadow.crysis.hu/dgv_error.png
To be honest, I have no idea what's wrong. I have tried to call the DataGridView's Invalidate, Update and Refresh methods - same results.
So...
How could I efficiently filter the results appearing in the DataGridView using linq to sql?
Why can't I use my FilteredList as a DataSource for the DataGridView?
What's the problem with the code above?
Thank you very much for your time (if you read all this) and help (in advance)!
So, I tried to follow what Marc Gravell advised, and implemented the System.Collections.IList interface instead of the generic one. It worked, so I could bind it to the DataSource property of the DataGridView, and it displayed all rows, but as I added a filter and began to scroll down (for some reason, the list isn't refreshed until I start to scroll - Invalidate(), Refresh() and Update() doesn't help it) it started to give those weird IndexOutOfRangeException-s as DataError-s.
Any ideas how to do this stuff? I can't believe that linq to sql with datagridview sucks so hard (sorry, but this is getting ridicolous)...
To work with DataGridView, you need to implement the non-generic IList, not the generic IList<T> (or simpler and better: inherit from BindingList<T>, which provides things like change notifications via INotifyPropertyChanged). For working with LINQ-to-SQL I have some info on usenet that might be useful (assuming it still holds water - it has been a while).
re "rest of the problem"... can you be more specific?
Re filtering LINQ-to-SQL efficiently, you don't want to use Predicate<T>; you want to use Expression<Func<T,bool>>; this allows you to pass this down to the database via Queryable.Where, i.e. (where you have an IQueryable<T> source) something like:
IQueryable<T> data = tableSource;
// then for each filter "expr"
{
data = data.Where(expr);
}
Writing a true filtered list is very tricky. I've done it for in-memory objects (I can't post the code, though) - but it takes a lot of object tracking etc. Unless you absolutely need this, it may be easier to keep things simple and just display simple snapsnots, tracking just additions/removals. For simple snapshots, just ToBindingList() may suffice...