My UI has a ListBox which is bound to a Collection. Right now this happens to be an ObservableCollection
My objective is to add objects to this Collection via the UI, and have the ListBox dynamically update, all while maintaining a sorted Collection.
I am aware that there is some SortedView that I can use in WPF. But that is not what I want - I need the actual Collection to remain sorted because my business logic requires a sorted collection.
One way that I thought of, is to create my own Collection class which uses a SortedList internally, and implements the INotifyCollectionChanged interface and produces NotifyCollectionChangedEventArgs event when the internal list changes. Sounds like a lot of work!
Is there a simple solution that I've missed?
Depending on your exact needs, the simplest approach is to keep your ObservableCollection, but wrap in in a new property of type ICollectionView:
public class MyViewModel {
private CollectionViewSource _collectionViewSource;
public ICollectionView MyCollectionView => _collectionViewSource.View;
public MyViewModel(ObservableCollection<MyDataItem> dataItems) {
_collectionViewSource = new CollectionViewSource() { Items = dataItems };
//Add sorting here using _collectionViewSource.SortDescriptions.Add(...)
}
You can use the wrapper property to extract a sorted list as needed.
Okay so I ended up inheriting from ObservableCollection, and overriding the Add() method.
This did the trick for me. Now my list is always sorted, and the ObservableCollection is the one that Notifies the UI of changes.
public class MyCollection : ObservableCollection<Int32>
{
public new void Add(Int32 x)
{
base.Add(x);
var oldList = new ObservableCollection<Int32>(this.OrderBy(c=>c));
Clear();
foreach(var i in oldList)
{
base.Add(i);
}
}
}
I'm a beginner with C#, any feedback on the code is appreciated.
Related
For my WPF application, I need CollectionViewSource to enable selection, filtering, sorting, and grouping in a collection. But CollectionViewSource is not a type safe collection like IList, the property View.CurrentItem is an object for example. We need to cast the items if we use them.
Are there any CollectionViewSource alternatives that support Generic?
Or maybe anybody know the reason why CollectionViewSource is not a generic?
=============================
I made a generic CollectionViewSource based on standard CollectionViewSource.
Any comment whether it is a better alternative for collection class that is instantiated outside XAML? Or there is another better alternative?
Edit 1: Add Generic CollectionViewSource
namespace Data
{
using System.Collections.Generic;
using System.Linq;
using System.Windows.Data;
public class CollectionViewSource<T> : CollectionViewSource
{
public T CurrentItem => this.View != null ? (T)this.View.CurrentItem : default(T);
public new IEnumerable<T> Source
{
get
{
return (IEnumerable<T>)base.Source;
}
set
{
base.Source = value;
}
}
public IEnumerable<T> ViewItems => this.View != null ? Enumerable.Cast<T>(this.View) : (IEnumerable<T>)null;
public CollectionViewSource(IEnumerable<T> source)
{
this.Source = source;
}
public bool Contains(T item)
{
return this.View != null && this.View.Contains(item);
}
public IEnumerable<T> Groups()
{
return this.View.Groups.Cast<T>();
}
public void MoveCurrentTo(T item)
{
this.View?.MoveCurrentTo(item);
}
}
}
You can actually just bind to your ObservableCollection (or any collection) and then call CollectionViewSource.GetDefaultView for that collection instance, then apply a filter, and your DataGrid (or other items controls) will get filtered. This way you can have your cake and eat it too :-)
The reason for this, I suspect, is because WPF list controls never actually bind to normal .NET collections, but always call CollectionViewSource.GetDefaultView behind the scenes, and that seems to return the same instance as the one you already created, if you created one.
Codebehind:
MySourceCollection = new[]
{
new ViewModel(1, "first"),
new ViewModel(2, "second"),
new ViewModel(3, "third"),
new ViewModel(4, "fourth")
};
MyListView = CollectionViewSource.GetDefaultView(MySourceCollection);
MyListView.Filter = o => ((ViewModel)o).Number >= 3;
XAML:
<DataGrid ItemsSource="{Binding MySourceCollection}" />
Result:
I don't know whether this is recommended, but I don't see any problem yet. Just remember that if you reinitialize your source list, you have to call CollectionViewSource.GetDefaultView again and reapply your filters.
The reason why its not generic is that the type safety should be in your underlying collection not your view.
The CollectionViewSource is purely for formatting the display of the data, so like a combo and list controls are not typed neither is CollectionViewSource and for exactly the same reason, because they need to work with anything that is given to them
as an example you have a Students Collection, you want to display this in a combo but your also want to be able to select "NEW STUDENT" new student isn't a student so can't be added to the student collection but is a perfectly valid combo item so while the underlying collection has to be Type safe, enforcing the same on the combo is limiting and not protective, out side of your view your code really shouldn't care if values are sorted or not that's usually just a human thing
as for your generic CollectionViewSource, it depends how your are using it if its a good idea not however the type safety should be superflous because your underlying collection should already be doing this.
I would suggest having an ObservableCollection<T> as the source of your CollectionViewSource and then just forgetting about Type safing the display
This might and impossible scenario and I may be trying to do something that I should not be doing in the first place but here it is.
I have a custom WPF Control which has two IEnumerable collections
The first collection (ItemsSource) is declared via the XAML and might be of any type of objects.
The second collection the one that I am implementing is again an IEnumerable which I want to initialize as ObservableCollection.
Here is my issue as I am restricted that both the collections are of the same type of objects (no I cannot use object as a type). For example the ItemsSource is of "MyItem" type objects and I want to initialize the second collection to be ObservableCollection().
Is this possible? Am i doing something that I should not be doing? Any hints will appreciated. On a side note if I pass the second collection via the XAML all is well, but I do not want to add such restriction to the feature I am implementing.
Edit:
Here are some code snippets to showcase the scenario:
The first collection, note that this collection is inherited from the System.Windows.Controls.ItemsControl class:
public IEnumerable ItemsSource { get; set; }
The second collection:
public IEnumerable SelectedItems
{
get
{
this.InitializeSelectedItemsCollectionIfRequired();
return (IEnumerable)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
private void InitializeSelectedItemsCollectionIfRequired()
{
if (this.GetValue(SelectedItemsProperty) == null)
{
// Here is where I want to initialize the second collection if it was not already set in via a Binding in the XAML
this.SelectedItems = new System.Collections.ObjectModel.ObservableCollection<"dont know how to pass correct type here">();
}
}
Since you don't know the exact type you could simply revert to the most basic type object
this.SelectedItems = new System.Collections.ObjectModel.ObservableCollection<object>();
I have a class that defines a custom GetEnumerator() function(by implementing IEnumerable<>). I use it to iterate in a contiguous manner over several ObservableCollection<LogEvent> that are in every TestStep. I have a private ObservableCollection<TestStep> that contains all the needed data.
I would like to use an instance of this class as the ItemsSource of a ListBox. However, the ListBox never gets updated when the underlying data(ObservableCollection<LogEvent>) is updated. Here's a sample of that class:
public class FlatLogViewModel : IEnumerable<LogEvent>
{
public FlatLogViewModel(ObservableCollection<TestStep> subSteps)
{
m_subSteps = subSteps;
}
public IEnumerator<LogEvent> GetEnumerator()
{
foreach (TestStep step in SubSteps)
{
// step.LogEvents is an ObservableCollection<LogEvent>
foreach (LogEvent logEvent in step.LogEvents)
yield return logEvent;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private ObservableCollection<TestStep> m_subSteps;
}
I'm not sure if I should/can implement INotifyCollectionChanged here. How can I know if ObservableCollection has been modified?
My question is: how can I get the ListBox to display the changes happening in LogEvents(which is of type ObservableCollection<LogEvent>)?
When theObservableCollection changes, how does the ListBox know? You have to implement like you mentioned INotifyCollectionChanged and then update the ItemSource with the new enumerable data inside the event handler.
ObservableCollection is an INotifyCollectionChanged. Use casting
var collectionChanged = yourObCollection as INotifyCollectionChanged;
if( collectionChanged !=null)
{
collectionChanged.CollectionChanged += YourEventHandler;
}
inside the handler do your own logic to update the item source
Have you considered binding to the original collection but running it through a converter in order to pull out the LogEvents?
The converter should be able to simply return subSteps.SelectMany(s => s.LogEvents).
I've done a lot of samples but nowhere encountered on my problem.
Namely, I would like to create a Grouped GridView, which consists of two groups, with the exception that each group is made up of completely different collection. For example, I would like to in the first group were Animals, and in the second, Cars.
I would also like to each of these groups had a different Template ;)
Make both your inner collection items derive from a common base class. When I did this I had an ItemBase class, and my Event, Story, and Party classes all derived from Item base.
Then, my groups collection items each contained a definition for Items of ObservableCollection. (I guess, thinking about it now, I could have used object as the implied base type, but I didn't) When coded this was actually populated with my derived classes, e.g.
Items.Add(new Event { Title = "I am an event" };
When you display the items in your grid, you will want to create a new class that derives from ItemTemplateSelector, and override the SelectTemplateCore(object item, DependencyObject container) method. My logic was as simple as
if(item is Event) { return EventTemplate; }
else if(item is Story) { return StoryTemplate }
else { return DefaultTemplate; }
(My Party item used the default template.)
Create a ObservableCollection and push your collection items.
Like This:
public class ScreenGroupModel
{
private ObservableCollection<object> _groupItems = new ObservableCollection<object>();
public ObservableCollection<object> GroupItems
{
get { return this._groupItems; }
}
public ScreenGroupModel()
{
}
public ObservableCollection<object> GetScreenGroups()
{
_groupItems.Add(new Class1);
_groupItems.Add(new Class2);
return _groupItems;
}
}
This Sample, simple collection showing. You can be used DataTemplateSelectors. Every kind of class, select a template.
ObservableCollection -> "object" type is important. Because, object is base type. You can be add, every kind class.
Regards ;)
I'm using Prism in WPF.
I was watching the Prism starter kit, and it has a ICollectionView. In that collection, I need to add the object selected in it. What object (or collection) should I use to add objects?
I mean in the image, I have two listbox, in the first one I've got a readonly collection and the second one is a list where can add or remove objects.
Your List should be bound to an ICollectionView that wraps an ObservableCollection. When you add, you add to this Observable collection.
I.e
private readonly ObservableCollection<Stock> listToAddTo;
public ICollectionView List2 { get; private set; }
Constructor
listToAddTo = new ObservableCollection<Stock>();
List2 = CollectionViewSource.GetDefaultView(listToAddTo);
Where List2 is what is bound to